-
[C++] 클래스 기본 문법 1C++/이것이 C++이다 2021. 2. 27. 01:55
이전 글에서 클래스가 어떤 아이디어에서 도입되었는지 살펴보았습니다.
객체지향 이라는 개념이 도입된 원인은 여러가지가 있겠지만
이전 글에서 집중했던 내용은 크게 두가지 입니다.
- 개발자와 사용자의 분리
- 소스코드의 유지보수성
위의 두 내용과 본문의 내용에 대한 관련성이 잘 보이지 않을 수 있습니다.
자세한 내용은 이후에 다룰 객체지향의 패러다임에서 다룰 수 있도록 하겠습니다.
이번 글은 클래스를 사용하는데 필요한 기본 문법에 대한 내용입니다.
1. 클래스 구성요소
더보기클래스는 다음과 같은 요소로 구성됩니다.
class [Class Name] { [Access Modifier]: [Member Variable]; [Member Function]; }
[Class Name] : 클래스의 이름
[Access Modifier] : 접근 제어 지시자
[Member Variable] : 멤버 변수
[Member Function] : 멤버 함수
간단한 예제와 함께 구체적으로 살펴보도록 하겠습니다.
#include <iostream> class MyClass{ public: int my_number; void print(){ std::cout << my_number << "\n"; } } int main(){ MyClass cls; cls.my_number = 5; cls.print(); }
5
위와 같은 식으로 클래스를 정의하고, 선언하고, 사용할 수 있습니다.
조금 더 자세한 내용은 다음 문단에서 살펴보도록 하겠습니다.
2. 접근 제어 지시자
더보기접근 제어 지시자 (Access Modifier)란 사용자의 클래스의 구성 요소에 대한 접근성을 제한하는 역할을 합니다.
왜 사용자가 클래스의 접근을 제한당해야 하는가에 대한 대답은 다음과 같이 짧게 대답할 수 있겠습니다.
[사용자가 개발자의 의도에서 벗어난 행동을 하는것을 막기 위해서]
예시로, 음향 기기의 볼륨 조절 장치를 생각 해 보겠습니다.
볼륨 조절은 대부분 0-100사이로 할 수 있는데, 이것에 대한 제한이 없다고 가정해보도록 하겠습니다.
그럴 경우, 음수값이 들어가거나, 100이 넘어간 아주 큰 값이 들어갈 수도 있으며
그러한 상황은 음향 기기나 사용자의 청각에 손상을 줄 수 있고, 그것은 개발자의 의도와는 거리가 있을 것 입니다.
이러한 의도를 벗어난 행동을 막기 위해 접근 제어 지시자를 이용한다고 할 수 있겠습니다.
C++의 접근 제어 지시자는 3종류가 있습니다.
public / protected / private
지시자를 명시적으로 기술하지 않을 경우, 기본 적용되는 접근 제어 지시자는 private이다.
지시자 상세 public 멤버에 대한 모든 외부 접근이 허용된다. protected 멤버에 대한 모든 외부 접근이 차단되나, 상속 관계에 있는 클래스는 접근이 허용된다. private 멤버에 대한 모든 외부 접근이 차단된다. protected에 언급되는 상속 에 관한 내용은 이 글에 정리되어 있습니다.
이번 글에서는 public과 private에 대한 예제를 살펴보도록 하겠습니다.
Example (Access Modifier)
#include <iostream> class MyClass{ private: int my_number1 = 5; public: int my_number2 = 10; void print(){ std::cout << "Number 1 : " << my_number1 << "\n"; std::cout << "Number 2 : " << my_number2 << "\n"; } }; int main(){ MyClass cls; cls.my_number1 = 10; // Line 16 cls.my_number2 = 15; cls.print(); }
E0265 : 멤버 "MyClass::my_number1"에 액세스 할 수 없습니다. C2248 : 'MyClass::mynumber1': private멤버에 액세스 할 수 없습니다.
위의 예제는 해당 오류와 함께 컴파일 되지 않습니다.
문제는 Line 16이라는 주석이 된 코드입니다.
위와 같이, private로 선언 된 변수, 함수는 클래스 외부에서의 접근이 허용되지 않습니다.
3. 생성자
더보기생성자 (Constructor)란 객체의 생성과 동시에 호출되는 특별한 형태의 클래스 멤버 함수입니다.
생성자의 특징은 다음과 같습니다.
- 객체가 생성될 때 자동으로 호출된다.
- 생성자 이름은 클래스 이름과 같다.
- 반환 형식이 없다.
- 정의가 되지 않더라도, 컴파일러가 디폴트 생성자를 자동으로 정의한다.
Example (Constructor)
#include <iostream> class MyClass{ public: MyClass(){ std::cout << "Class Construct\n"; } } int main(){ MyClass cls; }
Class Construct
위와 같이, 클래스를 선언할 때 생성자가 호출되는 것을 볼 수 있습니다.
생성자는 일반적으로 멤버 변수의 초기화에 이용되며, 생성자는 다중 정의 할 수 있습니다.
Example (Constructor Overloading)
#include <iostream> class MyClass{ public: int my_number; MyClass(){ my_number = 5; std::cout << "Class Construct : " << my_number << "\n"; } MyClass(int num){ my_number = num; std::cout << "Class Construct : " << my_number << "\n"; } } int main(){ MyClass cls1; MyClass cls2(10); }
Class Construct : 5 Class Construct : 10
이 예제처럼 매개변수를 이용한 변수의 초기화에 생성자를 이용할 수 있습니다.
하지만, 위와 같은 방법 외에 생성자 초기화 목록이라는 문법이 있습니다.
Example (Constructor Initialize List)
#include <iostream> class MyClass{ private: int my_number1 = 1; public: int my_number2 = 5; // Line 7 MyClass(int num1, int num2): my_number1(num1), my_number2(num2){ std::cout << "Class Construct : " << my_number1 << "\n"; std::cout << "Class Construct : " << my_number2 << "\n"; } } int main(){ MyClass cls(10, 20); }
Class Construct : 10 Class Construct : 20
생성자 초기화 리스트를 이용한 멤버 초기화 예제입니다.
물론, Line 7 처럼 선언과 동시에 초기화 할 수 있습니다.
추가로, 파라미터가 참조자일 경우, 반드시 초기화 리스트를 이용해야 합니다.
생성자 또한 접근 제어 지시자에 영향을 받습니다.
Example (private Constructor)
#include <iostream> class MyClass{ private: MyClass(){ my_number = 5; std::cout << "Class Construct : " << my_number << "\n"; } public: int my_number; MyClass(int num){ my_number = num; std::cout << "Class Construct : " << my_number << "\n"; } } int main(){ MyClass cls1; // Line 18 MyClass cls2(10); }
E0330 : "MyClass::MyClass()"에 액세스 할 수 없습니다. C2248 : 'MyClass::MyClass': private멤버에 액세스 할 수 없습니다.
위의 예제에서, MyClass(int num)생성자를 없앨 경우 MyClass는 일반적인 방법으로 생성하지 못 하는 클래스가 됩니다.
위와 같은 방법이 필요할 때가 몇 가지 있는데, 그것은 차후 디자인 패턴에서 이야기 해 보도록 하겠습니다.
4. 소멸자
더보기소멸자 (Destructor)는 객체가 소멸될 때 호출되는 특별한 형태의 클래스 멤버 함수입니다.
소멸자의 특징은 다음과 같습니다.
- 객체가 소멸될 때 자동으로 호출된다.
- 소멸자 이름은 클래스 이름 앞에 '~' 를 붙인 형태가 된다.
- 반환 형식이 없다.
- 매개변수가 없다 = 다중 정의 할 수 없다.
- 정의가 되지 않더라도, 컴파일러가 디폴트 소멸자를 자동으로 정의한다.
Example (Destructor)
#include <iostream> class MyClass{ public: int my_number; MyClass(int num): my_number(num){ std::cout << "Class Construct : " << my_number << "\n"; } ~MyClass(){ std::cout << "Class Destruct\n"; } } int main(){ MyClass cls(10); std::cout << "End of main()\n"; }
Class Construct : 10 End of main() Class Destruct
소멸자는 main함수가 끝난 이후에도 호출될 수 있음에 유의해야 합니다.
5. 선언과 구현의 분리
더보기클래스의 멤버 함수는 일반적인 함수처럼 선언과 구현을 분리할 수 있습니다.
Example (Seperate Declaration & Implementation)
#include <iostream> class MyClass{ private: int my_number; public: MyClass(int); void print(); } MyClass::MyClass(int num) : my_number(num){ std::cout << "Class Construct : " << my_number << "\n"; } void MyClass::print(){ std::cout << "My Number : " << my_number << "\n"; } int main(){ MyClass cls(10); cls.print(); }
Class Construct : 10 My Number : 10
구현에서의 접근은 네임스페이스처럼 :: 연산자를 이용합니다.
위와 같은 방식으로 선언과 구현을 분리할 경우 헤더파일과 cpp파일로 각각 선언과 구현을 분리하는 식으로 가독성에서 이점이 생깁니다.
6. 객체의 동적 생성 및 소멸
더보기new와 delete를 이용한 동적 할당은 클래스에도 적용이 가능합니다.
Example (Dynamic Allocation in Class)
#include <iostream> class MyClass{ public: int my_number; MyClass(int num): my_number(num){ std::cout << "Class Construct : " << my_number << "\n"; } ~MyClass(){ std::cout << "Class Destruct\n"; } } int main(){ MyClass* cls = new MyClass(10); delete cls; std::cout << "End of main()\n"; }
Class Construct : 10 Class Destruct End of main()
동적할당된 객체는 기존과 마찬가지로 delete를 이용한 할당 해제가 필요하게 됩니다.
메모리 관리에 조금 더 신경써야 하는 단점이 있지만 객체의 생성, 소멸시점을 명확하게 알 수 있다는 장점이 있다.
추가로, 배열 형식으로 할당이 가능하며, 이 경우는 delete[]를 통해 배열로 해제 해 주어야 합니다.
7. 메소드
더보기메소드 (Method) : (명) 방법
사전적인 정의와 같이, 클래스에서의 메소드는 클래스가 제공하는 기능을 의미합니다.
멤버 함수도 틀린 명칭은 아니지만, 클래스의 멤버 함수는 메소드라는 명칭을 많이 사용합니다.
메소드는 구체적으로 다음과 같이 구성되어 있습니다.
class MyClass{ public: void noraml_function(); const void const_function(); static void static_function(); virtual void virtual_function(); }
위의 const, static, virtual 예약어로 분류된 메소드들은 각자 자기만의 특징이 있습니다.
또한, 그 특징들이 일부 독립적이기 때문에 const static function과 같은 조합도 가능합니다.
각 예약어는 아래의 다른 글에 정리되어 있습니다.
const : 클래스 기본 문법 2 (문단 1. 상수형 메소드)
static : 클래스 기본 문법 2 (문단 4. 정적 멤버)
virtual : 가상 함수
8. this
더보기this라는 예약어는 클래스의 실제 인스턴스를 가리키는 포인터입니다.
클래스는 개발자가 내용과 기능을 명세해둔 설계 도면 이고
인스턴스는 그 설계 도면으로 만든 메모리가 할당된 실체라고 할 수 있겠습니다.
쉬운 비유로 표현해보면 클래스는 붕어빵 틀, 인스턴스는 그 틀로 만들어진 붕어빵 이라고 생각해볼 수 있습니다.
Example (this Pointer)
#include <iostream> class MyClass{ public: int my_number; MyClass(int num) : my_number(num){ std::cout << "Construct : " << this->my_number << "\n"; } void print(MyClass &cls){ std::cout << "My number : " << this->my_number << "\n"; std::cout << "Param number : " << cls.my_number << "\n"; } } int main(){ MyClass a(10), b(20); a.print(b); }
Construct : 10 Construct : 20 My number : 10 Param number : 20
본 예제에서 print()를 호출한 MyClass는 a입니다.
따라서 print()에서 this가 a를 가리키게 되는 것 입니다.
2에서 이어질 예정입니다.
짧지 않은 글 읽어주셔서 감사합니다.
'C++ > 이것이 C++이다' 카테고리의 다른 글
[C++] 깊은 복사와 얕은 복사 (0) 2021.03.30 [C++] 클래스 기본 문법 2 (0) 2021.03.12 [C++] 클래스를 살펴보기 전에 (0) 2021.02.27 [C++] 네임스페이스 (0) 2021.02.12 [C++] 함수 기본 (0) 2021.02.12