생성자의 특징
- 클래스 이름과 함수의 이름이 동일하다.
- 반환형이 선언되어 있지 않으며, 실제로 반환하지 않는다.
- 생성자도 함수이므로 오버로딩이 가능하다.
- 생성자도 함수이므로 매개변수의 디폴트 값 설정이 가능하다.
아래 코드를 통해서 생성자를 이해해보자.
#include <iostream>
using namespace std;
class SimpleClass
{
int num1;
int num2;
public:
SimpleClass(){
// XXX SimpleClass sc1();
// 함수 선언문인지 객체 생성문인지 모호하다.
num1=0;
num2=0;
}
SimpleClass(int n){
// SimpleClass sc2(100);
// SimpleClass * ptr2 = new SimpleClass(100);
num1=n;
num2=200;
}
SimpleClass(int n1, int n2){
// SimpleClass sc3(100, 200);
// SimpleClass * ptr3 = new SimpleClass(100, 200);
num1=n1;
num2=n2;
}
SimpleClass(int n1=0, int n2=0){
// 위의 2,3번을 주석처리하고 써야 한다.
num1=n1;
num2=n2;
}
void ShowData() const
{
cout<<num1<<' '<<num2<<endl;
}
};
int main(void)
{
SimpleClass sc1;
sc1.ShowData();
SimpleClass sc2(100);
sc2.ShowData();
SimpleClass sc3(100, 200);
sc3.ShowData();
return 0;
}
SimpleClass sc1();
이런식으로 선언할 수 없음에 유의하자. 매개변수가 선언되지 않았으므로 ()의 생략은 가능하지만, 생성자는 함수 원형과 똑같으므로 함수의 선언인지 생성자의 사용인지 모호하게 된다.
지금까지 정보 은닉을 목적으로 멤버 변수들을 private하게 선언하였다. 따라서 객체 멤버 변수의 초기화를 목적으로 Initmembers
라는 이름의 초기화 함수를 정의하고 호출해서 사용했다.
두 개의 a 객체를 함께 생성하는 A 클래스의 생성자 정의는 어떻게 해야 할까?
A 객체를 생성하는 과정에서 a 클래스의 생성자를 통해서 a 객체를 초기화 할 수는 없을까?
Rectangle::Rectangle(const int &x1, const int &y1, const int &x2, const int &y2)
:upleft(x1, y1), lowright(x2, y2){ }
위와 같이 생성자 함수 뒤에 :
를 붙여서 사용하면 된다.
멤버 이니셜라이저는 객체가 아닌 멤버의 초기화에도 사용할 수 있다.
class A
{
private:
int num1;
int num2;
public:
A(int n1, int n2)
:num1(5){
num2 = 8;
}
};
이렇게 사용할 경우, 초기화의 대상을 명확하게 할 수 있고 성능에서도 이점이 있다.
이니셜라이저를 사용하면 선언과 동시에 초기화가 이루어지는 형태로 바이너리 코드가 생성된다. 따라서,const 멤버 변수도 이니셜라이저를 사용하면 초기화가 가능하다!
앞서 클래스를 공부하며 나왔던 과일 장수 코드에서 사과의 가격(APPLE_PRICE
)은 변함이 없으므로 const 변수로 생성했었다. 이제 이걸 초기화 할 수 있다.
class FruitSeller
{
private:
const int APPLE_PRICE;
int numOfApples;
int myMoney;
public:
FruitSeller(int price, int num, int money)
: APPLE_PRICE(price), numOfApples(num), myMoney(money)
{
}
...
}
참조자도 선언과 동시에 초기화가 이루어져야 한다.
class BBB
{
private:
AAA &ref; // 참조자의 멤버 변수로의 선언
const int #
public:
BBB(AAA &r, const int &n)
: ref(r), num(n) // 이니셜라이저를 통한 초기화
{
}
void ShowYourName()
{
ref.ShowYourName();
cout<<"and"<<endl;
cout<<"I ref num "<<num<<endl;
}
};
객체의 생성과정
1. 메모리 공간의 할당
2. 이니셜라이저를 이용한 멤버 변수의 초기화
3. 생성자의 몸체 부분 실행
이니셜라이저가 선언되지 않았으면 1번과 3번으로 이루어진다. 하지만 생성자가 정의되어 있지 않다면?!
객체가 되기 위해서는 반드시 하나의 생성자가 호출되어야 한다.
디폴트 생성자
클래스의 정의에 예외를 두지 않기 위해 인자를 받지 않으며, 내부적으로 아무런 일도 하지 않는 생성자
아래의 A 클래스는 생성자를 정의하고 있지 않다.
class A
{
private:
int num;
public:
int GetNum { return num; }
};
생성자가 정의되어 있지 않으면, 디폴트 생성자가 자동으로 삽입되어 호출된다.
따라서, 실제 호출 시에는
class A
{
private:
int num;
public:
A(){} // 디폴트 생성자
int GetNum { return num; }
};
이렇게 디폴트 생성자가 삽입된다.
모든 객체는 한번의 생성자 호출을 동반한다.
A * ptr = new A;
이렇게 new 연산자를 이용한 객체의 생성 역시 객체의 생성 과정에서 생성자가 호출된다.
하지만, C언어의 malloc()
함수를 이용하면 클래스의 크기 정보만 바이트 단위로 전달되기 때문에 생성자는 호출되지 않는다.
따라서, 객체를 동적으로 할당하려면 반드시 new
연산자를 이용해야 한다.
디폴트 생성자는 생성자가 하나도 정의되어 있지 않을 때만 삽입된다.
class A
{
private:
int num;
public:
A(int n) : num(n) { }
};
위와 같은 경우에는 디폴트 생성자가 정의되지 않는다.
객체 생성 가능
A aaa(10);
A * aaaptr = new A(2);
객체 생성 불가능
A aaa;
A * aaaptr = new A;
만약 불가능 예시의 형태로 객체를 생성하기를 원한다면,
A() : num(0) { }
의 형태로 생성자를 추가해야 한다.
<출처 : 윤성우의 열혈 C++ 프로그래밍>
위 책을 공부하며 정리한 내용입니다.