포인터는 변수의 주소를 담고 있는 변수이다.
이 포인터 변수의 주소를 가리키는 것을 이중 포인터라고 한다.
변수 i의 주소값 : 100
포인터 pi의 주소값 : 200
int i;
int *pi;
pi = &i;
-----------
&i : 100
&pi : 200
# include <stdio.h>
int main()
{
int a = 10; // int형 변수의 선언과 초기화
int *pi; // 포인터 선언
int **ppi; // 이중 포인터 선언
pi = &a; // int형 변수의 주소를 저장한 포인터
ppi = π // 포인터의 주소를 저장한 이중 포인터
printf("--------------------------------------------\n");
printf("변수 변숫값 &연산 *연산 **연산\n");
printf("--------------------------------------------\n");
printf(" a%10d%10u\n", a, &a);
printf(" pi%10d%10u%10d\n", pi, &pi, *pi);
printf(" ppi%10u%10u%10u%10u\n", ppi, &ppi, *ppi, **ppi);
}
1) pi와 ppi가 변수명으로 사용되어 그 안의 값이 된다.
2) pi와 ppi에 & 연산을 한 결과는 자신의 주소 값을 의미한다.
3) ppi에 * 연산을 하면 ppi가 가리키는 대상 pi를 뜻한다.
4) ppi에 ** 연산을 하면 ppi가 가리키는 pi가 가리키는 대상이므로
변수 a가 된다.
int * 형 변수는 int형 변수의 주소를 담는 형태
int ** 형 변수는 (int *)형 변수의 주소를 담는 형태
실수형의 포인터도 같은 형태이다.
double a = 3.5;
double *pi = &a;
double **ppi = π
포인터는 변수이므로 주소 연산자를 사용하여 주소를 구할 수 있다.
주소는 상수이므로 주소 연산자를 쓸 수 없다.
int a;
int *pi = &a; // 주소를 포인터에 저장
π // 포인터에 주소 연산자 사용 가능 (O)
&(&a); // a의 주소에 다시 주소 연산자 사용 불가능 (X)
이중 포인터도 변수이므로 또 다른 포인터에 해당 주소를 저장하여 3중 포인터를 만들 수 있다.
double ***ppp; // 3중 포인터
4중 이상의 포인터도 사용할 수 있지만 가독성이 떨어져 잘 사용하지 않는다.
char *pa = "success";
char *pb = "failure";
이 두 포인터가 다른 문자열을 연결하도록 포인터 값을 바꾸는 함수
#include <stdio.h>
void swap_ptr(char **ppa, char **ppb)
{
char *pt;
pt = *ppa; // ppa가 가리키는 pa의 값을 pt에 저장
*ppa = *ppb; // ppb가 가리키는 pb의 값을 pa에 저장
*ppb = pt; // pt의 값을 ppb가 가리키는 pb에 저장
}
int main()
{
char *pa = "success";
char *pb = "failure";
printf("pa -> %s, pb -> %s\n", pa, pb); // 바꾸기 전 문자열
swap_ptr(&pa, &pb); // 함수 호출
printf("pa -> %s, pb -> %s\n", pa, pb); // 바꾼 후 문자열
}
함수의 인수로 문자열의 주소를 주어야하므로 주소를 담기 위해 매개변수로 이중 포인터가 필요하다.
배열명 = 배열의 첫 번째 요소의 주소
int형 포인터 배열의 이름 = int형 포인터의 주소 -> 이중 포인터에 저장해야 됨
#include <stdio.h>
void print_str(char **pps, int cnt) // 매개변수로 이중 포인터 사용
{
int i;
for (i = 0; i < cnt; i++) // 배열 요소 수만큼 반복
{
printf("%s\n", ppa[i]); // 이중 포인터를 배열명처럼 사용
}
}
int main()
{
char *ptr_ary[] = {"eagle", "tiger", "lion", "squirrel"}; // 초기화
int count; // 배열 요소 수를 저장할 변수
count = sizeof(ptr_ary) / sizeof(ptr_ary[0]); // 배열의 요소의 수 계산
print_str(ptr_ary, count); // 배열명과 배열 요소 수를 주고 호출
}
배열명이 주소를 가리키는 주소를 나타내기 때문에 이중 포인터로 매개변수를 선언해야 한다.
배열 전체를 하나의 변수로 생각하고 그 주소 구해보기
#include <stdio.h>
int main()
{
int ary[5];
printf(" ary의 값 : %u\t", ary); // 주소로서의 배열명의 값
printf("ary의 주소 : %u\n", &ary); // 배열의 주소
printf(" ary + 1 : %u\t", ary + 1);
printf(" &ary + 1 : %u\n", &ary + 1);
}
ary 자체를 주소로 쓰일 때는 첫 번째 요소를 가리킴 -> 대상의 크기가 4
&ary의 값은 배열 전체를 가리키기 때문에 가리키는 대상의 크기가 20
2차원 배열은 1차원 배열로 만든 배열이다.
2차원 배열의 이름 = 1차원 배열의 주소
배열 포인터로 2차원 배열의 이름을 저장할 수 있다.
(2차원 배열의 이름이 배열의 주소이기 때문)
#include <stdio.h>
int main()
{
int ary[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
int(*pa)[4]; // int형 변수 4개의 배열을 가리키는 배열 포인터
// 괄호가 없으면 포인터 배열이 되어버리므로 주의!
int i, j;
pa = ary;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
printf("%5d", pa[i][j]); // pa를 2차원 배열처럼 사용
}
printf("\n");
}
}
이 코드의 경우에는 배열 포인터를 사용하지 않고 ary를 직접 사용하는 것이 더 간단하다.
하지만 출력부분을 함수로 만들기 위해서는 배열 포인터가 필요하다.
#include <stdio.h>
void print_ary(int(*pa)[4]) // 매개변수는 배열 포인터
{
int i, j;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
printf("%5d", pa[i][j]); // pa를 2차원 배열처럼 사용
}
printf("\n");
}
}
int main()
{
int ary[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
print_ary(ary); // 배열명을 인수로 주고 함수 호출
}
int ary[3][4];
2차원 배열은 저장 공간이 메모리에 연속적으로 할당된다.
배열 포인터를 쓰면 1차원의 물리적 공간을 2차원의 논리적 구조로 사용할 수 있다.
ary[1]
= ary + 1
= 100 + (1 * sizeof(ary[0]))
= 100 + (1 * 16)
= 116 // ary[1]의 주소
(ary + 1) + 2 = ary + 3 = 148
*(ary + 1) = 116 // 요소 하나를 가리킴
*(ary + 1) + 2
= *(ary + 1) + (2 * sizeof(ary[1][0]))
= 116 + (2 * 4)
= 124
ary[1][2] = *(*(ary + 1) + 2)
❗ 2차원 배열 int ary[3][4];에서 다음 주소는 모두 같은 값을 가진다.
&ary // 2차원 배열 전체의 주소
ary // 첫 번째 부분배열의 주소
&ary[0] // 첫 번째 부분배열의 주소
ary[0] // 첫 번째 부분배열의 첫 번째 배열 요소의 주소
&ary[0][0] // 첫 번째 부분배열의 첫 번째 배열 요소의 주소
sizeof(ary) // 배열 전체의 크기 48바이트
sizeof(&ary[0]) // 주소의 크기 4바이트
sizeof(ary[0]) // 부분배열 전체의 크기 16바이트
sizeof(&ary[0][0]) // 주소의 크기 4바이트
함수명은 함수 정의가 있는 메모리의 시작 위치이다. (함수명 = 주소)
#include <stdio.h>
int sum(int a, int b)
{
return (a + b);
}
int main()
{
int (*fp)(int, int); // 함수 포인터 선언
int res; // 반환값을 저장할 변수
fp = sum; // 함수명을 함수 포인터에 저장
res = fp(10, 20); // 함수 포인터로 함수 호출
// res = (*sum)(10, 20); // 함수명에 괄호와 함께 간접 참조 연산를 사용하여 호출
// res = (*fp)(10, 20); // 이렇게도 사용 가능
printf("result : %d\n", res); // 반환값 출력
}
함수 포인터 선언부분에서
fp : 포인터 변수명
int ~ (int, int) : int (int, int)형 함수를 가리킨다
반드시 변수명을 *와 괄호로 묶어야한다.
int *fp(int, int); // 두 정수를 인수로 받고 주소를 반환하는 함수 선언