끄적끄적

반응형

1   다양한 포인터와 배열

int *pa1[10];   /* 포인터 배열 */

int (*pa2)[10]; /* 배열 포인터 */

int **pp;      /*다차원 포인터 */

void *vp;      /* void 포인터 */

int (*fnCmp)(int ,int) /* 함수 포인터 */

 

여기에서는 좀 더 다양한 포인터와 배열에 대해 살펴 보기로 합시다.  실제 사용하는 자세한 예는 동적 메모리 할당이나 프로그래밍 과정 및 프로젝트를 통해 소개될 것이며 여기에서는 기본적인 내용에 대한 소개를 하도록 하겠습니다.  그리고, 실제 여러분들이 주제를 정해 프로젝트를 해 나가는 경험을 통해 정확하게 이해하실 수 있을 것이라 생각됩니다.  여기에서는 이들에 대한 표현의 구분 및 기본적인 문법 사항을 점검하시길 바랍니다.

 

1.1      포인터 배열

  먼저, 포인터 배열에 대해서 얘기해 보도록 합시다.  포인터 타입을 원소로 하는 배열을 얘기합니다.  예를 여러명의 이름을 관리하기 위해 이차원 문자형 배열이 있다고 가정합시다.  이때 이들을 이름순으로 정렬하는 기능을 구현한다고 한다면 어떠한 타입의 변수가 필요할 지 생각해 보시기 바랍니다.  하나의 이름은 문자형 포인터로 관리할 수 있다는 것을 생각할 수 있다면 문자형 포인터를 원소로 하는 배열로 관리할 수 있다는 것까지 생각할 수 있을 것입니다.  아래의 소스를 분석하여 간략한 메모리 그림으로 그려보시길 바랍니다.

 

char sarr[4][10]={"hello","yahoo","jang","song"};  //4명의 이름을 관리하는 배열 sarr

char *parr[4]={NULL} ;  //이름순으로 sort하기 위해 선언된 포인터 배열

 

int   cnt=0, cnt2=0;  //sort에 필요한 loop  count변수

char *ptemp;         //sort에 필요한 임수 포인터 변수

 

while(cnt<4)   //sarr의 각 원소를 parr에 대입하는 초기화 과정

{

           parr[cnt] = sarr[cnt]; 

           cnt++;

}

 

 

/* 거품 정렬 – 정렬 알고리즘은 여기에서 이슈의 대상이 아닙니다. */

for(cnt=0;cnt<3;cnt++)

{

           for(cnt2=cnt+1;cnt2<4;cnt2++)

           {

                     if(strcmp(parr[cnt],parr[cnt2])>0) 

//parr[cnt]의 문자열이 parr[cnt2]의 문자열보다 크다면 – 사전식 비교

                     {

                                /* parr[cnt]와 parr[cnt2]값을 바꾸는 로직 */

                                ptemp = parr[cnt];

                                parr[cnt] = parr[cnt2];

                                parr[cnt2] = ptemp;

                               

                     }

           }

}

 

/* 정렬된 문자열 출력 출력*/

for(cnt=0;cnt<4;cnt++)

{

           printf("[%d]th [%s]\n",cnt,parr[cnt]);

}

 

  먼저, 문자열 배열을 정의하기 위해 문자형 이차원 배열을 선언하였고 정렬화된 상태로 관리하기 위해 포인터 배열을 선언하였습니다. 

 

char sarr[4][10]={"hello","yahoo","jang","song"};

char *parr[4] ={NULL};

 

  sarr의 경우에 원소의 개수는 4이고 원소타입은 char [10]; 즉, 원소타입은 문자형 배열입니다.  sarr[0]에서 sarr[3]은 sarr의 원소로써 크기가 10인 문자형 배열들 입니다.  즉, sarr의 각 원소(sarr[0]~sarr[3])은 각각 문자형 포인터 상수로 취급이 되며 이들을 관리하기 위해서 문자형 포인터를 원소로 하고 원소의 개수가 4인 포인터 배열 parr을 선언하였습니다.

 

while(cnt<4)

