책정리/혼자 연구하는 C,C++ 2

27장 캡슐화

GONII 2015. 2. 20. 12:17

27.1 정보 은폐

27.1.1 프로그램의 부품

구조적 프로그래밍 기법에서는 함수가 프로그램을 구성하는 기초적인 부품의 역할을 한다.

C++(객체 지향 프로그래밍)에서는 기존의 함수가 맡고 있던 역할을 객체가 대신한다.

객체 안에는 속성과 동작이 캡슐화되어 있으며 이런 객체들이 모이고 서로 상호 작용하면서 프로그램을 구동

   

객체가 소프트웨어의 부품 역할을 충실히 수행하려면 여러 가지 조건을 만족해야 한다. 우선 관련된 속성과 동작을 한 곳에 모아 스스로 동작할 수 있도록 하는 캡슐화가 가장 기본적인 조건이다.

그리고 곡 필요한 인터페이스만 외부로 노출하고 세부 구현은 숨기는 추상성의 조건도 만족해야 한다. 그래야 최소한의 정보만으로 객체를 쉽게 사용할 수 있으며 부주의한 사용자로부터 자신을 방어할 수도 있다. 캡슐화를 완성하고 추상성의 조건을 만족하여 완벽한 부품이 되기 위해 자신의 정보를 적당히 숨겨야 하며 이것이 정보 은폐의 개념이다. 정보 은폐는 캡슐화의 범주에 속하면서 동시에 추상화를 위한 필요조건이기도 하다.

27.1.2 몰라도 된다

정보 은폐의 개념을 가장 쉽게 단 한마디로 표현하면 '몰라도 된다'는 것

사용자가 굳이 알 필요가 없는 불필요한 정보는 숨김으로써 사용자는 최소한의 정보만으로 부품을 쉽게 사용

27.1.3 몰라야 한다

사용자는 공개된 멤버를 통해 의사 표현만 하고 객체는 지시대로 서비스하는 것이 합리적이다.

소프트웨어의 부품인 객체도 부주의한 사용으로부터 스스로를 방어해야 한다.

OOP는 객체 사용자의 요구 숙련도를 떨어뜨려 고급 인력이 아니더라도 개발을 할 수 있도록 한다. 그 주요한 핵심 중 하나가 객체 사용자가 불필요한 것을 신경쓰지 않도록 하고 관심을 가질 수 없도록 정보를 은폐하여 객체의 안전성을 높이는 것이다.

비공개 멤버에 대해 사용자가 몰라야 하는 또는 다른 이유는 클래스의 안정적인 기능 개선을 위해서이다.

비공개 멤버를 은폐하고 은폐된 멤버는 강제로 쓰지 못하도록 막는 것은 사용자의 자유를 구속하는 것이 아니다. 오히려 사용자가 알아야 할 정보를 최소화하여 쓰기 쉽도록 하며 부주의한 사용으로부터 스스로를 보호하여 신뢰성을 높이고 호환성을 유지한 채로 업그레이드 할 수 있도록 한다.

27.1.4 캡슐화 방법

멤버변수는 객체의 상태를 저장하는 중요한 정보들이므로 외부에서 함부로 변경하지 못하도록 숨기고 멤버 함수는 외부와 인터페이스를 이루는 수단이므로 공개한다.

물론 항상 그렇지는 않아서 어떤 멤버 변수는 공개하는 것이 더 편리한 경우도 있고 내부적인 동작에만 사용되는 멤버 함수는 숨길 수도 있다.

  • 예제 InfoHide

#include <iostream>

using namespace std ;

   

class student

{

private :

int stNum ;

char name[16] ;

unsigned score ;

bool testScore(int aScore)

{

return (aScore >= 0 && aScore <= 100) ;

}

public :

student(int aStNum)

{

stNum = aStNum ;

name[0] = 0 ;

score = 0 ;

}

int getStNum()

{

return stNum ;

}

const char *getName()

{

return name ;

}

void setName(char *aName)

{

strncpy(name, aName, 15) ;

}

unsigned getScore()

{

return score ;

}

void setScore(int aScore)

{

if ( testScore(aScore) )

{

score = aScore ;

}

}

} ;

   

