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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#include <iostream>
 
using namespace std;
 
class A{
 
public:
    A(){cout<<"A생성자 호출"<<endl;}
    /*virtual*/ ~A(){
        cout<<"A소멸자 호출"<<endl;
    }
};
 
 
 
class B : public A
{
public:
    int bb; 
 
    B(){cout<<"B생성자 호출"<<endl;}
    ~B(){cout<<"B소멸자 호출"<<endl;}
 
};
 
 
 
class C : public B
{
 
public:
    int cc; 
 
    C(){cout<<"C생성자 호출"<<endl;};
    ~C(){cout<<"C소멸자 호출"<<endl;};
 
};
 
int main()
{
//  C * c = new C();
//  delete c;
 
    /** 생성자호출
    * C클래스로 할당하였으므로, B로 할당하면 B부터
    * C클래스 생성자로 들어가보니 B클래스를 상속받아서 B클래스를 들어가보니
    * A클래스를 상속받았으므로 A클래스의 생성자를 호출후 지금까지 호출된
    + 생성자들각각 호출해주니 a->b->c
    * a(생) -> b(생) -> c(생)
    */
 
    /** 소멸자호출
    * C클래스 였기때문에 상속받은 순서반대로 
    * 즉! C클래스를 삭제하였으니까 C소멸자를 먼저호출하고나서,
    * 들어가보니 B클래스를 상속받았으니까 B클래스 소멸자를 호출
    * 그다음에 A소멸자를 호출
    * c(소)-> b(소) -> a(소)
    */
 
//-----------------------------------------------------------------------------
 
//    A * a = new C();
//    delete a; 
 
    /** 생성자호출
    * 이것도 위와 마찬가지로 C클래스로 항당하였으므로 
    * 상속받은 부모클래스를 차례대로 들어가게되고 끝까지 들어가서 생성자를 
    * 호출하면서 C클래스 생성자까지 내려오게된다.
    * a(생성자)-> b(생성자)-> c(생성자)
    */
 
    /** 소멸자호출
    * 이부분이 중요
    * A클래스를 삭제하였기때문에 먼저 A클래스의 소멸자를 호출하는데
    * A클래스는 상속받은게 없고 virtual 키워도로 소멸자가 감싸지도
    * 않았기때문에 소멸자는 a(소멸자) 한번만 호출되어 메모리누수
 
    * 해결방법은 A클래스의 소멸자에 virtual 키워드를 붙이는것으로 해결
    * A클래스 소멸자를 봤더니 virtual이다 하면, A클래스를 상속받은 자식
    * 소멸자에게 내려가게되고, 그자식도 virtual의 속성을 물려받게 되며,
    * 그자식을 상속받은 자식까지 계속 있을때까지 내려가게되서
    * 마지막 소멸자부터 호출하게 된다. 그리고 호출스택에 쌓인 생성자
    * 들을 차례차례 호출함으로써 끝이난다.
    * c(소멸자)->b(소멸자)->a(소멸자)
    */
 
//--------------------------------------------------------------------------
 
    B * b = new C();
    delete b;
 
    /** 생성자호출
    * 이것또한 위와 같다. C클래스로 할당하였으니까
    * a(생성자)->b(생성자)->c(생성자)
    */
 
    /** 소멸자호출
    * B클래스 소멸자를 봤더니 A클래스를 상속받았으므로 B소멸자를
    * 호출하고 나서, A클래스의 소멸자를 호출하게 된다.
 
    * 이렇게 되면, C클래스의 소멸자가 호출되지 않아 메모리 누수 
    * 해결방법은 B클래스의 생성자나 A클래스 소멸자에게 virtual 
    * 키워드를 붙이게되면, 자동으로 클래스를 상속받은 자식들은
    * virtual을 상속받게되어 모든 소멸자가 호출된다.
    * B클래스의 소멸자에 virtual을 붙이는경우는 
    * B클래스의 소멸자를 들어가보니 virtual 이라서 B클래스를 상속받은
    * 자식을 찾아 내려가게되고, 여기서는 C클래스 소멸자이므로, C까지 
    * 내려와서 C부터 소멸자를 호출하고, 호출스택에 쌓인 소멸자를 하나하나
    * 빼호출하므로, c(소멸자)->b(소멸자)->a(소멸자)
    */
 
    system("pause");
    return 0;
}

 

 

클래스 B는 A를 상속받았기때문에

 

부모는 자식으로 생성가능하고,

 