{

           parr[cnt] = sarr[cnt];

           cnt++;

}

 

  포인터 배열의 각 원소의 값을 각 문자열로 하기 위한 초기화 로직입니다.  이 처럼 포인터는 이미 유효한 메모리 주소값을 갖게 됨으로써 의미있는 값을 지니게 됩니다.  즉, 포인터 변수를 선언만 하고 사용하는 것은 프로그램에서 할당하지 않은 메모리에 접근하게 되는 경우가 발생하며 잘못된 사용을 유발하게 됩니다.  이러한 사용을 줄여나가기 위해서는 습관적으로 포인터 변수를 선언시에 NULL포인터 상수(실제 0이라는 값으로 정의되어 있음)로 초기화 하여 사용시에 유효한지 확인 후에 사용하면 잘못된 사용을 크게 줄일 수 있을 것입니다.  참고로 메모리 주소 0번지에서 64K 사이의 메모리 주소는 프로그램상에서 할당될 개연성이 전혀없는 메모리 주소입니다.(자세한 사항은 system programming이나 O/S에 관련된 문서나 서적을 참조하시길 바랍니다.)

 

/* 거품 정렬 – 정렬 알고리즘은 여기에서 이슈의 대상이 아닙니다. */

for(cnt=0;cnt<3;cnt++)

{

           for(cnt2=cnt+1;cnt2<4;cnt2++)

           {

                     if(strcmp(parr[cnt],parr[cnt2])>0) 

//parr[cnt]의 문자열이 parr[cnt2]의 문자열보다 크다면 – 사전식 비교

                     {

                                /* parr[cnt]와 parr[cnt2]값을 바꾸는 로직 */

                                ptemp = parr[cnt];

                                parr[cnt] = parr[cnt2];

                                parr[cnt2] = ptemp;

                               

                     }

           }

}

 

  포인터 배열은 원소가 포인터라는 것 말고는 배열과 동일하기 때문에 그 의미를 명확히 이해하면 사용은 어렵지 않습니다.  그리고, 개발도구를 통해 *을 붙여보았다가 안되면 &를 붙여보고 하는 쓸데없는 에너지 소모를 줄일 수 있습니다.  이들에 대한 자세한 사용은 동적 메모리 할당및 프로그래밍 과정을 통해 좀 더 이해하기로 하시고 여러분들이 직접 주제를 정하여 프로그래밍을 하면서 자신의 것으로 만들 수 있을 것입니다.  좀 더 자세한 사항을 책을 통해 전달하지 못하는 사항을 실습을 통해 보충해 주시길 바랍니다.

 

1.2      배열 포인터

배열 포인터는 원소 타입이 배열인 포인터를 의미합니다.  이의 사용은 구체적인 프로그램에서 다차원 배열을 입력매개변수로 받아서 처리를 할 필요성이 있을 때 사용하게 됩니다. 

 

예를 들어 50명으로 구성된 2개반의 국, 영, 수 과목의 성적을 관리하기 위해 int score[2][3][50]; 라는 배열이 있다고 과정을 했을 때 특정 학급의 국, 영, 수 점수를 출력하는 함수를 작성한다고 한다면 매개변수의 타입을 어떻게 결정을 해야 할까요?  score[0]은 1반의 점수를 관리하기 위한 관리명으로 score[1]은 2반의 점수를 관리하기 위한 관리명으로 사용하고 있을 것입니다.  그리고, 이들은 원소의 개수는 3이고 원소 타입은 int [50]이라고 할 수 있겠지요.  결국, 이들을 입력매개변수로 받기 위해서는 int [50]에 대한 포인터로 받아야 할 것입니다.  이때 사용되는 것이 배열 포인터 입니다.

 

int score[2][3][50]; //50명으로 구성된 2개반의 국,영,수 점수를 관리하기 위한 변수

const char *sarr[3]={“국어”,”영어”,”수학”};

..중략...

void fnListAll()

{

    int i=0;

    for(i=0; i<2 ; i++)

    {

        printf(“[%d]반의 점수입니다.\n”,i+1);

        fnListClass(score[i]); //score[i]는 특정 반을 관리하는 관리명이다.

    }  

}

 