void main ( void )

{

student kim(890612) ;

kim.setName("김천재") ;

kim.setScore(99) ;

cout << "학번 = " << kim.getStNum() << ", 이름 = " << kim.getName() << ", 점수 = " << kim.getScore() << endl ;

}

   

name 멤버 변수는 철저하게 숨겨 외부로부터 보호 하고 이 멤버를 읽고 쓸 때는 get/set 액세스 함수를 경유하여 클래스 작성자가 미리 정해 놓은 규칙을 따라야 한다.

27.1.5 자동차 클래스

  • 예제 CarObject

#include <iostream>

#include <windows.h>

#include <conio.h>

using namespace std ;

   

void        setPosition(int x, int y) ;

   

class car

{

private :

int gear ;

int angle ;

int rpm ;

public :

car()

{

gear = 0 ;

angle = 0 ;

rpm = 0 ;

}

void changeGear( int aGear)

{

if (aGear >= 0 && aGear <= 6)

{

gear = aGear ;

}

}

void rotageWheel( int delta )

{

int tAngle = angle+delta ;

if ( tAngle >= -45 && tAngle <= 45 )

{

angle = tAngle ;

}

}

void accel()

{

rpm = min(rpm+100, 3000) ;

}

void carBreak()

{

rpm = max(rpm-500, 0) ;

}

void run()

{

int speed ;

char mes[128] ;

setPosition(10, 12) ;

if (gear == 0 )

{

cout << "먼저 1~6키를 눌러 기어를 넣으시오" ;

return ;

}

if ( gear == 6 )

{

speed = rpm/100 ;

}

else

{

speed = gear * rpm/100 ;

}

sprintf (mes, "%d의 속도로 %s쪽 %d도 방향으로 , %s진중        ", abs(speed), (angle >= 0 ? "오른" : "왼"), abs(angle), (gear==6 ? "후" : "전") ) ;

cout << mes << endl ;

}

} ;

   

void main ( void )

{

car c ;

int ch ;

   

for (;;)

{

setPosition(10, 10) ;

cout << "1~5: 기어변속, 6:후진, 0:기어 중립" ;

setPosition(10, 11) ;

cout<< "위:엑셀, 아래:브레이크, 좌우:핸들, Q:종료" ;

if (kbhit()) ch = getch() ;

if ( ch == 0xE0 || ch == 0 )

{

ch = getch() ;

switch (ch)

{

case 75:

{

c.rotageWheel(-5) ;

break ;

}

case 77 :

{

c.rotageWheel(5) ;

break ;

}

case 72 :

{

c.accel() ;

break ;

}

case 80 :

{

c.carBreak() ;

break ;

}

}

}

else

{

if ( ch >= '0' && ch <= '6' )

{

c.changeGear(ch-'0') ;

}

else if ( ch == 'Q' || ch == 'q' )

{

exit (0) ;

}

}

c.run() ;

Sleep(100) ;

}

}

   

void        setPosition(int x, int y)

{

COORD pos= {x,y};

SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);

}

27.2 프렌드

27.2.1 프렌드 함수

정볼르 은폐하면 객체의 신뢰성이 높아지고 기능 개선도 용이한 것은 분명하지만 불편한 면이 있다. C++의 액세스 지정자는 너무 엄격해서 일단 숨기면 정상적인 문법으로는 외부에서 이 멤버를 참조할 수 없다. 물론 캐스트 연산자와 포인터를 사용하는 비정상적인 문법을 동원하면 가능할 수도 있지만 이렇게 하면 이식성과 확장성은 포기해야 한다.

예외적으로 지정한 대상에 대해서는 모든 멤버를 공개하는데 이를 프렌드 지정이라고 한다.

프렌드는 전역 함수, 클래스, 멤버 함수의 세 가지 수준에서 지정할 수 있다.

프렌드함수

프렌드로 지정하고 싶은 함수의 원형을 클래스 선언문에 적되 원형 앞에 friend라는 키워드를 붙이면 됨.

프렌드 지정을 하면 마치 클래스 소속의 멤버 함수인 것처럼 이 클래스의 모든 멤버를 자유롭게 액세스 할 수 있는 특권이 부여됨

   

  • 예제 FriendFunc

