'knowhow/Visual C++'에 해당되는 글 21건

friend 함수

knowhow/Visual C++ 2007. 8. 24. 10:21

* 객체 지향의 특징


1. 객체 중심의 프로그램방식


꼭 필요한 인터페이스만 외부로 노출하고 세부 구현은 숨기는 추상성의 조건도 만족해야 한다. 그래야 최소한의 정보만으로 객체를 쉽게 사용할 수 있으며 부주의한 사용자로부터 자신을 방어할 수도 있다.

 

캡슐화를 완성하고 추상성의 조건을 만족하여 완벽한 부품이 되기 위해 자신의 정보를 적당히 숨겨야 하며 이것이 정보 은폐의 개념이다. 정보 은폐는 캡슐화의 범주에 속하면서 동시에 추상화를 위한 필요조건이기도 하다.

 

     => 객체 중심으로 프로그램을 하고 클래스의 메서드 만을 이용하여 프로그램을 접근한다.

 

1) 정보 은폐의 개념

 

정보 은폐의 개념을 가장 쉽게 단 한마디로 표현하면 "몰라도 된다"는 것이다. 부품을 쓰는 사용자가 알아야 하는 것은 부품을 사용하는 방법뿐이지 부품의 내부 동작이나 상세 구조가 아니다. 사용자가 굳이 알 필요가 없는 불필요한 정보는 숨김으로써 사용자는 최소한의 정보만으로 부품을 쉽게 사용할 수 있게 된다.

 

   예) 자동차 운전과 엔진의 관계

 

   예) 이미지 Jpeg 뷰어 클래스

 

class JpegImage

{

private:                              // 내부적으로 숨겨진 몰라도 되는것

     BYTE *RawData;

     JPEGHEADER Header;

     void DeComp();

     void EnComp();

 

public:                               // 외부로 오픈된 인터페이스  - 읽고 저장하고 그리기만하면된다.

     Jpeg();

     ~Jpeg();

     BOOL Load(char *FileName);

     BOOL Save(char *FileName);

     void Draw(int x, int y);

};

 

   // 사용 예제

 

  JpegImage J;

  J.Load("c:\\Image\\PrettyGirl.jpg");

  J.Draw(10,10);

 

사용자가 이 클래스를 쓰기 위해 알아야 하는 것은 오로지 Load, Save, Draw등 공개된 멤버 함수뿐이다. 이 함수들이 바로 인터페이스 함수들이며 최소한의 인터페이스만 공개하는 것이 추상화의 정의이다.

 

 

2) 정보 은폐를 위한 클래스의 지원 기능 => 접근 지정자

 

C++은 클래스의 정보 은폐 기능을 지원하기 위해 private, public, protected 등의 액세스 지정자를 제공하며 액세스 지정자로 숨길 멤버와 공개할 멤버의 블록을 구성하도록 해 준다.

공개된 멤버는 외부에서 자유롭게 읽을 수 있지만 숨겨진 멤버를 참조하려고 시도하면 컴파일 과정에서 접근할 수 없다는 에러로 처리된다.

 

3) 왜 정보 은폐를 해야 하나 ?  사용자가 알아서 하게 하면 않되나 ?

 

JpegImage 클래스의 경우도 마찬가지이다. RawData 멤버에 압축을 풀기전의 이미지 정보가 저장되어 있는데 이 값을 사용자가 직접 조작하도록 내버려 두면 이미지가 손상될 위험이 있다.

압축을 해제하는 DeComp 함수는 이미지 데이터의 구조 판별과 헤더 분석이 끝나야만 호출할 수 있는데 사용자가 이런 주의 사항을 모르고 아무렇게나 DeComp를 호출하도록 허락해서는 안된다.

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


4) 정보 은폐의 방법

  [ 기본 공식 ]    멤버 변수는 숨기고 멤버 함수를 공개 한다.


모든 객체에 적합한 정보 은폐 공식은 없지만 대충의 가이드라인을 제시해 보면 이렇다. 멤버 변수는 객체의 상태를 저장하는 중요한 정보들이므로 외부에서 함부로 변경하지 못하도록 숨기고 멤버 함수는 외부와 인터페이스를 이루는 수단이므로 공개한다. 숨겨진 멤버 변수는 공개된 멤버 함수를 통해 정해진 방법으로만 액세스하도록 하는 것이 보통이다


