본문 바로가기
Basic/C/C++

[C++ 언어] 제 6 강 : static, const 맴버

by boxbop 2012. 5. 25.
반응형

 

 

 

 얼마만에 포스팅을 하는건지..... 다사다난했습니다....ㅠㅠㅠㅠㅠ 이제다시 블로그 활동 열심히 하려구요! 늦은시간에도 불구하고 C++ 6강을 강행하도록 하겠습니다! 하두 오랫만이라 ㅠㅠㅠㅠㅠㅠ 바보가 된 느낌....

 

[const 키워드]

 

 일단 const라는 키워드에 대해서 복습을 해봅시다! 복습은 과해도 괜찮아요~

 

1. const 키워드는 변수의 선언 앞에 붙어서 변수를 상수화 한다.

ex) const int number = 1;

      number = 2; //여기서 컴파일 오류가 발생합니다.

 

2. const 키워드는 포인터가 가리키는 데이터를 상수화 한다.

ex) int number = 1;

     const int* ptr = &number;

     *ptr = 2; // 컴파일 오류가 발생!!

 

3. const 키워드는 포인터 선언 시 이름 앞에 붙어서 포인터 자체를 상수화한다.

ex) int number1 = 1;

     int number2 = 2;

     int* const ptr = &number1;

     *ptr = 10; // 가능!!!

     ptr = &number2; // 컴파일 오류가 발생!!

 

 기억들 잘 나시나요~? 이번에는 맴버 변수를 상수화하고 초기화하는 방법에 대해서 알아보도록 하겠습니다.

 

[멤버 변수의 상수화 그리고 초기화]

 

class Student

{

   const int id;

   int age;

   char name[20];

};

 

이렇게 Student라는 클래스를 정의했다고 해봅시다. 멤버 함수라던지 클래스명.id 과 같은 방법으로 맴버 변수 id를 수정할 수 있을까요~? 물론 학생이라는 클래스를 사용하여 하나의 객체를 만들었을 때, 학번(id)라는 부분은 한번 정의되면 절대 바뀌지않는 불변의 값을 갖게 되므로 상수화를 시켜주는 것이 좋습니다. 그렇다면 초기화는 어떻게 해야되는 걸까요? 단순히 cons int id = 121212;와 같은 방법이라면 바보같지만 클래스를 정의하는 아무런 의미가 없겠죠?

 

 그래서 "멤버 이니셜라이져(member initializer)"라는 문법을 제공합니다. 이 문법을 이용하면 const 멤버 변수를 초기화하는 것이 가능하죠~

 

class Student

{

   const int id;

   int age;

   char name[20];

 

 public:

  Student(int _id, int _age, char* _name) : id(_id)

 {

    age = _age;

    strcpy(name,_name);

 }

 

};

 

 볼드 처리된 부분은 "맴버 변수 id를 매개 변수 _id로 초기화 하라" 라는 의미입니다. const 맴버 변수는 반드시 이니셜라이저를 사용하여 초기화해야 합니다.

 

[const 멤버 함수]

 

 마찬가지로 맴버 함수에도 const 키워드를 사용할 수 있습니다. 맴버 함수가 상수화되면, 이 함수를 통해서 맴버 변수의 값이 변경되는 것은 허용되지 않습니다.

 

class Student

{

   const int id;

   int age;

   char name[20];

 

 public:

  Student(int _id, int _age, char* _name) : id(_id)

 {

    age = _age;

    strcpy(name,_name);

 }

 

  void Show() const

{

   cout<<id<<","<<age<<","<<name<<endl;

 }

 

};

 

 Show() 함수는 단순한 출력함수 이기때문에 내부에 있는 맴버 변수를 특별하게 변경하는 일이 없습니다. 때문에 맴버 변수의 상수화를 해주는게 좋겠죠~?

 

 또 다른 예를 한번 들어보도록 하겠습니다.

 

class count

{

   int cnt;

 

public:

