생성자 정의 및 선언
앞서 배웠던 클래스는 거의 자신만의 매개 변수를 가졌습니다.
따라서 클래스 객체를 생성할 땐 매개 변수도 무조건 초기화해줘야 합니다.
생성자는 매개 변수 초기화만을 수행하는 public 함수이며, 객체를 선언하는 동시에 불러옵니다.
[생성자를 선언할 때의 규칙]
(1) 초기화를 위한 데이터만을 인수로 받습니다.
(2) 매개 변수를 초기화하는 함수이므로, 반환 값은 없습니다. 하지만 void 선언을 하지 않습니다.
(3) 초기화 방법이 여러개일 경우, 오버로딩 규칙에 의해 여러 개를 생성할 수 있습니다.
이제 직접 코드를 통해 생성자 선언과 사용 예시를 살펴보겠습니다.
먼저 class 선언입니다.
class player
{
private:
string name_;
int height_;
public:
player(const string& name, int height);
void Display();
};
C++
복사
운동선수 클래스를 만들었습니다. private 제어 지시자에 매개 변수를 선언하고, public엔 생성자를 선언했습니다. 앞서 말했던 대로, 반환 값이 없지만 void 선언을 하지 않은 것을 볼 수 있습니다. 매개 변수들을 보여줄 Display() 함수도 선언했습니다.
함수 정의를 해줍니다.
생성자는 void와 같은 선언이 없습니다.
player::player(const string& name, int height)
{
name_ = name;
height_ = height;
}
void player::Display()
{
cout << "선수의 이름 : " << name_ << endl;
cout << "선수의 키 : " << height_ << endl;
}
C++
복사
인수를 받아서 그대로 매개 변수 초기화를 진행합니다. Display() 함수는 일정 양식에 맞게 선수의 이름과 키를 보여주도록 합니다.
main() 코드
int main()
{
// 1player nadal("나달", 183);
nadal.Display();
// 2
player federer = player("페더러", 184);
federer.Display();
return 0;
}
C++
복사
두 명의 선수 객체를 선언했습니다. 테니스계에서 유명한 나달과 페더러입니다. 그런데 이들 객체를 생성하는 방법이 조금 다릅니다. 페더러 객체는 명시적으로 호출을 이용하여 선언했습니다. 이 코드들의 결과는 어떨까요?
결과
선수의 이름 : 나달
선수의 키 : 183
선수의 이름 : 페더러
선수의 키 : 184
C++
복사
이쁘게 잘 나온 것을 확인했습니다. 생성자는 클래스 매개 변수를 초기화하는 함수임을 인지하시고, 이제 디폴트 생성자로 넘어가겠습니다.
디폴트 생성자
디폴트 생성자는 매개 변수의 초깃값을 미리 정의해두는 것을 말합니다. 만약 사용자가 객체를 선언하며 초깃값을 명시하지 않으면 컴파일러에 의해 자동적으로 제공하도록 합니다. 단, 클래스에서 생성자가 단 하나도 정의되지 않았을 때만 자동으로 제공합니다. 생성자가 있다면 오류를 발생시킵니다.
초깃값을 명시하지 않고 객체를 생성하고 싶을 때 디폴트 생성자를 정의합니다.
[디폴트 생성자 정의]
(1) 디폴트 인수를 이용합니다.
=> 클래스에서 함수를 정의할 때 디폴트 인수를 미리 선언합니다.
// 클래스 선언class player
{
private:
string name_;
int height_;
public:
player(const string& name = "나달", int height = 183);
void Display();
};
// 함수 정의
player::player(const string& name, int height)
{
name_ = name;
height_ = height;
}
void player::Display()
{
cout << "선수의 이름 : " << this->name_ << endl;
cout << "선수의 키 : " << this->height_ << endl;
}
C++
복사
player(const string& name = "나달", int height = 183); 처럼 함수 원형을 정의할 때부터 디폴트 인수를 깔아놓고 시작하면 이것이 디폴트 생성자가 됩니다.
(2) 함수 오버로딩을 이용합니다.
=> 함수 오버로딩 규칙을 응용하여 여러 개의 생성자를 만드는 것입니다. 그중 하나의 생성자를 디폴트 생성자처럼 이용하는 것이 핵심입니다.
// 클래스 선언class player
{
private:
string name_;
int height_;
public:
player(const string& name, int height);
player();
void Display();
};
// 함수 정의
player::player(const string& name, int height)
{
name_ = name;
height_ = height;
}
player::player()
{
name_ = "쿠쿠롱루삥뽕";
height_ = 120;
}
C++
복사
하나는 name, height 매개 변수가 있으며, 다른 하나는 없습니다. C++은 이 둘을 오버로딩으로 인식하며, 사용자가 만약 디폴트 생성자를 사용하고 싶다면 매개 변수를 넣지 않고 객체를 정의하면 됩니다.
main() 함수입니다.
int main()
{
// 1player nadal("나달", 183);
nadal.Display();
// 2
player federer = player();
federer.Display();
return 0;
}
C++
복사
결과
선수의 이름 : 나달
선수의 키 : 183
선수의 이름 : 쿠쿠롱루삥뽕
선수의 키 : 120
C++
복사
[디폴트 생성자를 가지는 객체의 선언]
=> class이름 객체(); 는 자칫하면 함수로 인식될 수 있습니다. 따라서 디폴트 생성자로 객체를 선언할 땐 다음 3가지의 형태를 따릅니다.
// (1)
player nadal;
// (2)
player nadal = player();
// (3)
player *ptr_nadal = new player;
C++
복사
다만, 마지막 세번째 방법을 이용하고 난 후에는 delete를 통해 꼭 메모리 할당 해제를 해줘야 합니다.
복사 생성자
객체가 다른 객체를 복사하고 싶다면 어떻게 해야 할까요?
player nadal;
player federer = player("페더러", 184);
C++
복사
두 개의 객체가 생성되었습니다. 하나는 디폴트 생성자로 객체를 생성했고, 하나는 명시적으로 매개 변수를 정의했습니다.
그런데 nadal 객체에 federer 객체의 내용을 복사하고 싶습니다.
대입 연산자를 이용했습니다.
nadal = federer
C++
복사
이렇게 복사하는 것을 얕은 복사라고 합니다. 값을 복사하는 것이 아니라 값을 가리키는 포인터를 복사하는 것입니다. 객체 대입을 얕은 복사로 진행하면 문제가 생길 수 있습니다. 따라서 깊은 복사로 객체 대입을 진행해야 합니다.
깊은 복사란? 값 자체를 복사하는 것을 말합니다.
클래스에서는 이런 깊은 복사를 가능케 하는 복사 생성자를 정의할 수 있습니다.
복사 생성자는 자신과 같은 클래스 타입의 객체에 대한 참조를 인수로 받아서 자신을 초기화하는 생성자입니다.
다음은 복사 생성자를 정의한 클래스입니다.
// 복사 생성자는 클래스 객체를 인수로 받습니다.player(const player&);
// 클래스 정의class player
{
private:
string name_;
int height_;
public:
player(const string& name, int height);
player();
player(const player&);
void Display();
};
C++
복사
복사 생성자는 다음과 같은 방식으로 정의됩니다.
이를 보면 알 수 있듯이, 복사 생성자는 값을 복사하는 것에 초점을 맞추고 있습니다.
player::player(const player& another)
{
name_ = another.name_;
height_ = another.height_;
}
C++
복사
main() 함수입니다.
int main()
{
// 1
player federer = player("페더러", 184);
federer.Display();
// 깂 복사player nadal(federer);
nadal.Display();
return 0;
}
C++
복사
페더러 객체를 생성한 후, 나달 객체를 생성할 땐 복사 생성자를 이용했습니다.
이에 따라 페더러 객체의 정보가 모두 나달 객체에도 복사되었습니다.
결과
선수의 이름 : 페더러
선수의 키 : 184
선수의 이름 : 페더러
선수의 키 : 184
C++
복사
소멸자
객체의 수명이 끝나면 정리해주는 멤버 함수입니다.
클래스 이름과 같지만, 앞에 ~을 붙여줘서 구분합니다.
인수, 반환값이 없고, void 선언도 하지 않습니다. 단 하나만 정의합니다.
주의 사항
(1) 만약 new 키워드를 통해 동적 할당을 해서 객체를 생성했다면, delete 키워드를 이용해서 정리해줘야 합니다.
(2) 소멸자는 알아서 작동합니다.
class player
{
private:
string name_;
int height_;
public:
player(const string& name, int height);
player();
player(const player&);
~player();
void Display();
};
C++
복사
~player(); 코드를 통해 소멸자를 정의해줍니다.
소멸자 정의
player::~player()
{
cout << this->name_ << "객체가 소멸되었습니다." << endl;
}
C++
복사
소멸자는 그저 player::~player(){} 만 해도 되지만, 소멸되었다는 것을 알리기 위해 약간의 코드를 첨가했습니다.
main() 함수입니다.
int main()
{
// 1
player federer = player("페더러", 184);
federer.Display();
// 값 복사player nadal(federer);
nadal.Display();
return 0;
}
C++
복사
결과
선수의 이름 : 페더러
선수의 키 : 184
선수의 이름 : 페더러
선수의 키 : 184
페더러객체가 소멸되었습니다.
페더러객체가 소멸되었습니다.
C++
복사