#include <iostream>

using namespace std ;

   

class date ;

class time

{

friend void outToday( date &, time & ) ;

private:

int hour, min, sec ;

public :

time( int h, int m, int s ) { hour = h ; min = m ; sec = s ; }

} ;

   

class date

{

friend void outToday ( date &, time & ) ;

private :

int year, month, day ;

public :

date( int y, int m, int d ) { year=y ; month=m; day=d ; }

} ;

   

void outToday ( date &d, time &t )

{

cout << "오늘은 " << d.year << "년 " << d.month << "월 " << d.day << "일 이며 지금 시간은 " << t.hour << ":" << t.min << ":" << t.sec <<"입니다" << endl ;

}

   

void main ( void )

{

date d(2005, 01, 02) ;

time t(12, 34, 56) ;

outToday(d, t) ;

}

프렌드만이 문제 해결의 유일한 방법은 아니다.

   

27.2.2 프렌드 클래스

클래스를 통째로 프렌드로 지정할 수 있다.

  • 예제 FriendClass

#include <iostream>

using namespace std ;

   

class time

{

friend class date ;

private :

int hour, min, sec ;

public :

time(int h, int m, int s)

{

hour = h ;

min = m ;

sec = s ;

}

} ;

   

class date

{

private :

int year, month, day ;

public :

date( int y, int m, int d)

{

year = y ;

month = m ;

day = d ;

}

void outToday( time &t )

{

cout << "오늘은 " << year << "년 " << month << "월 " << day << "일 이며 지금 시간은 " << t.hour << ":" << t.min << ":" << t.sec <<"입니다" << endl ;

}

} ;

   

void main ( void )

{

date d (2005, 01, 02) ;

time t (12, 23, 45 ) ;

d.outToday( t ) ;

}

outToday 함수는 date 클래스의 멤버 함수로 선언되었지만 date가 time의 프렌드 클래스로 지정되어 있으므로 outToday는 time 객체의 모든 멤버를 읽을 수 있다.

   

27.2.3 프렌드 멤버 함수

프렌드 클래스 지정은 특정 클래스의 모든 멤버 함수들이 자신의 숨겨진 멤버를 마음대로 읽도록 허락하는 것이다. 멤버 함수의 수가 많을 경우 모든 멤버 함수들이 대상 클래스의 멤버를 액세스 할 필요가 없음에도 불구하고 허용 범위가 너무 넓어져 위험해 진다. 프렌드 멤버 함수는 특정 클래스의 특정 멤버 함수만 프렌드로 지정하는 것이며 꼭 필요한 함수에 대해서만 숨겨진 멤버를 액세스하도록 범위를 좁게 설정할 수 있는 장점이 있다.

  • 예제 FriendMem

#include <iostream>

using namespace std ;

   

class time ;

class date

{

private :

int year, month, day ;

public :

date ( int y, int m, int d ) { year = y ; month = m ; day = d ; }

void outToday(time &t) ;

} ;

   

class time

{

friend void date::outToday( time &t ) ;

private :

int hour, min, sec ;

public :

time( int h, int m, int s ) { hour = h ; min = m ; sec = s ; }

} ;

   

void date::outToday ( time &t )

{

cout << "오늘은 " << year << "년 " << month << "월 " << day << "일 이며 지금 시간은 " << t.hour << ":" << t.min << ":" << t.sec <<"입니다" << endl ;

}

   

void main ( void )

{

date d ( 2005, 01, 23 ) ;

time t ( 12, 34, 56 ) ;

d.outToday( t ) ;

}

27.2.4 프렌드의 특성

  1. 프렌드 지정은 단방향이며 명시적으로 지정한 대상만 프렌드가 된다.
  2. 프렌드 지정은 전이되지 않으며 친구의 친구 관계는 인정되지 않는다.
  3. 복수의 대상에 대해 동시에 프렌드 지정을 할 수 있지만 한 번에 하나씩만 가능하다.
  4. 프렌드 관계는 상속되지 않는다.

       

    프렌드는 OOP의 정보 은폐 원칙에 대한 일종의 예외이다. 숨겨 놓은 정보를 일기 위해 일일이 액세스 함수를 경유하는 것이 너무 불편하고 때로는 외부 함수가 내부 멤버를 액세스 해야 하는 불가피한 경우가 있는데 프렌드가 반드시 필요하지만 빈번하게 사용하는 것은 좋지 않다. 프렌드가 아니면 문제를 해결 할 수 없는 경우에 한해서 조심조심 사용해야 한다.

