[C++]

포인터 1 - 포인터, 참조 기초

럭키🍀 2021. 8. 24. 12:31

포인터 기초 #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