C++

C++ 포인터(Pointers) 총정리

김구티2 2024. 4. 29. 21:51

1. 포인터의 개념

포인터는 주소를 상징적으로 표현하는 것이다. 포인터는 프로그램이 참조에 의한 호출을 시뮬레이션하고, 동적 데이터 구조를 만들고 조작할 수 있게 해준다. 배열이나 다른 데이터 구조의 요소 위에서 반복하는 것이 포인터의 주요 사용 중 하나라 할 수 있겠다.

작업 중인 변수의 주소는 동일한 데이터 유형(int 또는 string 등)을 가리키는 포인터 변수에 할당된다.

 

포인터는 잘만 사용한다면, 코드를 줄이고 성능을 향상시킨다. 포인터는 문자열, 트리, 배열, 구조 및 함수를 검색하는 데 사용된다.
포인터를 사용하면 함수에서 여러 값을 반환할 수도 있다. 또한, 컴퓨터 메모리의 메모리 위치에 액세스할 수도 있다. 결국 모든 것은 사용하기 나름이다.

 

구문:

datatype *var_name; 
int *ptr;   // ptr can point to an address which holds int data

 

C++에서 포인트를 쓰는 방법은 다음과 같다.

① 포인터 변수 정의
② 변수의 주소를 반환하는 단항 연산자(&)를 사용하여 포인터에 변수의 주소를 할당
③ 해당 피연산자가 지정한 주소에 있는 변수 값을 반환하는 단항 연산자(*)를 사용하여 주소에 저장된 값에 액세스

 

데이터 유형을 포인터에 연결하는 이유는 데이터가 몇 바이트에 저장되어 있는지 알기 때문이다. 포인터를 늘리면, 포인터가 가리키는 데이터 유형의 크기만큼 포인터를 늘린다.

 

예시:

// C++ program to illustrate Pointers

#include <bits/stdc++.h>
using namespace std;
void gutis()
{
     int var = 20;

     // declare pointer variable
     int* ptr;

     // note that data type of ptr and var must be same
     ptr = &var;

     // assign the address of a variable to a pointer
     cout << "Value at ptr = " << ptr << "\n";
     cout << "Value at var = " << var << "\n";
     cout << "Value at *ptr = " << *ptr << "\n";
}
// Driver program
int main() 

     guti(); 
     return 0;
}

 

출력:

Value at ptr = 0x7ffe454c08cc
Value at var = 20
Value at *ptr = 20

2. 레퍼런스와 포인터

C++ 인수를 함수에 전달하는 방법은 총 3가지다.

① 값에 의한 호출 (Call-By-Value)

② 포인터 인수가 있는 참조에 의한 호출 (Call-By-Reference with a Pointer Argument)

③ 참조 인수가 있는 참조에 의한 호출 (Call-By-Reference with a Reference Argument)

 

예시:

// C++ program to illustrate call-by-methods

#include <bits/stdc++.h>
using namespace std;

// Pass-by-Value
int square1(int n)
{
     // Address of n in square1() is not the same as n1 in
     // main()
     cout << "address of n1 in square1(): " << &n << "\n";

     // clone modified inside the function
     n *= n;
     return n;
}
// Pass-by-Reference with Pointer Arguments
void square2(int* n)
{
     // Address of n in square2() is the same as n2 in main()
     cout << "address of n2 in square2(): " << n << "\n";

     // Explicit de-referencing to get the value pointed-to
     *n *= *n;
}
// Pass-by-Reference with Reference Arguments
void square3(int& n)
{
     // Address of n in square3() is the same as n3 in main()
     cout << "address of n3 in square3(): " << &n << "\n";

     // Implicit de-referencing (without '*')
     n *= n;
}
void guti()
{
     // Call-by-Value
     int n1 = 8;
     cout << "address of n1 in main(): " << &n1 << "\n";
     cout << "Square of n1: " << square1(n1) << "\n";
     cout << "No change in n1: " << n1 << "\n";

     // Call-by-Reference with Pointer Arguments
     int n2 = 8;
     cout << "address of n2 in main(): " << &n2 << "\n";
     square2(&n2);
     cout << "Square of n2: " << n2 << "\n";
     cout << "Change reflected in n2: " << n2 << "\n";

     // Call-by-Reference with Reference Arguments
     int n3 = 8;
     cout << "address of n3 in main(): " << &n3 << "\n";
     square3(n3);
     cout << "Square of n3: " << n3 << "\n";
     cout << "Change reflected in n3: " << n3 << "\n";
}
// Driver program
int main() { guti(); }

 

