포인터
-> 저장하는 값이 주소인 변수이다.
-> 다른 말로 "간접 참조"라고도 한다.
(프로그래밍에서 참조라는 말은 접근을 듯한다.)
-> 포인터는 변수에 직접적으로 접근하지 않고, 주소를 통해 접근을 한다.
주소 확인
&(어드레스 연산자)
-> 주소를 확인하고 하는 변수명 앞에 &를 붙여주면 주소를 확인할 수 있다.
int a = 10;
cout << "a: " << a << endl;
cout << "&a: " << &a << endl;
포인터 선언
*(에스크리터)
-> 포인터 선언은 자료형과 변수명 사이에 *를 추가한다.
포인터 변수의 초기화
int a = 10;
int* ptr = &a;
다음과 같이 포인터를 선언하게 될 경우 쓰레기 값이 초기화가 되버린다.
int* ptr;
쓰레기 값 초기화란
-> ptr이라는 변수에 쓰레기 값으로 초기화가 진행된다.
-> 초기화된 값을 주소로 읽으면 어디를 가리킬지 모르는 상황이 발생한다.
-> 만약, 해당 주소가 운영체제를 가동하는데 중요한 위치를 가리킨다면?
-> 그리고 그 값을 사용자가 변경한다면, 운영체제에 큰 문제가 발생할 가능성이 생긴다.
-> 단, 요즘의 운영체제는 할당받지 않은 메모리 접근의 시도가 있을 경우
-> 이를 감지하면 해당 프로그램을 강제로 종료시켜버린다고 한다.
포인터 변수를 초기화 할 때 값을 0으로 넣게되면 결과는 000000000 이런식으로 나오게 된다.
이것은 0번지 주소를 의미하는 것이 아니고, 아무런 곳도 가리키고 있지 않다는 의미이다.
그런데 만약 포인터 변수 초기화 값이 일반 변수의 초기화 값과 같다면 햇갈리는 경우가 생길 수가 있어,
이를 방지하기 위해 C++에는 nullptr이라는 것이 존재한다.
int* ptr1 = 0;
int* ptr2 = nullptr;
역참조 연산자
-> 포인터 변수 선언 시 사용한 *는 에스크리터이다.
-> 포인터 변수 선언 후 사용한 *는 역참조 연산자이다.
-> 변수명 앞에 역참조 연산자를 붙여주면, 해당 변수가 가지고 있는 주소로 찾아가 값을 확인해주는 역할을 수행한다.
역참조 연산자를 통해 원본 데이터에 접근하여 읽을 수 있다.
int a = 10;
int* ptr = &a;
cout << a << endl; // 결과 : 10
cout << &a << endl; // 결과 : 주소값
cout << ptr << endl; // 결과 : 주소값
cout << *ptr << endl; // 결과 : 10
포인터의 크기
-> 어떤 자료형이든 4byte의 크기를 가진다.
-> 개발 환경을 살펴보니 32bit 개발환경이다.
-> 32bit 운영체제에서 가용할 수 있는 메모리의 크기는 4GB이다.
-> 포인터는 1byte당 하나의 주소가 할당되니 4GB를 byte단위로 변화하면 약 42억 9천만개의 주소 개수가 나온다.
-> 각 주소를 저장하기 위해서 필요한 크기는 최대 4byte의 공간만 있으면 저장이 가능하다.
-> 4byte 정수 자료형의 저장 범위와 동일한 크기를 가진다.
확인 결과(32비트 기준)
cout << sizeof(char*) << endl;
cout << sizeof(bool*) << endl;
cout << sizeof(short*) << endl;
cout << sizeof(int*) << endl;
cout << sizeof(long*) << endl;
cout << sizeof(float*) << endl;
cout << sizeof(long long*) << endl;
cout << sizeof(double*) << endl;
cout << sizeof(long double*) << endl;
int*에 float* 대입이 가능한가?
-> 포인터 변수로 확인할 수 있는 값은 시작 주소밖에 없다.
-> 시작 주소부터 얼마만큼의 크기를 어떤 식으로 읽을지 결정하는 것이 자료형에 정보가 있다.
예를 들어 다음과 같이 대입을 하게되면 빨간 줄이 표시가 된다.
이유는 간단하다.
포인터 간의 자료형의 크기는 같지만 읽는 방식이 다르기 때문이다.
->int -> 정수, float -> 실수
그렇다면 크기도 같고 읽는 방식도 같으면 가능할까?
결과는 동일하다.
왜냐하면 자료형은 운영체제에 따라 크기가 바뀔 수가 있는 경우가 생길 수도 있기 때문에
기존에 작성하던 코드에 오류가 발생할 수가 있다.
따라서 타입이 다른 주소 값은 대입 받을 수 없다.
사용 예)
포인터를 통해서 _coin 변수에 정수를 입력하여 할당된 주소 값에 저장하고,
다른 함수에서 새로운 일반 변수 하나를 만들어서 _coin의 주소 값에 저장된 정수 값을 불러오는 것도 가능하다.
void Input_Coin(int* _coin)
{
cout << "소지금: ";
cin >> *_coin;
}
void Func()
{
int iCoin = 0
Input_Coin(&iCoin);
}
어떤 이유로든 매개변수로 nullptr가 넘어올 수 있는 경우가 아니라면,
일반적으로 참조자( & )를 포인터로써 사용하도록 한다.
함수에서 매개변수를 통해 값을 반환할 때(out 매개변수)는 포인터를 사용하며,
매개변수 이름 앞에 out을 붙인다.
void GetScreenDimension(uint32_t* const outWidth, uint32_t* const outHeight)
{
}
호출
uint32_t width;
uint32_t height;
GetScreenDimension(&width, &height);
위 항목의 out 매개 변수는 반드시 null이 아니어야 한다. (함수 내부에서 if 문 대신 assert를 사용할 것)
assert 함수는 #include<cassert>를 추가해야한다.
void GetScreenDimension(uint32_t* const outWidth, uint32_t* const outHeight)
{
Assert(outWidth);
Assert(outHeight);
}
매개 변수가 클래스 내부에서 저장될 때는 포인터를 사용한다.
void AddMesh(Mesh* const mesh)
{
mMeshCollection.push_back(mesh);
}
매개 변수가 void 포인터여야 하는 경우는 포인터를 사용한다.
void Update(void* const something)
{
}
'Programming > C++ Basic' 카테고리의 다른 글
C++ 배열과 포인터 (0) | 2020.08.28 |
---|---|
C++ const와 포인터 (0) | 2020.08.28 |
C++ 재귀 함수 (0) | 2020.08.25 |
C++ 함수 오버로딩, 디폴트 매개 변수 (0) | 2020.08.25 |
C++ 함수의 선언부, 정의부 (0) | 2020.08.25 |