: InfoHide

#include <Turboc.h>

 

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; }

     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()

{

     Student Kim(8906123);

     Kim.SetName("김천재");

     Kim.SetScore(99);

     printf("학번=%d, 이름:%s, 점수:%d\n",

          Kim.GetStNum(),Kim.GetName(),Kim.GetScore());

}

 




2. 프랜드 함수


=> 예외 적인 정보 접근의 허용

  이유 : C++의 액세스 지정자는 너무 엄격해서 일단 숨기면 정상적인 문법으로는 외부에서 이 멤버를 참조할 수 없다. 어떤 경우에는 이런 정보 은폐 기능이 방해가 될 수도 있기 때문에 예외적으로 지정한 대상에 대해서는 모든 멤버를 공개할 수 있는데 이를 프렌드 지정이라고 한다.



1) 프랜드 속성의 지정 방식

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


다음은 func 함수를 Some 클래스의 프렌드로 지정한 것이다.

 

class Some

{

     friend void func();

     ....

};

 

=> Some 클래스 선언부에서 func 함수를 프렌드로 지정했으므로 이 클래스의 모든 멤버를 자유롭게 액세스할 수 있는 특권이 부여된다. private영역에 있건 public 영역에 있건 어떤 멤버 변수든지 읽고 쓸 수 있으며 모든 멤버 함수를 자유롭게 호출할 수 있다.

 

 

2) 프랜드 함수의 사용

 

=>OutToday함수는 이 두 클래스의 객체를 인수로 전달받아 날짜와 시간을 동시에 출력한다. 그러기 위해서 OutToday는 양쪽 클래스의 모든 멤버를 읽을 수 있어야 하는데 Date나 Time의 멤버 함수로 포함되면 한쪽밖에 읽을 수 없게 된다

한 함수가 동시에 두 클래스의 멤버 함수가 될 수는 없다.

 

=> 이럴 때 OutToday를 멤버 함수가 아닌 전역 함수로 정의하고 양쪽 클래스에서 이 함수를 프렌드로 지정하면 된다. 이렇게 되면 OutToday는 Data의 year, month, day와 Time의 hour, min, sec을 모두 읽을 수 있다. 마치 양쪽 클래스의 멤버 함수인 것처럼 숨겨진 멤버를 자유롭게 액세스한다

 

 

: FriendFunc

#include <Turboc.h>

 

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)

{

     printf("오늘은 %d년 %d월 %d일이며 지금 시간은 %d:%d:%d입니다.\n",

          d.year,d.month,d.day,t.hour,t.min,t.sec);

}

 

 

void main()

{

     Date D(2005,01,02);

     Time T(12,34,56);

     OutToday(D,T);

}

 

 

=> 프랜드 방식만이 접근이 가능한가 ? 아니다

이 예제의 경우 프렌드만이 문제를 해결하는 유일한 방법은 아니다. 프렌드 지정을 하는 대신 각 클래스에 숨겨진 멤버를 대신 읽어주는 Get, Set 액세스 함수를 공개 영역에 작성하고 OutToday는 액세스 함수들로 값을 읽을 수도 있다. 하지만 이런 방법이 번거롭고 귀찮기 때문에 프렌드 지정이라는 좀 더 간편한 방법을 사용하는 것이다.

 

 

3. 프랜드 클래스

 

=>

두 클래스가 아주 밀접한 관련이 있고 서로 숨겨진 멤버를 자유롭게 읽어야 하는 상황이라면 클래스를 통째로 프렌드로 지정할 수 있다. 클래스 선언문내에 프렌드로 지정하고 싶은 클래스의 이름을 적어주면 된다.

 

예) Any 클래스를 Some 클래스의 프렌드로 지정하는 것이다.

 

class Some

{

     friend class Any;

     ....

};

 

 

: FriendClass

#include <Turboc.h>

 

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)

     {

          printf("오늘은 %d년 %d월 %d일이며 지금 시간은 %d:%d:%d입니다.\n",

              year,month,day,t.hour,t.min,t.sec);

     }

};

 

void main()

{

     Date D(2005,01,02);

     Time T(12,34,56);

     D.OutToday(T);

}

 

예) 프랜드 클래스의 적용 사례

 

MFC 라이브러리의 경우 다음과 같은 프렌드 클래스의 예가 많이 있으며

 