이때 부모를 소멸시, 부모에 가상소멸자가 아니면, 자식의 소멸자는 호출되지 않는다. 메모리 누수

 

또한

 

B * b = new B(); 는 자식을 delete하면 부모의 가상소멸자 상관없이 상속받은 base클래스의 소멸자까지 호출합니다.

 

virtual 키워드는 부모클래스에 적용됩니다.

 

 

-주의-

 

무조건 소멸자에 virtual을 붙이는건 안좋음
 - virtual함수가 있으면 vptr(가상 함수 테이블 포인터)가 발생하기 때문에 용량이 커지고 다른 언어와의 호완성이 떨어지게 된다.

여러 클래스로 파상되는 기본 클래스 일때는 소멸자에 virtual을 붙여야 함
 - 그렇지 않으면 파생된 클래스를 삭제 할때 기본 클래스의 소멸자만 호출이 되서 파생된 부분은 삭제도 못하고 그대로 남는다.
 - 가상 소멸자가 안붙으면 기본 클래스로 쓸 생각이 없는것으로 판단하는게 좋음

가상 소멸자가 없는 클래스는 상속받으면 무지 위험해짐
 - class SpecialNode : public Node //이 때 Node는 가상 소멸자가 없다고 하자
 - SpecialNode *pSN = new SpecialNode("일단은 아무 내용");
 - Node  * pNode;
 - pNode = pSN;
 - delete pNode; //이상황에서 메모리가 제거 되는건 Node의 메모리만 제거됨
 - STL컨테이너 들은 전부 가상 소멸자가 없으니 조심

 

 

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
#include <stdio.h>
 
 
int main()
{
 
    unsigned int iNum; //십진수
    int iCnt; //자릿수
 
    printf("십진수를 입력 : ");
    scanf("%d", &iNum);
    printf("%d의 이진수는 ", iNum);
 
    for( iCnt = sizeof(iNum) * 8 - 1; 0 <= iCnt; --iCnt ){
 
        //( iNum >> iCnt ) & 1    :    쉬프트 연산자를 이용해 한 비트씩 1과 &연산한다
        printf("%d", (iNum>>iCnt)&1);
        if( 0 == (iCnt%4) ){
            putchar(' ');
        }
    }
    putchar('\n');
    return 0;
}
 


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
43
44
45
46
47
48
49
#include <stdio.h>
 
void f( int num1, int num2 ){
 
    int num3 = 1;
    while(num3 != 0 ){
 
        num3 = num1 % num2;
        num1 = num2;
        num2 = num3;
    }
    printf("유클리드 호제법 최대공약수는 %d입니다.\n", num1 );
 
}
int main()
{
    int n1, n2;
    fputs("최대공약수 인자 설정 : ", stdout);
    scanf("%d %d", &n1, &n2 );
 
    int i, max;
    for(i = 1; i <= n1; i++)
    {
        if( n1 % i == 0 && n2 % i == 0 ){
            max = i;
        }
    }
    printf("%d와 %d의 최대 공약수는 %d입니다.\n", n1, n2, max);
 
    f(n1, n2);
 
 
 
    ////////////////////////////////////다른방법
    int ucNum;
    ucNum = n1;
    if( n1 > n2)
        ucNum = n2;
 
    for(; 0<ucNum; --ucNum){
        if( n1 % ucNum == 0 && n2 % ucNum == 0 ){
 
            printf("최대공약수: %d, 최소공배수 : %d \n", ucNum, n1*n2/ucNum );
            break;
        }
    }
 
    return 0;
}

 

 

 

약수란? 

1은 모든 수의 약수이고, 어떤 수는 자기 자신의 약수이다. 또한 어떤 정수도 0으로 나눌 수 없다. 따라서 0은 어떤 수의 약수도 아니다.

 


 

 

1) 공약수 : 두 개 이상의 자연수의 약수 중에서 공통인 것




--------------------------------------------------------------------------------------------------------

1. 최대공약수

(1) 공약수 : 두 개 이상의 자연수의 약수 중에서 공통인 것



최대공약수

(2) 최대공약수 : 공약수 중에서 가장 큰 수
(3) 최대공약수의 성질 : 두 개 이상의 자연수의 공약수는 그들의 최대공약수의 약수이다.
(4) 서로소 : 공약수가 1뿐인 두 자연수

2. 최대공약수 구하는 법

(1) 나눗셈을 이용한 방법
① 공통인 소인수로 각 수를 나눈다.
② 몫이 서로소가 될 때까지 계속 나눈다.
③ 공통으로 나온 소인수를 모두 곱한다.