출력:

address of n1 in main(): 0x7fffa7e2de64
address of n1 in square1(): 0x7fffa7e2de4c
Square of n1: 64
No change in n1: 8
address of n2 in main(): 0x7fffa7e2de68
address of n2 in square2(): 0x7fffa7e2de68
Square of n2: 64
Change reflected in n2: 64
address of n3 in main(): 0x7fffa7e2de6c
address of n3 in square3(): 0x7fffa7e2de6c
Square of n3: 64
Change reflected in n3: 64

 

C++에서는 기본적으로 인수가 값 별로 전달되고, 호출된 함수에서 변경된 내용은 전달된 변수에 반영되지 않는다. 변경 내용은 호출된 함수에 의해 만들어진 클론으로 만들어지는 것이다. original copy를 직접 수정하거나 copy의 오버헤드를 피하기를 원한다면, 우리는 Pass-by-Reference를 사용해야 한다.

3. 포인터와 배열의 이름

배열 이름은 배열의 첫 번째 요소의 주소를 포함하며, 이는 상수 포인터와 같은 역할을 한다. 즉, 배열 이름에 저장된 주소는 변경할 수 없는 것이다. 예를 들어 val이라는 배열이 있으면, val과 &val[0]을 서로 교환하여 사용할 수 있다.

 

예시:

// C++ program to illustrate Array Name as Pointers
#include <bits/stdc++.h>
using namespace std;
void guti()
{
     // Declare an array
     int val[3] = { 5, 10, 20 };

     // declare pointer variable
     int* ptr;

     // Assign the address of val[0] to ptr
     // We can use ptr=&val[0];(both are same)
     ptr = val;
     cout << "Elements of the array are: ";
     cout << ptr[0] << " " << ptr[1] << " " << ptr[2];
}
// Driver program
int main() { guti(); }

 

출력:

Elements of the array are: 5 10 20

 

 

Pointer ptr이 인수로 함수에 전송되면, 배열 val도 유사한 방식으로 접근할 수 있다.

4. 포인터 표현과 산술

다음과 같은 포인터에 대해 제한된 산술 연산 세트를 수행할 수 있다.
① 증분(++)
② 감소(--)
③ 포인터에 정수(+ 또는 +=) 추가
④ 포인터에서 정수 제거( - 또는 -= )
⑤ 두 포인터의 차(p1-p2)

 

*참고: 포인터 산술은 배열에서 수행되지 않으면 의미가 없다.

 

예시:

// C++ program to illustrate Pointer Arithmetic
#include <bits/stdc++.h>
using namespace std;
void guti()
{
     // Declare an array
     int v[3] = { 10, 100, 200 };

     // declare pointer variable
     int* ptr;

     // Assign the address of v[0] to ptr
     ptr = v;

     for (int i = 0; i < 3; i++) {
          cout << "Value at ptr = " << ptr << "\n";
          cout << "Value at *ptr = " << *ptr << "\n";

          // Increment pointer ptr by 1
          ptr++;
    }
}

// Driver program
int main() { guti(); }

 

출력:

Value at ptr = 0x7ffe5a2d8060
Value at *ptr = 10
Value at ptr = 0x7ffe5a2d8064
Value at *ptr = 100
Value at ptr = 0x7ffe5a2d8068
Value at *ptr = 200

 

5. 고급 포인터 표기법

2차원 숫자 배열에 대한 포인터 표기법을 생각해 보도록 한다. 다음 선언을 생각해 보자.

int nums[2][3]  =  { { 16, 18, 20 }, { 25, 26, 27 } };

 

일반적으로, nums[ i ][ j ] 는 *(*(nums+i)+j) 와 같다.

6. 포인터와 문자열 리터럴

