클래스에서 객체가 생성될 때와 사라질 때 자동으로 실행되는 특별한 함수들입니다.
정의: 객체가 메모리에 태어나는 순간 자동으로 실행되는 특별한 함수입니다.
목적: 아기가 태어나면 이름을 지어주듯, 객체가 태어날 때 변수들에 첫 번째 값(초기값)을 넣어주는 역할을 합니다.
특징: 클래스 이름과 똑같이 생겼고, 우리가 직접 부르지 않아도 MyClass m1;이라고 쓰는 순간 컴퓨터가 알아서 실행합니다.
~를 붙입니다.1) 지역 객체의 경우
함수 내부에서 생성된 객체는 해당 함수({ })가 끝나는 순간 소멸합니다.
class MyClass {
public:
MyClass() { cout << "생성자 호출" << endl; } // (3)
~MyClass() { cout << "소멸자 호출" << endl; } // (5)
};
void testObj() {
cout << "testObj() 함수 호출 중..." << endl; // (2)
MyClass obj2; // 생성자 호출
cout << "testObj() 함수 끝..." << endl; // (4)
} // obj2 소멸자 자동 호출 시점
int main() {
cout << "=== main 시작 ===" << endl; // (1)
testObj();
cout << "=== main 종료 ===" << endl; // (6)
}
2) 전역 객체의 경우
전역 공간에 선언된 객체는 프로그램 시작 시 가장 먼저 생성되고, 프로그램이 완전히 종료될 때 소멸합니다.
class MyClass {
public:
MyClass() {
cout << "생성자 호출" << endl; //(1)
}
~MyClass() {
cout << "소멸자 호출" << endl; //(6)
}
};
MyClass obj; // 0) 전역 객체: main보다 먼저 생성!
void testObj() {
cout << "testObj() 함수 호출 중..." << endl; //(3)
cout << "testObj() 함수 끝..." << endl; //(4)
} // obj2 소멸자 자동 호출
int main(void) {
cout << "=== main() 함수 시작 ===" << endl; //(2)
testObj(); // 객체 생성 -> 생성자는 자 동 호출 (void testObj로)
cout << "=== main() 함수 종료 ===" << endl; //(5)
}
컴퓨터는 내부적으로 멤버 함수를 호출할 때 "어떤 객체가 나를 불렀는지"를 알기 위해 객체의 주소를 몰래 넘겨줍니다.
void printData() → void printData(MyClass* this)data라고만 써도 컴퓨터는 this->data로 해석하여 호출한 객체의 데이터를 정확히 찾아갑니다.int main() {
MyClass a, b; // 객체 a와 b가 만들어졌어요.
a.data = 10;
b.data = 20;
a.printData(); // (A) 호출
b.printData(); // (B) 호출
}
- (A) a.printData();를 실행할 때
컴퓨터는 a의 메모리 주소(예: 0x100)를 파악합니다.
printData 함수를 실행하면서 몰래 this = &a; 라고 주소를 넘겨줍니다.
함수 안에서 cout << data;를 만나는 순간, 컴퓨터는 this가 가리키는 a의 주소로 가서 10을 꺼내옵니다.
- (B) b.printData();를 실행할 때 (이미지의 &b 부분)
이번엔 b의 메모리 주소(예: 0x200)를 파악합니다.
똑같은 printData 함수를 쓰지만, 이번엔 몰래 this = &b; 라고 주소를 바꿔서 넘겨줍니다.
함수 안에서 this는 이제 b를 가리키므로, 20을 꺼내오게 됩니다.
클래스 내부의 변수(멤버 변수)를 private에 숨겨 안전하게 보호하고, 외부에서는 정해진 함수(public)를 통해서만 접근하게 만드는 방식입니다.
class MyClass {
private:
int real; // 외부 접근 불가 (금고)
public:
int getReal() { return real; } // Getter
void setReal(int r) { real = r; } // Setter
};
통합 예제
class MyClass {
// 1. 외부에서 사용해야 하는 생성자와 함수는 반드시 public에 선언합니다.
public:
// 기본 생성자
MyClass() {
real = 0;
imag = 0;
}
// 매개변수가 있는 생성자
MyClass(int r, int i) {
real = r;
imag = i;
}
// Getter / Setter
int getReal() { return real; }
void setReal(int r) { real = r; }
int getImag() { return imag; }
void setImag(int i) { imag = i; }
// 2. 실제 데이터(멤버 변수)는 안전하게 private에 보관합니다.
private:
int real;
int imag;
};
int main(void) {
// 객체 생성 방법 3가지
MyClass m1; // 기본 생성자 호출 (real=0, imag=0)
MyClass m2 = MyClass(5, 6); // 명시적 생성
MyClass m3(7, 8); // 암시적 생성 (가장 많이 쓰임)
cout << "m1: " << m1.getReal() << " + " << m1.getImag() << endl;
cout << "m2: " << m2.getReal() << " + " << m2.getImag() << endl;
cout << "m3: " << m3.getReal() << " + " << m3.getImag() << endl;
return 0;
}
이름은 같지만 매개변수(재료)의 개수나 타입을 다르게 하여 여러 개의 생성자를 만드는 기술입니다.
class MyClass {
public:
// 1번 생성자: 매개변수(재료)가 0개
MyClass() {
real = 0; imag = 0;
}
// 2번 생성자: 매개변수(재료)가 2개 (int, int)
// 이름은 1번과 똑같지만 매개변수 개수가 틀리죠? -> 오버로딩!
MyClass(int ral, int img) {
real = ral; imag = img;
}
};
MyClass m1; // 이라고 쓰면 컴퓨터가 1번을 호출합니다.
MyClass m2(5, 6); //이라고 쓰면 재료가 2개니까 2번을 호출합니다.
데이터 타입이 틀린 경우
재료의 개수가 같아도 종류(타입)가 다르면 오버로딩이 됩니다.
MyClass(int a) : 정수를 받는 버전
MyClass(double a) : 실수를 받는 버전
컴퓨터는 함수를 찾을 때 이름만 보는 게 아니라,
(이름 + 매개변수 구성)을 통째로 하나의 세트로 봅니다.
Student() 와 Student(int score) 는 이름은 같지만, 컴퓨터 입장에서는
"재료가 없는 버전"과 "숫자 재료가 하나 필요한 버전"으로 확실히 구분해서
인식할 수 있습니다.
즉 "오버로딩은 이름은 하나로 통일하되, 재료(매개변수)의 '개수'나 '타입'을 다양하게 준비해서
여러 상황에 대처하는 기술이다!"
class MyClass {
public:
// 1. 내가 특별한 메뉴(매개변수 생성자)를 하나 만들었습니다.
MyClass(int a) {
cout << "숫자 전달받음!" << endl;
}
};
int main() {
MyClass m1; // 2. 에러 발생! "기본 메뉴(디폴트 생성자)가 없는데요?"
}
아무것도 안 했을 때 : (디폴트 생성자 자동 생성): 분식집에 메뉴판이 비어있으면,
주인은 손님이 올 때를 대비해 아주 기본적인 '그냥 김밥' 하나를 미리 준비해 둡니다.
그래서 우리는 아무 말 없이 "김밥 하나요!" 해도 김밥을 먹을 수 있습니다.
특별한 메뉴를 만들었을 때 (매개변수 생성자 선언): 우리가 메뉴판에 '참치 김밥(5,000원)'이라는 특별한 메뉴를 딱 적어 넣는 순간! 주인은 "오, 이제 이 집은 고급 참치 김밥 전문점이구나?
이제 그냥 김밥은 안 만들어야지."
문제가 생기는 상황: 이때 어떤 손님이 와서 그냥 "김밥 하나요!(MyClass m1;)"라고 하면, 주인님은 "우리 집엔 이제 참치 김밥밖에 없어요! 그냥 김밥은 안 팔아요!"라고 화를 냅니다. 이게 바로 에러가 나는 상황입니다.
비슷한 초기화 코드가 중복되는 것을 방지하기 위해, 하나의 생성자가 다른 생성자를 호출하는 방식입니다.
[생성자 위임 방식 (효율적)]
초기화 리스트(:)를 사용하여 다른 생성자에게 일을 맡깁니다.
#include <iostream>
using namespace std;
class Time {
private:
int h, m, s;
public:
// 1. 기본 생성자: 모든 값을 0으로 초기화
Time() : h(0), m(0), s(0) {
cout << "기본 생성자 호출" << endl;
}
// 2. 생성자 위임: Time()을 먼저 불러서 0으로 세팅한 뒤 s만 변경
Time(int s) : Time() {
this->s = s;
cout << "Time(int s) 호출" << endl;
}
// 3. 생성자 위임: Time(s)를 불러서 처리한 뒤 m만 추가로 변경
Time(int m, int s) : Time(s) {
this->m = m;
cout << "Time(int m, int s) 호출" << endl;
}
// 4. 모든 값을 직접 받는 생성자
Time(int h, int m, int s) : Time(m,s){
this->h = h;
}
};
[노가다 방식 (비효율적)]
매번 모든 변수를 일일이 대입해줘야 하므로 코드가 길어지고 실수가 생길 수 있습니다.
#include <iostream>
using namespace std;
class Time {
private:
int h, m, s;
public:
// 1. 기본 생성자: 모든 값을 0으로 초기화
Time() : h(0), m(0), s(0) {
cout << "기본 생성자 호출" << endl;
}
// 2. 생성자 위임: Time()을 먼저 불러서 0으로 세팅한 뒤 s만 변경
Time(int s) {
h = 0;
m = 0;
this->s = s;
}
// 3. 생성자 위임: Time(s)를 불러서 처리한 뒤 m만 추가로 변경
Time(int m, int s) {
h=0;
this->m = m;
this->s = s;
}
// 4. 모든 값을 직접 받는 생성자
Time(int h, int m, int s) {
this->h = h;
this->m = m;
this->s = s;
}
};
요약: 외부(main 함수 등)에서 클래스 내부의 private 영역은 직접 건드릴 수 없으므로, 생성자나 Getter/Setter라는 '공식적인 통로'를 이용해 소통해야 합니다.