CDocument가 CView의 프렌드

CTime이 CTimeSpan의 프렌드

CToolBar가 CToolTipCtrl의 프렌드

CPropertySheer가 CPropertyPage의 프렌드

 

모두 아주 밀접한 관계에 있는 클래스들인데 MFC의 구조를 공부해 보면 이 클래스들이 왜 프렌드여야 하는지 알게 될 것이다.

CView와 CDocument는 하나의 실체에 대해 각각 외부와 내부를 다루는 관련있는 클래스이다.

CView는 자신이 화면에 출력할 데이터를 읽기 위해 CDocument의 멤버를 마음대로 읽을 수 있어야 하며

CToolTipCtrl 클래스는 툴팁을 보여줄 버튼이나 영역을 구하기 위해 CToolBar의 멤버를 액세스해야 한다.

 

 

4. 클래스내의 프랜드 멤버함수

 

=>프렌드 멤버 함수는 특정 클래스의 특정 멤버 함수만 프렌드로 지정하는 것이며 꼭 필요한 함수에 대해서만 숨겨진 멤버를 액세스하도록 범위를 좁게 설정할 수 있는 장점이 있다.

개념은 프렌드 함수와 동일하되 다른 클래스에 속한 멤버 함수라는 것만 다르다. 클래스 선언부에 프렌드로 지정하고자 하는 멤버 함수의 원형을 friend 키워드와 함께 적어주면 된다. 다음 예는 Any::func 멤버 함수를 Some 클래스의 프렌드로 지정한다.

 

class Some

{

     ....

    friend void Any::func(Some &S);

};

 

이렇게 선언하면 Any클래스의 func 멤버 함수는 Some 클래스의 모든 멤버를 액세스할 수 있다.

 

=> 순서에 주위

     프렌드 멤버 함수는 프렌드로 지정되는 클래스 소속이며 통상 대상 클래스를 인수로 전달받기 때문에 프렌드 지정을 포함하는 클래스를 먼저 선언하고 프렌드 멤버 함수를 포함한 클래스를 전방 선언해야 한다.

 

 

: FriendMem

#include <Turboc.h>

 

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)

{

     printf("오늘은 %d년 %d월 %d일이며 지금 시간은 %d:%d:%d입니다.\n",

          year,month,day,t.hour,t.min,t.sec);

}

 

void main()

{

     Date D(2005,01,02);

     Time T(12,34,56);

     D.OutToday(T);

}

 

 

 [ 프랜드 클래스이 주의사항 ]

 

 1) 프렌드 지정은 단방향이며 명시적으로 지정한 대상만 프렌드가 된다. A가 B를 프렌드로 선언했다고 하더라도 B가 A를 프렌드로 선언하지 않으면 A는 B의 프렌드가 아니다. 그래서 B는 A의 모든 멤버를 읽을 수 있지만 A는 그렇지 못하다.

 

2) 프렌드 지정은 전이되지 않으며 친구의 친구 관계는 인정하지 않는다.

A는 B를 프렌드 선언했고 B는 C를 프렌드 선언했다. 그래서 B는 A를 마음대로 액세스할 수 있으며 C도 B를 마음대로 액세스할 수 있다. 그러나 C는 A의 숨겨진 멤버를 액세스할 수 없다

 

 

3)복수의 대상에 대해 동시에 프렌드 지정을 할 수 있지만 한번에 하나씩만 가능하다. A가 B, C를 동시에 프렌드로 지정하고 싶을 때 다음처럼 해야 한다.

 

class A

{

     friend class B;

     firend class C;

     ....

 

4) 프렌드 관계는 상속되지 않는다. A가 B를 프렌드로 지정하면 B는 A를 액세스할 수 있다. 그러나 B로부터 파생된 D는 A의 프렌드가 아니므로 A를 마음대로 액세스할 수 없다.

 

 

[ 프랜드는 클래스 은폐와 추상화의 본연의 의미와 반하나 ]

 

어떻게 생각하면 프렌드는 은폐된 정보의 기밀성을 완전히 무시해 버리므로 무척 위험한 장치처럼 생각될 수도 있다. 그러나 개발자의 명시적인 지정에 의해서만 예외를 인정하며 그것도 클래스의 선언부에서만 지정할 수 있고 클래스 외부에서는 지정할 수 없으므로 위험하지는 않다. 프렌드 지정에 의해 공개되는 범위는 지정한 클래스나 함수에 국한되므로 전면적인 공개와는 성질이 다르다

 

 

 