------------------------------------------------------------------------------------------------------------------

  

1. 최소공배수

(1) 공배수 : 두 개 이상의 자연수의 공통인 배수



최소공배수

(2) 최소공배수 : 공배수 중 가장 작은 수

(3) 최소공배수의 성질 : 두 개 이상의 자연수의 공배수는 그들의 최소공배수의 배수이다.

2. 최소공배수 구하는 방법

(1) 나눗셈을 이용한 방법
① 두 수 이상의 공통인 소인수로 각 수를 나눈다.
② 나누어 떨어지지 않는 수는 그대로 내린다.
③ 몫이 서로소가 될 때까지 계속 나눈다.
④ 공통으로 나온 소인수에 마지막 몫을 모두 곱한다.


 

최소공배수는 두수의 곱에 최대공약수로 나눠주면 됩니다.

 

 

 

이걸 왜하냐?

 

최소공배수 -> 3일마다 오는 기차와 7일마다 오는 배가 있다. 오늘 기차와 배가 왔다면 몇일 후에 기차와 배가 동시에 올까? ( 21일 )

 

1 |3, 7

    ----

    3  7

최대공약수 1, 최소공배수 3*7 /1 = 21일

 

--------------------------------------------------------------------------------------------------------------------------

최대공약수 -> 과자 20봉지와 음료수 12캔이 있다. 이걸 공평하게 나눠주려 하면 최대 몇명에게 나눠줄 수 있을까? (하나의 과자나 음료수를 나눠서는 안된다.)


정리하자

 

i am a boy -> yob a ma i 바꾸는 코드

 

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
#include <stdio.h>
#include <tchar.h>
#include <string>
 
void getReverseStr( TCHAR * str ){
    
    // NULL문자를 제외한 문자열 길이를 리턴
    int len= _tcslen(str);    
    for(int i =0; i < len/2; ++i ){
 
        // 배열이 0 부터시작이므로, 문자열길이 - 1 
        TCHAR temp = str[i];
        str[i] = str[len-i-1];
        str[len-i-1] = temp;
    }
 
    _tprintf(L"Reverse : %s\n" , str);
}
 
int main(){
 
    /**
    * wprintf는 지역 설정에 영향을 받는 함수입니다.
    * 그래서 한글 출력을 하기 전에 지역을 설정하는 함수를 호출해야 합니다.
    * 이를 위해 Visual C++은 setlocale, _wsetlocale이라는 함수들을 제공합니다.
    * 대한민국으로 설정하려면 다음과 같이 호출해 주세요.
    */
 
    _wsetlocale(LC_ALL, L"korean");
 
    TCHAR pStr[100] = _T("i am a boy");
    getReverseStr(pStr);
    system("pause");
    return 0;
}

 

 

 

 

