관리 메뉴

드럼치는 프로그래머

[C/C++] 클래스에서 사용되는 static 멤버 변수 및 함수 본문

★─Programing/☆─C | C++

[C/C++] 클래스에서 사용되는 static 멤버 변수 및 함수

드럼치는한동이 2010. 1. 5. 17:10

static 멤버 변수를 이해하기 위해서 먼저 알아야 하는 개념은 일반 static 변수이다.

다음 코드는 함수 내에서 static을 선언하는 경우이다. 이 것이 우선 이해가 되어야 클래스에서 사용되는

static을 이해할 수 있을 것이다.

 

// 첫 번째 예제

#include <stdio.h>

 

void func1( void )
{
    static int count = 0;

    printf( "%d \n", ++count );
}

 

void main( void )
{
    func1();
    // count = 9;   ,  컴파일 에러, func1()의 지역 변수에 접근이 불가능

    func1();
}

 

위 코드를 실행하면 결과는 다음과 같다.

 

count = 1
count = 2

 

위 코드를 설명하자면, static은 func1() 함수가 사용하는 전역 변수이다. 전역 변수라면

다음과 같이 코드를 변경해 볼 수 있다.

 

// 두 번째 예제

#include <stdio.h>

 

int count = 0;

void func1( void )
{
    printf( "%d \n", ++count );
}

 

void main( void )
{
    func1();
    count = 9;    // 전역 변수에 접근이 가능
    func1();

}

 

위 코드를 실행하면 결과는 다음과 같다.

 

count = 1
count = 10

 

이와 같이 변경해도 우리는 같은 결과를 얻을 수 있다.

그렇다면 '차라리 이렇게 사용하는 것이 낫지 않을까?' 라는 생각이 드는데, 맞는 얘기이기는

하다. 하지만 아주 중요한 하나의 차이점이 있다. 무엇일까? 이미 이 답을 알면 본인의 C 언어

실력을 한 번쁨 의심(?)해 봐야 한다. 왜냐하면 보통 내공은 아니기 때문이다. 실력이 매우 좋다는

얘기다.

 

첫 번째 예제와 두 번째 예제의 차이점은 변수의 결합성과 관련이 있다. 첫 번째 변수는 main()

함수에서 절대로 사용할 수 없다. 하지만 두 번째 변수는 main() 함수에서도 사용할 수 있다.

결합성(결합도)은 변수가 다른 곳에서 얼마나 참조될 수 있는가에 대한 것으로, 이 것은 좋은 프로그램과

나쁜 프로그램을 구별하는 기준이 된다. 즉 결합도가 낮을 수록 좋은 프로그램이 된다.

 

그래서 결합도를 낮추기 위해 func1() 함수에서만 사용하는 변수는 그 함수 내에 선언하고 사용하는 것이

좋다. 그런데 다음과 같이 func1() 함수의 count 변수에 static을 사용하지 않으면, 실행 결과는 첫 번째

예제와 전혀 다르게 나온다.

 

// 세 번째 예제

#include <stdio.h>

 

void func1( void )
{
    int count = 0;

    printf( "%d \n", ++count );
}

 

void main( void )
{
    func1();
    func1();
}

 

 func1() 함수가 호출될 때 마다 count 변수는 새로 선언되고 0으로 초기화된다.

위 코드를 실행하면 결과는 다음과 같다.

 

count = 1
count = 1

 

우리가 원하는 것은 func1() 함수가 호출될 때마다 1씩 증가하는 것이다. 그리고 변수의 결합성을 낮추는

(다른 함수에서 사용하지 못하도록 하는) 것이다. 그러기 위해서는 다시 첫 번째 예제로 돌아가야 하는데,

다음과 같이 static 변수를 사용하면, count 변수는 func1()에서 선언되었기 때문에, func1()에서만 사용하는

전역 변수가 된다.

 

// 네 번째 예제

#include <stdio.h>

 

void func1( void )
{
    static int count = 0;

    printf( "%d \n", ++count );
}

 