5. this (호출한 객체의 번지를 가리키는 포인터 상수이다)

 

=> 객체별 자신을 가리키는 포인터다

 

예) 단순 클래스로 본 this의 의미

 

: this

#include <Turboc.h>

 

class Simple

{

private:

     int value;

 

public:

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

     void OutValue() {

          printf("value=%d\n",value);

     }

};

 

void main()

{

     Simple A(1), B(2);

     A.OutValue();

     B.OutValue();

}

 

 [ 문제 제시 ]

=>OutValue 함수가 자신을 호출한 객체를 어떻게 아는가 하는 점이다.

이 함수의 본체 코드에서 value라는 멤버를 이름만으로 참조하고 있는데 이 멤버가 과연 누구의 value인지 어떻게 판단하는가? 함수는 호출원으로부터 정보를 전달받을 때 인수를 사용하는데 OutValue 함수의 원형을 보면 어떠한 인수도 받아들이지 않는다. 입력값인 인수가 없으면 함수의 동작은 항상 같을 수밖에 없음에도 불구하고 OutValue는 호출한 객체에 따라 다른 동작을 할 수 있다.

 

=> 그 이유를 설명하자면 main에서 OutValue를 호출할 때 어떤 객체 소속의 멤버 함수를 호출할 것인지 소속 객체를 함수 이름앞에 밝혀 주었기 때문이다.

 코드를 보면 A.OutValue();B.OutValue(); 식으로 작성되어 있어 사람이 눈으로 보기에도 두 호출문은 구분된다.

그러나 함수의 입장에서는 자신을 호출한 문장앞에 붙어 있는 A. , B. 따위의 객체 이름을 읽을 수 없으며 인수로 전달되는 값만이 의미가 있다.

 

 

=> A.OutValue() 호출문은 컴파일러에 의해 다음과 같이 재해석된다.




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

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


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

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


=> this->value는 B 객체의 value멤버를 의미한다.

멤버 함수가 객체들에 의해 공유되려면 호출한 객체를 구분해야 하고 그러기 위해서는 호출 객체의 정보를 함수의 인수로 전달해야 하는데 이 처리를 개발자가 직접해야 한다면 무척 귀찮을 것이다. 이 작업은 모든 멤버 함수에 공통적으로 필요한 조치이며 획일적이기 때문에 개발자가 별도로 명시하지 않아도 컴파일러가 알아서 자동으로 해 주도록 되어 있다.




6. 정적 멤버 변수


=> 개념 :  클래스의 바깥에 선언되어 있지만 클래스에 속하며 객체별로 할당되지 않고 모든 객체가 공유하는 멤버이다


1) 전역 카운트 변수를 사용 - 비 클래스적인 접근


: ObjCount

#include <Turboc.h>

 

int Num=0;                  // 전역 변수 선언

 

class Count

{

private:

     int Value;

 

public:

     Count() { Num++; }

     ~Count() { Num--; }

     void OutNum() {

          printf("현재 객체 개수 = %d\n",Num);

     }

};

 

void main()

{

     Count C,*pC;

     C.OutNum();

 

     pC=new Count;

     pC->OutNum();

 

     delete pC;

 

     C.OutNum();

 

     printf("크기 = %d\n",sizeof(C));

}

 

 => 정적이든 동적이든 객체가 생성, 파괴될 때는 생성자와 파괴자가 호출되며 이 함수들이 Num을 관리하고 있으므로 Num은 항상 생성된 객체의 개수를 정확하게 유지한다. 디버거로 한 줄씩 실행해 가면서 Num 변수의 값을 관찰해 보면 이 변수가 생성된 객체수를 정확하게 세고 있음을 확인할 수 있다. 애초에 원하는 목적은 달성했지만 이 예제는 전혀 객체 지향적이지 못하다.

 

[ 문제점 ]

 

1) 클래스와 관련된 중요한 정보를 왜 클래스 바깥의 전역 변수로 선언하는가가 일단 불만이다

2) 또한 전역 변수는 은폐할 방법이 없기 때문에 외부에서 누구나 마음대로 집적거릴 수 있다. 어떤 코드에서 고의든 실수든 Num=1234; 라고 대입해 버리면 생성된 객체수가 1234개라고 오판하게 될 것이다.