i am a boy -> yob a ma i -> boy a am i 로 변환하는코드

 

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
char * reverse_string( const char* ori )
{
    /**
     * 문자열을 fgets함수로 입력받으면 enterkey 문자까지 함께 들어간다.
     */
    int arrayCnt = 0, oriSize = strlen(ori)+1;
    char * rStr = (char*)(malloc(sizeof(char) * oriSize) );
    memset(rStr, 0, sizeof(char) * oriSize );
 
    //oriSize -= 2; // NULL문자 ENTER 키값 빼주고
    do{
        if( ori[oriSize] != 0 && ori[oriSize] != 10 )
         rStr[arrayCnt++]= ori[oriSize];
    }while( --oriSize >= 0 );
 
    rStr[arrayCnt] = '\0';
 
    /**
     * NULL 문자를 위에서 다시 넣어주는 이유는
     * 바꿔준 문자의 끝을 알리기 위해서 넣어주는 것이다.
     * 그렇지 않으면, 쓰게기 값이 들어간다. 
     * 처음부터 rStr을 초기화 해주는것도 방법이 될수있다.
     */
    return rStr;
}
char * reverse_word( const char* ori )
{
    /**
     * 입력받은 스티링이 yob_a_ma_i\0 총 10바이트 == gets함수
     * gets함수로 스트링을 받을경우 동적배열 생산시 길이에 + 1 해줘야함
     * 안해주면 0 - 9 즉 10개의 배열로 구성됨 사실상 \0 문자를 넣을때가 없어짐     
     * 하지만 fgets함수로 받을경우 enterkey 포함 11바이트 길이가 넘어온다.
     * +1 안해줘도 문자열 갯수에 맞게 널문자까지 알아서 들어감
     * 하지만 함수에서 문자열끝을 입력하므로 +1을 나둬도 상관없고, 
     * 1바이트가 아까으면 fgets함수와 gets함수에따라 바꿔주자
     */
    
    int arrayCnt = 0, wordCnt = 0, oriSize = strlen( ori )+1;
    char * rStr = (char*)(malloc(sizeof(char) * oriSize) );        
    memset(rStr, 0, sizeof(char) * oriSize ); 
 
    size_t cnt = 0;
    const char *p, *t; 
 
    for( p=ori; *ori; )
    { 
        // 공백이나 문자열의 끝이 나오는 위치 찾기 (fgets함수 enterkey 체크포함)
        while( *ori != ' ' && *ori != 0 && *ori != 10 ) 
        {
            ori++; 
        }
 
        // 현재 위치를 임시로 저장.     
        t = ori+1;
 
        // 현재 위치부터 거꾸로 출력 문자열에 저장. 
        while( ori != p ) 
        {
            rStr[cnt++] = *(--ori); 
        }
 
        // 하나의 단어가 끝나면 공백 삽입 
        rStr[cnt++] = ' ';
        p=t, ori=t;
 
        if( cnt == oriSize )
            break;
    } 
    // 문자열의 끝 마크. 
    rStr[cnt-1] = '\0'
 
    return rStr;
}
int main()
{
    /**
     * i am a boy -> yob a am i -> boy a am i 바꾸기
     */
    //char * str1 = "i am a boy";
    char str1[20];
    memset( str1, 0, sizeof(str1));
 
    fputs("InputString : ", stdout );
    //fgets(str1, sizeof(str1), stdin);
    gets(str1);
 
//    char * str2 =reverse_string( str1 );
//    puts(str2);
    // 이함수에 전달할때 쓰레기값.... 일단 생각해보자
 
    char * str3 =reverse_word( str1 );    
    puts(str3);
 
    //free(str2);    
    free(str3);    
 
    system("pause");
    return 0;
}


 

1. const 초기화 방법

 

상수는 선언과 동시에 반드시 초기화 해주어야 합니다. 그렇지 않으면 쓰레기 값으로 초기화 될테고 한번 초기화가 되었으므로, 생성자를 통한 초기화는 더 이상 허용되지 않습니다.

 

그래서 멤버 이니셜라이져 라는 문법이 제공된다.

 

const int id;
int age;
Person(int _id, int _age) : id(_id), age(_age) //=> 생성자 몸체보다 먼저 실행,

{
    this->id = _id; //=> 생성자가 호출될때 초기화
}

 

 

2. const 멤버 함수
const로 생성한 함수내에서는 아래의 규칙을 따른다.

- 멤버 변수의 값 변경 허용 안됨
void show() const{
    age = 30; // 컴파일 에러, 변경 불가함
}

- 멤버 변수 값의 변경에 대한 기회제공도 불가
int* getPtr() const{ // => const int* getPtr* const { 로 고치면 정상
    return &age;    // 컴파일 에러, 주소를 전달하면 기회가 제공되므로 에러가 발생
}

void show() const{
    log(); // 컴파일 에러 발생, 해당 log함수내에서 age 변수를 조작할 가능성이 있기때문에
           // 에러가 발생한다. 만약 고치려면 log 함수도 const가 되어야 한다.
}

void log() {
   cout<<"age의 값="<<age<<endl; 
}


3. const 객체
- 데이터의 변경이 허용되지 않는 객체
- const 함수 이외에는 호출 불가
const Person p;
p.log(); // 이 log함수는 반드시 const로 선언되어 있어야 한다.

4. const와 함수 오버로딩
const도 함수 오버로딩에 포함된다.
즉 void log() const;
void log(); 
두 메소드를 작성해도 된다.

 

5. static 과 클래스

첫째, main 함수가 호출되기도 전에 메모리 공간에 올라가서 초기화 된다. 따라서 public으로 선언이 된다면, 객체 생성 이전에도 접근이 가능하다.

둘째, 객체의 멤버로 존재하는 것이 아니다. 다만 선언되어 있는 클래스 내에서 직접 접근할 수 있는 권한이 부여된 것이다.

 

static 멤버는 위와같은 특징을 지닌다.

 

