ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • C언어 기초#11 이중포인터, 함수포인터, 배열포인터, void포인터
    Programming 기초/C Language 2023. 4. 18. 21:57

    * 이중 포인터(double pointer) = 포인터의 포인터(pointer to pointer)

    **q는 *q가 가리키는 위치의 내용이다. *q는 q가 가리키는 위치의 내용이다.

    #include<stdio.h>
    
    int main(void)
    {
    	int i = 100;	//정수 변수 선언
    	int* p = &i;	// 포인터 p는 i를 가리킨다.
    	int** q = &p;	// 이중포인터 q는 p를 가리킨다.
    
    	*p = 200;	// p를 통하여 i에 200 저장
    	printf("i=%d\n", i);
    	
    	**q = 300;	// q를 통하여 i에 300 저장
    	printf("i=%d\n", i);
    	
    	return 0;
    }
    -------------------------------------------------
    i=200
    i=300

    이중 포인터가 가장 많이 사용되는 상황은 외부에서 정의된 포인터 값을 함수의 인수로 받아서 변경하려고 하는 경우이다.

    #include<stdio.h>
    
    void set_pointer(char** q);
    char* proverb = "All that glisters is not gold.";
    
    int main(void)
    {
    	char* p = "zzz";
    	set_pointer(&p);	// 포인터 p의 주소를 복사전달
    	printf("%s \n", p);
    	return 0;
    }
    
    void set_pointer(char** q)	// 이중 포인터 q를 통하여 외부의 포인터 p를 변경한다.
    {
    	*q = proverb; // 간접 참조 연산자 * 
    }
    #include<stdio.h>
    
    void set_pointer(char** q);
    char* proverb = "All that glisters is not gold.";
    
    int main(void)
    {
    	char* p = "zzz";
    	set_pointer(p);	// p의 값을 복사 전달
    	printf("%s \n", p);
    	return 0;
    }
    
    void set_pointer(char* q)	// 함수 내에서의 변화가 main 함수에 적용되지 않는다.
    {
    	q = proverb;
    }

     

    * 포인터 배열(an array of pointers)

    - 배열의 원소가 포인터인 배열이다.

    - int* ap[10]; 에서 [ ] 연산자가 *연산자보다 우선순위가 높으므로 ap는 먼저 배열이 되고 int*(포인터)들의 배열이 된다.

    char* fname[4][10] = {
    	"apple",
        "blueberry",
        "orange",
        "melon"
    };

    위의 문장은 메모리를 낭비한다. 아래와 같이 행들의 길이가 가변적으로 변할 수 있는 래그드 배열(ragged array) 방법으로 작성한다.

    char* fname[4] = {
    	"apple",
        "blueberry",
        "orange",
        "melon"
    };

    * 배열 포인터(a pointer to an array)

    - 배열을 가리키는 포인터.

    - int (* pa) [10] 의 형식이다. int [10]을 가리키는 포인터이다.

    #include<stdio.h>
    
    int main(void)
    {
    	int a[5] = { 1, 2, 3, 4, 5 };
    	int(*pa)[5];	// int[5] 배열에 대한 포인터 선언
    	int i;
    
    	pa = &a;	// 배열 포인터에 배열의 주소를 계산하여 대입한다.
    	for (i = 0; i < 5; i++)
    		printf("%d \n", (*pa)[i]);	// 배열 포인터를 이용하여서 배열의 각 원소에 접근한다. *pa가 배열이 된다.
    
    	return 0;
    }

    포인터 배열 vs 배열 포인터

    • 포인터 배열 : int* ap[10] 포인터들의 배열이다.
    • 배열 포인터 : int (*ap)[10] 배열int[10]을 가리키는 포인터이다.

     

    *함수 포인터(function pointer)

    함수 포인터는 함수가 시작되는 주소를 가리킨다.

     

    반환형 (*함수포인터이름)(매개변수1, 매개변수 2, ...);
    함수를 가리키는 포인터를 선언한다. 반드시 괄호가 필요하다. 왜냐하면 괄호에 의하여 pf가 먼저 포인터가 되어야 한다. int *pf(int, int)는 정수형 포인터를 반환하는 pf라는 함수가 된다.
    예 : int (*pf) (int, int);
    void (*pf) (double);

    함수 포인터는 반환형, 매개 변수 등이 정확히 일치하는 함수만을 가리킬 수 있다.

    int sub(int, int);	//함수 원형 정의
    int (*pf)(int, int);	// 함수 포인터 정의
    ...
    pf = sub;	// 함수의 이름을 함수 포인터에 대입. 함수포인터는 &를 쓰지 않는다.

    함수 포인터를 이용하여 함수를 호출할 수 있다. 만약 매개 변수만 일치하면 이름이 다르더라도 함수를 바꿔가며 가리킬 수 있다.

    result = (*pf) (10, 20);

    대부분의 컴파일러는 (*pf)대신 pf를 함수 이름처럼 사용해서 호출하는 것을 허용한다.

    result = pf(10, 20);

     

    * 함수 포인터의 배열

    반환형 (*배열 이름[배열의 _크기]) (매개변수 목록);
    예 : int (*pf[5]) (int, int);
    void (*pf[5]) (double);

    // 함수 포인터 배열 계산기
    #define _CRT_SECURE_NO_WARNINGS	// scanf() 경고 무시
    #include <stdio.h>
    //함수 원형 정의
    void menu(void);
    int add(int x, int y);
    int sub(int x, int y);
    int mul(int x, int y);
    int div(int x, int y);
    void menu(void)
    {
    	printf("=======================\n");
    	printf("0. 덧셈\n");
    	printf("1. 뺄셈\n");
    	printf("2. 곱셈\n");
    	printf("3. 나눗셈(몫)\n");
    	printf("4. 종료\n");
    	printf("=======================\n");
    }
    int main(void)
    {
    	int choice, result, x, y;
    	// 함수 포인터 배열을 선언하고 초기화한다.
    	int (*pf[4]) (int, int) = { add, sub, mul, div }; // 함수포인터배열
    
    	while (1)
    	{
    		menu();
    
    		printf("메뉴를 선택하시오:");
    		scanf("%d", &choice);
    
    		if (choice < 0 || choice >= 4)	// 4번메뉴 또는 1~4선택 외 입력시 프로그램종료.
    			break;
    
    		printf("2개의 정수를 입력하시오: ");
    		scanf("%d %d", &x, &y);
    
    		result = pf[choice](x, y);	//함수 포인터를 이용한 함수 호출
    		printf("연산 결과 = %d\n", result);
    	}
    	return 0;
    }
    int add(int x, int y)
    {
    	return x + y;
    }
    
    int sub(int x, int y)
    {
    	return x - y;
    }
    
    int mul(int x, int y)
    {
    	return x * y;
    }
    int div(int x, int y)
    {
    	return x/ y;
    }

     

    * 다차원 배열과 포인터

    c에서는 행우선 방법(row-major)으로 다차원 배열을 메모리에 저장한다.

    다차원 배열의 포인터 연산은 아래 그림과 같이 해석된다.

    #include <stdio.h>
    #define ROWS 4
    #define COLS 3
    
    int m[ROWS][COLS] = { {10, 20, 30}, {10, 20, 30}, {10, 20 ,30}, {10, 20, 30} };
    double get_total_avg(int m[][COLS])
    {
    	int* p, *endp;
    	double sum = 0.0;
    
    	p = &m[0][0];
    	endp = &m[ROWS - 1][COLS - 1];
    	
    	while (p <= endp)
    		sum += *p++;
    	sum /= ROWS * COLS;
    	return sum;
    }
    
    void main(void)
    {
    	printf("m배열의 평균 : %f\n", get_total_avg(m));
    	return 0;
    }

     

    * const 키워드

    const char *p 
    const 키워드가 *연산자 앞에 있으면 포인터가 가리키는 대상이 변경되지 않는다는 것을 의미한다.
    char *const p;
    const 키워드가 * 연산자 다음에 있으면 포인터 자체가 변경되지 않는다는 것을 의미한다. 하지만 포인터가 가리키는 값은 변경할 수 있다.

     

    * volatile 키워드

    주로 동일한 메모리를 여러 개의 프로세스나 스레드가 사용할 때 필요. volatile은 다른 프로세스나 스레드가 값을 항상 변경할 수 있으니 값을 사용할 때마다 다시 메모리에서 읽으라는 것을 의미한다. 따라서 이것은 컴파일러의 최적화를 방해한다. 하지만 불시에 변경되는 값을 처리하는 경우에는 불가피하다.

    volatile char *p;
    p가 가리키는 내용이 수시로 변경되니 사용할 때마다 다시 로드하라는 의미.

     

    *void 포인터

    • 포인터를 선언할 당시에는 아직 구체적으로 대상물이 정해지지 않은 경우에 유용.
    • void형 포인터는 순수하게 메모리의 주소만을 가지고 있는 변수이다.

     

    - '포인터의 형변환' 복습 -
    double *pd = &f; //변수 f의 주소를 포인터 pd에 대입.
    int *pi;

    pi = (int*)pd; // double형 포인터를 int형 포인터로 변환
    • *연산자를 사용하려면 반드시 명시적인 대상을 가리키는 포인터 타입으로 형변환을 하여야 한다.
    • 증감 연산자도 사용이 불가하다.
    void *vp;
    *vp // 오류
    *(int*)vp; // void형 포인터를 int형 포인터로 변환한 후에 간접참조한다.
    
    vp++; // 오류.

     

    *main 함수의 인수

    main()도 함수이므로 변수와 반화값을 가질 수 있다. 

    int main(int argc, char *argv[])
    {
    	...
    }
    • argc는 명령어가 가지는 인수들의 개수
    • argv는 명령어가 가지는 인수들을 문자열 형태로 전달한다.
    • 만들어진 프로그램은 최종적으로 확장자가 exe인 실행 파일이 되어서 하드 디스크에 저장된다. 
    • argv[]배열의 원소들은 명령어 행에 있는 인수들의 주소를 가지게 된다.
    #include <stdio.h>
    
    int main(int argc, char* argv[])
    {
    	int i = 0;
    
    	for (i = 0; i < argc; i++)
    		printf("명령어 행에서 %d번째 문자열 = %s\n", i, argv[i]);
    	return 0;
    }

    이 프로그램을 실행시킬 때는 이전과는 다르게 해야 한다. 두 가지 방법이 있다.

    1. 첫 번째는 DOS 창을 이용하는 것.
    2. 두 번째는 visual Studio에서 명령 인수를 입력하는 방법이다. [프로젝트] -> [프로젝트_이름.exe속성]을 선택, 디버깅 탭에서 명령 인수 칸에 원하는 문자열을 입력한다.

     

    댓글

Designed by Tistory.