2) 전역 변수를 클래스화


 Num을 Count 클래스안에 캡슐화해야 한다.


class Count

{

private:

     int Value;

    int Num;

 

public:

     Count() { Num++; }

     ~Count() { Num--; }

     void OutNum() {

          printf("현재 객체 개수 = %d\n",Num);

     }

};


=> 문제는 변수를 클래스 별로 가진다는 점 , 전역 적인 역활수행 불가



3) 정적 멤버 변수의 사용


Num은 클래스의 멤버이면서 클래스로부터 생성되는 모든 객체가 공유하는 변수여야 한다. 이것이 바로 정적 멤버 변수의 정의이며 이 문제를 풀 수 있는 유일한 해결책이다.


예)


class Count

{

private:

     int Value;

    static int Num;

 

public:

     Count() { Num++; }

     ~Count() { Num--; }

     void OutNum() {

          printf("현재 객체 개수 = %d\n",Num);

     }

};

int Count::Num=0;  

int Count::Num;                // 초기화 해주지 않아도 0으로 초기화 된다.


=> 선언 방식

 클래스 내부의 선언은 Num이 Count 클래스 소속이며 정수형의 정적 멤버 변수라는 것을 밝히고 외부의 정의는 Count에 속한 정적 멤버 Num을 선언하고 0으로 초기화한다는 뜻이다. 외부 정의에 의해 메모리가 할당되며 이때 초기값을 줄 수 있다.

 

=> 특징

1) 정적 멤버 변수는 객체와 논리적으로 연결되어 있지만 객체 내부에 있지는 않다.

2) 정적 멤버 변수를 소유하는 주체는 객체가 아니라 클래스이다.

3) 객체 크기에 정적 멤버의 크기는 포함되지 않으며 sizeof(C) = sizeof(Count)는 객체의 고유 멤버 Value의 크기값인 4가 된다.

 

 

7. 정적 멤버 함수 => 정적 변수와 함수접근이 가능하다.

 

=> 객체와 직접적으로 연관된다기보다는 클래스와 연관되며 생성된 객체가 하나도 없더라도 클래스의 이름만으로 호출할 수 있다.

일반 멤버 함수는 객체를 먼저 생성한 후 obj.func() 형식으로 호출한 객체에 대해 어떤 작업을 한다.

이에 비해 정적 멤버 함수는 Class::func() 형식으로 호출하며 클래스 전체에 대한 전반적인 작업을 한다. 주로 정적 멤버 변수를 조작하거나 이 클래스에 속한 모든 객체를 위한 어떤 처리를 한다.

 

예)  정적 멤버 함수의 사용 - 정적 변수에 대한 제어를 클래스 생성전에 가능하다.

 

: ObjCount2

#include <Turboc.h>

 

class Count

{

private:

     int Value;

    static int Num;

 

public:

     Count() { Num++; }

     ~Count() { Num--; }

     static void InitNum() {

          Num=0;

     }

     static void OutNum() {

          printf("현재 객체 개수 = %d\n",Num);

     }

};

int Count::Num;

 

void main()

{

     Count::InitNum();           // 클래스 생성 전에 접근 가능

     Count::OutNum();

     Count C,*pC;

     C.OutNum();

     pC=new Count;

     pC->OutNum();

     delete pC;

     pC->OutNum();

     printf("%d",sizeof(C));

}

 

=> 비 정적 변수에 접근이 않된다. (생성전에)

static void InitNum()

{

      Num=0;     

     Value=5;           // 접근 불가

}

 

 

9. 정적 멤버의 활용 용도

 

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

데이터 베이스 연결이나 네트웍 연결, 윈도우 클래스 등록 등과 같이 단 한번만 하면 되는 초기화는 정적 멤베 함수에서 하고 그 결과를 정적 멤버 변수에 저장한다. 이런 전역 초기화는 일반적으로 두 번 할 필요도 없고 두 번 초기화하는 것이 허용되지도 않는다. 그래서 객체별로 초기화해서는 안되며 클래스 수준에서 딱 한번만 초기화하고 그 결과는 모든 객체가 공유한다.

 

예) DB 서버에 연결하는 과정은 굉장히 느리고 리소스를 많이 차지하기 때문에 객체별로 따로 연결하지 않고 딱 한번만 연결해야 한다. 이럴 때 사용하는 것이 바로 정적 멤버이다.

 

 