27.3 정적 멤버

27.3.1 this

  • 예제 THIS

#include <iostream>

using namespace std ;

   

class simple

{

private :

int value ;

public :

simple(int avalue) : value(avalue) { }

void outValue()

{

cout << "value = " << value << endl ;

}

} ;

   

void main ( void )

{

simple a( 1 ), b ( 2 ) ;

a.outValue() ;

b.outValue() ;

}

멤버 함수를 호출 할 때 호출한 객체의 정보가 함수에게 암시적으로 전달된다는 것이다.

멤버 함수는 호출한 객체별로 다른 동작을 할 수 있고 복수 개의 객체가 멤버함수를 공유할 수 있게 된다.

   

우리 눈에 명시적으로 보이지는 않지만 outValue 함수는 자신을 호출한 객체의 번지를 인수로 전달 받는다.

이때 전달받은 숨겨진 인수를 this라고 하는데 호출한 객체의 번지를 가리키는 포인터 상수이다.

   

class형의 멤버 함수들은 class *const this를 받아들이며 this로부터 객체의 고유한 멤버를 액세스 할 수 있다.

멤버 함수

실제 모양

func( )

func ( this )

func ( int a )

func ( this, int a )

func ( char *p, double d )

func ( this, char *p, double d )

this 키워드는 지금 이 함수를 실행하고 있는 객제 그 자체를 표현하는 1인칭 대명사이다.

멤버 함수가 객체를 칭할 필요가 있을 때는 this를 직접 사용해야 한다.

   

객체가 자신을 스스로 삭제하고자 할 때도 this 키워드를 쓴다. 치명적인 에러나 프로그램 종료시 delete this; 한줄이면 가볍게 생을 마감할 수 있다.

   

27.3.2 정적 멤버 변수

정적 멤버 변수는 클래스의 바깥에 선언되어 있지만 클래스에 속하며 객체별로 할당되지 않고 모든 객체가 공유하는 멤버이다.

  • 예제 ObjCount

#include <iostream>

using namespace std ;

   

int num = 0 ;

class count

{

private :

int value ;

public :

count() { num++ ; }

~count() { num-- ; }

void outNum()

{

cout << "현재 객체 개수 = " << num << endl ;

}

} ;

   

void main ( void )

{

count C, *pC ;

C.outNum() ;

pC= new count ;

pC->outNum() ;

delete pC ;

C.outNum() ;

   

cout << "크기 = " << sizeof(C) << endl ;

}

원하는 목적은 달성했지만 이 예제는 전혀 객체 지향적이지 못하다.

전역변수는 세 가지 면에서 문제가 있다.

  • 전역 변수의 문제점
    • 클래스와 관련된 중요한 정보를 왜 클래스 바깥의 전역변수로 선언하는가가 일단 불만이다. 자신의 정보를 완전히 캡슐화하지 못했으므로 이 클래스는 독립적인 부품으로 동작할 수 없다.
    • 전역변수가 있어야만 동작할 수 있으므로 재사용하고자 할 경우 항상 전역변수와 함께 배포해야 한다. 클래스만 배포해서는 제대로 동작하지 않는다.
    • 전역변수는 은폐할 방법이 없기 때문에 외부에서 누구나 마음대로 집적거릴 수 있다. 어떤 코드에서 고의든 실수든 num=123;라고 대입해버리면 생성된 객체수가 123개라고 오판하게 된다.

       

    객체가 외부의 전역변수와 연관되는 것은 캡슐화, 정보 은폐, 추상성 등 모든 OOP 원칙에 맞지 않다.

  • 예제 ObjCount2

#include <iostream>

using namespace std ;

   

   

class count