static 멤버의 초기화는 생성자가 적절하다 생각하겠지만, 이는 적절하지 않다. 왜냐하면 생성자는 객체 생성시 호출되지만, static 멤버는 main함수가 호출되기도 전에 초기화되어야 하기 때문이다.

 

그래서 static 멤버 초기화 문법적요소를 제공한다.

 

class Person {
   static int count;
}
int Person::count=0; // 반드시 이렇게 초기화 해야한다.

* static 멤버의 특징
- 클래스 변수, 클래스 함수라 한다.
- main 함수 호출 이전에 메모리 공간에 올라가서 초기화 한다.(전역함수와 동일)
- 선언된 클래스의 객체내에 직접 접근 허용
- static 멤버 초기화문으로 초기화 해야한다.

 

 

6. 클래스 멤버로 상수를 정의하는 경우

class SoSimple

{

   public:
      const static int a=0;

      const static int b=10;
};

 

cout<<SoSimple::a<<endl;

cout<<SoSimple::b<<endl;

 

이렇게

 

const static으로 선언된 변수는 선언과 동시에 초기화가가능합니다.


정리중...  

 

 

#include <stdlib.h>

int **array1 = malloc( row * sizeof(int *));
for (i = 0; i < row; i++)
array1[i] = malloc( col * sizeof(int));

또는

int **array2 = malloc( row * sizeof(int *));
array2[0] = malloc( row * col * sizeof(int));
for (i = 1; i < row; i++)
array2[i] = array2[0] + i * col;

[ 함수 포인터 ]

 

함수 포인터란?

"함수의 이름은 메모리상에 존재하는 함수의 위치를 가리키는 포인터" 

 

함수 포인터의 포인터 타입

 

 int func( int a )

 {

     a++;

     return a;

 }

 

위의 코드를 풀어서 보면,

리턴타입이 int형이고 int형 인자 하나를 전달 받는 함수다.

함수 포인터의 포인터 타입을 결정하는것은 리턴형과 전달인자이다.

 

int (*fPtr1)(int);  

위의 선언은 리턴타입이 int형이고 매개변수로 int형 변수 하나를 받는 함수포인터인 것이다. 

 

[코드]


 

위의 코드는 함수포인터의 선언부터 호출까지 보여줍니다.

 

함수포인터를 왜 쓸까? 

 

함수포인터는 운영체제에게 우리가 정의한 함수를 알려 줄 때 유용하게 사용되는 개념입니다.

우리가 정의한 함수의 이름을 운영체제에 전달하면 운영체제는 전달된 함수 이름을 이용해서 우리를 대신해 함수를 호출해줍니다. 이때 해당되는 개념이 콜백함수(CallBack Function)라고도 하죠.

 

뿐만아니라, 리턴타입과 매개변수의 갯수만 맞게 함수포인터를 선언하게되면, 

각각 기능을 하는 함수를 여러개를 정의 하고, 선언한 함수포인터로 필요에 맞게 변경만 시켜주는 swich부분에서도 많이 쓰이곤 합니다.

 

순수 저의 생각과 경험으로 함수포인터를 보자면 ... 

 

일단 예를 들어 봅시다. 글보단 코드를 짜서 설명하는게 아무래도 쉽겠죠;;;



 

위의 코드는 아주 간단하게 함수포인터를 이용해서 구현한 버튼 처리입니다.

이 코드는 함수포인터를 활용해서 코드가 얼마나 간결해지고, 가독성이 높은지 설명하는 소스이므로, 많은 기능은 빠져 있습니다.

 

간략하게 소스를 보자면,

우선 GUI_BUTTON 구조체를 보면, 선택인덱스와 함수포인터(CALLBACK함수)가 있습니다.

그리고 GUI_BUTTON 구조체를 이용해 배열을 하나 만들었습니다. 또한 우리가 버튼을 선택했을때 CALLBACK 되는, 즉! 호출되는 함수도 funcBtn1, funcBtn2로 정의 하였습니다.

 

main() 함수에선 등록되어있는 버튼을 하나하나 화면상에 보여주고, 등록되어있는 버튼중에 인덱스를 입력하게 되면, 함수포인터로가 가르키는 함수를 호출하게 되는 구조입니다.

 

일단 알겠는데,

꼭 저렇게 함수포인터를 사용해서 코딩을 해야하나? 아직까지도 확 FEEL이 오지는 않습니다.

 

하지만,

코드를 자세히보면 우리는 main()을 건드릴(?) 필요가 없습니다.