 int*  GetPtr() const    //컴파일 에러

{

   return &cnt;

 }

void Show() const     //컴파일 에러

{

   ShowIntro();

   cout<<cnt<<endl;

 }

void ShowIntro()

{

   cout<<" count의 값 :" <<cnt<<endl;

 }

};

 

 위 예제에서는 두 부분에서 컴파일 에러가 발생합니다. 첫 번째 함수에서는 함수 내에서 맴버 변수를 조작하지 않음에도 불구하고 컴파일 에러를 발생시킵니다. 두 번째 함수에서도 마찬가지 입니다. 그렇다면 왜 컴파일 에러가 발생할까요?

 

 답은 상수화된 함수는 상수화되지 않은 함수의 호출을 허용하지 않을 뿐만 아니라, 맴버 변수의 포인터를 리턴하는 것도 허용하지 않습니다. 무슨 말인지 선뜻 이해가 안가시죠? 첫 번째 함수를 다시 한번 살펴봅시다. 이 함수는 상수 함수임에도 불구하고 맴버 변수의 포인터를 리턴하고 있습니다. 포인터를 말이죠. 그렇다면 이 포인터를 전달받은 영역에서 이 리턴 값을 가지고 충분히 맴버 변수의 조작이 가능해집니다. 즉 이 함수 자체가 데이터 조작의 경로가 되기 때문입니다. 두번째 함수는 그렇다면 왜 컴파일 에러를 발생시킬까요? 첫 번째 함수보다는 좀 더 이해하기 어렵긴 합니다. 그러나 ShowIntro() 함수는 상수화된 함수가 아니기 때문에 맴버 변수를 조작할 가능성이 있기 때문입니다. 때문에 ShowIntro() 함수의 호출을 허용하지 않는 것 입니다.

 

 컴파일 에러를 해결하기 위해 다음과 같이 수정해봅시다.

 

class count

{

   int cnt;

 

public:

 const int*  GetPtr() const   

{

   return &cnt;

 }

void Show() const   

{

   ShowIntro();

   cout<<cnt<<endl;

 }

void ShowIntro() const

{

   cout<<" count의 값 :" <<cnt<<endl;

 }

};

 

 이렇게 함으로써 GetPtr() 함수의 포인터가 가리키는 데이터 자체를 상수화한다는 의미입니다. 나머지는 설명안해도 이해하시겠죠? 참고로 const가 뒤에 붙어있으면 포인터 자체를 상수화하고 const가 앞에 붙어있으면 포인터가 가리키는 데이터를 상수화 한다는거 기억하시고 계시죠?

 

[const 객체]

 

 변수를 선언할 때 const 키워드를 붙여서, 변수를 상수화하듯이 객체도 생성과 동시에 상수화하는 것이 가능합니다.

 

ex)

 int main()

{

   const AAA aaa(1);

   return 0;

 }

 

[const와 함수 오버로딩]

 

함수 오버로딩은 상수 함수냐 아니냐에 따라서도 성립이 됩니다.

즉, void function(int n) const { .... }

     void function(int n) { .... }

위 2개의 함수는 서로 다른 함수로 취급합니다.

 

ex)

 class AAA

{

  int number;

 

public:

 

  AAA(int _number) : number(_number) {} //초기화 (이니셜라이져)

  void Show()

  {

     cout<<" Show() 호출 "<<endl;

     cout<<number<<endl;

  }

  void Show() const

  {

    cout<<" Show() const 호출 "<<endl;

    cout<<number<<endl;

  }

};

 

int main()

{

  const AAA aaa1(1);

  AAA aaa2(2);

 

  aaa1.Show();

  aaa2.Show();

  return 0;

 }

 

 ====> 출력결과

 Show() const 호출

 1

 Show() 호출

 2

 

참고하실 사항은 보통 상수 함수보다 상수화되지 않은 함수가 우선순위가 높습니다. 즉 둘 다 호출이 가능한 상황이라면 상수화되지 않은 함수가 호출된다는 뜻입니다. 그리고 상수 객체는 상수화되어 있지 않은 함수는 호출이 불가능합니다.

 

[클래스 static]

 

 이번에는 static키워드를 가지고 공부해봅시다. 마찬가지로 클래스의 맴버 변수나 맴버 함수를 static으로 선언할 수 있습니다.

어떤 상황에서 static 키워드를 사용하면 좋을까요? 그 중 한가지는 전역 변수가 필요한 상황입니다. 다음 예제를 먼저 살펴봅시다.

 

 int count = 1;

 

class Person

{

  char name[20];

  int age;

public:

  Person(char* _name, int _age)

  {

    strcpy(name,_name);

    age = _age;

    cout<<count++<<" 번째 Person 객체 생성 "<<endl;

   }

  void Show()

  {

    cout<<name<<","<<age<<endl;

   }

};

 

int main(void)