{

private :

int value ;

static int num ;

public :

count() { num++ ; }

~count() { num-- ; }

void outNum()

{

cout << "현재 객체 개수 = " << num << endl ;

}

} ;

int count::num = 0 ;

   

void main ( void )

{

count C, *pC ;

C.outNum() ;

pC= new count ;

pC->outNum() ;

delete pC ;

C.outNum() ;

   

cout << "크기 = " << sizeof(C) << endl ;

}

static 키워드를 붙여 정적 멤버임을 명시했다.

int num; 선언은 어디까지나 이 멤버가 count의 멤버라는 것을 알릴 뿐이지 메모리를 할당하지는 않는다. 그래서 정적 멤버 변수는 외부에서 별도로 선언 및 초기화해야 한다.

   

count 클래스 선언문 뒤에 num 변수를 다시 정의했는데 이때 반드시 어떤 클래스 소속인지 :: 연산자와 함께 소속을 밝혀야한다.

   

헤더 파일의 클래스 선언부에 정적 멤버 변수에 대한 내부 선언이 있고 구현 파일에 정적 멤버 변수에 대한 외부 정의 및 초기값 지정이 온다.

  

   

27.3.3 정적 멤버 함수

정적 멤버 함수의 개념도 정적 멤버 변수의 경우와 비슷하다. 객체와 직접적으로 연관된다기보다는 클래스와 연관되며 생성된 객체가 하나도 없더라도 클래스의 이름만으로 호출 할 수 있다.

  • 예제 ObjFunc

#include <iostream>

using namespace std ;

   

class count

{

private :

int value ;

static int num ;

   

public :

count() { num++ ; }

~count() { num-- ; }

static void initNum()

{

num = 0 ;

}

static void outNum ()

{

cout << "현재 객체 개수 = " << num << endl ;

}

} ;

int count::num ;

   

void main ( void )

{

count::initNum() ;

count::outNum() ;

count C, *pC ;

C.outNum() ;

pC = new count ;

pC->outNum() ;

   

delete pC ;

pC->outNum() ;

   

cout << "크기 = " << sizeof(C) << endl ;

}

정적 멤버 변수 num을 정의할 때 0으로 초기화하지 않았으며 이 작업은 새로 추가된 정적 멤버 함수 initNum이 담당한다.

   

객체의 개수를 출력하는 outNum함수도 개별 객체에 대한 함수가 아니기 때문에 정적 멤버 함수로 수정할 수 있다. outNum 함수가 객체로부터 호출되지 않으므로 이제 객체가 전혀 생성되지 않은 상태에서도 출력이 가능하다.

   

27.3.4 정적 멤버의 활용

  1. 단 한 번만 해야 하는 전역 자원의 초기화

    데이터 베이스 연결이나 네트워크 연결, 윈도우 클래스 등록 등과 같이 단 한 번만 하면 되는 초기화는 정적 멤버 함수에서 하고 그 결과를 정적 멤버 변수로 저장한다.

  2. 읽기 전용 자원의 초기화

    객체는 스스로 동작할 수 있지만 때로는 외부의 환경이나 자원에 대한 정보를 필요로 한다. 각 정보가 읽기 전용이 아니라 객체별로 다른 값을 가져야 하는 경우라면 얘기가 달라지겠지만 일반적으로 장식이나 정보 취득에 사용되는 자원들은 읽기 전용이며 실행 중에 값이 변하지 않는다. 이런 자원들은 반드시 정적 멤버로 관리해야 하며 그렇지 않을 경우 속도나 크기면에서 아주 불리해진다.

  3. 모든 객체가 공유해야 하는 정보 관리

    중요한 계산을 하는 객체의 경우 계산에 필요한 기준값이 있을 수 있다.

27.4 상수 멤버

27.4.1 상수 멤버

상수 멤버는 한 번 값이 정해지면 변경될 수 없는 멤버이다.

  • 예제 ConstMember

#include <iostream>

using namespace std ;

   

class mathCalc

{

private :

const double pie ;

public :

mathCalc(double apie) : pie(apie) { }

void doCalc(double r)

{

cout << "반지름 " << r << " 인 원의 둘레 = " << r*2*pie << endl ;

}

} ;

   

