C++ 에서 클래스란 객체 지향 프로그래밍의 추상화를 사용자가 정의할 수 있는 사용자 정의 타입으로 구현한 것이다. C++ 의 구조체와 마찬가지로 멤버로 변수와 함수를 가질 수 있다.
C++ 에서 클래스와 구조체는 기본 접근 지정자의 차이만 있을 뿐 나머지는 동일하게 작동한다.
구조체의 기본 접근 지정자는 public, 클래스의 기본 접근 지정자는 private 이다.
클래스는 class 키워드를 사용하여 정의한다.
class ClassName
{
Access Specifier:
MemberValType MemberValName;
...
MemberFunc();
}
// Student 클래스
class Student
{
public:
char Name[10];
int Grade = 4;
double GPA = 3.8;
int Func1() {};
void Func2() {};
};
클래스의 정의는 클래스의 형태(틀)만 정의한 것이다. 이 형태를 가지고 객체를 생성할 수 있다.
ClassName InstanceName;
Student NewStudent; // Student 클래스 객체 NewStudent 생성
이렇게 메모리에 대입되어 생성된 클래스 타입의 객체를 인스턴스(Instance)라고 한다. 하나의 클래스에서 여러 개의 인스턴스를 생성할 수 있으며 인스턴스는 독립된 메모리 공간에 자신만의 멤버 변수를 가지지만, 멤버 함수는 모든 인스턴스가 공유한다.
클래스에서 멤버 연산자 . 를 이용해 외부에서 클래스의 멤버에 접근할 수 있다.
InstanceName.MemberName;
#include <iostream>
// Student 클래스 정의
class Student
{
public:
char Name[10];
int Grade = 0;
double GPA = 0.0;
int Func1() {};
void Func2() {};
};
int main(void)
{
// Student 객체 NewStudent 생성
Student NewStudent;
// NewStudent 객체 멤버 접근
NewStudent.Grade = 90;
NewStudent.GPA = 3.8;
std::cout << NewStudent.Grade << std::endl;
std::cout << NewStudent.GPA << std::endl;
return 0;
}
90
3.8
접근 지정자는 객체 지향 프로그래밍의 정보 은닉을 위한 키워드읻. C++에서는 다음 3가지 유형의 접근 지정자를 제공한다.
public |
어디서는 직접 접근이 가능 |
|---|---|
protected |
상속받는 파생 클래스에서는 public 처럼 취급되지만, 외부에서는 직접 접근 불가능 |
private |
상속받는 파생 클래스를 포함해 외부에서 직접 접근 불가능 |
클래스의 기본 접근 지정자는 private 으로 접근 지정자를 명시하지 않으면 private 으로 선언된다.
#include <iostream>
class Student
{
// 기본 접근제어 지시자
int Default_Grade;
// public 으로 선언
public:
int Public_Grade;
// private 으로 선언
private:
int Private_Grade;
// protected 는 이후 상속에 대한 글에서 다루겠습니다.
protected:
};
int main(void)
{
Student NewStudent;
// public - 외부에서 직접접근 가능
NewStudent.Default_Grade = 95;
// private - 외부에서 직접접근 불가능
NewStudent.Public_Grade = 95; // 오류!
// 기본 접근제어 지시자 -> private
NewStudent.Private_Grade = 95; // 오류!
return 0;
}
C++ 클래스의 생성자는 클래스의 객체가 생성될 때 자동으로 호출되는 멤버 함수이다. 일반적으로 생성자는 클래스의 멤버 변수를 초기활하거나 클래스를 사용하는데 필요한 초기 설정이 필요한 경우 사용한다.
생성자는 다음과 같은 특징을 갖는다.
void 형도 아님)class Example
{
public:
// 생성자 (Constructor)
Example()
{
Value = 0;
}
private:
int Value;
};
클래스의 생성자는 일반 멤버 함수와 마찬가지로 오버 로딩이 가능하다.
class Example
{
public:
// 생성자 (Constructor)
Example()
{
Value = 0;
}
Example(int v)
{
Value = v;
}
private:
int Value;
};
객체는 클래스의 메모리 공간의 할당 이후 생성자의 호출까지 이루어져야 객체라 할 수 있다. 다시 말하면 객체가 되기 위해서는 반드시 하나의 생성자가 호출되어야 한다.
아래의 코드에서 Example 클래스의 생성자를 따로 만들어 주지 않았지만 객체가 생성된 것을 확인할 수 있다.
#include <iostream>
class Example
{
public:
void Func()
{
std::cout << "Example 객체" << std::endl;
}
private:
int Value;
};
int main(void)
{
Example NewExample;
NewExample.Func();
return 0;
}
Example 객체
이처럼 생성자가 정의되지 않은 클래스는 C++ 컴파일러가 자동으로 기본 생성자를 생성해준다. 기본 생성자는 인자가 없고, 내부적으로 아무 작업도 하지 않는다.
class Example
{
public:
// 생성자가 없으면 다음과 같은 기본 생성자가 삽입된다.
Example() {}
void Func()
{
cout << "Example 객체" << endl;
}
private:
int Value;
};
만약 클래스의 생성자가 정의되어 있다면 컴파일러는 기본 생성자를 생성하지 않는다.
#include <iostream>
class Example
{
public:
// 생성자 정의
Example(int v)
{
Value = v;
}
void Func()
{
std::cout << "Example 객체" << std::endl;
}
private:
int Value;
};
int main(void)
{
// 생성자가 정의되었다면 기본 생성자는 자동으로 생성되지 않습니다.
Example NewExample = Example(); // 오류!
return 0;
}
사용자가 직접 생성자를 명시적으로 호출할 수도 있다.
#include <iostream>
class Example
{
public:
Example()
{
std::cout << "생성자 1 호출" << std::endl;
Value = 0;
}
Example(int v)
{
std::cout << "생성자 2 호출" << std::endl;
Value = v;
}
private:
int Value;
};
int main(void)
{
// 생성자 1 사용
Example NewExample1 = Example();
// 생성자 2 사용
Example NewExample2 = Example(5);
return 0;
}
생성자 1 호출
생성자 2 호출
C++ 클래스의 소멸자는 객체가 소멸할 때 반드시 호출되는 멤버 함수이다. 생성자가 클래스의 초기화를 돕는다면 소멸자는 클래스의 릴리즈를 돕도록 설계되어있다.
소멸자는 다음과 같은 특징을 갖는다.
~ 가 붙은 형태이다.void 형도 아님)class Example
{
public:
// 소멸자 (Destructor)
~Example() {}
private:
int Value = 0;
};
소멸자도 생성자와 마찬가지로 따로 정의하지 않으면 C++ 컴파일러가 내부적으로 아무 작업도 하지 않는 기본 소멸자를 자동으로 생성해준다.
#include <iostream>
class Example
{
public:
// 소멸자가 없으면 다음과 같은 기본 소멸자가 삽입된다.
// ~Example() {}
private:
int Value;
};
int main(void)
{
Example NewExample = Example();
// 소멸자를 정의하지 않았지만 명시적으로 호출이 가능하다.
NewExample.~Example();
return 0;
}
클래스의 멤버 변수들이 단순히 값 형식이라면 소멸자는 크게 필요 없지만 동적 메모리, 파일과 같은 다른 리소스라면 객체가 소멸되기 전 동적 할당된 메모리 영역을 반환하는 등 어떠한 종류의 작업을 해야 한다. 이때 소멸자를 이용할 수 있다.
class Example
{
public:
Example(int _Size)
{
// 메모리 동적 할당
Array = new int[static_cast<size_t>(_Size)];
Size = _Size;
}
~Example()
{
// 동적 할당한 메모리를 삭제
delete[] Array;
}
private:
int* Array;
int Size;
};
C++ 에서 소멸자는 프로그래머가 명시적으로 호출할 수 있지만 객체 소멸시 자동으로 호출되기 때문에 명시적으로 호출하지는 않는다.
C++ 에서 소멸자의 호출 시기는 컴파일러가 알아서 처리하게 되며 객체가 선언된 메모리 영역별로 소멸자가 호출되느 시기는 다음과 같다.
| --- | --- |
#include <iostream>
class Example1
{
public:
~Example1()
{
std::cout << "Example1 소멸" << std::endl;
}
};
class Example2
{
public:
~Example2()
{
std::cout << "Example2 소멸" << std::endl;
}
};
class Example3
{
public:
~Example3()
{
std::cout << "Example3 소멸" << std::endl;
}
};
void Func(Example2 _Ex)
{
}
int main(void)
{
Example1 NewExample1 = Example1();
Example2 NewExample2 = Example2();
Example3* NewExample3 = new Example3;
std::cout << "Func 함수 호출" << std::endl;
Func(NewExample2);
std::cout << "Func 함수 종료" << std::endl;
std::cout << "\\n동적할당 메모리 반환" << std::endl;
delete NewExample3;
std::cout << "\\nmain 함수 종료" << std::endl;
return 0;
}
Func 함수 호출
Example2 소멸
Func 함수 종료
동적할당 메모리 반환
Example3 소멸
main 함수 종료
Example2 소멸
Example1 소멸