버튼생성을 하고, 버튼이 선택되었을때 처리되는 함수(ex. funcBtn3)만들어서 연결(?)만 시켜주면, main() 함수에서 이루어지는 로직을 그대로 사용할 수 있는것이죠.

 

main()함수에서는 버튼을 등록시켰으니 화면상에 등록되어진 버튼을 보여줄테고, 그 버튼을 선택하면 우리가 새로정의해놓은 함수가 실행되겠죠.

 

위의 코드는 간단하게 구현되어서 그렇지 프로그램이 커지고 몇만줄의 코딩을 하면서, 모듈화 하기에는 여간 힘든게 아닙니다. 그런데 위의 예제 처럼 함수포인터를 활용하게 되면, 우리는 유지보수나 리펙토링 하기가 쉬워집니다. 가독성도 높죠.

 

아직도 왜 사용하는지 해깔리네요~ 확실한게 없네요( 유지보수, 리펙토링, 가독성 높아진다네 )

추상적으로만 보여지네요. 사실 이부분이 함수포인터는 가장 중요한게 아닙니다. ( 아래 CALLBACK, 동적바인딩부분이 중요함 )

 

버튼을 정의하고 처리하는 함수만 만들면되니 유지보수하기에도 쉬워질테고, 코드가 간결하니 가독성도 높아지고, main()함수에서 버튼을 좀더 빠르게, 호출하기위해 리펙토링을 시작했다고 치면 main()함수에서 함수를 호출하는 부분만 손을 조금 바주면 빠르게 작업이 가능하게되죠.

 

무엇보다 확실한 한방은? main() 함수는 메인 로직만 있고, 추가작업이나 건드리지 않고, 함수포인터 없이 이 프로그램을 구현해 보면, 함수포인터의 필요성을 느끼게 되는거죠~ 이게 가장 확실하긴 합니다. 프로그램을 만드는거야 어떻게 해서든 어렵지는 않게 만들수 있습니다. 문제는 몇백개가 되는 버튼을 관리하고 호출하기에는 코드가 간결해지기 힘듭니다. 명시적으로 잘 보이지도 않죠.

 

정리를 해보자면, 

 

위의 예제프로그램에서는 오히려 우리에게 프로그램이 요청하는 꼴이 되는 것입니다.

우리가 할일은 버튼만 등록해주면 끝이니까

 

이 프로그램은 우리에게

"내가 리턴타입이 void형이고 매개변수가 없는 함수포인터를 만들어놨으니, 프로그래머들은 함수포인터 타입에 맞게 버튼을 정의해서 나한테 입력해줘~ 그럼 내가 그 함수 처리해줄께"

 

가장중요한 함수포인터의 활용 중 CALLBACK에 해당되는 사항이죠~ 흔히들 예기하는 운영체제에 등록하고, 자동으로 운영체제에서 호출되는 함수들은 모두 함수 포인터를 지녔습니다.

 

이 모든건 프로그램 실행 동안에 즉! Runtime 동안에 동적으로 처리되는 사항입니다.

이것을 함수의 동적바인딩(프로그램 실행 도중에 어떤함수를 실행할지 선택)이라고 하죠.

 

결과적으로 함수포인터는 동적바인딩을 가능하게 하기위해서 필요한 것이죠~

 

동적바인딩 자세한 사항은 아래 링크에서 참고

http://blog.naver.com/PostView.nhn?blogId=julyflower50&logNo=60018310034 

 

어느정도 이해가 가셨는지 모르겠지만, 

분명한건 쓰고안쓰고는 프로그래머 몫입니다. 상황에 맞게 대처 해야합니다.

 

 

함수 포인터를 가까이 하는 습관을 기르자.

 

typedef VOID (CALLBACK *PCALLBACKGUIEVENT) ( UINT nEvent, int nControlID );

 

─────────────────────────────────────────────

 

[ void형 포인터 ]

 

void형 포인터란?

변수이건, 함수이건, 포인터주소값이던, 어떠한 뭐든 담을 수 있는 포인터이다.

 

void형 포인터는 주소값을 저장하는 것 이외에 아무것도 할 수 없기때문에, 명시적 형변환 과정을 무조건 거쳐야 한다.

 


 

 

void형 포인터는 언제, 왜쓰는가?

 

void형 포인터는 포인터의 타입이 결정되지 않는 상태에서 주소값을 미리 저장해놓고, 포인터 타입은 나중에 결정할때 사용한다. 주로 동적할당시 많이 사용한다. malloc함수 리턴타입이 (void*)

 

