#include <iostream>
using namespace std;
class Point
{
public :
int x;
int y;
};
class Rectangle
{
public :
Point upLeft;
Point lowRight;
public:
void ShowRecInfo()
{
cout << "좌 상단 : " << '[' << upLeft.x << ", ";
cout << upLeft.y << ']' << endl;
cout << "우 하단 : " << '[' << lowRight.x << ", ";
cout << lowRight.y << ']' << endl;
}
};
int main()
{
Point pos1 = { -2,4 };
Point pos2 = { 5,9 };
Rectangle rec = { pos2,pos1 };
rec.ShowRecInfo();
return 0;
}
위 코드에서 x와 y는 public으로 선언되어 어디서든 값을 지정할 수 있다. 이 경우 어떠한 문제가 생기는가?
- 점의 좌표는 지정할 수 있는 범위가 한정되어 있는데 그러한 오류 처리를 하지 못한다.
- 직사각형을 의미하는 Rectangle 객체의 좌 상단 좌표 값이 우 하단 좌표값보다 크다.
제한 된 방법으로 접근을 허용해 잘못된 값이 임의로 지정되지 않도록 하자.
// Point.h
#ifndef __POINT_H_
#define __POINT_H_
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);
};
#endif
//Point.cpp
#include <iostream>
#include "Point.h"
using namespace std;
bool Point::InitMembers(int xpos, int ypos)
{
if (xpos < 0 || ypos < 0)
{
cout << "벗어난 범위의 값 전달 " << endl;
return false;
}
x = xpos;
y = ypos;
return true;
}
int Point::GetX() const {
return x;
}
int Point::GetY() const {
return y;
}
bool Point::SetX(int xpos)
{
if (xpos < 0 || xpos >100)
{
cout << "벗어난 범위의 값 전달 " << endl;
return false;
}
x = xpos;
return true;
}
bool Point::SetY(int ypos)
{
if (ypos < 0 || ypos >100)
{
cout << "벗어난 범위의 값 전달 " << endl;
return false;
}
y = ypos;
return true;
}
해당 코드에서는 멤버 변수 x와 y를 private로 선언해서 임의로 값이 저장되는 것을 방지하였다.
값이 잘못 전달되는 경우 오류 메세지를 출력하도록 하였고, 이 과정을 "정보 은닉"이라고 한다.
정리하여
멤버 변수를 private로 지정 + 해당 변수에 접근하는 함수를 별도로 정의해서 안전한 방식으로 멤버 변수에 값을 전달하는 방법으로 정보은닉을 구현한다.
int GetX() const;
int GetY() const;
void ShowRecInfo() const;
함수 뒤에 const선언이 추가되면 "이 함수 내에서는 멤버 변수에 저장된 값을 변경하지 않겠다" 라는 의미이다.
class SimpleClass
{
private:
int num;
public:
void InitNum(int n)
{
num = n;
}
int GetNum()
{
return num;
}
void ShowNum() const
{
cout << GetNum() << endl;
}
};
다음 코드에서 ShowNum() 함수는 const 선언으로 정의되었으므로, const 가 아닌 num을 바로 출력하는 것에서 에러가 생긴다. const로 선언되지 않는 함수(GetNum()함수)는 멤버 변수에 꼭 저장된 값을 변경하지 않더라도 변경할 수 있는 가능성을 가지고 있는 함수이다. 따라서 변경이 가능한 함수의 호출을 아예 허용하지 않는다.
class EasyClass
{
private:
int num;
public:
void InitNum(int n)
{
num = n;
}
int GetNum()
{
return num;
}
};
class LiveClass
{
private:
int num;
public:
void InitNum(const EasyClass& easy)
{
num = easy.GetNum(); // easy는 const EasyClass 자료형 (매개변수에 의하여)
//num은 LiveClass 필드의 객체
}
};
여기서 num = easy.GetNum() 부분에서 에러가 발생한다.
왜냐하면, InitNum함수에서 easy는 const참조자인데, 이 const 참조자를 대상으로 값을 변경할 수 있는 함수를 호출할 수 없기 때문이다. 따라서, GetNum()을 const로 바꿔주면 정상적으로 GetNum 함수의 호출이 가능하다.
문제 04-1
#include <iostream>
using namespace std;
class FruitSeller
{
private:
int APPLE;
int NUM;
int MONEY;
public:
void InitMembers(int price, int num, int money)
{
APPLE = price;
NUM = num;
MONEY = money;
}
int SaleApples(int money)
{
int num = money / APPLE;
NUM -= num;
MONEY += money;
return num;
}
void ShowSalesResult()
{
cout << "남은 사과 : " << NUM << endl;
cout << "판매 수익 : " << MONEY << endl;
}
};
class FruitBuyer
{
int MONEY;
int APPLE;
public:
void InitMembers(int money)
{
MONEY = money;
APPLE = 0;
}
void BuyApple(FruitSeller& seller, int money)
{
APPLE += seller.SaleApples(money);
MONEY -= money;
}
void ShowBuyResult()
{
cout << "현재 잔액 : " << MONEY << endl;
cout << "사과 개수 : " << APPLE << endl;
}
};
int main()
{
FruitSeller seller;
seller.InitMembers(1000, 20, 0);
FruitBuyer buyer;
buyer.InitMembers(5000);
buyer.BuyApple(seller, 2000);
cout << "과일 판매자의 현황 " << endl;
seller.ShowSalesResult();
cout << "과일 구매자의 현황" << endl;
buyer.ShowBuyResult();
return 0;
}
다음 FruitSaleSim.cpp 에서 '사과의 구매를 목적으로 0보다 작은 수를 전달할 수 없다' 는 조건을 유지할 수 있는 장치가 없다. 이 제약사항을 만족시킬 수 있도록 예제를 변경해보고, 안정성을 높이도록 일부 함수를 const로 지정해보자.
>답
#include <iostream>
using namespace std;
class FruitSeller
{
private:
int APPLE;
int NUM;
int MONEY;
public:
void InitMembers(int price, int num, int money)
{
APPLE = price;
NUM = num;
MONEY = money;
}
int SaleApples(int money) const
{
if (money < 0)
cout << "잘못된 값 전달" << endl;
int num = money / APPLE;
NUM -= num;
MONEY += money;
return num;
}
void ShowSalesResult()
{
cout << "남은 사과 : " << NUM << endl;
cout << "판매 수익 : " << MONEY << endl;
}
};
class FruitBuyer
{
int MONEY;
int APPLE;
public:
void InitMembers(int money)
{
MONEY = money;
APPLE = 0;
}
void BuyApple(const FruitSeller& seller, int money)
{
if (money < 0)
cout << "잘못된 값 전달" << endl;
APPLE += seller.SaleApples(money);
MONEY -= money;
}
void ShowBuyResult()
{
cout << "현재 잔액 : " << MONEY << endl;
cout << "사과 개수 : " << APPLE << endl;
}
};
int main()
{
FruitSeller seller;
seller.InitMembers(1000, 20, 0);
FruitBuyer buyer;
buyer.InitMembers(5000);
buyer.BuyApple(seller, 2000);
cout << "과일 판매자의 현황 " << endl;
seller.ShowSalesResult();
cout << "과일 구매자의 현황" << endl;
buyer.ShowBuyResult();
return 0;
}
- 이렇게 했는데 SaleApples에서 NUM과 MONEY를 못바꾼다는 에러가 뜬다. 어떻게 해결하는거지?
캡슐화
캡슐화란, 하나의 목적 하에 둘 이상의 기능이 모여서 하나의 목적을 달성하는 것을 말한다.
#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를 만들었다.
class Capsule
{
private :
SinivelCap sin;
SneezeCap sne;
SnuffleCap snu;
public:
void Take() const {
sin.Take();
sne.Take();
snu.Take();
}
};
class ColdPatient
{
public:
void TakeCapsule(const Capsule& cap) const { cap.Take(); }
};
int main()
{
Capsule cap;
ColdPatient sufferer;
sufferer.TakeCapsule(cap);
return 0;
}
ColdPatient에서 파생된 객체 sufferer의 TakeCapsule함수를 호출하여 Capsule객체를 넣어주고, Take()함수를 호출하면 멤버 변수 sin, sne, snu의 Take()함수가 호출되어 세가지 cout가 출력된다.
문제 04-2
#include <iostream>
using namespace std;
class Point
{
private:
int xpos, ypos;
public:
void Init(int x, int y)
{
xpos = x;
ypos = y;
}
void ShowPointInfo() const
{
cout << "[" << xpos << ", " << ypos << "]" << endl;
}
};
class Ring {
private:
Point p1;
Point p2;
int p1_radius, p2_radius;
public:
void Init (int x1, int y1, int radius_1, int x2, int y2, int radius_2)
{
p1.Init(x1, y1);
p2.Init(x2, y2);
p1_radius = radius_1;
p2_radius = radius_2;
}
void ShowRingInfo()
{
cout << "Inner Circle Info.. " << endl;
cout << "radius : " << p1_radius << endl;
p1.ShowPointInfo();
cout << "Outter Circle Info.. " << endl;
cout << "raidus : " << p2_radius << endl;
p2.ShowPointInfo();
}
};
int main()
{
Ring ring;
ring.Init(1, 1, 4, 2, 2, 9);
ring.ShowRingInfo();
return 0;
}
캡슐화가 잘 된건지 모르겠다. Circle 클래스를 수정해서 radius 관련 멤버 변수를 선언해도 될 듯한데..
04-3 생성자와 소멸자
생성자란, 다음 두가지 형태를 띠는 함수를 말한다.
- 클래스의 이름과 함수의 이름이 동일하다.
- 반환형이 선언되어 있지 않으며, 실제로 반환하지 않는다.
#include <iostream>
using namespace std;
class SimpleClass
{
private:
int num;
public:
SimpleClass(int n)
{
num = n;
}
int GetNum() const
{
return num;
}
};
생성자를 이용하면 객체를 생성하는 동시에 초기화가 가능하다,
#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;
}
void ShowData() const
{
cout << num1 << " " << num2 << endl;
}
};
int main()
{
SimpleClass sc1;
sc1.ShowData();
SimpleClass sc2(100);
sc2.ShowData();
SimpleClass sc3(100, 200);
sc3.ShowData();
return 0;
}
main 함수의 첫줄 SimpleClass sc1; 을 보자. SImpleClass의 객체 sc1 메소드를 선언한 케이스이다.
SimpleClass sc1();
다음과 같이 함수를 호출하였을 때 문제점은 무엇일까? 다음 코드를 보고 원인을 분석해보자
#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()
{
SimpleClass sc1();
SimpleClass mysc = sc1();
mysc.ShowData();
return 0;
}
SimpleClass sc1()
{
SimpleClass sc(20, 30);
return sc;
}
main 함수에서 SimpleCLass sc1()은 클래스 객체의 생성이 아닌 함수의 원형 선언이 된다.
#include <iostream>
using namespace std;
class FruitSeller
{
private:
int APPLE;
int NUM;
int MONEY;
public:
FruitSeller(int price, int num, int money)
{
APPLE = price;
NUM = num;
MONEY = money;
}
int SaleApples(int money)
{
int num = money / APPLE;
NUM -= num;
MONEY += money;
return num;
}
void ShowSalesResult() const
{
cout << "남은 사과 : " << NUM << endl;
cout << "판매 수익 : " << MONEY << endl;
}
};
class FruitBuyer
{
int MONEY;
int APPLE;
public:
FruitBuyer(int money)
{
MONEY = money;
APPLE = 0;
}
void BuyApple(FruitSeller& seller, int money)
{
APPLE += seller.SaleApples(money);
MONEY -= money;
}
void ShowBuyResult()
{
cout << "현재 잔액 : " << MONEY << endl;
cout << "사과 개수 : " << APPLE << endl;
}
};
int main()
{
FruitSeller seller(1000,20,0);
FruitBuyer buyer(5000);
buyer.BuyApple(seller, 2000);
cout << "과일 판매자의 현황 " << endl;
seller.ShowSalesResult();
cout << "과일 구매자의 현황" << endl;
buyer.ShowBuyResult();
return 0;
}
Init함수를 지우고 생성자로 멤버변수에 값을 할당하였다.
멤버 이니셜라이저를 이용한 멤버 초기화(Member Initializer)
class Rectangle
{
private:
Point upLeft;
Point lowRight;
public:
Rectangle(const int& x1, const int& y1, const int& x2, const int& y2)
:upLeft(x1, y1), lowRight(x2, y2)
{
}
};
Rectangle 생성자안에서 사용된 upLeft, lowRight는 Rectangle 클래스의 객체이다.
객체 upLeft의 생성과정에서 x1과 y1을 인자로 전달받는 생성자를 호출하는 코드이다.
> Point 클래스와 Rectangle 클래스의 이니셜라이저를 이용한 생성자 flow를 이해해보자
1단계 : 메모리 공간의 할당
// 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
#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 (xpos < 0 || xpos >100)
{
cout << "벗어난 범위의 값 전달 " << endl;
return false;
}
x = xpos;
return true;
}
bool Point::SetY(int ypos)
{
if (ypos < 0 || ypos >100)
{
cout << "벗어난 범위의 값 전달 " << endl;
return false;
}
y = ypos;
return true;
}
GetX랑 GetY함수는 엑세스 함수(access function)라고 하는데, 멤버 변수를 private로 선언하면서 클래스 외부에서 Point 클래스의 멤버 변수에 접근하기 위해 사용된다.
2단계 : 이니셜라이저를 이용한 멤버 변수의 초기화
#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_H_
#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.GetX() << ", ";
cout << upLeft.GetY() << ']' << endl;
cout << "우 하단 : " << '[' << lowRight.GetX() << ", ";
cout << lowRight.GetY() << ']' << endl << endl;
}
Member Initializer를 이용해 x1과 y1, x2와 y2에 값을 할당한다.
3단계 : 생성자의 몸체부분 실행
#include <iostream>
#include "Point.h"
#include "Rectangle.h"
using namespace std;
int main()
{
Rectangle rec(1, 1, 5, 5);
rec.ShowRecInfo();
return 0;
}
rec 객체를 생성하여 x1 = 1, y1 = 1, x2 = 5, y2 = 5 대입
Point 클래스의 생성자에 의해 upLeft의 매개변수 xpos 와 ypos에 (1,1) , (5,5)가 대입되고, 최종적으로 멤버 변수와 값은 다음과 같이 표시된다.
rec.upLeft.x = 1
rec.upLeft.y = 1
rec.lowRight.x = 5
rec.lowRight.y = 5
따라서 ShowRecInfo()에 의한 GetX() 등의 함수에 적절한 값이 호출된다.
const변수와 마찬가지로, 참조자도 선언과 동시에 초기화가 이루어져야 한다.
#include <iostream>
using namespace std;
class AAA
{
public:
AAA()
{
cout << "empty object" << endl;
}
void ShowYourName()
{
cout << "I am class AAA" << endl;
}
};
class BBB
{
private:
AAA& ref;
const int& num; // 참조자가 멤버 변수로 등장했다!
public:
BBB(AAA& r, const int& n)
: ref(r), num(n) //ref 참조자를 r로 초기화, num 참조자를 n으로 초기화
{
}
void ShowYourName()
{
ref.ShowYourName();
cout << "and" << endl;
cout << " I ref num" << num << endl;
}
};
int main()
{
AAA obj1; //empty object
BBB obj2(obj1, 20);
obj2.ShowYourName(); // I am class AAA and I am ref num 20
return 0;
}
이니셜라이저로 BBB 클래스의 참조자 멤버변수 ref, num에 값을 대입하였고, ShowYourName 함수에서 호출하였다.
'Langauge > C++' 카테고리의 다른 글
[열혈 C++ 프로그래밍] Chapter05(복사 생성자) 정리 및 문제 해결 (0) | 2021.02.16 |
---|---|
[열혈 C++ 프로그래밍] Chapter04(클래스의 완성) 정리 및 문제 해결 #2 (0) | 2021.02.14 |
[열혈 C++ 프로그래밍] Chapter03 - 클래스의 기본 정리 및 문제 해결 (0) | 2021.02.11 |
[열혈 C++ 프로그래밍] Chapter02 - C언어 기반의 C++ 2 (0) | 2021.02.10 |
C++ 벡터(vector) 사용법 정리 (0) | 2021.02.07 |