게임 개발/C++

[C++] 포인터 vs 참조

유행성바코드 2025. 3. 11. 21:54

목차

  • 01. Intro
  • 02. 포인터 vs 참조
    • 연산자
    • 특징
  • 03. 마무리
  • 04. 연관 내용

01. Intro

오늘은 [포인터]와 [참조]의 특징과 차이점에 대해서 알아보겠습니다.

 


02. 포인터 vs 참조

먼저, 포인터와 참조에 대한 연산자부터 알아본 뒤에 둘의 특징과 차이점에 대해 알아보겠습니다.

 

연산자

여기서 사용되는 포인터(*)와 참조(&) 연산자각각 포인터 선언 연산자와 참조 연산자입니다.

같은 연산자 기호를 사용하지만, 포인터 접근 연산자(*)주소 연산자(&)도 있습니다.

이 네 가지 연산자에 대해서 먼저 알아보도록 하겠습니다.

 

참조 연산자(&)

#include <iostream>

using namespace std;

int main()
{
	int num = 100;
	// 참조 연산자(&)
	int& ref = num;

	return 0;
}

참조 연산자는 변수에 대해서 새로운 별명을 가지게 합니다.

즉, ref는 num의 새로운 별명이 되고, ref 값을 변경하면, num 값도 변경됩니다.

조사식을 통해 확인하니 num과 ref의 메모리 위치는 일치합니다.

즉, num 값을 변경해도 ref 값도 변경됩니다.

그래서, 참조 연산자는 기존 변수에 대한 새로운 별명을 가지게 한다고 합니다.

 

주소 연산자(&)

#include <iostream>

using namespace std;

int main()
{
	int num = 100;
	// 주소 연산자(&)
	int* ptr = &num;

	return 0;
}

선언할 때가 아니고, 변수 앞에 & 연산자는 주소 연산자로 작동합니다.

그래서, 해당 변수의 메모리 주소값을 가져올 수 있습니다.

포인터 변수는 주소값을 저장하는 변수이므로 주소 연산자를 통해서 저장할 수 있습니다.

 

포인터 선언 연산자(*)

#include <iostream>

using namespace std;

int main()
{
	int num = 100;
	// 포인터 선언 연산자(*)
	int* ptr;

	return 0;
}

변수를 선언할 때, * 연산자는 포인터 선언 연산자로 작동합니다.

즉, 포인터 변수로 선언되어 주소값을 저장할 수 있습니다.

 

포인터 접근 연산자(*)

#include <iostream>

using namespace std;

int main()
{
	int num = 100;
	int* ptr = &num;
	// 포인터 접근 연산자(*)
	cout << *ptr << endl;

	return 0;
}

선언할 때가 아니고, 변수 앞에 * 연산자를 붙이면, 포인터 접근 연산자로 작동합니다.

즉, 포인터 변수에 저장된 주소값에 * 연산자로 메모리 주소로 이동해 저장되어 있는 값에 접근할 수 있습니다.

 

특징

이제 포인터와 참조에 대한 특징과 차이점에 대해서 알아보도록 하겠습니다.

 

선언

	// 포인터 선언 방식
	int* ptr = &num;
	// 참조 선언 방식
	int& ref = num;

포인터 변수를 선언할 때는 포인터 선언 연산자(*)를 사용합니다.

참조 변수를 선언할 때는 참조 연산자(&)를 사용합니다.

 

메모리 주소 저장

#include <iostream>

using namespace std;

int main()
{
	int num = 100;

	int* ptr = &num;
	int& ref = num;

	return 0;
}

ref는 num에 대한 별명이므로 같은 메모리 주소값을 가지고 있습니다.

하지만, ptr 포인터 변수는 num에 대한 주소값을 가지고 있는 변수이므로 ptr 변수에 대한 별도의 메모리 주소를 가지고 있습니다.

 

초기화 상태

#include <iostream>

using namespace std;

int main()
{
	int num = 100;

	// 포인터
	int* ptr;
	ptr = &num;

	// 참조
	// int& ref;
	int& ref = num;

	return 0;
}

포인터는 nullptr 또는 NULL 값을 가질 수 있어 선언만 하고 초기화를 하지 않아도 됩니다.

하지만, 참조(reference)는 NULL이 불가능해 반드시 초기화를 해야 합니다.

즉, 참조 변수는 어떤 값을 항상 가지고 있어야 합니다.

 

그래서, 포인터 변수에 접근할 때는 nullptr 검사를 반드시 해야 합니다.

if(ptr)
if(ptr != nullptr)

두 조건식을 통해 현재 포인터 변수가 어떤 변수에 대한 주소값을 가지고 있는지 확인할 수 있습니다.