void main ( void )

{

mathCalc M(3.1416) ;

M.doCalc( 5 ) ;

}

상수 멤버가 모든 객체에 대한 항상 같은 값을 가진다면 객체를 생성할 때마다 매번 초기화할 필요 없이 정적 멤버로 선언한 후 딱 한번만 초기화 할 수도 있다.

   

const double mathCalc::pie = 3.1416 ;

   

  • 예제 constMemberInit

#include <iostream>

using namespace std ;

   

class enemy

{

private :

const int speed ;

   

public :

enemy(int aSpeed) : speed(aSpeed) { }

void move()

{

cout << speed << "의 속도로 움직인다. " << endl ;

}

} ;

   

void main ( void )

{

enemy e1(10), e2(20) ;

e1.move() ;

e2.move() ;

}

객체가 생성될 때 성성자를 통해 딱 한번만 초기화한다.

27.4.2 상수 멤버 함수

상수 멤버 함수는 멤버값을 변경할 수 없는 함수이다.

멤버값을 단순히 읽기만 한다면 이 함수는 객체의 상태를 바꾸지 않는다는 의미로 상수 멤버 함수로 지정하는 것이 좋다. 클래스 선언문의 함수 원형 뒤쪽에 const 키워드를 붙이면 상수 멤버 함수가 된다.

class some

{

public :

int value ;

private :

int GetValue() const ; // 상수 멤버 함수

} ;

  • 예제 ConstFunc

#include <iostream>

using namespace std ;

   

class position

{

private :

int x, y ;

char ch ;

   

public :

position(int ax, int ay, char ach)

{

x = ax ;

y = ay ;

ch = ach ;

}

void outPosition() const

{

cout << "x=" << x << " y = " << y << endl ;

}

void moveTo(int ax, int ay)

{

x = ax ; y = ay ;

}

} ;

   

void main ( void )

{

position here(1, 2, 'a') ;

here.moveTo(20, 5) ;

here.outPosition() ;

   

const position there (3, 4, 'b') ;

// there.moveTo(40, 10) ; // 에러 발생

there.outPosition() ;

}

문자를 읽기 위한 outPosition 함수는 값을 읽기만 하므로 const로 선언되어 있고 moveTo 함수는 위치를 옮기기 위해 x, y 멤버의 값을 변경하므로 const가 아니다.

   

there는 상수 객체로 선언되었으므로 상수 멤버인 outPosition만 호출할 수 있으며 moveTo 호출문은 에러로 처리한다.

   

상수에 어떤 값을 대입하여 변경할 수 없는 것과 마찬가지고 상수 객체의 상태를 변경하는 함수를 호출하는 것도 불가능하다.

   

outPosition의 원형에 const를 빼 버리면 there.outPosition() 호출조차도 에러로 처리된다.

   

이름과 인수 목록이 같더라도 const가 있는 함수와 그렇지 않은 함수를 오버로딩 할 수 있다.

27.4.3 mutable

mutable은 C++에서 새로 추가된 키워드인데 영어 뜻 그대로 번역하면 변덕스럽다는 뜻이다.

상수의 반대 의미로 사용되며 "수정 가능" 정도로 이해하면 된다.

  • 예제 mutable

#include <iostream>

using namespace std ;

   

class some

{

private :

mutable int v ;

   

public :

some() {}

void func() const { v = 0 ; }

} ;

   

void main ( void )

{

some s ;

s.func() ;

const some t ;

t.func() ;

}

func 함수는 상수 멤버로 선언되었지만 멤버 변수 v의 값을 변경할 수 있다.

v가 상수 멤버 함수에게도 값을 변경할 수 있는 mutable로 선언되었기 때문이다.

t는 상수 객체로 선언되었지만 마찬가지로 v를 변경할 수 있다.

   

mutable은 상수 멤버 함수나 상수 객체의 상수성을 완전히 무시해 버린다.

   

  • 예제 mutableinfo

#include <iostream>

using namespace std ;

   

class position

