C++ 포인터

yu-podong·2021년 3월 16일
0

CPP

목록 보기
2/9
post-thumbnail

포인터의 기본


변수의 주소

포인터(pointer) : 변수의 주소를 나타냄

  • 변수의 주소 = 변수가 포함하고 있는 제일 첫 번째 바이트의 주소
    ex. int a는 4바이트의 메모리 공간 보유
    각 바이트의 주소가 100,101,102,103일 때 a의 주소는?
    => 첫 번째 바이트의 주소인 100 -> &a == 100

포인터 변수

주소값을 저장하는 변수로, 포인터라고 약칭으로 부릅니다.
항상 가리키고자 하는 변수의 타입을 지정해 두어야 합니다.

why? 메모리에는 타입에 대한 정보가 저장되어 있기 않기 때문!
그러므로, 포인터 변수의 타입을 통해 데이터의 크기, 종류를 알아냄

사용방법 : int *p2; float* p2; <- 다양한 타입의 포인터 변수 존재

  • 간접 참조 연산자 : 포인터 변수가 가리키는 실제 변수의 값을 참조
#include <iostream>
using namespace std;

int main() {
	int n = 10;
    char c = 'A';
    
    int *np = &n;	//np는 n의 주소값을 가짐
    char *cp = &c; 	//cp는 c의 주소값을 가짐
    
    cout << *np << '\n';	//10
    cout << *cp << '\n';	//'A'
    
    *np = 20;	//n에 20 저장됨
    
    cout << *np << '\n';	//20
    cout << *cp << '\n';	//'A'
}

📢포인터를 사용할 때 주의할 점!📢

  1. 항상 포인터 변수는 NULL or 0으로 초기화 할 것
// 아래의 경우, p에 어떤 주소값이 들어있는지 모름
// 그럼 p가 가리키고 있는 어떤 메모리에 뭐가 들어있을지도 모르는데 값을 수정한다면?
// 시스템 상의 중요한 부분을 건들수도 있는 <아주 위험한 상황>
int *p;
*p == 100;
  1. 항상 포인터 변수가 0 or NULL값을 가지고 있는지 확인할 것
    -> 안전성 UP

Q. 그럼 void* 타입의 포인터 변수도 존재할까?
A. 당연히 존재한다! 하지만 void 포인터가 가리키는 데이터의 종류와 크기를 알 수 없으므로    개발자 스스로 기억해야 한다는 단점이 존재한다.


포인터와 배열


1차원 배열과 포인터

배열의 첫번째 원소에 접근하려면 배열의 이름을 사용하면 된다.
즉, 배열 이름 = 배열의 시작 주소

int array[5];
int *p = array;	//p에는 array[0]의 주소가 저장

array[2] = 1;
cout << array+4;	//array[4]의 주소 출력
cout << *(array+2);	//array[2]의 값인 1 출력
p = p+2;		//p = (array+2) -> p = &array[2]이므로 array[2]의 주소 저장
+p = 10;		//p가 가리키는 array[2]에 10 저장

물론 포인터를 선언할 때, 같은 방법으로 배열의 다른 원소의 주소로 초기화 가능!
int *p2 = &array[2]; == int *p3 = (array + 2);

배열의 원소를 탐색하는 방법

  1. 포인터 이용
int nArray[10];
int *p = nArray;

for(int i=0; i<10; i++)
	cout << *(p + i);
  1. 배열의 이름 이용
int nArray[10];

for(int i=0; i<10; i++)
	cout << *(nArray + i);	//nArray[i]와 동일

2차원 배열과 포인터

생각보다 이 부분이 많이들 어렵다고 느끼는 것 같다.

2차원 배열은 1차원 배열들의 배열이라고 생각하면 쉽다.
일단 2차원 배열을 선언할 때를 생각해보자.

int arr[2][2] = {
	{1,2},	//arr[0]에 해당
	{3,4}	//arr[1]에 해당
};
arr[0]arr[0][0]arr[0][1]
arr[1]arr[1][0]arr[1][1]
  1. 2차원 배열의 이름
    • arr == &arr[0] == *arr == arr[0] -> 주소
    • arr + 1 == &arr[1] == *(arr+1) == arr[1] -> 주소
    • **arr = arr[0][0] -> 값
    • **(arr+1) == arr[1][0] -> 값

  1. 부분 배열의 이름
    • *arr[0] == arr[0][0]
    • *arr[1] == arr[1][0]

        위에서 arr[0]은 1행의 시작주소, arr[1]은 2행의 시작주소이다.

포인터 배열

