C언어 기초#11 이중포인터, 함수포인터, 배열포인터, void포인터
* 이중 포인터(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;
}
이 프로그램을 실행시킬 때는 이전과는 다르게 해야 한다. 두 가지 방법이 있다.
- 첫 번째는 DOS 창을 이용하는 것.
- 두 번째는 visual Studio에서 명령 인수를 입력하는 방법이다. [프로젝트] -> [프로젝트_이름.exe속성]을 선택, 디버깅 탭에서 명령 인수 칸에 원하는 문자열을 입력한다.