{

  Person p1("box", 10);

  Person p2("bop", 20);

 

  return 0;

 }

 

 위 예제에서 볼드 처리된 부분을 주의 깊게 살펴봅시다. Person 객체가 생성될 때마다 참조해야 하는 변수를 전역 변수로 선언해 두고 있습니다. 때문에 모든 객체가 공유할 수 있고 객체의 수를 카운트 하는 것이 가능하겠죠?

 그러나 위 예제의 문제점은 존재합니다. 전역변수 count는 Person클래스에 종속적이지만 그럼에도 불구하고 전역 변수로 선언되어 있어서 다른 영역에서 접근할 위험이 존재합니다. 그렇다면 다음과 같이 변경해봅시다.

 

  

class Person

{

  char name[20];

  int age;

  int count;

public:

  Person(char* _name, int _age)

  {

    count = 1;

    strcpy(name,_name);

    age = _age;

    cout<<count++<<" 번째 Person 객체 생성 "<<endl;

   }

  void Show()

  {

    cout<<name<<","<<age<<endl;

   }

};

 

int main(void)

{

  Person p1("box", 10);

  Person p2("bop", 20);

 

  return 0;

 }

 이렇게 수정하면 원하는 결과 값이 나올까요? p1도 p2도 출력은 "1 번째 Person 객체 생성" 이라고만 뜰겁니다. 즉, 생성되는 객체마다 독립된 count 변수를 가지게 됩니다. 그렇다면 다시 다음과 같이 수정해봅시다.

 

class Person

{

  char name[20];

  int age;

  static int count;

public:

  Person(char* _name, int _age)

  {

    strcpy(name,_name);

    age = _age;

    cout<<count++<<" 번째 Person 객체 생성 "<<endl;

   }

  void Show()

  {

    cout<<name<<","<<age<<endl;

   }

};

 

int Person::count = 1; //static 맴버 초기화

 

int main(void)

{

  Person p1("box", 10);

  Person p2("bop", 20);

 

  return 0;

 }

 

 위의 예제는 우리가 원하는 조건을 모두 만족시키며 의도한 결과값을 출력합니다. 결론부터 말씀드리자면

static 맴버는 main 함수가 호출되기도 전에 메모리 공간에 올라가서 초기화됩니다. 따라서 public으로 선언이 된다면 객체 생성 이전에도 접근이 가능합니다. 그리고 객체의 맴버로 존재하는 것이 아닙니다. 다만 선언되어 있는 클래스 내에서 직접 접근할 수 있는 권한이 부여된 것 입니다. 이 두가지 의미 잘 이해하시길 바랍니다.

 

 추가적으로 좀 더 설명 드리자면 객체가 생성되지 않음에도 불구하고 static 맴버 변수에 접근 하는 것이 가능하다는 사실입니다. 왜냐면 static 맴버는 main 함수가 호출되기도 전에 메모리상에 초기화되어 올라가기 때문입니다. 그리고 static 맴버는 객체의 맴버가 아니라는 점입니다. 다만 이미 초기화되서 메모리 공간에 올라가 있는 n에 직접 접근할 수 있는 권한만 가지고 있다는 의미입니다.

 

 다음 예제는 따로 설명안하겠습니다. 소스와 출력값을 차분히 분석해보시길 바랍니다.

 

1.

 class AAA

{

public:

  static int n;

};

 

int AAA::n = 1;

 

int main(void) //객체를 생성하지 않았음에도 static변수를 출력

{

   cout<<AAA::n<<endl;

   AAA::n++;

   cout<<AAA::n<<endl;

 

   return 0;

 }

 

====>결과값

1

2

============

2.

 class AAA

{

   int val;

   static int n;

public:  //n은 객체의 맴버 변수가 아니지만 맴버 변수처럼 직접 접근의 권한을 가짐

   AAA(int a = 0)

 { val = a, n++; }

   void Show

 { cout << val <<","<<n<<endl; }

};

 

int AAA::n = 1;

 

int main(void)

{

  AAA a1(10);

  a1.Show();

 

  AAA a2(20);

  a2.Show();

 

  return 0;

}

 

====>결과값

val: 10

n: 2

val: 20

n: 3

============

 

 이번 강은 여기까지 하도록 하겠습니다. 너무 오랜만에 학술 포스팅을 한거라 어떤지 모르겠네요. 그래도 대충 감은 잡은 듯 하니까 앞으로 다시 열심히 해야겠습니다. 다음 강부터는 상속에 대하여 공부해보도록 하겠습니다.

 

 

 

 

반응형