malloc 함수처럼 타입에 상관없이, 공통되는 분모가 생길때 void형 포인터를 선택하거나,

다양한 자료형을 저장할때 등등

'0x0001 > C, C++' 카테고리의 다른 글

[C++] const, static 클래스 멤버  (0) 2019.02.08
[C언어] 메모리 관리와 동적할당  (0) 2019.02.08
[C언어] 다차원 배열, 다중 포인터  (0) 2019.02.07
[C언어] const 키워드  (0) 2019.02.07
[C언어] extern "C"  (0) 2019.02.07

[ 다차원 배열 ]

 

다차원 배열이란 ?

2차원 이상의 배열을 의미하는 것이다. 2차원배열, 3차원배열 등등

 

2차원 배열 선언과 동시에 초기화

int main(void)

{

 //요소 전체 초기화

 int buila[3][3] = { 

                            { 1, 2, 3 },                          1  2  3

        { 4, 5, 6 },            =>           4  5  6

        { 7, 8, 9 }                           7  8  9

     };

 

//일부 요소만 초기화, 이런경우 부족한부분은 0으로 채워진다.

int buila2[3][3] = {

{ 1 },                                1  0  0

{ 4, 6 },              =>           4  6  0

{ 7, 8, 9}                           7  8  9

      };

 

//1차원 배열 초기화 형태를 취하는경우, 초기화 리스트의 수가 배열요소의 수보다 적은경우에는 나머지 요소들은 0으로 초기화 된다.                 

1  2  3

int buila3[3][3] = { 1, 2, 3, 4, 5, 6, 7 };   =>          4  5  6

7  0  0

 

//배열의 크기를 알려주지 않고 초기화하기, 최소한 한개의 인덱스는 가지고 있어야한다. 첫번째 인덱스만 생략 가능하다. 2 x 4 or 4 x 2

 

int buila4[][4]   = { 1, 2, 3, 4, 5, 6, 7,  8 }; 

 

....

}

 

 

2차원 배열 이름의 포인터 타입 

 


 

위의 코드에서 보듯이

 

a[0] == a는 같다,  

a[0] == a[0][0]은 같다. a[0]은 a[0][0]을 가르킨다.

a[1] == a[1][0]은 같다. a[1]은 a[1][0]을 가르킨다.

a[2] == a[2][0]은 같다. a[2]은 a[2][0]을 가르킨다.  

 

 

─────────────────────────────────────────────

2차원 배열 이름을 이용한 포인터 연산  

 


 

위의 코드를 보면

 

a는    a[0][0]을 가르킨다.

a+1는 a[1][0]을 가르킨다.

a+2는 a[2][0]을 가르킨다. 

 

즉!

a    == a[0] 같다. a[0]은 a[0][0]을 가르킨다.

a+1 == a[1] 같다. a[1]은 a[1][0]을 가르킨다.

a+2 == a[2] 같다. a[2]은 a[2][0]을 가르킨다. 

 

"2차원 배열 이름은 포인터 연산 시 행 단위로 이동한다." 

"int a[3][2];  // (4바이트 * 2) 행단위로 차이가 난다 "

"int a[2][3];  // (4바이트 * 3) 행단위로 차이가 난다."

 

자 이제 위의 내용을 정리해보면 배열이름의 포인터 타입이 나온다. 

 

int arr[2][4]; => int (*pArr)[4];  

 

읽어보면,

int형 변수를 가르키고 포인터연산시 4칸씩 이동하는 pArr포인터

 

위에 [4]는 포인터 연산에 따른 증가 혹은 감소의 폭을 말한다.

포인터 연산에 의해 값이 1 증가 및 감소 될경우 4칸씩(행단위 이동이니까) 이동하는 포인터라는 것

pArr이 가리키는 대상이 int형 데이터이므로 (4 * 4) 이동하게 될것이다.

 

 

 

 

─────────────────────────────────────────────

함수 인자로 다차원 배열 전달하기 

 

 

[코드] 



 

[결과]


 

위의 코드는?

int (*ptr)[4] 포인터 타입으로 받을수있는 서로다른 2차원배열의 요소값을 다중for문을 이용해서 출력하는 예제 입니다. 

 

여기서 잠깐!!  

int (*pArr)[4];    //int형 변수를 요소로 지니고 포인터 연산시 4칸씩 이동하는 2차원 배열을

                          가리키는 포인터