void main( void )
{
    func1();
    // count = 9;   ,  컴파일 에러, func1()의 지역 변수에 접근이 불가능

    func1();
}

 

즉 위의 코드는 다음과 같이 변경해서 생각할 수 있다.

 

// 다섯 번째 예제

#include <stdio.h>

 

int count = 0;         // func1() 함수에서만 접근 가능한 전역 변수

void func1( void )
{
    printf( "%d \n", ++count );
}

 

void main( void )
{
    func1();
    func1();
}

 

실제로 컴파일러는 static 변수의 값을 위와 같은 형태로 변환하여 전역 변수처럼 취급한다.

여기서 중요한 것은 접근 권한인데, count가 전역 변수이긴 하지만 static으로 선언되었기 때문에

컴파일러는 func1() 이외의 다른 함수에서 이 변수를 사용할 수 없도록 막아 준다. 막아 준다는

의미는 컴파일 에러가 난다는 얘기이다.

 

자! 그러면 이렇게 생각하면 될 것이다. 함수 내에서 static으로 변수를 선언해야 하는 경우는

'전역 변수를 선언하고, 특정 함수에서만 사용하고 싶을 때' 라고 생각하면 된다.

 

이 것 외에도 static 전역 변수도 있는데, 이것은 여러 개의 파일을 컴파일 할 때 변수 이름의

충돌을 막아 준다. 즉 '그 파일에서만 그 변수를 사용하고 싶을 때' 사용하면 된다.

 

또하나 static 함수인데, 이  것도 마찬가지로 여러 개의 파일을 컴파일 할 때 함수 이름의 충돌을

막아 준다. 즉 '그 파일에서만 그 함수를 사용하고 싶을 때' 사용하면 된다.

 

-------------------------------------&^.^;--------------------------------------

 

위의 것이 잘 이해되었다면 이제는 클래스에서 사용되는 static에 대하여 고찰해 보자.

우선 클래스가 생성될 때마다 count를 세고 싶기 때문에 다음과 같이 코드를 작성한다면,

 

#include <stdio.h>

class T
{
public:
    int count;
    T() { printf( "count = %d \n", ++count ); }
};

void main( void )
{
    T t1;
    T t2;
}

출력 결과는 다음과 같다. 왜 이런 값이 나오는지 이유는 간단하다. t1, t2 객체가 생성될 때 각각의

객체에 대해서 count 멤버 변수가 존재하게 되며, 이 때 count 멤버 변수는 초기화되지 않는다.

그렇기 때문에 count는 아래와 같은 값이 출력된다.

 

count = -858993459
count = -858993459

 

이 것을 초기화 하고 싶다면 다음과 같이 코드를 작성해야 한다.

 

#include <stdio.h>

 

class T
{
public:
    int count;
    T() : count(0) { printf( "count = %d \n", ++count ); }
};

 

void main( void )
{
    T t1;
    T t2;
}

그러면 출력 결과는 다음과 같다.

 

count = 1
count = 1

 

우리가 원하는 것이 객체가 생성될 때마다,  count가 하나 증가되길 원하는 것이라면,

이 또한 해결 방법이 모호하다. 그래서 다음과 같이 시도해 볼 것이다.

 

#include <stdio.h>

 

int count;

 

class T
{
public:

    T() { printf( "count = %d \n", ++count ); }
};

 

void main( void )
{
    T t1;
    T t2;
}

 

count = 1
count = 2

 

이제는 count를 사용해서 생성된 객체의 수를 셀 수 있다. 그렇지만 위의 코드에서 설명했던 것과

마찬가지로 변수에 대한 결합성이 높아진다. count 변수는 T 클래스 뿐만 아니라 main() 함수에서조차

접근할 수 있으며, 또 다른 여러 함수에서도 접근이 된다. 이런 것은 좋지 않은 코딩에 속한다.

그래서 이 문제를 해결하기 위해 class에도 static 멤버 변수가 존재한다. 다음 코드를 보자.

 

#include <stdio.h>

 

class T
{
public:
    static int count;
     T() { printf( "count = %d \n", ++count ); }
};

 