: GlobalInit

#include <Turboc.h>

 

class DBQuery

{

private:

     static HANDLE hCon;

     int nResult;

 

public:

     DBQuery() { };

     static void DBConnect(char *Server, char *ID, char *Pass);

     static void DBDisConnect();

     BOOL RunQuery(char *SQL);

     // ....

};

HANDLE DBQuery::hCon;

 

void DBQuery::DBConnect(char *Server, char *ID, char *Pass)

{

     // 여기서 DB 서버에 접속한다.

     // hCon = 접속 핸들

}

 

void DBQuery::DBDisConnect()

{

     // 접속을 해제한다.

     // hCon=NULL;

}

 

BOOL DBQuery::RunQuery(char *SQL)

{

     // Query(hCon,SQL);

     return TRUE;

}

 

void main()

{

     DBQuery::DBConnect("Secret","Adult","doemfdmsrkfk");

     DBQuery Q1,Q2,Q3;

 

     // 필요한 DB 질의를 한다.

     // Q1.RunQuery("select * from tblBuja where 나랑 친한 사람");

 

     DBQuery::DBDisConnect();

}

 

 

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

객체는 스스로 동작할 수 있지만 때로는 외부의 환경이나 자원에 대한 정보를 필요로 한다. 예를 들어 정확한 출력을 위해 화면 크기를 알아야 할 경우도 있고 장식을 위해 외부에 정의된 예쁜 비트맵 리소스를 읽어야 하는 경우도 있다. 이런 정보들은 일반적으로 한번 읽어서 여러 번 사용할 수 있는 읽기 전용이기 때문에 객체별로 이 값을 일일이 조사하고 따로 유지할 필요가 없다.

 

: ReadOnlyInit

#include <Turboc.h>

 

class Shape

{

private:

     int ShapeType;

     RECT ShapeArea;

     COLORREF Color;

 

public:

     static int scrx,scry;

     static void GetScreenSize();

};

 

int Shape::scrx;

int Shape::scry;

 

void Shape::GetScreenSize()

{

     scrx=GetSystemMetrics(SM_CXSCREEN);

     scry=GetSystemMetrics(SM_CYSCREEN);

}

 

void main()

{

     Shape::GetScreenSize();

     Shape C,E,R;

     printf("화면 크기 = (%d,%d)\n",Shape::scrx,Shape::scry);

}

 

3) 모든 객체가 공유해야 하는 정보 관리 => 전역 용도의 값

중요한 계산을 하는 객체의 경우 계산에 필요한 기준값이 있을 수 있다. 예를 들어 환율이나 이자율 따위는 금융, 재무 처리에 상당히 중요한 기준값으로 작용하며 기준값에 따라 계산 결과가 달라진다. 이런 값들은 프로그램이 동작중일 때도 수시로 변할 수 있지만 일단 정해지면 모든 객체에 일관되게 적용된다.

 

: ShareInfo

#include <Turboc.h>

 

class Exchange

{

private:

     static double Rate;

 

public:

     static double GetRate() { return Rate; }

     static void SetRate(double aRate) { Rate=aRate; }

     double DollarToWon(double d) { return d*Rate; }

     double WonToDollar(double w) { return w/Rate; }

};

double Exchange::Rate;

 

void main()

{

     Exchange::SetRate(1200);

     Exchange A,B;

     printf("1달러는 %.0f원이다.\n",A.DollarToWon(1.0));

     B.SetRate(1150);

     printf("1달러는 %.0f원이다.\n",B.DollarToWon(1.0));

}

 

 

 10 .상수 변수


1) 상수 멤버

상수 멤버는 한번 값이 정해지면 변경될 수 없는 멤버이다. 클래스 전체에서 참조하는 중요한 상수가 있다면 이를 상수 멤버로 정의하여 클래스에 포함시킬 수 있다.

 

: ConstMember

#include <Turboc.h>

 

class MathCalc

{

private:

     const double pie;

 

public:

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

     void DoCalc(double r) {

          printf("반지름 %.2f인 원의 둘레 = %.2f\n",r,r*2*pie);

     }

};

 

void main()

{

     MathCalc M(3.1416);

     M.DoCalc(5);

}

 

