-
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; }
이 프로그램을 실행시킬 때는 이전과는 다르게 해야 한다. 두 가지 방법이 있다.
- 첫 번째는 DOS 창을 이용하는 것.
- 두 번째는 visual Studio에서 명령 인수를 입력하는 방법이다. [프로젝트] -> [프로젝트_이름.exe속성]을 선택, 디버깅 탭에서 명령 인수 칸에 원하는 문자열을 입력한다.
'Programming 기초 > C Language' 카테고리의 다른 글
C언어 기초#13 스트림과 파일 입출력 printf scanf 심화, fopen(), fclose(), 이진 파일 읽고 쓰기, 버퍼링, fseek(), rewind(), ftell(), foef() (0) 2023.05.03 C언어 기초#12 전처리 및 비트 필드 (0) 2023.04.28 C언어 기초#10 구조체와 포인터, 공용체(union), 열거형(enum), typedef (0) 2023.04.15 C언어 기초#9 문자열(string) (0) 2023.04.13 C 언어 기초#8 포인터(pointer) (0) 2023.04.13