{

private :

int x, y;

char ch ;

mutable char info[256] ;

   

public :

position (int ax, int ay, char ach)

{

x = ax ;

y = ay ;

ch = ach ;

}

void outPosition() const

{

cout << x << endl ;

cout << y << endl ;

cout << ch << endl ;

}

void moveTo(int ax, int ay)

{

x = ax;

y = ay ;

}

void makeInfo() const

{

sprintf(info, "x=%d, y=%d, ch=%c",x,y,ch);

}

void outInfo() const

{

cout << info << endl ;

}

} ;

   

void main ( void )

{

const position here(11, 22, 'z') ;

here.makeInfo() ;

here.outInfo() ;

}

객체의 현재 상태를 문자열로 출력하기 위해 info라는 문자열 버퍼를 멤버 변수로 선언했으며 이 버퍼에 상태를 조립하는 makeInfo와 outInfo 함수를 선언했다.

   

객체의 속성이 아닌 멤버에 대해 예외적으로 아무나 값을 변경할 수 있도록 하는 장치가 바로 mutable이다.

   

상수를 사용하면 귀찮아지는 점들도 많이 있다. 하지만 분명한 것은 처음부터 원칙대로 상수 지정을 제대로 하게 되면 확실히 프로그램의 안전성이 높아진다는 것이다.

27.5 클래스 실습

27.5.1 DArray 클래스

  • 예제 dArray

#include <iostream>

using namespace std ;

   

#define ELETYPE int

class dArray

{

protected :

ELETYPE *ar ;

unsigned size ;

unsigned num ;

unsigned growby ;

   

public :

dArray( unsigned asize = 100, unsigned agrowby = 10 ) ;

~dArray() ;

void Insert( int idx, ELETYPE value ) ;

void Delete( int idx ) ;

void Append( ELETYPE value ) ;

   

ELETYPE getAt( int idx ) { return ar[idx] ; }

unsigned getSize() { return size ; }

unsigned getNum() { return num ; }

void setAt( int idx, ELETYPE value ) { ar[idx] = value ; }

void dump( char *sMark) ;

} ;

   

dArray::dArray( unsigned asize, unsigned agrowby )

{

size = asize ;

growby = agrowby ;

num = 0 ;

ar = (ELETYPE *)malloc(size * sizeof(ELETYPE)) ;

}

   

dArray::~dArray()

{

free(ar) ;

}

   

void dArray::Insert( int idx, ELETYPE value )

{

unsigned need ;

   

need = num + 1 ;

if ( need > size )

{

size = need + growby ;

ar = (ELETYPE *)realloc(ar, size*sizeof(ELETYPE)) ;

}

memmove(ar+idx+1, ar+idx, (num-idx)*sizeof(ELETYPE)) ;

ar[idx] = value ;

num++ ;

}

   

void dArray::Delete( int idx )

{

memmove(ar+idx, ar+idx+1, (num-idx-1)*sizeof(ELETYPE)) ;

num-- ;

}

   

void dArray::Append( ELETYPE value )

{

Insert( num, value ) ;

}

   

void dArray::dump( char * sMark )

{

unsigned i ;

cout << sMark << " => 크기 = " << size << ", 개수 = " << num << " : " ;

for ( i = 0 ; i < num ; i++ )

{

cout << getAt( i ) << ' ' ;

}

cout << endl ;

}

   

void main ( void )

{

dArray ar ;

int i ;

   

for ( i = 1 ; i <= 8 ; i++ )

{

ar.Append( i ) ;

ar.dump("8개 추가") ;

}

ar.Insert( 3, 10 ) ;

ar.dump( "10 삽입" ) ;

ar.Insert( 3, 11 ) ;

ar.dump( "11 삽입" ) ;

ar.Insert( 3, 12 ) ;

ar.dump( "12 삽입" ) ;

ar.Delete( 7 ) ;

ar.dump( "요소 7 삭제" ) ;

}

비공개 영역(private)이 아닌 보호 영역(protected)에 이 멤버를 선언한 것은 상속을 고려해서이다.

구조체를 초기화하던 initArray 함수의 기능은 생성자로 옮겨졌으며 사용자가 깜박 잊고 배열을 초기화하지 않는 실수를 원천적으로 차단했다

배열을 해제하는 기능은 소멸자로 옮겨 졌으며 사용자가 혹시 uninitaraay호출을 까먹더라도 메모리 누수는 발생하지 않는다.

