Hoon222y

[1219-3] 정적 멤버 - 정적 멤버 함수 / 함수 포인터 본문

코딩/교육

[1219-3] 정적 멤버 - 정적 멤버 함수 / 함수 포인터

hoon222y 2018. 12. 19. 21:34

정적 멤버 함수 : 객체가 아닌 클래스와 연관되어 모든 객체에 공통적인 작업을 처리한다. 정적 멤버 함수는 객체에 의해 호출되는 것이 아니어서 호출 객체인 this는 전달되지 않는다. 그래서 정적 멤버 함수는 정적 멤버만 참조할 수 있으며, 일반 멤버(비 정적 멤버)는 엑세스 할 수 없다.


 일반 멤버 함수 : 객체와 함께 호출되는 방식으로 반드시 객체가 있어야 사용 가능하다. 

 정적 멤버 함수: C언어의 함수와 동일하되 클래스 안에 있는 전역변수이다. 전역변수이므로 객체 호출 없이 사용 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Clazz{
public:
    int var;                //일반 멤버 변수
    static int s_var;       //정적 멤버 변수
    
    // 일반 멤버 함수 : 객체와 함께 호출되는 방식으로 반드시 객체가 있어야 한다.
    void func(){}
    // 정적 멤버 함수 : C언어의 함수와 동일하되 다만 클래스 안에 있는 전역함수 이다.
    static void s_func(){}
};
 
int main(){
 
    Clazz::s_func();        //정적 멤버 함수는 전역 함수이므로 객체 호출없이 호출이 가능하다.
    Clazz obj;              //일반 멤버 함수는 객체가 있어야만 호출이 가능하므로 객체를 생성하야 한다.
    obj.func();
}
cs

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Clazz{
public:
    int var;                //일반 멤버 변수
    static int s_var;       //정적 멤버 변수
    
    
    //일반 멤버 함수 안에서는 모든 심볼에 접근이 가능하다.
    void func(){            //void func(Clazz* this))
        var = 0;            //가능    this -> car = 0;
        s_var = 0;          //가능
        func();             //가능    this -> func();
        s_func();           //가능
    }
    
    //정적 멤버 함수 안에서는 정적 심볼만 접근 가능하다.
    static void s_func(){       //void s_func()
        //만약에 CLass o; 선언하고 o.var = 0; 은 가능하지만 그냥은 안된다.
        //Clazz o; o.var = 0 ; 가능
        
        //var = 0;          //불가능. 이 안에는 객체를 꺼낸적이 없기 때문에            this -> var = 0;
        s_var = 0;          //가능
        //func();           //불가능
        s_func();           //가능
    }
};
cs


 해당 코드를 보자. 해당 코드에서 볼 수 있듯 일반 멤버 함수 내에서는 모든 심볼에 접근이 가능하다. static 자체가 전역이기 때문에 사용하는데 당연히 문제가 발생하지 않는다. 하지만 정적 멤버 함수 안에서는 정적 심볼을 제외하고는 접근이 불가능 하다. 하지만 정적 멤버 함수 내에서 객체를 생성하게 되는 경우 (18번 줄)에는 사용이 가능하다. 



이번에는 함수 포인터에 대해서 알아보자.

 함수포인터 : 특정 함수에 대한 메모리 주소를 담는 것을 의미한다. 

 1. 함수 포인터 선언 방법 : 리턴타입(* 변수명) ([매개 변수 , .....])

 2. 함수 주소를 얻는 방법 : 함수의 이름이 곧 함수의 시작 주소

 3. 함수를 호출하는 방법 : 함수 호출 연산자(  ()  )를 사용한다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;
 
int foo() {
    cout << "foo" << endl;
    return 0;
}
 
int main(){
    //포인터 선언 방법 : 타입 * 변수명
    int i;
    int* pI = &i;
    
    //1. 함수 포인터 선언 방법 : 리턴타입(* 변수명)([매개변수 .... ];)
    //i = foo();
    int(* pF)();            //return 타입을 int*로 가지는 pF라는 함수를 선언한것
                            //따라서 원래 int* pf()하려고 했는데 *와()의 우선순위를 두기위해 (* pF)로 묶어두었다.
    //2. 함수의 주소를 얻는 방법 : 함수의 이름이 곧 함수의 시작 주소
    pF = foo;
    //3. 함수를 호출하는 방법 : 함수 호출연산자(   ()   )를 사용
    pF();
}
cs


16번째 줄을 살펴보자. (* pF)라는 코드는 함수의 주소를 담을 수 있는 pF라는 변수가 생기는 것이다. 그 뒤에 괄호가 ()이렇게 붙은것은 받는 파라미터가 없음을 의미한다. 여기서는 foo()라는 함수 즉, 파라미터가 없는 함수를 담기 위해서 괄호안에 아무것도 없는 것이다.


 여기서 살펴볼 점이 있다. 먼저 예를 들어 foo라는 함수가 매개변수가 있다고 가정하자. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int foo(int a) {
    cout << "foo" << endl;
    return 0;
}
 
int main(){
    //포인터 선언 방법 : 타입 * 변수명
    int i;
    int* pI = &i;
    
    //1. 함수 포인터 선언 방법 : 리턴타입(* 변수명)([매개변수 .... ];)
    //i = foo();
    int(* pF)(int);            //return 타입을 int*로 가지는 pF라는 함수를 선언한것
                            //따라서 원래 int* pf()하려고 했는데 *와()의 우선순위를 두기위해 (* pF)로 묶어두었다.
    //2. 함수의 주소를 얻는 방법 : 함수의 이름이 곧 함수의 시작 주소
    pF = foo;
    //3. 함수를 호출하는 방법 : 함수 호출연산자(   ()   )를 사용
    pF(4);
}
cs