// 정적 변수와 연동 가능

 

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

 

class MathCalc

{

private:

    static const double pie;

 

public:

    MathCalc() { }

     void DoCalc(double r) {

          printf("반지름 %.2f인 원의 둘레 = %.2f\n",r,r*2*pie);

     }

};

const double MathCalc::pie=3.1416;

 

void main()

{

    MathCalc M;

     M.DoCalc(5);

}

 

 

[IDEA] 객체별로 상수가 다른값을 가지게 하기

 

정적 상수 멤버는 클래스가 소유하기 때문에 객체별로 값을 따로 가질 수는 없다.

 열거형이나 매크로 상수도 마찬가지로 한 번 값이 정해지면 생성되는 모든 객체가 같은 값을 사용하는 수밖에 없다.

 상수가 객체별로 다른 값을 가져야 한다면 이때 쓸 수 있는 유일한 방법은 생성자의 초기화 리스트뿐이다.

 

 

: ConstMemberInit

class Enemy

{

private:

     const int Speed;

 

public:

     Enemy(int aSpeed) : Speed(aSpeed) { }

     void Move() {

          printf("%d의 속도로 움직인다.\n",Speed);

     }

};

 

void main()

{

     Enemy E1(10), E2(20);

     E1.Move();

     E2.Move();

}

 

=> Enemy 클래스는 게임의 적군을 표현하는 클래스인데 각 객체별로 고유한 속도를 가지되 한번 정해진 속도가 객체 내에서 불변이라면 Speed라는 상수 멤버를 선언한다. 그리고 객체가 생성될 때 생성자를 통해 딱 한번만 초기화한다.

 

 

2) 상수 멤버 함수

 

상수 멤버 함수는 멤버값을 변경할 수 없는 함수이다. 멤버값을 단순히 읽기만 한다면 이 함수는 객체의 상태를 바꾸지 않는다는 의미로 상수 멤버 함수로 지정하는 것이 좋다.

 

=> 어떤 멤버 함수가 값을 읽기만 하고 바꾸지는 않는다면 const를 붙여주는 것이 원칙이며 이 원칙대로 클래스를 작성해야 한다.

 

 

예)

class Some

{

private:

     int Value;

 

public:

     int SetValue(int aValue);        // 비상수 멤버 함수

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

};

 

: ConstFunc

#include <Turboc.h>

 

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 { gotoxy(x, y);putch(ch); }

     void MoveTo(int ax, int ay) { x=ax; y=ay; }                                 // 상수로 지정하면 에러가 발생 한다.

};

 

void main()

{

     Position Here(1,2,'A');

 

     Here.MoveTo(20,5);

     Here.OutPosition();

 

     const Position There(3,4,'B');

     There.MoveTo(40,10);               

     There.OutPosition();

}

 

 

 [ 포인터와 상수 연동 ]

void func(const Position *Pos);

 

이 함수로 전달되는 Pos는 상수 지시 포인터이므로 Pos는 func 함수 안에서 상수 객체이다. 따라서 Pos 객체에 대해서는 상수 멤버 함수만 호출할 수 있다.

 

 [ 상수변수를 통한 같은 이름의 함수 호출 ]

 

함수의 상수성은 함수 원형의 일부로 포함된다. 그래서 똑같은 이름과 인수 목록이 같더라도 const가 있는 함수와 그렇지 않은 함수를 오버로딩할 수 있다. 즉, 다음 두 함수는 이름과 취하는 인수가 같더라도 다른 함수로 인식된다.

 

void func(int a, double b, char c) const;

void func(int a, double b, char c);

 

컴파일러는 상수 객체에 대해서는 위쪽의 상수 멤버 함수를 호출할 것이고 그렇지 않은 경우는 아래쪽의 비상수 멤버 함수를 호출할 것이다. 객체가 상수일 때와 그렇지 않을 때의 처리를 다르게 하고 싶다면 두 타입의 함수를 따로 제공하는 것도 가능하다

 

 

 

 

[ 예제 - 동적배열을 클래스로 변환 ]

 

동적 배열을 하나의 클래스안에 캡슐화해 보기로 하자. 구조적 프로그래밍 기법으로 작성한 동적 배열이 어떻게 클래스로 변환되는지 볼 수 있을 것이다

 

 

: DArray

#include <Turboc.h>

#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

블로그 이미지

요다할아범

,