void fnListClass(int (*inp)[50]) //원소의 개수가 50이며 원소 타입이 int 배열에 대한 포인터 inp

{

    int i=0;

    for(i=0; i<3; i++)

    {

        printf(“[%s]과목 점수입니다.\n”,sarr[i]);

        fnListSubject(inp[i]);  //특정 과목을 관리하는 관리명이다.

    }

}

void fnListSubject(int *inp)

{

    int i = 0;

    for(i=0 ; i<50 ; i++)

    {

        printf(“[%d]번 [%d]점\n”,inp[i]);

    }

}

 

 먼저, score의 선언을 보면 원소의 개수가 2이며 원소 타입은 int [3][50]입니다.  즉, score[0]은 1반의 점수를 score[1]은 2반의 점수를 관리하는 원소입니다. 

int score[2][3][50]; //50명으로 구성된 2개반의 국,영,수 점수를 관리하기 위한 변수

const char *sarr[3]={“국어”,”영어”,”수학”};

 

void fnListAll()

{

    int i=0;

    for(i=0; i<2 ; i++)

    {

        printf(“[%d]반의 점수입니다.\n”,i+1);

        fnListClass(score[i]); //score[i]는 특정 반을 관리하는 관리명이다.

    }  

}

 

 

참고로 포인트 선언에 있어 const 가 *보다 앞에 있으면 원소의 내용을 수정하지 못하게 하는 것이고 *보다 뒤에 있으면 포인터 상수라는 것입니다. 즉, const char *p =”hello”; 에서는 p++은 가능하고 *p=’k’와 같은 구문은 불가능합니다.  그리고, char * const p=”hello”;에서는 p++이 불가능하고 *p=’k’ 구분은 사용 가능합니다.  물론, const char * const p = “hello”; 와 같이 선언되어 있으면 p++이나 *p=’k’ 모두 불가능한 표현이 되겠지요.

 

1.3      다중 포인터

다중 포인터는 포인터 타입에 대한 포인터 타입을 얘기합니다.  이는 해당 변수가 갖는 값이 프로그램의 메모리 주소이며 해당 값에 해당되는 메모리에 있는 값 또한 프로그램 메모리 주소로 취급하여 관리하겠다는 것입니다.  즉, 간접 연산을 한 값 또한 포인터 타입이 된다는 것이죠.  역시 예를 들어서 설명을 해야 겠네요.

 

 앞에 포인터 배열에서 char *parr[4];와 같은 표현을 사용했었는데 parr은 무슨 타입으로 취급을 할까요?  배열명이 원소에 대한 포인터 상수로 취급한다는 것은 익히 얘기를 하였습니다.  즉, parr은 원소 타입이 char형 포인터이므로 parr은 char형 포인터에 대한 포인터 상수로 취급한다고 얘기를 할 수 있겠지요.

 

char *parr[4]={NULL} ;  //이름순으로 sort하기 위해 선언된 포인터 배열

 

void fnListAll(char **in,unsigned size)

{

    unsigned cnt = 0;

    for(cnt=0; cnt<size; cnt++)

    {

        printf(“[%d]: [%s]\n”,cnt+1, in[cnt]);

    }

}

...중략...

void foo()

{

   ...중략...

fnListAll(parr);

...중략...

}

 

 위의 예처럼 포인터 배열을 입력 매개변수로 받기 위해서도 다중 포인터는 필요하게 될 것입니다.  이 외에도 아래의 예처럼 지역변수가 포인터 타입일 때 호출하는 함수를 통해 해당 되는 포인터 타입의 지역변수의 값을 변경하고자 할 때 등 여러 곳에서 사용이 됩니다. 

 

void foo()

{

   int a = 2, b= 3;

   int *pa = &a;

   int *pb = &b;

swap(&pa,&pb);

}

void swap(int **in1, int **in2)

{

    int *temp = 0;

    temp = *in1;

    *in1 = *in2;

    *in2 = temp;

}

 

반응형
Please Enable JavaScript!
Mohon Aktifkan Javascript![ Enable JavaScript ]