포인터(pointer) : 변수의 주소를 나타냄
100,101,102,103
일 때 a의 주소는?주소값을 저장하는 변수로, 포인터라고 약칭으로 부릅니다.
항상 가리키고자 하는 변수의 타입을 지정해 두어야 합니다.
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'
}
NULL
or 0
으로 초기화 할 것// 아래의 경우, p에 어떤 주소값이 들어있는지 모름
// 그럼 p가 가리키고 있는 어떤 메모리에 뭐가 들어있을지도 모르는데 값을 수정한다면?
// 시스템 상의 중요한 부분을 건들수도 있는 <아주 위험한 상황>
int *p;
*p == 100;
0
or NULL
값을 가지고 있는지 확인할 것Q. 그럼 void* 타입의 포인터 변수도 존재할까?
A. 당연히 존재한다! 하지만 void 포인터가 가리키는 데이터의 종류와 크기를 알 수 없으므로 개발자 스스로 기억해야 한다는 단점이 존재한다.
배열의 첫번째 원소에 접근하려면 배열의 이름을 사용하면 된다.
즉, 배열 이름 = 배열의 시작 주소
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);
int nArray[10];
int *p = nArray;
for(int i=0; i<10; i++)
cout << *(p + i);
int nArray[10];
for(int i=0; i<10; i++)
cout << *(nArray + i); //nArray[i]와 동일
생각보다 이 부분이 많이들 어렵다고 느끼는 것 같다.
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] |
arr == &arr[0]
== *arr == arr[0]
-> 주소arr + 1 == &arr[1]
== *(arr+1) == arr[1]
-> 주소**arr = arr[0][0]
-> 값**(arr+1) == arr[1][0]
-> 값*arr[0] == arr[0][0]
*arr[1] == arr[1][0]
위에서 arr[0]은 1행의 시작주소, arr[1]은 2행의 시작주소이다.
포인터 배열 = 포인터 변수들을 배열의 원소로 갖는 배열이다.
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차원 포인터 배열도 같은 방법으로 원하는 배열의 주소를 원소로 넣으면 된다.
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차원 문자배열과 문자열 포인터의 차이는 무엇일까?
- 초기화
- 2차원 문자 배열
모든 문자열이 배열에 복사 즉, 문자열을 구성하는 문자 하나하나가 배열의 원소로 저장- 문자열 포인터 배열
문자열 상수의 주소값만 복사 즉, 문자열을 구성하는 문자 하나하나가 배열에 저장X
- 원소의 변경
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 속성을 부여하는 형태가 2가지가 있다.
int n1 = 10;
int n2 = 20;
int* const p = &n1;
p = &n2; //fail
*p = 20; //OK
int n1 = 10;
int n2 = 20;
const int* p = &n1;
p = &n2; //OK
*p = 20; //fail
int n1 = 10;
int n2 = 20;
const int* const p = &n1;
p = &n2; //fail
*p = 20; //fail