이와 같이 foo함수가 int a 를 받는다면 13번째 줄에 괄호 안에 int 형 파라미터가 들어간다고 작성을 해주어야 한다. 이때 16번째 줄에서 함수 주소를 받는데는 아무런 영향이 없지만 함수를 호출할 때는 괄호 안에 인자를 넣어주어야 한다


 또한 1단계와 2단계를 합쳐서 사용 가능하다 .즉

1
2
3
4
5
6
7
8
//1. 함수 포인터 선언 방법 : 리턴타입(* 변수명)([매개변수 .... ];)
//i = foo();
int(* pF)() = foo;            //return 타입을 int*로 가지는 pF라는 함수를 선언한것
     
//2. 함수의 주소를 얻는 방법 : 함수의 이름이 곧 함수의 시작 주소
// pF = foo;
//3. 함수를 호출하는 방법 : 함수 호출연산자(   ()   )를 사용
pF();

cs


 이런식으로 3번째 줄에서 int(* pF)() = foo; 이런식으로도 선언이 가능하다.


 이번에는 일반 멤버 함수 포인터 선언방법에 대해서 알아보자.

선언 방법 : 리턴타입(클래스명:: *변수명)([매개변수, .....])인데 이때 주의할 점은 일반 함수의 경우 이름이 암묵적으로 함수의 시작주소로 해석되지 않기 때문에 반드시 주소 변환 연산자 (&)를 사용해야 한다


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
using namespace std;
 
//전통적인 함수(전역함수)
void func(){ cout << "func" << endl; }
 
class Clazz{
public:
    void func(){ cout << "clazz : func" << endl; }
    static void s_func(){ cout << "clazz: z_func" << endl; }
};
 
//정적 멤버 함수는 C언어의 함수포인터에 의해 호출될 수 있다.
int main(){
    void(*fp1)() = func;                    //이 func()는 전역함수 func()를 의미한다.
    fp1();
    void(*fp2)() = Clazz::s_func;           //static 이고, clazz안에 선언되어 있기 때문에 이처럼 사용 가능
    fp2();
    
    //void(*fp3)() = Clazz::func;           //이렇게 하면 오류이다. Clazz 객체가 선언되지 않았기 때문에 사용 불가
    //void(*fp3)() = &Clazz::func;          //이렇게 하면 왼쪽과 오른쪽의 타입이 달라서 오류
    
    // 일반 멤버 함수 포인터의 선언방법 : 리턴타입(클래스명::*변수명)([매개변수 , ....])
    // 그리고 일반 멤버 함수의 이름은 암묵적으로 함수의 시작 주소로 해석되지 않기떄문에
    // 반드시 주소 반환 연산자(&)를 사용해야 한다.
    void(Clazz::*fp3)() = &Clazz::func;
    //이렇게 한다음 FP3()이렇게 하면 실행 안된다.    // why?.....
    
    Clazz obj;
    //obj.fp3();                            //이건 안됨
    //fp3(&obj);                            //이것도 안됨
    
    (obj.*fp3)();                           //이건 되는건데 역참조 어쩌고 ..
    // 객체를 사용하여 멤버 함수 포인터를 호출하는 방법
    // (객체명.*함수포인터)({매개변수, ....})
    
    Clazz* pObj = new Clazz;
    //(pObj.*fp3)();                        //이건 안됨
    (pObj->*fp3)();                         //이렇게 해주어야 한다.
    //포인터를 사용하여 멤버 함수 포인터를 호출하는 방법
    //(포인터 ->*함수포인터)([메개변수,......]);
}
cs


객체를 사용하여 맴버 함수 포인터를 호출하는 방법 : (객체명.*함수포인터)([매개변수,...])  

포인터를 사용하여 멤버 함수 포인터를 호출하는 방법 : (포인터->*함수 포인터)([매개변수 , ....])  이다. 

 지금 26번째 줄로 인하여 fp3이 일반 멤버 함수 포인터이다. 이를 이용하여 33번째, 39번째 줄이 가능한 것이다. 포인터를 선언하고 각각 객체와 포인터를 사용하여 멤버 함수 포인터를 호출하였다.  안되는 경우인 30,31,38번째 줄을 꼭 확인하자.



(추가 연습문제) - 간단히 읽어볼 것 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//연습문제
// 1. 계산을 수행하는 클래스를 설계
// 제곱하는 기능, 덧셈기능, 뺄셈기능, 원의 넒이를 구하는 기능
// PI값을 제공하는 기능
#include <iostream>
using namespace std;
 
//정적 멤버의 활용 1. 상태를 가지지 않고 관련 있는 함수나 변수를 하나로 묶어 처리하는 경우 
// (이것을 디자인 패턴에서는 모노스테이트 패턴이라고 한다.)
class Calc{
public:
    //현재 클래스는 객체의 상태를 가지고 있지 않으므로 굳이 객체를 생성할 필요가 없다.
    //따라서 객체가 없어도 기능을 사용할 수 있도록 멤버 함수를 모두 정적으로 변경한다.
    //앞에 전부 static 을 붙인다.
    
    static int square(int x){ return x*x; }
    static int add(int a, int b){ return a + b; }
    static int sub(int a, int b){ return a - b; }
    static double get_area(int r){ return square(r)* PI; }
    
    static const double PI;
    //현재 클래스는 기본 생성자가 존재하므로 객체를 생성할 수 있다.
    //이제 불필요한 객체 생성을 막기 위하여 기본 생성자를 private 영역에 정의한다.
private:
    Calc(){}
};
const double Calc::PI = 3.14;           //class 안에서 선언하고 밖에서 정의한다.
int main(){
    //Calc c;                           //private에 선언했기 떄문에
    cout << Calc::get_area(2<< endl;
    cout << Calc::PI << endl;
}
cs


Comments