[C++] C++에서 사용되는 개념 13탄(포인터와 참조)

Patrick!·2023년 2월 26일
0
post-thumbnail

1. 참조는 포인터의 상위호환인건가!?

C++ 에는 문법이 아주 예술적인(?) 부분이 많다. -> (그만큼 이해하기 어렵다)
이런 부분이 바로 포인터를 배우면서 자연스럽게 등장하는 참조의 개념이다.

C++ 를 배울때 참조 = 변수의 별명 과 같은 개념이라고 간단히 배우고 넘어간 부분이 기억난다.
물론 어느정도 맞다고는 하지만 이를 사용하면서 분명 일반변수, 포인터와는 다른 부분이 있을거라고 생각한다.

참조는 어떻게 선언하는가?
[Tpye]&(주소연산자) 참조변수이름 = 적용할 변수; 로 구성된다.

#include <iostream>
using namespace std;

int main() {

    int number = 1;
    int& reference = number; -> 참조선언
    reference = 3;
}

number 라는 바구니에 reference 라는 다른 이름을 명명한 것으로 받아들여야한다.
앞으로reference 바구니에 뭘 꺼내거나 넣으면, 실제 number 바구니(진짜)에 그 값을 꺼내거나 넣고 있다는 것이다.

하지만 C++에서는 포인터와 참조에 대한 구분을 확실하게 하는 편이다.

벌써부터 변태적인 부분 배운다는 거에 설렌다

사용하는 방법이 어떻게 보면 포인터와 매우 비슷한데 !? 물론 이는 어디까지나 '문법적으로' 비슷할 뿐이다.

struct Statinfo {
    int hp;
    int attact;
    int defence;
};

위의 구조체에 값을 전달하는 방식을 가지고 차이를 알 수 있다.

int main() {
	Statinfo info;
    PrintInfobyCopy(info);
    PrintInfobyPtr(&info);
    PrintInfobyRef(info);
}

위의 함수들을 선언하고 이의 차이를 알아보자.

1) 값 전달 방식
[매개변수][RET][지역변수(info)] [매개변수(info(           ))][RET][지역변수]
void PrintInfobyCopy(Statinfo info) {
    cout << "------------" << endl;
    cout << "HP : " << info.hp << endl;
    cout << "ATT : " << info.attact << endl;
    cout << "DEF : " << info.defence << endl;
    cout << "------------" << endl;
}

PrintInfobyCopy(Statinfo info) 의 경우, 일반적인 변수를 인자값으로 받아 복사를 진행하는 방식으로 값이 전달된다.
C++에서 사용되는 개념 11탄 (포인터의 기능)(이는 지난번에 배운 포인터를 활용하여 함수를 만드는 부분에서 다루었다.)

2) 주소 전달 방식
[매개변수][RET][지역변수(info)] [매개변수(&info)][RET][지역변수]
void PrintInfobyPtr(Statinfo* info) {
    cout << "------------" << endl;
    cout << "HP : " << info->hp << endl;
    cout << "ATT : " << info->attact << endl;
    cout << "DEF : " << info->defence << endl;
    cout << "------------" << endl;
}

PrintInfobyPtr(Statinfo* info) 에서는 Statinfo* = 12byte로 계산되어 복사가 진행된다.

3) 참조 전달 방식
void PrintInfobyRef(Statinfo& info) {
    cout << "------------" << endl;
    cout << "HP : " << info.hp << endl;
    cout << "ATT : " << info.attact << endl;
    cout << "DEF : " << info.defence << endl;
    cout << "------------" << endl;
}

PrintInfobyRef(Statinfo& info) 에서는 Statinfo& = 12byte로 포인터를 사용하는 것과 같다.
하지만 확연한 차이점은 일반변수 + 포인터의 개념처럼 문법이 작동한다는 것이다.
(값 전달 처럼 편리하게 사용하고 ! 주소 전달처럼 주고값을 이용해 진짜를 조작하는 일석이조의 방식을 진행할 수 있다.)

그럼 참조만 이용하면 일반변수와 포인터의 상위호환을 이용하는건가 ?!

2. 포인터와 참조 (세기의 대결)

그럴리가 없는 C++는 언제나 나에게 변태같은 원리를 건내고 있다.