int* pArr[4];      //int형 변수의 주소값 4개를 저장할 수 있는 배열

 

 

 

 int* arr1[5];               =>     int** p1;          //1차원 배열이니까 포인터에 포인터선언

 int* arr2[3][5];          =>     int* (*p2)[5];   //2차원 배열이니까 (*p2)선언 및 포인터 연산시 [5]칸

 int** arr3[5];             =>     int*** p3;

 int*** arr4[3][5]        =>     int*** (*pArr)[5];

 

해깔린다. 명백히 해놓자.

  

[ 다중 포인터 ]

 

다중포인터란?

포인터의 포인터 혹은 더블포인터라 불리는 포인터이다.

 

ex ) int ** pP;

 

포인터는 기본적으로 그 대상 변수의 양(사이즈)이 많거나, 함수안에서 사용하는 목적으로 만듭니다.

(어디까지나 나의 생각 기준 입니다.)

 

그럼 다중포인터는 왜 필요해서 쓰는걸까?  

 

예를 들어봅시다.

void malloc_Func(int *x)

{

    x = (int*)malloc(sizeof(int)*100);

}

 

void main()

{

    int *p = NULL;

    malloc_Func(p); //문제점!!

}

 

위의 코드는 함수에서, 호출한 쪽의 포인터에 메모리 할당을 하기 위해서 간단하게 코딩한것입니다.

 

언뜻보기엔 문제가 없어 보이지만, 이 코드에는 문제가 있습니다.

 

간단하게 정리하자면, int* p 와  int * x 는 각각의 포인터 변수이고, 매개변수로 p가 전달됨으로써

x는 p가 가르키는 (NULL)을 똑같이 가르킬 뿐이지, 각각의 포인터라는 뜻입니다.

위의 코드대로 x에 메모리 할당을 해봤자, 각각의 포인터 변수이니까 p는 할당이 안되는것이죠

 

즉 같은 곳을 가르키는 전혀다른 포인터 변수 라는것입니다. 이걸 그냥 복사본이라고 하죠

int *x 는 int * p 하고는 상관이 없는 것입니다.

 

그럼 왜 복사본이 되는것일까?

이유는 간단합니다. 포인터 변수에다가 포인터 변수를 담았으니, 주소값이 대입이 이루어집니다.

변수는 다르지만, 같은것을 가르키게 되는것이죠. Call-By-Value

 

그럼 이문제를 어떻게 해결하면 좋을까요?

우리는 int * p 를 x 가 참조하여 x 를 통해 p를 변경하면 가능합니다.

쉽게 말해 x가 p를 가르키게 만들면, x에다가 할당하면 p가 할당이 되는 것입니다. x가 p의 주소값을 가르키기 때문에 x에다가 동적 할당 하면, 결국 p의 주소에 메모리가 할당 되는것이죠

 

자 그럼 더블 포인터에 의한 Call-By-Reference 바꿔보죠

void malloc_Func(int **x) // main()의 p 변수 자체를 가리키는, "p에 대한 포인터".

{

    *x = (int*)malloc(sizeof(int)*100); // *x가 p와 같음. main()의 p 가 새 메모리를 가리키게 함.

}

void main()

{

     int *p = NULL;

     malloc_Func(p);

}

 

위의 코드에서 더블포인터를 사용해서 메모리 할당을 하였습니다.

왜냐? p 변수 자체를 가르키는 포인터를 정의 하기위해서 더블포인터를 사용한것이죠.

이렇게 되면, 매개변수 x는 p가 가르키는 (NULL)을 가르키게 되는게 아니라,

포인터변수 p를 가르키게 되겠죠.

즉 매개변수 x로 p를 바꿀수있게 되는것입니다.(*x == p) 가 되는것이죠. 

 

그럼 main함수에 있는 포인터 p는 함수에서 할당된 새메모리를 가르키게 되는것입니다.

더블포인터를 사용해서 깔끔하게 수정이 됐습니다. 

 

다중,더블포인트는 쓰이는데는 여러가지가 있습니다.

하지만, 쓰이는곳이 조금씩 틀릴뿐이지, 여기서 크게 의미가 바뀌는 부분은 없습니다.

    

'0x0001 > C, C++' 카테고리의 다른 글

[C언어] 메모리 관리와 동적할당  (0) 2019.02.08
[C언어] 함수 포인터, void형 포인터  (0) 2019.02.07
[C언어] const 키워드  (0) 2019.02.07
[C언어] extern "C"  (0) 2019.02.07
[C언어] 재귀적 함수, 가변인자 함수  (0) 2019.02.07

+ Recent posts