[C++] 챕터 4
정보 은닉
정보은닉의 이해
class Point
{
// 정보 은닉 실패
public :
int x; //x좌표의 범위는 0 이상 100이하
int y; // y좌표의 범위는 0 이상 100 이하
};
class Rectangle
{
public :
Point upLeft;
Point lowRight;
public :
void ShowRecInfo()
{
cout<<"up left :"<< upLeft.x << upLeft.y<<endl;
cout<<"up left :"<< lowRight.x << lowRight.y<<endl;
}
}
int main (void)
{
Point pos1={-2, 4};
Point pos2={5,9};
Rectangle rec = {pos2, pos2};
rec.ShowRecInfo();
return 0;
}
- 예제에서 보이듯이 멤버 변수의 외부 접근을 허용하면 잘못된 값이 저장되는 문제 발생
- 따라서 멤버변수의 외부 접근 방어, 정보 은닉
- Point 멤버 변수에는 0~100 이외의 값이 들어오는 것을 막는 장치가 없다.
- Rectangle의 멤버 변수에는 좌우 정보가 뒤 바뀌어 저장되는 것을 막을 장치가 없다.
Point 클래스의 정보 은닉 결과
- 클래스의 멤버변수를 private으로 선언하고, 해당 변수에 접근하는 함수를 별도로 정의해서, 안전한 형태로 멤버 변수의 접근을 유도.
class Point
{
private :
int x;
int y;
public :
bool InitMembers(int xpos, int ypos);
int GetX() const;
int GetY() const;
bool SetX(int xpos);
bool SetY(int ypos);
};
bool Point::SetX(int xpos)
{
if(0 > xpos || xpos > 100)
{
cout<<"벗어난 범위의 값 전달"<<endl;
return false;
}
x = xpos;
return true;
}
Rectangle 클래스의 정보 은닉 결과
class Rectangle
{
private :
Point upLeft;
Point lowRight;
public :
bool InitMembers(const Point &ul, const Point &lr);
void showRecInfo() const;
};
bool Rectangle::InitMembers(const Point &ul, const Point &lr)
{
if(ul.GetX() > lr.GetX() || ul.GetY()>lr.GetY())
{
cout<<"잘못된 위치정보 전달"<<endl;
return false;
}
upleft = ul;
lowRight = lr;
return true;
}
const 함수
- 멤버함수의 const 선언
// const 함수 내에서는 동일 클래스에 선언된 멤버변수의 값을 변경하지 못한다!
int GetX() const;
int GetY() const;
void ShowRecInfo() const;
// 이둘은 멤버 함수 입니다.
int GetNum()
{
return num;
}
void ShowNum()
{
cout<<GetNum()<<endl; // 컴파일 에러 발생
// const 함수는 const가 아닌 함수를 호출하지못한다!
}
void InitNum(const EasyClass &easy)
{
num = easy.GetNum(); // 컴파일 에러 발생
// const로 상수화된 객체를 대상으로는 const 멤버함수만 호출이 가능하다
}
캡슐화
콘택 600과 캡슐화
- 약의 복용 순서가 정해져 있다고 한다면, 캡슐화가 매우 필요한 상황
- 캡슐화란! 관련 있는 모든 것을 하나의 클래스 안에 묶어두는 것!
class SinivelCap // 콧물 처치용 캡슐
{
public :
void Take() const {cout<<"콧물이 삭~ 납니다" <<endl;}
};
class SneezeCap // 재채기 처치용 캡슐
{
public :
void Take() const {cout<<"재채기가 맞습니다"<<endl;}
};
class SnuffleCap //코막힘 처치용 캡슐
{
public :
void Take() const {cout<<"코가 뻥 뚤립니다"<<endl;}
};
캡슐화된 콘택600
class CONTAC600
{
private :
// 코감기와 관련 있는 것을 하나의 클래스로 묶었다.
SinivelCap sin;
SneezeCap sne;
SnuffleCap snu;
public :
void Take() const
{
sin.Take();
sne.Take();
snu.Take();
}
생성자의 이해
- 클래스의 이름과 동일한 이름의 함수이면서 반환형이 선언되지 않았고 실제로 반환하지 않는 함수를 가리켜 생성자라고한다.
class SimpleClass
{
private :
int num;
public :
SimpleClass(int n) // 생성자(constructor)
{
num = n;
// 생성자는 객체 생성시 딱 한번만 호출 된다. 따라서 멤버 변수의 초기화에 사용할 수 있다.
}
int GetNum() const
{
return num;
}
};
SimpleClass sc(20);
생성자의 함수적 특성
- 생성자도 함수의 일종이므로 오버로딩이 가능하다.
- 오버로딩 : 같은 이름의 메서드 여러개를 가지면서 매개변수의 유형과 개수가 다르도록 하는 기술
SimpleClass()
{
num1 = 0;
num2 = 0;
}
SimpleClass(int n)
{
num1 = 0;
num2 = 0;
}
SimpleClass(int n1, int n2)
{
num1 = n1;
num2 = n2;
}
SimpleClass(int n1 = 0, int n2 = 0)
{
num1 = n1;
num2 = n2;
}
Point, Rectangle 클래스에 생성자 적용
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);
};
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::Point(const int &xpos, const int &ypos)
{
x = xpos;
y = ypos;
}
Rectangle::Rectangle(const int &x1, const int &y1, const int &x2, const int &y2)
:upLeft(x1,y1), lowRight(x2,y2)
{} //객체 upLeft/lowRight 의 생성과정에서 x1/x2와 y1/y2를 인자로 전달 받는 생성자를 호출 하라.
// 이런식으로 초기화할 경우, 선언과 동시에 초기화되는 형태로 바이너리가 구성된다.
- 1단계 : 메모리 공간의 할당
- 2단계 : 이니셜라이저를 이용한 멤버변수(객체)의 초기화
- 3단계 : 생성자의 몸체부분 실행
디폴트 생성자
-
생성자를 정의하지 않으면 인자를 받지 않고, 하는 일이 없는 디폴트 생성자라는 것이 컴파일러에 의해서 추가된다.
-
따라서 모든 객체는 무조건 생성자의 호출 과정을 거쳐서 완성된다.
class AAA
{
private :
int num;
public :
int GetNum {return num;}
};
-------->
class AAA
{
private :
int num;
public:
AAA(){} // 디폴트 생성자
int GetNum {return num;}
};
소멸자의 이해
class AAA
{
// empty class
};
--->
class AAA
{
public :
AAA() {}
~AAA() {} // 생성자와 마찬가지로 소멸자도 정의하지 않으면 디폴트 소멸자가 삽입된다.
};
소멸자의 활용
- 생성자에서 할당한 메모리 공간을 소멸시키기 좋은 위치가 소멸자이다.
class Person
{
private :
char * name;
int age;
public :
Person(char * myname, int myage)
{
int len = strlen(myname)+1;
name= new char[len];
strcpy(name, myname);
age = myage;
}
void ShowPersonInfo() const
{
cout<<"name : "<<name<<endl;
cout<<"age : " <<age<< endl;
}
~Person()
{
delete []name;
cout<<"called destructor~"<<endl;
}
};
클래스와 배열 그리고 this 포인터
객체 배열과 객체 포인터 배열
// 객체 배열, 객체로 이뤄진 배열
// 배열 생성시 객체가 함께 생성
// 이 경우 호출되는 생성자는 void 생성자
Person arr[3];
Person * parr = new Person[3];
// 객체 포인터 배열! 객체를 저장할 수 있는 포인터 변수로 이뤄진 배열
// 별도의 객체 생성 과정을 거쳐야한다.
Person * arr[3];
arr[0] = new Person(name, age);
arr[1] = new Person(name, age);
arr[2] = new Person(name, age);
this 포인터의 이해
- this 포인터는 그 값이 결정되어 있지 않은 포인터
- this 포인터는 this가 사용된 객체 자신의 주소값을 정보로 담고 있는 포인터 이기 때문 ```cpp class SoSimple { private : int num; public : SoSimple(int n) : num(n) { cout«num«this«endl; } void ShowSimpleData() { cout«num«endl; } SoSimple * GetThisPointer() { return this; } };
int main(void) { SoSimple sim1(100); SoSimple * ptr1 = sim1.GetThisPointer(); cout«ptr1«”, “; ptr1 -> ShowSimpleData();
SoSimple sim2(200);
SoSimple * ptr2 = sim2.GetThisPointer();
cout<<ptr2<<", ";
ptr -> ShowSimpleData();
return 0; }
// output
// num = 100, addr = 0012FF60 // 0012FF60 , 100
// num = 200, addr = 0012FF48 // 0012FF48, 100
### This 포인터의 활용
- this -> num1 은 멤버변수 num1을 의미한다.
- 객체의 주소값으로 접근할 수 있는 대상은 멤버 변수이지 지역변수가 아니기 때문이다.
```cpp
class TwoNumber
{
private :
int num1;
int num2;
public :
TwoNumber(int num1, int num2)
{
this -> num1 = num2;
this -> num2 = num2;
}
/// same
TwoNumber(int num1, int num2)
: num1(num1), num2(num2)
{
//empty
}
};
Self-reference 의 변환
class SelfRef
{
private:
int num;
public :
SelfRef(int n) : num(n)
{
cout<<"객체 생성"<<endl;
}
SelfRef& Addr(int n)
{
num += n;
return *this;
}
SelfRef& ShowTwoNumber()
{
cout<<num<<endl;
return *this;
}
};
int main(void)
{
SelfRef obj(3);
SelfRef &ref=obj.Adder(2);
obj.ShowTwoNumber();
ref.ShowTwoNumber();
ref.Adder(1).ShowTwoNumber().Adder(2).ShowTwoNumber();
return 0;
}
//output
// 5
// 5
// 6
// 8