일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- AI
- 지도학습
- GPT-4
- OpenAI
- LG
- regression
- 티스토리챌린지
- supervised learning
- LG Aimers
- 머신러닝
- LLM
- PCA
- LG Aimers 4th
- 오블완
- 딥러닝
- Classification
- Machine Learning
- gpt
- 해커톤
- ChatGPT
- deep learning
- 회귀
- 분류
- Today
- Total
SYDev
C++ Chapter 04-3 : 생성자(Constructor)와 소멸자(Destructor)(1) 본문
생성자(Constructor)
지금까지는 정보은닉을 위해서 객체 생성을 하기 전에 Init이라는 함수를 정의하고 호출했다. 그러나 매번 이같은 과정을 반복하기 불편하기 때문에 우리는 생성자(Constructor)라는 개념을 이용할 것이다. '생성자'를 통해서 우리는 객체의 생성과 동시에 초기화가 가능해진다.
다음은 생성자의 특징을 알아보기 위한 간단한 클래스이다.
class SimpleClass
{
private:
int num;
public:
SimpleClass(int n) //(생성자(constructor))
{
num = n;
}
int GetNum() const
{
return num;
}
};
위 클래스에서 SimpleClass라는 함수가 생성자(Constructor)이며 다음과 같은 특징을 가진다.
- 클래스의 이름과 함수의 이름이 동일하다.
- 반환형이 선언되지 않으며, 실제로 반환하지 않는다.
- 객체 생성 시에 딱 한 번 호출된다.
또한 원래는 객체를 생성할 때 다음과 같은 방식으로 했지만,
SimpleClass sc; // 전역, 지역 및 매개변수의 형태
SimpleClass * ptr = new SimpleClass // 동적 할당의 형태
생성자를 사용하면 객체를 생성할 때 다음과 같이 생성자에게 정보 인자를 전달해야 한다.
SimpleClass sc(20); // 생성자에게 20 전달
SimpleClas * ptr = new SimpleClass(30); // 생성자에게 30 전달
이어서 아래 예제로 생성자의 다음과 같은 특징을 파악할 수 있다.
- 생성자는 함수의 일종이므로 오버로딩이 가능하다.
- 생성자는 함수의 일종이므로 매개변수에 '디폴트 값'을 설정할 수 있다.
#include <iostream>
using namespace std;
class SimpleClass
{
private:
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;
}
*/ 나머지 생성자를 모두 주석처리하고 해당 생성자의 주석을 풀면 compile 문제 없음
void ShowData() const
{
cout<<num1<<' '<<num2<<endl;
}
};
int main(void)
{
SimpleClass sc1; // SimpleClass sc1()은 안 됨
sc1.ShowData();
SimpleClass sc2(100); //SimpleClass * ptr2 = new SimpleClass(100);도 가능
sc2.ShowData();
SimpleClass sc3(100, 200); //SimpleClass * ptr3 = new SimpleClass(100, 200);도 가능
sc3.ShowData();
return 0;
}
0 0
100 0
100 200
위 예제에서 우리는 main 함수에 들어있는 호출문이 모두 가능하다는 것을 알 수 있다. 또한, 매개변수가 선언되지 않았을 때 호출문 SimpleClass c1;이 되는 것으로 보아 호출문 SimpleClass c1();도 가능할 것 같지만 이는 제한된다. 그 이유에 대해서 다음 예제를 통해 살펴보자.
#include <iostream>
using namespace std;
class SimpleClass
{
private:
int num1;
int num2;
public:
SimpleClass(int n1 = 0, int n2 = 0)
{
num1 = n1;
num2 = n2;
}
void ShowData() const
{
cout<<num1<<' '<<num2<<endl;
}
};
int main(void)
{
SimpleClass sc1(); // 함수의 원형 선언
SimpleClass mysc = sc1(); // sc1함수의 반환값으로 객체 초기화
mysc.ShowData();
return 0;
}
SimpleClass sc1()
{
SimpleClass sc(20, 30);
return sc;
}
20 30
위 예제에서 보이듯이 함수는 보통 전역적으로 선언하지만, 함수 내부에 지역적으로 선언도 가능하다. 따라서 SimpleClass sc1();과 같은 함수의 원형 선언문을 void형 생성자의 호출문으로 인정해 버리면 컴파일러에서 이를 구별하지 못해 오류가 발생할 것이다. 이런 문제를 방지하고자 해당 형태의 문장은 함수의 원형선언에만 사용된다.
생성자의 개념에 대해 배웠으니 이번에는 Chapter 03의 예제 FruitSaleSim1.cpp에 적용해보자.
#include <iostream>
using namespace std;
class FruitSeller
{
int APPLE_PRICE;
int numOfApples;
int myMoney;
public:
FruitSeller(int price, int num, int money) // 생성자 정의
{
APPLE_PRICE = price;
numOfApples = num;
myMoney = money;
}
int SalesApples(int money)
{
int num = money/1000;
numOfApples -= num;
myMoney += money;
return num;
}
void ShowSalesResult()
{
cout<<"남은 사과 : "<<numOfApples<<endl;
cout<<"판매 수익 : "<<myMoney<<endl;
}
};
class FruitBuyer
{
int myMoney;
int numOfApples;
public:
FruitBuyer(int money) // 생성자 정의
{
myMoney = money;
numOfApples = 0;
}
void BuyApples(FruitSeller &seller, int money)
{
numOfApples += seller.SalesApples(money);
myMoney -= money;
}
void ShowBuyResult()
{
cout<<"현재 잔액 : "<<myMoney<<endl;
cout<<"사과 개수 : "<<numOfApples<<endl;
}
};
int main(void)
{
FruitSeller seller(1000, 20, 0); // 생성자 호출
FruitBuyer buyer(5000); // 생성자 호출
buyer.BuyApples(seller, 2000);
cout<<"과일 판매자의 현황"<<endl;
seller.ShowSalesResult();
cout<<"과일 구매자의 현황"<<endl;
buyer.ShowBuyResult();
return 0;
}
과일 판매자의 현황
남은 사과 : 18
판매 수익 : 2000
과일 구매자의 현황
현재 잔액 : 3000
사과 개수 : 2
멤버 이니셜라이저(Member Initializer)를 이용한 멤버 초기화
이번엔 생성자를 앞의 게시글에서 배운 Point, Rectangle 클래스 예제에도 적용해보자(값의 범위를 제한하는 코드는 편의를 위해 제거함). 참고로, 이전에 정의한 InitMembers 함수를 지우지 않고 생성자 내부에서 이 함수를 호출하여 사용할 수도 있다.
우선 Point.h, Point.cpp부터 살펴보자.
파일명 : Point.h
#ifndef __POINT_H_
#define __POINT_H_
class Point
{
private:
int x;
int y;
public:
Point(const int &xpos, const int &ypos); // 생성자
int GetX() const;
int GetY() const;
bool SetX(int xpos);
bool SetY(int ypos);
};
#endif
파일명 : Point.cpp
#include <iostream>
#include "Point.h"
using namespace std;
Point::Point(const int &xpos, const int &ypos)
{
x=xpos;
y=ypos;
}
int Point::GetX() const {return x;}
int Point::GetY() const {return y;}
bool Point::SetX(int xpos)
{
if(0>xpos || xpos>100)
{
cout<<"벗어난 범위의 값 전달"<<endl;
return false;
}
x=xpos;
return true;
}
bool Point::SetY(int ypos)
{
if(0>ypos || ypos>100)
{
cout<<"벗어난 범위의 값 전달"<<endl;
return false;
}
y=ypos;
return true;
}
이제 다음으로 Rectangle.h, Rectangle.cpp 클래스의 생성자 정의를 생각해봐야 하는데, Rectangle 클래스는 두 개의 Point 클래스 객체를 멤버로 지니고 있어 Rectangle 객체 생성 과정에서 Point 클래스 생성자를 이용해 Point 객체를 초기화할 수 있다. 이때 필요한 것이 바로 '멤버 이니셜라이저(member initializer)'이다.
멤버 이니셜라이저의 사용 방법에 대해서는 다음 예제를 통해 살펴보자.
우선 Rectangle 생성자가 두 점의 정보를 직접 전달받게 헤더파일을 수정한다.
파일명 : Rectangle.h
#ifndef __RECTANGLE_H_
#define __RECTANGLE_H_
#include "Point.h"
class Rectangle
{
private:
Point upLeft;
Point lowRight;
public:
Rectangle(const int &x1, const int &y1, const int &x2, const int &y2);
void ShowRecInfo() const;
};
#endif
다음은 Rectangle 클래스 정의 파트를 살펴보자.
파일명 : Rectangle.cpp
#include <iostream>
#include "Rectangle.h"
using namespace std;
Rectangle::Rectangle(const int &x1, const int &y1, const int &x2, const int &y2)
:upLeft(x1, y1), lowRight(x2, y2) // 멤버 이니셜라이저
{
//empty
}
void Rectangle::ShowRecInfo() const
{
cout << "좌 상단: " << '[' << upLeft.GetX() << ',';
cout << upLeft.GetY() << ']' << endl;
cout << "우 하단: " << '[' << lowRight.GetX() << ',';
cout << lowRight.GetY() << ']' << endl<< endl;
}
생성자 호출문장 아래에 새로 추가된 :upLeft(x1, y1), lowRight(x2, y2)가 '멤버 이니셜라이저'로서 각각 의미하는 바는 다음과 같다.
- "객체 upLeft의 생성과정에서 x1과 y1을 인자로 전달받는 생성자를 호출하라."
- "객체 lowRight의 생성과정에서 x2과 y2을 인자로 전달받는 생성자를 호출하라."
이렇게 변경된 코드를 실행하는 main함수는 다음과 같다.
파일명 : RectangleConstructor.cpp
#include <iostream>
#include "Point.h"
#include "Rectangle.h"
using namespace std;
int main(void)
{
Rectangle rec(1, 1, 5, 5);
rec.ShowRecInfo();
return 0;
}
좌 상단: [1,1]
우 하단: [5,5]
지금까지의 내용을 모두 종합한 객체의 생성과정을 다음과 같다.
- 1단계 : 메모리 공간의 할당
- 2단계 : 이니셜라이저를 이용한 멤버변수(객체)의 초기화
- 3단계 : 생성자의 몸체부분 실행
추가적으로 2단계인 이니셜라이저 선언은 선택사항이지만 생성자는 우리가 정의하지 않더라도 '디폴트 생성자(default constructor)'라는 게 자동으로 삽입되어 호출된다.
출처 : 윤성우, <윤성우의 열혈 C++ 프로그래밍>, 오렌지미디어, 2010.05.12
'Programming Lang > C++' 카테고리의 다른 글
C++ Chapter 04-3 : 생성자(Constructor)와 소멸자(Destructor) 문제풀이 (0) | 2023.07.16 |
---|---|
C++ Chapter 04-3 : 생성자(Constructor)와 소멸자(Destructor)(2) (0) | 2023.07.16 |
C++ Chapter 04-2 : 캡슐화(Encapsulation) 문제풀이 (0) | 2023.07.15 |
C++ Chapter 04-2 : 캡슐화(Encapsulation) 문제풀이 (0) | 2023.07.15 |
C++ Chapter 04-2: 캡슐화(Encapsulation) (0) | 2023.07.15 |