2024. 4. 12. 18:22ㆍ[C++]/C++ 언어 기초
'생성자'라는 것을 이용하면 객체도 생성과 동시에 초기화할 수 있다.
생성자의 이해
이러한 유형의 함수를 가리켜 '생성자'라고 하고, 다음의 형태를 가지고 있다.
- 클래스의 이름과 함수의 이름이 동일하다.
- 반환형이 선언되어 있지 않으며, 실제로 반환하지 않는다.
- 객체 생성 시 딱 한번 호출된다.
생성자 호출
- 생성자도 함수의 일종이니 오버로딩이 가능하다.
- 생성자도 함수의 일종이니 매개변수에 '디폴트 값'을 설정할 수 있다.
Constructor1.cpp
#include <iostream>
using namespace std;
class SimpleClass
{
int num1;
int num2;
public:
SimpleClass()
{
num1=0;
num2=0;
}
SimpleClass(int n)
{
num1=n;
num2=0;
}
SimpleClass(int n1, int n2)
{
num1=n1;
num2=n2;
}
/*
SimpleClass(int n1=0, int n2=0)
{
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;
}
0 0
100 0
100 200
- 위와 같이 오버로딩이 가능하다. 또한, 디폴트 값도 설정이 가능하다.
다음과 같이 문장을 구성하면 안된다.
SimpleClass sc1()
이는 두가지로 해석할 수 있다.
- 객체의 생성 : SimpleClass의 객체 sc1을 생성하고, 이 sc1은 인자를 받지 않는 생성자를 호출한다.
- 함수의 원형 선언 : Sc1은 함수의 이름이고, void형 함수이며 반환형은 SimpleClass이다.
컴파일러는 1인지 2인지 구분이 불가해서 위와 같이 문장을 구성하는 것은 불가능하다.
Point, Rectangle 클래스에 생성자 적용 예시
- Rectangle 클래스의 생성자 정의는 조금 더 생각을 해야한다.
- Ractangle 클래스는 두 개의 Point 객체를 멤버로 지니고 있어서 Rectangle 객체가 생성되면, 두 개의 Point 객체가 함께 생성된다.
"Rectangle 객체를 생성하는 과정에서 Point 클래스의 생성자를 통해 Point 객체를 초기화할 수 없을까?
'멤버 이니셜라이저(member initializer)'를 이용한 멤버 초기화
class Rectangle{
private:
Point upLeft;
Point lowRight;
public:
Rectangle(const int &x1, const int &y1, const int &x2, const int &y2);
void ShowRecInfo() const;
};
Rectangle::Rectangle(const int &x1, const int &y1, const int &x2, const int &y2)
:upLeft(x1, x2), lowRight(x2,y2)
{
//empty
}
이 중에서 다음의 내용이 '멤버 이니셜라이저'이다.
:upLeft(x1, y1), lowRight(x2, y2)
이것을 의미하는 바는 각각 다음과 같다.
- 객체 upLeft의 생성과정에서 x1과 y1을 인자로 전달받는 생성자를 호출하라
- 객체 lowRight의 생성과정에서 x2과 y2을 인자로 전달받는 생성자를 호출하라
마지막으로 우리는 객체의 생성과정을 다음과 같이 정리할 수 있다.
- 1단계: 메모리 공간의 할당
- 2단계: 이니셜라이저를 이용한 멤버변수(객체)의 초기화
- 3단계: 생성자의 몸체부분 실행
'멤버 이니셜라이저(member initializer)'를 이용한 변수 및 const 상수(변수) 초기화
- '멤버 이니셜라이저'는 객체가 아닌 멤버의 초기화에도 사용할 수 있다.
num1(n1)
- num1을 n1 값으로 초기화하라는 뜻이 된다.
- 일반적으로 멤버변수의 초기화에 있어서는 이니셜라이저를 선호하는 편이다.
- 초기화의 대상을 명확히 인식할 수 있다.
- 성능에 약간의 이점이 있다.
- 이니셜라이저의 초기화는 선언과 동시에 초기화되는 형태이므로, 참조자의 초기화도 가능하다.
디폴트 생성자(Default Constructor)
- 객체가 되기 위해서는 반드시 하나의 생성자가 호출되어야 한다.
- 생성자를 정의하지 않는 클래스에는 C++ 컴파일러에 의해서 디폴트 생성자라는 것이 자동으로 삽입된다.
- 디폴트 생성자는 인자를 받지 않으며, 내부적으로 아무런 일도 하지 않는 생성자이다.
- 생성자를 정의하지 않으면 인자를 받지 않고, 하는 일이 없는 디폴트 생성자라는 것이 컴파일러에 의해서 추가된다.
- 따라서 모든 객체는 무조건 생성자의 호출 과정을 거쳐서 완성된다.
생성자 불일치
- 매개변수가 void형으로 선언되는 디폴트 생성자는, 생성자가 하나도 정의되어 있지 않을 때에만 삽입이 된다.
- 즉, 다음과 같이 정의된 클래스에는 디폴트 생성자가 삽입되지 않는다.
private 생성자
- 지금까지 객체의 생성이 클래스의 외부에서 진행됐기 때문에 생성자는 public으로 선언되어야 한다.
- 그러나 클래스 내부에서 객체를 생성한다면, 생성자를 private으로 선언해도 된다
PrivateConstructor.cpp
#include <iostream>
using namespace std;
class AAA
{
private:
int num;
public:
AAA() : num(0) {}
AAA& CreateInitObj(int n) const
{
AAA * ptr=new AAA(n);
return *ptr;
}
void ShowNum() const { cout<<num<<endl; }
private:
AAA(int n) : num(n) {}
};
int main(void)
{
AAA base;
base.ShowNum();
AAA &obj1=base.CreateInitObj(3);
obj1.ShowNum();
AAA &obj2=base.CreateInitObj(12);
obj2.ShowNum();
delete &obj1;
delete &obj2;
return 0;
}
0
3
12
소멸자의 이해와 활용
객체생성시 반드시 호출되는 것이 생성자라면, 객체소멸시 반드시 호출되는 것은 소멸자이다.
소멸자는 다음의 형태를 갖는다.
- 클래스의 이름 앞에 '~'가 붙은 형태의 이름을 갖는다.
- 반환형이 선언되어 있지 않으며, 실제로 반환하지 않는다.
- 매개변수는 void형으로 선언되어야 하기 때문에 오버로딩도, 디폴트 값 설정도 불가능하다.
- 이러한 소멸자는 대게 생성자에서 할당한 리소스의 소멸에 사용된다.
- 예를 들어서 생성자 내에서 new 연산자를 이용해서 할당해 놓은 메모리 공간이 있다면, 소멸자에서는 delete 연산자를 이용해 메모리 공간을 소멸한다
Destructor.cpp
#include <iostream>
#include <cstring>
using namespace std;
class Person
{
private:
char * name;
int age;
public:
Person(char * myname, int myage) //생성자
{
int len=strlen(myname)+1;
name=new char[len]; //생성자에서 new 키워드로 메모리 할당
strcpy(name, myname);
age=myage;
}
void ShowPersonInfo() const
{
cout<<"이름: "<<name<<endl;
cout<<"나이: "<<age<<endl;
}
~Person() //소멸자
{
delete []name; //소멸자에서 메모리 해제
cout<<"called destructor!"<<endl;
}
};
int main(void)
{
Person man1("Lee dong woo", 29);
Person man2("Jang dong gun", 41);
man1.ShowPersonInfo();
man2.ShowPersonInfo();
return 0;
}
이름: Lee dong woo
나이: 29
이름: Jang dong gun
나이: 41
called destructor!
called destructor!
실습 문제
문제 1
앞서 제시한 문제 4-2를 해결하였는가? 당시만 해도 생성자를 설명하지 않은 상황이기 때문에 별도의 초기화 함수를 정의 및 호출해서 Point, Circle, Ring 클래스의 객체를 초기화 하였다. 이 때 구현한 답에 대해서 모든 클래스에 생성자를 정의해보자
내 풀이
#include "stdafx.h"
#include <iostream>
#include <cstring>
using namespace System;
using namespace std;
class Point
{
private:
int xpos, ypos;
public:
Point(int x, int y):xpos(x), ypos(y){}
void ShowPointInfo() const
{
cout << "[" << xpos << ", "<< ypos << "]" << endl;
}
};
class Circle{ //
private:
Point pos;
int radius;
public:
Circle(int x, int y, int r) : pos(x, y), radius(r){ }
void ShowCircleInfo() const{
cout<<"radius: "<<radius << endl;
pos.ShowPointInfo();
}
};
class Ring{
private:
Circle c1;
Circle c2;
public:
Ring(int x1, int y1, int r1, int x2, int y2, int r2):c1(x1, y1, r1), c2(x2, y2, r2){}
void ShowRingInfo() {
cout << "Inner Circle Info..." << endl;
c1.ShowCircleInfo();
cout << "Outer Circle Info..." << endl;
c2.ShowCircleInfo();
}
};
int main (void)
{
Ring ring(1, 1, 4, 2, 2, 9);
ring.ShowRingInfo();
return 0;
}
문제 2
명함을 의미하는 NameCard 클래스를 정의해보자. 이 클래스에는 다음의 정보가 저장되어야 한다.
- 성명
- 회사이름
- 전화번호
- 직급
단, 직급 정보를 제외한 나머지는 문자열의 형태로 저장을 하되, 길이에 딱 맞는 메모리 공간을 할당 받는 형태로 정의하자 그리고 직급 정보는 int형 멤버변수를 선언해서 저장을 하되, 아래의 enum 선언을 활용해야 한다.
enum{CLERK, SENIOR, ASSIST, MANAGER};
위의 enum 선언에서 정의된 상수는 순서대로 사원, 주임, 대리, 과장을 뜻한다.
그럼 다음 main 함수와 실행의 예를 참조하여, 이 문제에서 원하는 형태대로 NameCard 클래스를 완성해보자
int main(void)
{
NameCard manClerk("Lee", "ABCEng", "010-1111-2222", COMP_POS::CLERK);
NameCardman SENIOR("Hong", "OrangeEng", "010-3333-4444", COMP_POS::SENIOR);
NameCard manAssist("Kim", "SoGoodComp", "010-5555-6666", COMP_POS::ASSIST);
manClerk.ShowNameCardInfo();
manSENIOR.ShowNameCardInfo();
manAssist.ShowNameCardInfo();
return 0;
}
내 풀이
#include "stdafx.h"
#include <iostream>
#include <cstring>
using namespace System;
using namespace std;
namespace COMP_POS
{
enum{CLERK, SENIOR, ASSIST, MANAGER};
void ShowPositionInfo(int pos){
switch(pos){
case CLERK:
cout<<"사원"<<endl;
break;
case SENIOR:
cout<<"주임"<<endl;
break;
case ASSIST:
cout<<"대리"<<endl;
break;
case MANAGER:
cout<<"과장"<<endl;
break;
}
}
}
class NameCard{
private:
char * name;
char * company;
char * phone;
int position;
public:
NameCard(char * _name, char * _company, char * _phone, int pos):position(pos){
name = new char[strlen(_name)+1];
company = new char[strlen(_company)+1];
phone = new char[strlen(_phone)+1];
strcpy_s(name, strlen(_name) + 1, _name);
strcpy_s(company, strlen(_company) + 1, _company);
strcpy_s(phone, strlen(_phone) + 1, _phone);
}
void ShowNameCardInfo(){
cout<<"이름: "<<name<<endl;
cout<<"회사: "<<company<<endl;
cout<<"전화번호: "<<phone<<endl;
cout<<"직급: "; COMP_POS::ShowPositionInfo(position);
cout<<endl;
}
~NameCard(){
delete []name;
delete []company;
delete []phone;
}
};
int main (void)
{
NameCard manClerk("Lee", "ABCEng", "010-1111-2222", COMP_POS::CLERK);
NameCard manSENIOR("Park", "OrangeEng", "010-3333-4444", COMP_POS::SENIOR);
NameCard manAssist("Kim", "SoGoodComp", "010-5555-6666", COMP_POS::ASSIST);
manClerk.ShowNameCardInfo();
manSENIOR.ShowNameCardInfo();
manAssist.ShowNameCardInfo();
return 0;
}
'[C++] > C++ 언어 기초' 카테고리의 다른 글
18. [C++]복사 생성자(Copy Constructor) (0) | 2024.04.15 |
---|---|
04-05 은행계좌문제 2단계 (0) | 2024.04.15 |
15. [C++] 객체지향 프로그래밍의 이해, 정보은닉, 캡슐화 (0) | 2024.04.12 |
14. [C++] 클래스(Class)와 객체(Object) (1) | 2024.04.12 |
13. [C++] C++에서의 구조체 (0) | 2024.04.12 |