포인터 배열 = 포인터 변수들을 배열의 원소로 갖는 배열이다.

1차원 포인터 배열

char* ptr_arr[3];
char a=‘A’, b=‘B’, c=‘C’; 

ptr_arr[0] = &a;
ptr_arr[1] = &b;
ptr_arr[2] = &c;

cout << *ptr_arr[0]; //'A' 출력
ptr_arr[0]ptr_arr[1]
&a&b

2차원 포인터 배열

2차원 포인터 배열도 같은 방법으로 원하는 배열의 주소를 원소로 넣으면 된다.

int ary1[4]={1, 2, 3, 4}; 
int ary2[4]={11, 12, 13, 14}; 
int ary3[4]={21, 22, 23, 24};

int *ptr_ary[3]={ary1, ary2, ary3}; 
cout << *ptr_ary[0];	//arr1[0];

문자열 포인터 배열

문자열들을 효율적으로 저장할 수 있는 방법이며, 문자열 상수 = 문자열의 시작 주소
즉, 문자열 포인터 배열로 문자열을 수정할 수 없다.
 why? 문자열 상수는 데이터의 수정이 불가능한 영역에 위치하고 있기 때문이다!

char* ptr_ary[ ] = { "dog", "elephant", "horse", "tiger", "lion" };

그럼 2차원 문자배열과 문자열 포인터의 차이는 무엇일까?

  1. 초기화
  • 2차원 문자 배열
    모든 문자열이 배열에 복사 즉, 문자열을 구성하는 문자 하나하나가 배열의 원소로 저장
  • 문자열 포인터 배열
    문자열 상수의 주소값만 복사 즉, 문자열을 구성하는 문자 하나하나가 배열에 저장X
  1. 원소의 변경
    2차원 문자 배열은 원소를 변경할 수 있다.
    char_arr[0][1] = 'i'; //OK!
    문자열 포인터 배열을 변경 불가능하다.
    ptr_arr[0][1] = 'i'; //Fail!

이중 포인터

'어떤 변수의 주소'를 가리키는 '포인터 변수의 주소'를 가리키는 '포인터'
즉, 이중 포인터 -> 포인터 -> 어떤 변수

int n = 10;
int* pn = &n;
int** ppn = &pn;

배열 포인터

배열을 가리키는 포인터이다. 이 부분도 상당히 헷갈리는 부분이다. (본인 또한 다시보니 어렵..)
예를 들어 int (*arr)[2]이면 행의 개수는 상관없이 열의 개수가 2개인 배열의 주소를 저장한다는 뜻이다.
그러므로, 2차원 배열을 포인터로 사용할 때 유용하다.

int arr[2][3] = {  {11, 12, 13},   {21, 22, 23}  };
int (*parr)[3]; // 배열포인터 
int *p;     int **pt;

parr = arr; 
cout << parr << arr;            // 주소 ptr == M 
cout << parr+1 << arr+1;    // 주소 3 X 4byte 만큼 늘어남 parr+1 == arr+1 
cout << *(parr+1)  << parr[1] << *(arr+1) << arr[1] ;  // 주소 
cout << **(parr+1)  << **(arr+1) << *arr[1] << arr[1][0];  // 원소 값 : 21

p = arr[0]; 
cout << p << arr[0] << *arr;   // p == arr[0] == *arr 각 원소의 시작주소 
cout << *(p+1) << *(M[0]+1) << *(*M+1); // 원소 값 : 12

pt = &p;   // pt = arr; (X) 
cout << *pt << p ;   // *pt == p 
cout << **pt << *p;  // **pt == *p

const와 포인터


포인터 변수에 const 속성을 부여하는 형태가 2가지가 있다.

  1. 포인터 변수 자체에 const 속성 부여
    -> 포인터 변수가 가리키는 주소를 변경하지 못하게 된다.
int n1 = 10;
int n2 = 20;
int* const p = &n1;

p = &n2;	//fail
*p = 20;	//OK
  1. 포인터 변수가 가리키는 변수에 const 속성 부여
    -> 포인터 변수가 가리키고 있는 변수의 값을 변경하지 못하게 된다.
int n1 = 10;
int n2 = 20;
const int* p = &n1;

p = &n2;	//OK
*p = 20;	//fail
  1. 둘 다 const속성 부여
    -> 포인터 변수의 값(=주소), 포인터가 가리키는 변수의 값 모두 변경할 수 없다.
int n1 = 10;
int n2 = 20;
const int* const p = &n1;

p = &n2;	//fail
*p = 20;	//fail

0개의 댓글