포인터 기초 #1
일반 변수들은 유효범위가 있다보니깐 언제 어디서든 접근하기 쉽지 않은 경우가 있다. 경우에 따라서 직접 접근해 값을 변경하고 싶을때가 있는데 이때 주로 사용하는 것이 포인터라는 주소바구니이다.
포인터는 [TYPE *]의 형태를 가지고 있으며 64비트 운영체제/x64프로그램에서는 8바이트며 32비트 운영체제나 x86에선 4바이트 고정크기이다.
포인터변수 이름앞에 *를 붙여서 주소바구니가 가리키는 주소로 가서 무언갈 할수 있다.
*이 변수 선언에 붙으면 주소를 저장하는 바구니란 뜻이고 사용할때 붙으면 포탈을 타고 순간이동하란 뜻이다.
포인터변수의 크기는 고정인데도 TYPE은 왜 붙여주냐묜 주소에 가서 얼마만큼 읽어야할지를 알려준다.
int* ptr = &number;
int value1 = *ptr;
*ptr = 2;
//타입 불일치
_int64* ptr2 = (_int64*)&number; // 강제로 캐스팅해서 int형 변수의 주소를 _int64형 포인터에 넣었다.
*ptr2 = 0xAABBCCDDEEFF; // number에 가보면 다음메모리까지 덮어써서 쓰고있다. 0000AABB가 다음에 있겠지.
ptr은 int형이므로 &number로 가서 4바이트를 읽는데 타입불일치가 일어나면 메모리침법이 일어날 수도 있다.
포인터 기초 #2
void SetHp(int* hp)
{
*hp=100;
}
int main()
{
int hp=1;
SetHp(&hp);
cout<<hp<<endl; // 100
}
포인터 연산
1)주소연산자(&)
-해당 변수의 주소를 알려주세요
-더 정확히 말하면 해당 변수 타입(TYPE)에 따라서 TYPE* 반환
2)산술 연산자(+,-)
int* :(오른쪽에서부터 생각해보면)*포인터 타입이네 8바이트 주소를 담는 바구니! 주소를 따라가면 int(4바이트 정수형) 바구니가 있다고 가정해라!
int number = 1; number++; //1 증가
int* pointer = &number; pointer++; //4 증가 00f7808에서 008780c로 4만큼 증가한다.
포인터에서 +나-등 산술 연산으로 더하거나 빼면 한번에 TYPE의 크기만큼 이동하란 뜻이다. 배열에서 순회할때 주로 사용한다. 다음.이전 바구니로 이동하고 싶을 때 사용한다.
3)간접 연산자(*)
-포탈을 타고 해당 주소로 슝- 이동
*pointer = 3; //number가 3으로 바뀜
4)간접 멤버 연산자(->)
- *간접 연산자(포탈타고 해당 주소로 GOGO)
-.구조체의 특정 멤버를 다룰 때 사용(어셈블리 언어로 보면 사실상 그냥 덧셈)
- ->는 *와 . 한방에!
포인터 실습
참조 기초
-값 전달 vs. 주소 전달
변수/구조체를 포인터를 이용해 매개변수로 주소를 전달하는 방식과 값 자체를 전달하는 방식이 있다. 값 전달 방식은 함수 내에서 복사가 일어나고 주소 전달방식은 원본을 넘겨준다. 함수 내에서 값을 읽기만 할때는 별 차이가 없다. 그러나 값 전달 방식으로 함수에 구조체를 넘겨주면 구조체의 크기가 엄청 클 때 엄청 큰 값이 복사되므로
문제가 생길 수 있다. 주소전달이면 8바이트만 필요한데!
-참조 타임은 C에는 없고 C++에서 새로 생긴 개념이다.
-TYPE& reference = &[TYPE형 변수 이름];
int& reference = number; // number라는 바구니에 refernece라는 또 다른 이름을 부여한 것.
reference = 3;
선언할 때 항상 참조 하는 값을 같이 넣어줘야한다. 사용하는 방식은 값전달 같으나 로우레벨(어셈블리)관점에서 보면 포주소전달(포인터)와 동일하다.
참조타입의 이름을 변수에 별명으로 붙여줘서 참조형의 이름으로 변수에 접근해 읽고 쓸수 있다.
-참조형을 왜 만들었을까..? 포인터와 다른 장점을 생각해보면 ->대신 .으로 접근하능하다는것..
-함수에 참조 전달을 하면 주소를 넘기는것이기 때문에 8바이트인것은 동일하다.
StatInfo구조체가 1000바이트짜리 대형구조체라하면
값 전달 : StatInfo넘기면 1000바이트가 복사되는
주소 전달 : StatInfo*는 8바이트
참조 전달: StatInfo&는 8바이트
값 전달처럼 편리하게 사용하고 주소 전달처럼 주소값을 이용해 원본을 사용할 수 있는 일석이조의 방식..!
포인터 vs. 참조
성능적으로는 똑같다. 사용성 입장에서는 참조가 쬐끔 더 편하다. 구럼 포인터는 언제쓰지..?🤷♂️
1)편의성 관련
편의성이 좋다는게 꼭 장점만은 아니다. 포인터는 주소를 넘기니 확실하게 원본을 넘긴다는 힌트를 줄 수 잇는데 참조는 자연스럽게 모르고 지나칠 수도 있음!
void PrintInfoByPtr(StatInfo* info); // 포인터
void PrintInfoByRef(StatInfo& info); // 참조
PrintInfoByPtr(&Player);
PrintInfoByRef(Player); // 참조호출하나 값 호출과 헷갈릴수 있다..?
헷갈리는 부분에 대해서 마음대로 고치는 것을 막기 위해 const를 사용해 고치지 못하도록 막을 수 있다.
->그래서 const랑 &(reference)랑 함께 등장하는 경우가 많다(비교 연산자 오버리ㅏ이딩)
*참고로 포인터도 const와 함께 사용가능한데 *을 앞에 붙이느냐 뒤에 붙이느냐에 따라 의미가 달라진다. (주소값을 바꾸고 싶은건지/주고가 가리키는 값을 바꾸고 싶은건지 나뉜다.) ,물론 둘 다 막고 싶으면 둘 다 붙이면 된다.
//별 뒤에 const가 붙으면?
info = &globalInfo; //주소값을 바꾸는 것을 막고 싶을 때
void PrintInfoByPtr(StatInfo* const info); //별 뒤에 오는 const는 주소값을 못바꾸도록 막는다.
//별 앞에 const가 붙으면?
info->hp=1000; // info가 가리키는 값을 바꾸는 것을 막고 싶을 때
void PrintInfoByPtr(const StatInfo* info); //void PrintInfo(StatInfo const* info)와 동일하다.
2)초기화 여부
- 참조타입은 바구니의 2번째 이름이기 떄문에 참조타입은 참조하는 대상이 없으면 안된다. 참조타입은 항상 선언과 함께 초기화를 같이 해줘야 한다.
-반면 포인터는 그냥 어떤 주소라는 의미이므로 대상이 실존하지 않을 수도 있다.
-참조타입은 주소에 +,-연산 을 할 수 없다.
-포인터는 없다는 의미로 (아무것고 가리키지 않는다.) nullptr을 줄 수 있다. (참고로 포인터를 사용할때는 항상 nullptr을 체크하는게 좋다)
-결국 성능차이엔 없으니 정해진 답은 없고 팀바이팀인 경우가 많다. 강사는 없는 경우도 고려해야한다면 null체크를 필수로 하는 pointer를 사용할 때도 있고 readonly라면 const와 ref&를 사용한다. 또 일반적으로는 ref를 사용하고 바꿔야야 할 경우 OUT을 붙인다.
#define OUT
void ChangeInfo(OUT const StatInfo& info);
ChangeInfo(OUT info);
물론 다른사람이 이미 포인터를 사용하는 경우라면 따르는 경우가 많다.
참조로 사용하던 걸 포인터로 넘겨주려면..?
//포인터로 사용하던 것을 참조로 넘겨주려면?
StatInfo* pointer = &Player;
PrintInfoByRef(*pointer) //주소가 가리키는 곳으로 보내주는 *
//참조로 사용하던걸 포인터로 넘겨주려면?
StatInfo& reference = Player;
PrintInfoByPtr(&reference); //별칭의 주소를 넘겨준다.
'[C++]' 카테고리의 다른 글
[C++ 20] Module (1) | 2024.11.13 |
---|---|
CHAR/ TCHAR/ WCHAR (0) | 2022.04.21 |
Modern C++ #2 스마트 포인터(smart pointer) (0) | 2021.09.19 |
STL #1 Vector (0) | 2021.08.24 |