[C++] 클래스 기본 문법 1
이전 글에서 클래스가 어떤 아이디어에서 도입되었는지 살펴보았습니다.
객체지향 이라는 개념이 도입된 원인은 여러가지가 있겠지만
이전 글에서 집중했던 내용은 크게 두가지 입니다.
- 개발자와 사용자의 분리
- 소스코드의 유지보수성
위의 두 내용과 본문의 내용에 대한 관련성이 잘 보이지 않을 수 있습니다.
자세한 내용은 이후에 다룰 객체지향의 패러다임에서 다룰 수 있도록 하겠습니다.
이번 글은 클래스를 사용하는데 필요한 기본 문법에 대한 내용입니다.
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에서 이어질 예정입니다.
짧지 않은 글 읽어주셔서 감사합니다.