string 리터럴은 null-terminated 문자 시퀀스를 포함하는 배열이다. string 리터럴은 type 문자와 terminating null 문자의 배열이며, 각 요소는 type const char(*문자를 수정할 수 없음)이다.

 

이것은 문자 그대로 "guti"를 나타내는 배열을 선언하고, 그 첫 번째 요소에 대한 포인터가 ptr에 할당된다. 만약 "guti"가 주소 1800에서 시작하는 메모리 위치에 저장된다고 한다면, 우리는 이전의 선언을 다음과 같이 나타낼 수 있다:

포인터와 배열이 식에서 동일한 방식으로 작동하므로 ptr을 사용하여 문자열 리터럴의 문자에 액세스할 수 있다. 예를 들면 다음과 같다:

char ptr = 0;
char x = *(ptr+3);
char y = ptr[3];

7. 포인터 to 포인터

C++에서 데이터나 다른 포인터를 가리키는 포인터를 만들 수 있다. 구문은 포인터를 선언하는 동안, 각 수준의 indirection에 대한 단항 연산자(*)를 필요로 한다.

 

char a;
char *b;
char ** c;
a = ’g’;
b = &a;
c = &b;

 

여기서 b는 'g'를 저장하는 char를 가리키고 c는 포인터 b를 가리킨다.

8. 보이드(Void) 포인터

이것은 유형이 없음을 나타내는 C++에서 사용할 수 있는 특수한 유형의 포인터다. 보이드 포인터는 유형이 없는 값을 가리키는 포인터인 것이다. 따라서 길이가 결정되지 않고, 참조 해제 속성도 결정되지 않는다. 보이드 포인터는 어떤 데이터 유형을 가리킬 수 있기 때문에 큰 유연성을 가지고 있다고 해석할 수도 있다. 이러한 포인터는 직접 참조 해제될 수 없다. 우선적으로, 이러한 포인터는 참조 해제되기 전에 특정 데이터 유형을 가리키는 다른 포인터 유형으로 변환되어야 한다.

 

예시:

// C++ program to illustrate Void Pointer
#include <bits/stdc++.h>
using namespace std;
void increase(void* data, int ptrsize)
{
     if (ptrsize == sizeof(char)) {
            char* ptrchar;

            // Typecast data to a char pointer
            ptrchar = (char*)data;

            // Increase the char stored at *ptrchar by 1
            (*ptrchar)++;
            cout << "*data points to a char"
                    << "\n";
      }
      else if (ptrsize == sizeof(int)) {
            int* ptrint;

            // Typecast data to a int pointer
            ptrint = (int*)data;

            // Increase the int stored at *ptrchar by 1
            (*ptrint)++;
            cout << "*data points to an int"
                    << "\n";
      }
}
void geek()
{
      // Declare a character
      char c = 'x';

      // Declare an integer
      int i = 10;

      // Call increase function using a char and int address
      // respectively
      increase(&c, sizeof(c));
      cout << "The new value of c is: " << c << "\n";
      increase(&i, sizeof(i));
      cout << "The new value of i is: " << i << "\n";
}
// Driver program
int main() { geek(); }

 

출력:

*data points to a char
The new value of c is: y
*data points to an int
The new value of i is: 11

9. 유효하지 않은(Invalid) 포인터

포인터는 유효한 주소를 가리켜야 하지만, 반드시 (*배열의 경우와 같이) 유효한 요소를 가리켜야 하는 것은 아니다. 이를 유효하지 않은, Invalid 포인터라고 한다. 초기화되지 않은 포인터도 Invalid 포인터다.

 

int *ptr1;
int arr[10];
int *ptr2 = arr+20;

 

여기서 ptr1은 초기화되지 않으므로 유효하지 않은 포인터가 되고, ptr2는 arr의 경계를 벗어나므로 유효하지 않은 포인터가 된다.

 

*참고: 유효하지 않은 포인터가 반드시 컴파일 오류를 발생시키는 것은 아니다.

10. 널(Null) 포인터

Null 포인터는 그저 유효하지 않은 주소가 아닌, 아무 곳도 가리키지 않는 포인터다. 포인터를 Null로 할당하는 방법은 2가지이다.

 

int *ptr1 = 0;
int *ptr2 = NULL;

728x90