-
[04/25~] C++a 2022. 4. 25. 08:55반응형
04-2 캡슐화
하나의 캡슐로 이뤄진 약이 복용자에게 제공되는 기능은 '재채기, 콧물, 코막힘'의 완화이다. 그런데 이러한 약이 재채기용, 콧물요, 코막힘용으로 나눠져 있다면, 그래서 코감기에 걸렸을 때 세 알의 약을 복용해야 한다면, 이는 캡슐화가 이뤄지지 않은 상황이다. 하나의 목적 하에 둘 이상의 기능이 모여서 하나의 목적을 달성하고 있다. 다시 말해서 캡슐화가 돼 있는 상황이다.
#include <iostream> using namespace std; class SinivelCap{ public : void Take() const { cout<<"콧물 완치"<<endl;} }; class SneezeCap{ public: void Take() const { cout<<"재채기 완치"<<endl;} }; class SnuffleCap{ public: void Take() const { cout<<"코막힘 완치"<<endl;} }; class CONSTC699{ private: SnivelCap sin; SneezeCap sne; SnuffleCap snu; public: void Tak() const{ sin.Take(); sne.Take(); snu.Take();} }; class ColdPatient{ public : void TakeCONSTC699(const CONSTC699 &cap) const { cap.Take();} }; int main(void){ CONSTC699 cap; ColdPatient sufferer; sufferer.TakeCONSTC699(cap); return 0; }
04-3 생성자와 소멸자
지금까지는 객체를 생성하고 객체의 멤버변수 초기화를 목적으로 InitMembers라는 이름의 함수를 정의하고 호출하였다. 정보은닉을 목적으로 멤버변수들을 private으로 선언했으니 어쩔 수 없는 일이었다. 그런데 '생성자'라는 것을 이용하면 객체도 생성과 동시에 초기화할 수 있다.
1. 생성자의 이해
생성자의 이해를 위해 간단한 클래스 하나를 정의하겠다.
class SimpleClass{ private: int num; public: SimpleClass(int n){ num = n; } int GetNum() const{ return num; } };
SimpleClass라는 클래스에 SimpleClass라는 함수가 있다. 이게 바로 생성자이다. 반환형은 선언되지 않으며, 실제로 반환하지 않는다.
이러한 생성자는 다음의 특징을 갖는다.
"객체 생성 시 딱 한 번 호출된다."
이전에 생성자를 정의하지 않았을 때, 우리는 다음과 같은 방식으로 객체를 생성하였다.
SimpleClass sc;
SimpleClass *ptr = new SimpleClass;그러나 생성자가 정의되었으니, 객체생성과정에서 자동으로 호출되는 생성자에게 전달할 인자의 정보를 다음과 같이 추가해야 한다.
SimpleClass sc(20);
SimpleClass *ptr = new SimpleClass(30);생성자도 함수의 일종이니, 오버로딩이 가능하며 매개변수에 '디폴트 값'을 설정할 수 있다.
2. 멤버 이니셜라이져(Member initializer)를 이용한 멤버 초기화
다음은 생성자가 추가된 Rectangle 클래스의 선언이다.
class Rectangle{ private: Point upLeft; Point lowRight; public: Rectangle(const int &x1, const int &y1, const int &x2, const int &y2); void ShowRecInfo() const; };
생성자는 직사각형을 이루는 두 점의 정보를 직접 전달할 수 있게 정의하였다. 물론 이 정보를 통해서 두 개의 Point 객체가 초기화되어야 한다. 그럼 이어서 Rectangle 클래스의 생성자 정의를 보이겠다.
Rectangle::Rectangle(const int &x1, const int &y1, const int &x2, const int &y2) :upLeft(x1, y1), lowRight(x2,y2){ }
이 중에서
:upLeft(x1, y1), lowRight(x2,y2)
이 부분이 '멤버 이니셜라이져' 이다. 그리고 이것의 의미는 다음과 같다.
" 객체 upLeft의 생성과정에서 x1과 y1을 인자로 전달받는 생성자를 호출하라. "
" 객체 lowRight의 생성과정에서 x2과 y2를 인자로 전달받는 생성자를 호출하라. "
이렇듯 멤버 이니셜라이저는 멤버변수로 선언된 객체의 생성자 호출에 활용된다. 그럼 Point, Rectangle클래스와 관련해서 완성된 전체 예제를 보이겠다.
[Point.h]
#ifdef __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(){ return x;} int Point::GetY(){ return y;} bool Point::SetX(int xpos){ if(0>xpos || xpos >100){ cout<<"out of range"<<endl; return false; } x = xpos; return true; } bool Point::SetY(int ypos){ if(0>ypos || ypos > 100){ cout<<"out of range"<<endl; return false; } y = ypos; return true; }
[Rectangle.h]
#ifndef __RECTANGLE_H_ #define __RECTANGLE_H_ #incude "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.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) {} void Rectangle::ShowRecInfo() const { cout<<"upLeft"<<'['<<upLeft.GetX()<<","; cout<<upLeft.GetY()<<"]"<<endl; cout<<"lowRight"<<"["<<lowRight.GetX()<<","; cout<<lowRight.GetY()<<"]"<<endl; }
[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) 메모리의 공간 할당
2) 생성자를 통한 멤버변수의 초기화
3) 생성자의 몸체 부분 실행
c++의 모든 객체는 위의 세 과정을 순서대로 거쳐서 완성된다. 물론 이니셜라이저가 선언되지 않았다면, 메모리 공간의 할당과 생성자의 몸체부분의 실행으로 객체 생성은 완성된다.
" 그럼 생성자도 정의되어 있지 않다면, 메모리 공간의 할당만으로 객체 생성이 완성되는가?"
그건 아니다. 생성자는 이니셜라이저처럼 선택적으로 존재하는 대상이 아니다. 우리가 처음에 정의했던 클래스에는 생성자가 존재하지 않았다. 따라서 생성자는 있을 수도 있고, 없을 수도 있는 것으로 생각하기 쉽다. 하지만 생성자는 반드시 호출이 된다. 우리가 생성자를 정의하지 않으면, '디폴트 생성자'가 자동으로 호출이 된다.
3. 멤버 이니셜라이저를 이용한 변수 및 const 상수 초기화
멤버 이니셜라이저는 객체가 아닌 멤버의 초기화에도 사용할 수 있다. 프로그래머는 생성자의 몸체에서 초기화 하는 방법과 이니셜라이저를 이용하는 초기화 방법 중에서 선택이 가능하다. 그러나 일반적으로 멤버 변수의 초기화에 있어서는 이니셜라이저를 선호하는 편이다.
- 초기화의 대상을 명확히 인지할 수 있다.
- 성능에 약간의 이점이 있다.
많은 c++ 프로그래머들은 이니셜라이저가 더 명확한 표현이라고 생각한다.
num1(n1) == int num1 = n1;
이니셜라이저를 통해 초기화되는 멤버는 선언과 동시에 초기화가 이뤄지는 것과 같은 유형의 바이너리 코드를 구성한다. 반면, 생성자의 몸체에서 보인 초기화는 선언과 초기화를 별도의 문장에서 진행하는 형태로 바이너리 코드가 생성된다. 이를 통해 다음의 가능성을 발견하게 된다. const 변수는 선언과 동시에 초기화해야 하기 때문이다.
"const 멤버변수도 이니셜라이저를 사용하면 초기화가 가능하다"
이니셜라이저를 이용한 예시는 다음과 같다.
#include <iostream> using namespace std; 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) { } int SlaeApples(int money) //사과를 파는 행위 { } void ShowSaleResult() const { } }; class FruitBuyer{ private: int myMoney; int numOfApples; public: FruitBuyer(int money) : myMoney(money), numOfApples(0) { } void BuyApples(FruitSeller &seller, int money){ } void ShowBuyResult() const{ } }; int main(void){ // }
4. 이니셜라이저의 이러한 특징은 멤버변수로 참조자를 선언할 수 있게 합니다.
const 변수와 마찬가지로 '참조자'도 선언과 동시에 초기화가 이뤄져야 한다. 따라서 이니셜라이저를 이용하면 참조자도 멤버변수로 선언될 수 있다.
[ReferencMember.cpp]
#include <iostream> using namespace std; class AAA{ public: AAA(){ cout<<"empty object"<<endl; } void ShowYorName(){ cout<<"I'm class AAA"<<endl; } }; class BBB{ private: AAA &ref; const int # public: BBB(AAA &r, const int &n) :ref(f), num(n) { } void ShowYourName(){ ref.ShowYourName(); cout<"and"<<endl; cout<<"I ref num"<<num<<endl; } }; int main(void){ AAA obj1; //객체 생성 시 생성자를 호출합니다. BBB obj2(obj1, 20); //참조자로 선언된 멤버변수를 초기화 합니다. obj2.ShowYourName(); return 0; }
5. 디폴트 생성자
메모리 공간의 할당 이후에 생성자의 호출까지 완료돼야 '객체'라고 할 수 있다. 즉, 객체가 되기 위해서는 반드시 하나의 생성자가 호출되어야 한다.
new 연산자가 아닌 malloc 함수를 이용하면 생성자는 호출되지 않는다. malloc 함수 호출 시 실제로는 클래스의 크기 정보만 바이트 단위로 전달되기 때문에 생성자가 호출될 리가 없다.
6. private 생성자
객체의 생성은 보통 클래스의 외부에서 진행되기 때문에 생성자는 public으로 선언돼야 한다. 그런데 클래스 내부에서 객체를 생성한다면, private으로 선언해도 된다.
반응형'a' 카테고리의 다른 글
[디논설] number representation (0) 2022.05.02 [프로세서] (0) 2022.04.27 [디논설] half adder / full adder / ripple carry adder / look-ahead adder (0) 2022.04.21 [수치해석] (0) 2022.04.20 [그리디알고리즘] 집합 커버 문제 / 강의실 배정 문제 (1) 2022.04.17