📚포인터의 개요

포인터는 변수의 일종인데 포인터가 일반 변수와 다른 점은 실질적인 데이터값을 갖는 것이 아니라 데이터가 저장된 기억 장소의 주소를 갖는다. 변수는 변수에 대입된 자료값과 자신에게 할당된 기억공간의 주소를 가지고 있다. 포인터를 사용하면 접근하고자 하는 변수의 기억공간 주소를 저장할 수 있게 된다. 따라서 변수의 기억공간에 직접 접근할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

void main()
{
    int a = 3;
    int b = 10;
    int table[5] = { 1, 2, 3, 4, 5 };

    printf("a의 주소는 %x \n", &a);
    printf("b의 주소는 %x \n", &b);
    printf("배열 table의 주소는 %x \n", table);
    printf("배열 table의 첫번째 원소의 주소는 %x \n", &table[0]);
    printf("배열 table의 두번째 원소의 주소는 %x \n", &table[1]);
}



📚포인터 변수의 선언

📄포인터 변수의 선언

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>

void main()
{
    int a, b;
    int* p;     // 변수 p를 포인터 변수로 선언

    a = 10;

    // & 주소연산자: 모든 변수에 대한 주소값을 구하는 연산자
    // 포인터 변수 p에 변수 a의 주소값을 대입
    p = &a;

    // * 내용연산자: 포인터 변수의 자료를 구하는 연산자
    // 포인터 변수 p가 가리키는 주소의 내용(a값 10)을 변수 b에 저장
    b = *p;
}


📄포인터 변수의 참조

포인터 변수의 참조란 어떤 포인터 변수가 가리키는 내용을 읽어 내거나 값을 대입하는 것을 말한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int *p, i = 1;

// 변수 i의 값 1을 p가 가리키는 주소에 대입해야 하는데
// 포인터 변수 p가 기억공간 내 몇번지를 가리키는지 알 수 없음
*p = i;


// 상수 5를 p가 가리키는 주소에 대입해야 하는데
// 포인터 변수 p가 기억공간 내 몇번지를 가리키는지 알 수 없음
*p = 5;

// 포인터 변수가 가리키는 번지 안에 값을 대입할 때 사용하는 가장 정확한 방법
// 변수 i의 주소를 포인터 변수 p에 넣고 그 주소의 기억장소에 값을 대입
p = &i;
*p = 10;


1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

void main()
{
    int* p, i = 1, j;

    p = &i;
    j = *p;
    ++j;

    printf("*p = %d \n", *p);
    printf("p = %x \n", p);
    printf("j = %d \n", j);
}


📄void형 포인터

컴파일 시 자료형을 결정하지 못하고, 프로그램 실행 시 포인터의 자료형이 결정되는 경우 void형 포인터를 사용한다. 어떤 자료형을 가지는 기억공간의 주소를 넘겨주는지 알려줘야 하기 때문에 반드시 명시적 형변환이 필요하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

void main()
{
    int a = 10;
    char b = 'a';
    void* p = NULL;   // void형 포인터 선언 및 초기화

    p = (int*)&a;     // void형 포인터 p에 int형 변수 a의 주소를 명시적 형변환하여 대입
    
    printf("*p = %d \n", *(int*)p);

    p = (char*)&b;    // void형 포인터 p에 char형 변수 b의 주소를 명시적 형변환하여 대입
    
    printf("*p = %c \n", *(int*)p);
}



📚포인터 연산

포인터 변수에 1을 증감하는 것은 포인터가 가리키고 있는 주소를 중심으로 이후, 이전의 자료가 있는 곳을 가리키는 것을 의미한다. 쉽게 말해, 값의 변화가 아니고 주소의 이동이다. 포인터 변수 p가 있을 때, p를 1 증가시키면 증가되는 주소는 *p의 자료형의 byte수가 된다. 예를 들면 int형 포인터 변수에 1을 증가시키면 4byte 이후 자료가 있는 곳을 가리킨다. 또한 포인터 연산은 덧셈과 뺄셈만 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>

