응용 포인터

이지우·2022년 11월 16일
0

C언어

목록 보기
15/17
  • 이중 포인터
  • 배열 포인터
  • 함수 포인터

이중 포인터

포인터는 변수의 주소를 담고 있는 변수이다.
이 포인터 변수의 주소를 가리키는 것을 이중 포인터라고 한다.

변수 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 = &pi;		// 포인터의 주소를 저장한 이중 포인터
    
    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가 된다.
  1. 포인터를 변수명으로 쓰면 그 안의 값이 된다.
  2. 포인터에 & 연산을 하면 포인터 변수의 주소가 된다.
  3. 포인터의 * 연산은 화살표를 따라간다.

이중 포인터의 형태

int * 형 변수는 int형 변수의 주소를 담는 형태

int ** 형 변수는 (int *)형 변수의 주소를 담는 형태

실수형의 포인터도 같은 형태이다.

실수형 변수 포인터

double a = 3.5;
double *pi = &a;
double **ppi = &pi;


주소와 포인터의 차이

포인터는 변수이므로 주소 연산자를 사용하여 주소를 구할 수 있다.
주소는 상수이므로 주소 연산자를 쓸 수 없다.

int a;			
int *pi = &a;	// 주소를 포인터에 저장
&pi;			// 포인터에 주소 연산자 사용 가능 (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

  1. 배열은 전체가 하나의 논리적인 변수이다.
    배열 전체가 하나의 변수와 같은 역할을 한다.
    ex) sizeof(ary) <- 배열 전체의 크기 계산
  1. 배열의 주소에 정수를 더하면 배열 전체의 크기를 곱해서 더한다.
    배열의 주소에 정수 연산은 보통 2차원 이상의 배열에서 사용한다.
    1차원 배열에서는 할당되지 않은 메모리 영역에 접근하게 됨

2차원 배열과 배열 포인터

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);         // 배열명을 인수로 주고 함수 호출
}

2차원 배열 요소의 두 가지 의미

int ary[3][4];
  • 2차원 배열 ary의 논리적 배열 요소의 개수는? 3개
  • 2차원 배열 ary의 물리적 배열 요소의 개수는? 12개

2차원 배열의 요소를 참조하는 원리

2차원 배열은 저장 공간이 메모리에 연속적으로 할당된다.
배열 포인터를 쓰면 1차원의 물리적 공간을 2차원의 논리적 구조로 사용할 수 있다.

위 예제의 2차원 배열에서 7번째 물리적 요소에 접근하기

  1. 7번째 물리적 요소는 두 번째 부분배열 ary[1]에 속한다.
ary[1]
= ary + 1
= 100 + (1 * sizeof(ary[0])) 
= 100 + (1 * 16)
= 116 // ary[1]의 주소
  1. 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
  1. 이 주소의 요소를 쓰기 위해서는 * 연산자를 또 사용하여야 한다.
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);		// 두 정수를 인수로 받고 주소를 반환하는 함수 선언
profile
노력형 인간

0개의 댓글