아 오늘 자료구조 수업을 들었는데
포인터에 대해서 굉장히 자세하게 알려 주셨다
나름 재미있었어서 복습겸 정리
만약 cout << ptr을 하면, 2000이 출력될 것이고
cout << *ptr을 하면 Dereferencing을 하여 12를 출력할 것임.
int a = 12;
int *p1 = &a
int **p2 = &p1cout << *p1;
cout << **p2;
Dereferencing 횟수는 몇 번 쫓아가면 원하는 Object가 있는지를 의미함.
그래서 p2를 통해 a를 출력하고 싶다면, Dereferencing을 두 번 붙이면 된다.
cf. 아래 코드에서, q는 pointer가 아니다. 가장 가까이 있는 p만 pointer이다.
int *p, q
int ptr; // integer 변수의 주소를 저장함
char q; // character 변수의 주소를 저장함
위의 두 포인터는 다른 Type의 변수 주소를 저장하지만
사실 포인터의 크기는 모두 동일하다
어차피 주소를 저장하는 데 타입이 뭐가 중요해
(실제로 찾아보니 컴파일러의 설정에 따라 포인터 변수의 크기가 달라지는 것 같다.
16bit에서는 2바이트, 32bit에서는 4바이트, 64bit에서는 8바이트)
그러나 최종적으로, 그 포인터를 따라가서 해당 주소에 무슨 타입이 있는지 알기 위해서 표기한다.
int a[2] = { ... };
int *p = &a;float b[2] = { ... };
float *q = &b;
이 상태에서 p++, q++를 한다면?
두 표기법 모두 배열의 두 번째 원소를 접근하는 코드이다.
그러나 p는 int 크기만큼, q는 float 크기만큼 더해져야 한다.
때문에 포인터에 type을 표기하는 거임.
void *q
최종적으로 뭘 가리키고 있는지 모르겠다는 포인터
그 어떤 타입이든 객체든 가리킬 수 있는 포인터 (심지어는 함수도 가능)
어떤 것이든 주소값만 대입하면 된다.
int a = 10;
float b = 10.1f;void *aPtr = a;
void *bPtr = b;
그러나 사용할 때에는 조심해야 함. 이 포인터가 어떤 타입을 가리키는지 모르기 때문.
최종적으로 무슨 type이 되었는지 알려 주고 사용해야 함.
cout << *(int*)aPtr => (int*)로 형변환
cout << *(float*)bPtr => (float*)로 형변환
void print() { ... }
void (*funcPtr)(int);
funcPtr = print;
funcPtr(); // print 함수 실행
포인터는 함수도 가리킬 수 있음.
선언할 때에는 반환형과 인수를 명시해 주면 된다.
그리고 이런 함수형 포인터는 typedef를 이용하여 자료형으로 만들 수도 있음.
typedef void(*funcPointer)(void);
funcPointer p = print;
마지막으로, void 포인터는 함수도 가리킬 수 있다고 하였다.
void 포인터를 생성하여 함수를 가리킨 후, 나중에 함수 포인터로 형변환하면 됨.
typedef void(*funcPointer)(void);
...
void* ptr;
ptr = print;
((funcPointer)ptr)(); // 함수 포인터로 형변환하여 함수 호출
원래 Null은 어떤 특정한 값이 아니라 "아무것도 가리키지 않겠다"라는 symbol이었음.
그래도 일단 정의해야 하니 #define NULL 0 을 했는데...
그래서 NULL + 3과 같은 연산도 가능함. 이런 목적으로 만든 게 아닌데...
때문에 cpp에서는 자체적으로 연산을 막는 nullptr을 사용할 수 있음.
참조