void main()
{
    int* p;
    int a[5] = { 1, 2, 3, 4, 5 };
    p = &a[0];                                // 배열 첫 번째 원소 주소값을 포인터 변수 p에 저장

    printf("*p     == %d \n", *p);
    printf("*p++   == %d \n", *p++);        // 포인터 p의 값을 출력 후 주소 1 증가

    printf("*++p   == %d \n", *++p);        // 주소 1 증가 후 포인터 p의 값을 출력

    p = p + 3;                              // 포인터 p의 주소 3 증가
    printf("*p        == %d \n", *p);
    printf("a[3]      == %d \n", a[3]);
    printf("*p + 3    == %d \n", *p + 3);   // p번지 내용에 3을 더한 값
    printf("*(p - 3)  == %d \n", *(p + 3)); // (p번지 - 3) 번지의 내용
}



📚포인터와 배열

📄char형 포인터

문자열을 처리할 때 char형 배열을 사용해도 되지만 char형 포인터를 사용해도 된다.

1
char *c = "Hello";

c의 값은 문자열 “Hello”가 들어있는 기억공간의 시작 주소이다. 그리고 문자열 끝에는 null문자가 붙는다. 따라서 *c의 값은 문자열 “Hello”가 아니고 문자열의 첫 번째 문자 “H”가 된다.


1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

void main()
{
    char* c = "Hello";

    for (int i = 0; i < 5; ++i)
    {
        printf("*(c+%d): %c \n", i, *(c + i));
    }
}


📄포인터와 배열

배열은 포인터의 일부분이다. 모든 배열은 포인터로 표현할 수 있다.

1
2
char* str, s[] = "Hello";
str = s;

str이 가리키는 기억장소의 내용은 배열 s의 첫번째 원소 “H”이다. *(str + 1)은 s[1]인 “e”와 같다. 배열 이름 그 자체가 포인터라 배열의 이름만 가지고도 포인터처럼 사용할 수 있다.


포인터를 이용한 2차원 배열 참조

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

void main()
{
    static int arr[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
    int* p;

    p = arr[0];

    for (int i = 0; i < 3; ++i)
    {
        for (int j = 0; j < 3; ++j)
        {
            printf("%d ", *p);

            ++p;
        }

        printf("\n");
    }
}


📄포인터와 배열의 차이

  • 배열과 포인터는 호환이 가능하지만 포인터 변수에는 다른 주소 값을 넣을 수 있지만 배열 변수에는 다른 값을 넣을 수 없다는 차이가 있다.

  • 배열 이름의 값을 변경하는 것은 불가능하나 포인터에서는 ++p 또는 –p와 같이 값이 변경 될 수 있다는 차이점이 있다.

  • 배열과 포인터는 기억공간의 확보 방식에 차이가 있다. 배열은 기억공간에 자료 영역을 고정적으로 확보하는데 포인터는 유동적으로 자료 영역을 확보한다. 포인터는 필요할 때만 자료용 기억공간을 확보하고 그렇지 않을 때는 자료용 기억공간을 확보하지 않는다. 따라서 가변적인 자료를 사용해야할 때 배열 보다 포인터를 사용하는 것이 효율적이다.



📚포인터 배열

포인터들의 배열을 포인터 배열이라고 한다. 즉, 배열의 원소가 포인터로서 포인터의 집합을 의미한다. 포인터 배열은 주로 문자열 배열을 처리할 때 사용된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

void main()
{
    int a[] = { 1, 2, 3, 4 };
    int b[] = { 5, 6, 7, 8 };

    int* pa[2];
    pa[0] = a;
    pa[1] = b;

    printf("*(pa[0]) = %d \n", *(pa[0]));
    printf("*(pa[0] + 1) = %d \n", *(pa[0] + 1));
    printf("*pa[1] = %d \n", *pa[1]);
    printf("*pa[1] + 15 = %d \n", *pa[1] + 15);
}



📚이중 포인터

이중 포인터는 자료가 있는 곳을 2중으로 가리키는 포인터이다. 이중 포인터가 가리키는 주소에는 주소값이 들어있다. 다시 이중 포인터가 가리키는 주소를 찾아가면 자료를 참조하게 된다.

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

void main()
{
    char a = 'a', *p, **pp;
    p = &a;
    pp = &p;

    printf("**pp = %c", **pp);
}



Tags: ,

Categories:

Updated:

Leave a comment