포인터와 참조가 같은 기능이라면 애초에 다르게 불릴 이유가 없다. 원래부터 다른 것이다.

a. 편의성과 관련하여

  • 성능적인 측면 -> 포인터 = 참조 (서로 차이가 없다.)
  • 편의성 측면 -> 참조 > 포인터 (이는 극히 개인의 차이로 달라질 수 있다.)

편의성은 어디까지나 여러방면에서 작용하게 되는데 작성하는 입장에서 편할 수도 있지만 코드를 읽는 사람 입장에서도 해당한다.

우리가 일상에서 코드를 작성하다보면 함수의 이름/인자값만 보고 어떠한 로직이 실행되는지,
데이터의 주소에 있는 원본 데이터를 조작하는지 여부를 알기란 어렵다.

포인터와 참조를 사용하는 함수의 이름이 같다고 가정하자. (실제로 이럴 순 없지만!)

  • 포인터 함수 -> 포인터 주소를 인자값으로 받기에 확실하게 원본을 건드린다는 힌트를 내포
void AttackAndAvoid(Player* player) { logic... }

int main() {
	AttackAndavoid(&knight);
}
함수의 인자값만 보더라도 난 원본을 건드리는 '포인터'라는 것을 자랑하듯이 나타내고 있다.
  • 참조 함수 -> 이는 인자값이 일반적인 함수의 인자값과 다를바 없이 생겼다.
void AttackAndAvoid(Player& player) { logic... } -> 이 부분에서는 차이를 알 수 있다.

int main() {
	AttackAndAvoid(player); -> 이 부분만 본다면 이는 확실하게 참조타입인지 아닌지를 판별할 수 없다.
}

이를 정말 온마우스를 하거나 직접 찾지 않고서는 알 수 없게 생겼다.
만약 여기서 이 코드를 작성한 사람이 아닌 다른 누군가가 착각하여 원본 데이터를 바꾸는 로직을 작성했다면 큰일이 일어날 수 있다.

그렇다고 물러날 프로그래머들이 아니었다.

언제나 그랬듯이 이를 해결하기 위한 방법을 연구하는 사람들이 진정한 프로그래머이지 않을까 싶다.

이러한 부분을 막기 위한 방법으로 사용되는 것이 '상수화(const)' 가 있다.

void PrintInfo(const Statinfo* info) {
    cout << "------------" << endl;
    cout << "HP : " << info->hp << endl;
    cout << "ATT : " << info->attact << endl;
    cout << "DEF : " << info->defence << endl;
    cout << "------------" << endl;
}

함수의 인자값에도 const를 선언할 수 있는데 이를 어디에 선언하는지에 따라
상수화되는 대상이 달라질 수 있다는 것을 알아두자!!

  1. const를 별 뒤에 붙인다면? => Statinfo* const info
    info라는 바구니의 내용물(주소)을 바꿀 수 없게 된다.
    info는 주소값을 갖는 바구니 -> 이 주소값이 고정이다.
void PrintInfo(Statinfo* const info)
  1. const를 별 앞에 붙인다면? => const Statinfo* info
    info 가 '가리키고 있는' 바구니의 내용물을 바꿀 수 없게 된다.
    주소값의 '원격' 바구니의 내용물을 바꿀 수 없다.
void PrintInfo(const Statinfo* info) {

b. 초기화 여부

  • 참조타입 -> 바구니의 2번째 이름 이라는 '별칭'의 개념이라고 생각하면 되는데, 문제는 참조하는 대상이 없다면 사용할 수 없다.

  • 포인터타입 -> 어떤 ~~ 주소라는 의미를 내포하고 있다. 대상이 실존하지 않을 수 있다.

그럼 포인터의 null은 어떻게 표현하는건가 ?
nullptr -> null pointer 라는 개념으로 표현한다.

하지만 포인터는 nullptr 상태로 존재하여 이를 사용하거나 접근하려 한다면 충돌이 발생한다.
그렇기에 항상 포인터의 값이 제대로 존재하는지 or 유효한 포인터를 사용하는지에 대한 여부는 항상 확인이 필요하다.

profile
C++와 Unreal Engine / C#과 Unity / Katalon Studio를 통한 자동화 테스트 등을 하루하루 공부한 기록

0개의 댓글