void main( void )
{
    T t1;
    T t2;
}

 

위와 같이 클래스에서도 static을 사용할 수 있도록 했으며, 이 것은 마치 함수에서 static을 사용하는 것과

비슷한 효과를 제공한다. 단 다음과 같이 반드시 추가적인 선언이 뒤따라야 한다.

 

#include <stdio.h>

 

class T
{
public:
    static int count;
    T() { printf( "count = %d \n", ++count ); }
};

 

int  T::count;      // 매우 중요

 

void main( void )
{
    T t1;
    T t2;
}

 

이렇게 int  T::count를 선언하지 않으면 다음과 같은 에러가 발생된다.

 

Compiling...
t2.cpp
Linking...
t2.obj : error LNK2001: unresolved external symbol "public: static int T::count" (?count@T@@2HA)
Debug/t1.exe : fatal error LNK1120: 1 unresolved externals
Error executing link.exe.


 

그럼 선언은 되었고, 이제 컴파일 후 실행하면 우리가 원하는 결과가 나온다.

 

count = 1
count = 2

 

결과는 매우 만족스럽다. 하지만 이제 부터 고민해봐야 할 것이 매우 많다. static 멤버 변수의 또 하나의

특징은 객체가 선언되지 않아도 사용할 수 있다는 것이다. 단 public 멤버에 한해서만 가능하다.

다음 예를 보자.

 

#include <stdio.h>

 

class T
{
public:
    static int count;
    T() { printf( "count = %d \n", ++count ); }
};

 

int  T::count;      // 매우 중요

 

void main( void )
{

    T::count = 100;      // 객체가 생성되지 않아도 접근 가능


    T t1;
    T t2;
}


 

출력 결과는 아래와 같다.

 

count = 101
count = 102

 

main() 함수에서 static 멤버 변수에 직접 접근할 수 없도록 하려면 count 멤버 변수를 private 영역에

선언해야 한다. 다음 예를 보자.

 

#include <stdio.h>

 

class T
{

private:                    
    static int count;      // private 영역에 선언


public
:
    T() { printf( "count = %d \n", ++count ); }
};

 

int  T::count;

 

void main( void )
{

    T::count = 100;      //  접근 불가능

 

    T t1;
    T t2;
}


 

다음으로 static 멤버 함수에 대해 살펴보자.

 

#include <stdio.h>

 

class T
{
public:
    static int count;

    int value;
    T() { printf( "count = %d \n", ++count ); }
    static void func() { printf( "%d", value ); }

};

 

int  T::count;      // 매우 중요

 

void main( void )
{
    T t1;
    T t2;
}

 

위 코드는 다음과 같은 컴파일 에러를 발생시킨다.

 

Compiling...
t2.cpp
c:\gsi\temp\t1\t2.cpp(11) : error C2065: 'value' : undeclared identifier
Error executing cl.exe.


 

T 클래스에 정의되어 있는 value 함수에 접근하고자 할 때, value 변수에 대해 생뚱맞다게도

선언되지 않은 정의자(undeclared identifier) 에러가 발생한다. 이쯤 되면 같은 클래스가

아닌가 하는 의심이 들 정도다. 맞다. static 멤버 함수는 T형 클래스 안에 선언되어 있지만

실제로는 껍데기만 T 클래스일 뿐이며, 다른 클래스 일반 함수들과 전혀 다르게 동작한다.

 

클래스 내에 선언되어 있는 static 함수는 오직 static 멤버 변수에만 접근이 가능하다.

왜냐하면, 그것은 객체의 생성과 관련이 있는데, 일반 멤버 변수는 객체의 생성을 통해서만

호출될 수 있다. 다음 예를 보자.

 

#include <stdio.h>

 

class T
{
public:

    int value;
    T() {}
    void func() { printf( "%d", value ); }

};

 

void main( void )
{
    func();        // 에러

    T::func();    // 에러
}


 

위 코드는 다음과 같이 컴파일 에러가 발생한다. 클래스 내에 있는 func() 함수를 main() 함수에서 직접