참고로 위 두 조건문은 같은 의미입니다.

 

if(ptr)은 동작할 때, 내부적으로 if(ptr != nullptr) 또는 if(ptr != 0)으로 변환되어 작동합니다.

또한, 포인터가 자동으로 bool 타입으로 변환할 수 있기 때문입니다.

그래서, nullptr, NULL, 0은 false로 유효한 포인터는 true로 평가되어 작동하므로 위 조건문이 정상적으로 작동할 수 있습니다.

 

  • nullptr

nullptr은 널 포인터 상수입니다.

C++11 버전 이상부터 사용할 수 있습니다.

NULL 보다 훨씬 안전하게 사용할 수 있습니다.

 

  • NULL

NULL은 매크로 정의 지시어로 정의된 전처리 지시어이며 0의 값을 가지고 있습니다.

그래서, 조건문에 0으로도 포인터의 유효성을 판단할 수 있습니다.

C와 C++ 모두 사용할 수 있지만, 오버로딩된 함수를 사용할 때, 포인터형 매개 변수의 함수가 아니라 정수형 매개 변수의 함수가 실행될 위험이 있어 nullptr을 사용하는 것이 더 안전합니다.

 

재할당

#include <iostream>

using namespace std;

int main()
{
	int num1 = 100;
	int num2 = 200;

	// 포인터
	int* ptr;
	ptr = &num1;
	ptr = &num2;

	// 참조
	int& ref = num1;
	ref = num2;

	return 0;
}

포인터 변수는 단순히 변수에 대한 주소값을 가지고 있습니다.

그래서, 포인터 변수의 주소값을 다른 변수의 주소값으로 변경하면, 포인터 접근 지정자(*)를 통해 다른 변수로의 접근이 가능합니다.

하지만, 참조는 처음 참조한 변수에서 변경이 불가능합니다.

즉, ref는 num1에 대한 별명이므로 ref = num2 코드에서는 단순히 num2의 값을 ref 데이터로 대입합니다.

그 결과, ref와 num1의 데이터는 200의 값을 가집니다.

 

연산 지원

ptr++;
ref++;

포인터 연산을 하는 경우 가리키는 대상이 변경됩니다.

하지만, 레퍼런스는 처음 연결된 변수의 값을 변경합니다.

 

즉, 포인터는 포인터 연산이 가능합니다. ( 다른 대상으로 변경 가능 )

참조는 참조 연산이 불가능합니다. ( 다른 대상으로 변경 불가능 )

 

동적 할당

#include <iostream>

using namespace std;

int main()
{

	// 포인터
	int* ptr = new int(100);
	delete ptr;

	return 0;
}

포인터는 주소값을 가진 변수이므로 동적 할당이 가능합니다.

동적 할당으로 생성된 변수나 객체의 주소를 포인터 변수의 주소값으로 가질 수 있기 때문입니다.

 

참조 변수는 반드시 초기화되어야 하므로 동적 할당이 불가능합니다.

동적 할당은 런타임에서 메모리를 할당하는데, 참조 변수는 선언할 때, 즉시 초기화를 해줘야 합니다.

그리고, 참조는 한 번 연결되면, 변경이 불가능합니다.

하지만, 동적 할당으로 연결이 된다고 가정하면, 할당된 메모리를 해제할 방법이 없습니다.

따라서, 참조 변수는 동적 할당을 할 수 없습니다.

 

 

 


03. 마무리

오늘은 [포인터][참조]에 대한 선언 방식과 둘의 차이점에 대해서 자세히 살펴봤습니다.

둘의 초기화 방식, 재할당 등 많은 차이점이 있었지만, 공통점도 있습니다.

함수 매개 변수의 타입으로 전달할 경우, 복사가 되지 않아 성능 절약에 도움 됩니다.

또한, 함수 내에서 변경된 값이 외부에서도 반영됩니다.

그래서, 알고리즘 문제나 데이터가 큰 경우에는 함수 매개 변수 타입으로 포인터나 참조 타입을 활용합니다.

 

다음에는 다른 주제를 한 번 작성해 보겠습니다.


04. 연관 내용

 


 

읽어주셔서 감사합니다.

틀린 내용 지적은 언제나 환영입니다!

'게임 개발 > C++' 카테고리의 다른 글

[C++] 전방 선언  (2) 2025.03.13
[C++] 접근 지정자  (0) 2025.03.12
[C++] mutable 키워드  (0) 2025.03.10
[C++] const 키워드  (0) 2025.03.09
[C++] 캐스팅 - 5. reinterpret_cast  (0) 2025.03.08