27.5.2 지구와 태양

  • 예제 EarthObject

#include <iostream>

#include <math.h>

#include <windows.h>

#include <conio.h>

using namespace std ;

   

void        setPosition(int x, int y) ;

   

class sun

{

private :

int x, y ;

char ch ;

   

public :

sun(int ax, int ay, char ach) : x(ax), y(ay), ch(ach) {;}

void show()

{

setPosition( x, y ) ;

putch(ch) ;

}

int getX() const

{

return x ;

}

int getY() const

{

return y ;

}

} ;

   

class earth

{

private :

int r ;

int x, y ;

char ch ;

const sun *pSun ;

   

public :

earth(int ar, char ach, sun *apSun) : r(ar), ch(ach), pSun(apSun) {;}

void revolve(double angle)

{

hide() ;

x = int(cos(angle*3.1416/180)*r*2) ;

y = int(sin(angle*3.1416/180)*r) ;

show() ;

}

void show()

{

setPosition( pSun->getX() + x, pSun->getY() + y ) ;

cout << ch << endl ;

}

void hide()

{

setPosition( pSun->getX() + x, pSun->getY() + y );

putch(' ') ;

}

} ;

   

void main ( void )

{

sun s(40, 12, 's') ;

earth e(10, 'e', &s) ;

   

system("cls") ;

s.show() ;

   

for( double angle = 0 ; !kbhit() ; angle += 10 )

{

e.revolve(angle) ;

Sleep(500) ;

}

}

   

void        setPosition(int x, int y)

{

COORD pos= {x,y};

SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);

}

27.5.3 MatrixOop

  • 예제 MatrixOOP

#include <iostream>

#include <windows.h>

#include <conio.h>

using namespace std ;

#define MAX                1024

   

void        setPosition(int x, int y) ;

   

class signal

{

private :

bool exist ;

char ch ;

int x, y ;

int distance ;

int nFrame ;

int nStay ;

void hide()

{

setPosition( x, y ) ;

putch(' ') ;

}

void show()

{

setPosition( x, y ) ;

putch(ch) ;

}

   

public :

signal()

{

exist = false ;

}

bool isExist() const

{

return exist ;

}

void generate( char ach= ' ', int adistance = 0, int anFrame = 0 ) ;

void move() ;

} ;

   

void signal::generate( char ach/*= ' '*/, int adistance/* = 0*/, int anFrame/* = 0*/ )

{

exist = true ;

if (ach == ' ')

{

ch =rand() % 'z'-'a'+1+'A' ;                

}

else

{

ch = ach ;

}

x = rand() % 80 ;

y = 0 ;

   

if( adistance == 0 )

{

distance = rand() % 14 + 9 ;

}

else

{

distance = adistance ;

}

if ( anFrame == 0 )

{

nFrame = nStay = rand() % 15+5 ;

}

else

{

nFrame = nStay = anFrame ;

}

show() ;

}

   

void signal::move()

{

if ( --nStay == 0 )

{

nStay = nFrame ;

hide() ;

if( ++y < distance )

{

show() ;

}

else

{

exist = false ;

}

}

}

   

void main ( void )

{

signal s[MAX] ;

int i ;

   

for ( system("cls"), rand(); !kbhit(); Sleep(50) )

{

if( rand()%15 == 0 )

{

for ( i = 0 ; i < MAX ; i++ )

{

if (!s[i].isExist())

{

s[i].generate() ;

break ;

}

}

}

for ( i = 0 ; i < MAX ; i++ )

{

if ( s[i].isExist() )

{

s[i].move() ;

}

}

}

}

   

void        setPosition(int x, int y)

{

COORD pos= {x,y};

SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);

}

   

  • 예제 SignalManager
  • 예제 InsertCoin
반응형

'책정리 > 혼자 연구하는 C,C++ 2' 카테고리의 다른 글

29장 상속  (0) 2015.02.28
28장 연산자 오버로딩  (0) 2015.02.27
26장 생성자  (0) 2015.02.20
25장 클래스  (0) 2015.02.20
목차  (0) 2015.02.19