호출할 수 없으며, static 멤버 함수가 아닌 non-static 멤버 함수를 직접 호출할 수 없다.

 

Compiling...
t2.cpp
c:\gsi\temp\t1\t2.cpp(19) : error C2065: 'func' : undeclared identifier
c:\gsi\temp\t1\t2.cpp(21) : error C2352: 'T::func' : illegal call of non-static member function
        c:\gsi\temp\t1\t2.cpp(11) : see declaration of 'func'
Error executing cl.exe.


 

그렇다면, static 멤버 함수는 어떨까? static 멤버 함수는 객체가 생성되지 않아도 호출이 가능하다.

다음 예를 보자.

 

#include <stdio.h>

 

class T
{
public:

    static int value;
    T() {}
    static void func() { printf( "%d", value ); }

};

 

int  T::value; 

 

void main( void )
{
    func();        // 에러

    T::func();    // static 함수 호출 가능

 

    T t;

    t.func();      // 가능

}


 

일반 멤버 변수 및 멤버 함수와 static 멤버 변수와 멤버 함수에 대한 특징을 정리하자.

 

일반 멤버 변수

. 객체가 생성될 때마다 객체의 수만큼 생성된다.

. 일반 멤버 변수를 객체의 생성 없이 직접 접근할 수 없다.

. static 멤버 함수에서 접근할 수 없다.

. 일반 멤버 함수에서 접근할 수 있다.

 

일반 멤버 함수

. 객체가 생성되지 않아도 함수는 먼저 생성되어 있다. 그래서 언제든지 호출할 수도 있다. 단 객체가 생성되지

  않았을 때의 호출은 아직 생성되지 않은 멤버 변수에 접근할 수 있기 때문에 프로그램을 다운시킬 수 있다.

  이 것은 [강좌-클래스의 쓰레기 포인터 판단하기]을 참조하기 바란다.

. 함수는 객체마다 하나씩 생성되는 것이 아니며, 오직 하나만 생성된다. 각 객체는 그 함수를 공유하여 사용하는

  것 뿐이다.

. 스레드에서 호출될 수 없다.

. static 멤버 함수를 호출할 수 있다.

. 상속할 수 있다.

. this 포인터의 사용이 가능하다.

 

static 멤버 변수

. 객체의 생성과 관계 없이 생성되며, 객체에서 사용하는 전역 변수라고 생각하면 된다.

. 반드시 int  T::count;와 같은 추가적인 선언이 필요하다. 그렇지 않은 경우 LINK 에러가 발생된다.

. private 영역에 선언되어 있으면, 멤버 함수에서만 접근 가능하고, public 영역에 선언되어 있으면,

  멤버 함수 및 다른 함수에서 직접 접근할 수 있다.

. 객체가 사용하는 전역 변수이기 때문에 오직 하나만 생성된다. 객체 변수들은 객체가 생성될 때마다

  만들어 진다.

. 일반 멤버 함수 및 static 멤버 함수에서 모두 접근 가능하다.

 

static 멤버 함수

. 일반 멤버 변수를 접근할 수 없다.

. 일반 멤버 함수를 호출할 수 없다.

. static 멤버 변수를 사용할 수 있다.

. 상속할 수 없다.

. 스레드에서 함수를 사용 가능하다.

. public 영역에 선언되는 경우 전역에서 사용 가능하다.

. this 포인터의 사용이 불가능하다. this 포인터는 객체가 생성되었을 때만 사용할 수 있기 때문이다.

  (객체가 없을 때 호출될 수 있는 static 멤버 함수가 객체 포인터(this)에 접근하는 것은 당연히 불가능)

 

 

static 멤버 함수는 특별한 경우에 사용되는데, 그 용도를 살펴보자.

 

. 객체의 생성 없이 그 클래스의 일부 기능을 함수를 통해 제공할 때 사용한다.

. 일반 멤버 함수는 스레드용 함수로 호출이 안되지만, static 멤버 함수는 스레드용 함수로 사용된다.



[출처] http://cafe.naver.com/pplus.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=187
Comments