본문 바로가기
C++

[C++] Pointer(포인터) Reference(레퍼런스:참조자) 변수

by 청양호박이 2022. 1. 12.

포인터와 레퍼런스 변수에 대해서 알아보겠습니다. 아마 어느정도 개념만 알고있는 경우도 있을것이고, 알고는 있지만 실제로 어떤차이가 있는지 모르는 경우도 있을 것 입니다. 그래서 오늘은 두가지 변수에 대해서 정리해 보고, 몇가지 사용에 대해서 알아보겠습니다.

 

 

1. Pointer(포인터) 변수


type* [변수명]

 

다음과 같이 생겼습니다. type은 변수의 type이고 (int, float, etc...) 바로 뒤에 '*' 를 붙여줍니다. 그리고 한칸을 띄고 원하는 포인터 변수의 변수명을 넣어줍니다. (ex. int* a)

 

이렇게 생긴 포인터 변수는 아래의 특징을 가지고 있습니다. 

 

  1. 초기화가 불필요하며, null 이 가능함
  2. 포인터 변수에는 메모리 주소를 넣어야 하기 때문에, '&'를 사용해서 할당 (ex. &[변수명])

 

[Test - 일반변수를 포인터 변수에 할당]

#include <iostream>

int main(int argc, char** argv) {
	int* a;
	int b = 10;
	
	a = b;
}

==============================================================
[Result]
[Error] invalid conversion from 'int' to 'int*' [-fpermissive]
    7 |  a = b;

위와 같이 error가 발생하고 해당 코드를 보시다시피, int형 변수 b를 포인터 변수 a에 넣으려고 했기 때문입니다. 그렇다면, 어떻게 해야할까요?? 위에 언급한 '&'를 사용하면 됩니다.

#include <iostream>

int main(int argc, char** argv) {
	int* a;
	int b = 10;
	
	a = &b;
	
	std::cout << a << '\n';
	std::cout << b << '\n';
}

===================================
[Result]
0x78fe14
10

정상적으로 동작함을 확인했습니다.

 

 

2. Reference(레퍼런스:참조자) 변수


type& [변수명]

 

다음과 같이 생겼습니다. type은 변수의 type이고 (int, float, etc...) 바로 뒤에 '&' 를 붙여줍니다. 그리고 한칸을 띄고 원하는 포인터 변수의 변수명을 넣어줍니다. (ex. int& a)

 

이렇게 생긴 레퍼런스 변수는 아래의 특징을 가지고 있습니다. 

 

  1. 초기화가 반드시 필요하기 때문에, null이 불가함
  2. 대상을 직접 할당하며, 메모리 주소를 할당하면 안됨
  3. 할당 시, 리터럴 상수는 불가함
  4. 일반 변수로 생성하여 값을 할당하면 신규로 메모리에 생성되어 독립적으로 활동하지만, 레퍼런스 변수는 기존 변수를 업데이트 함

 

[Test1 - 초기화 하지 않음]

#include <iostream>

int main(int argc, char** argv) {
	int& a;
}

====================================================
[Result]
error: 'a' declared as reference but not initialized
    4 |  int& a;

초기화를 안했다고 진철하게 Compiler가 error를 발생시켜 줍니다.

 

[Test2 - 대상 직접 할당, 메모리 주소 할당 불가]

#include <iostream>

int main(int argc, char** argv) {
	int b = 10;
	int& a = b;
		
	std::cout << a << '\n';
}

[Result]
10

======================================

#include <iostream>

int main(int argc, char** argv) {
	int b = 10;
	int& a = &b;
}

[Result]
error: invalid conversion from 'int*' to 'int' [-fpermissive]
    5 |  int& a = &b;

[Test3 - 대상 직접 할당 시 리터럴 상수 불가]

#include <iostream>

int main(int argc, char** argv) {
	int& a = 10;
		
	std::cout << a << '\n';
}

=============================================================================
[Result]
error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'
    5 |  int& a = 10;

[Test4 - 기존변수의 업데이트]

#include <iostream>

int main(int argc, char** argv) {
	int a = 10;
	int& b = a;
	int c = a;
	
	b++;
		
	std::cout << a << '\n';
	std::cout << b << '\n';
	std::cout << c << '\n';
}

==================================
[Result]
11
11
10

a는 원본 변수이고, b는 a의 레퍼런스 변수입니다. 마지막으로 c는 일반변수로 a의 값을 할당했습니다. 이 경우에 b를 1만 더해보면 어떤 결과가 있을까요?? 레퍼런스 변수를 1을 더했기때문에, 원본에 1을 더하게 되었고... 일반변수는 원본과 독립적으로 동작하기 때문에 변화가 없습니다.

 

 

3. 몇가지 사용법


[포인터와 레퍼런스 변수의 혼용]

#include <iostream>

int main(int argc, char** argv) {
	int a = 10;
	int& b = a;
	int* c = &b;
	
	*c = 15;
		
	std::cout << a << '\n';
	std::cout << b << '\n';
	std::cout << c << '\n';
}

====================================
[Result]
15
15
0x78fe0c

일반 변수를 만들고, 레퍼런스 변수에 해당 변수로 초기화를 진행합니다. 마지막으로 포인터 변수를 생성하여 레퍼런스 변수의 주소를 넣으면 완성이 되는데... 해당 포인터 주소에 원하는 int값을 넣어주면 일반 변수의 값이 변경이 됩니다.

 

[배열 변수의 레퍼런스 할당]

type (&변수명)[배열크기] = 일반 배열 변수

#include <iostream>

int main(int argc, char** argv) {
	int a[2] = {10, 20};
	int (&b)[2] = a;
	
	std::cout << a[0] << '\n';
	
	b[0] = 100;
	
	std::cout << a[0] << '\n';
}

=================================
[Result]
10
100

다음과 같이 레퍼런스 배열 변수를 만들어서 일반 배열 변수를 할당할 수 있습니다. 레퍼런스 배열의 위치값을 변경하면 원본 배열의 위치값도 바뀌게 됩니다.

 

단, 레퍼런스 배열의 경우 일반적인 방식으로 작성하면 아래와 같이 error가 발생합니다.

#include <iostream>

int main(int argc, char** argv) {
	int a[2] = {10, 20};
	int& b[2] = a;
}

=================================================
[Result]
error: declaration of 'b' as array of references
    5 |  int& b[2] = a;

 

[함수의 레퍼런스 리턴]

#include <iostream>

int& func(int& t){
	t = 1000;
	return t;
}

int main(int argc, char** argv) {
	int a = 10;
	int& b = a;
	int& c = func(b);
	
	c++;
	
	std::cout<< a << '\n';
	std::cout<< b << '\n';
	std::cout<< c << '\n';
}

=====================================
[Result]
1001
1001
1001

방식은 간단합니다. 일반변수를 생성하고, 해당 일반변수를 레퍼런스 변수를 하나 추가하여 할당을 합니다. 함수를 추가로 만들고 해당 함수는 call by reference방식으로 parameter에 레퍼런스 변수로 설정합니다. 그렇게되면 함수내 t 변수는 결국 a 일반변수를 참조하게 됩니다.

 

함수내에서 레퍼런스 변수 t에 변형을 가하면 a도 변경이 됩니다. 마지막으로 return을 레퍼런스 변수로 하게 되면, c도 결국 a를 참조하게 되기 때문에... a = b = c 모두 연결이 되게 됩니다. 따라서 c에 1을 더하게 되면 모두 1이 더해지게 되는 구조지요~~!!

 

 

0. 마치며


C++ FAQ를 보다보면... 아래와 같은 말이 있습니다. 

Use references when you can and pointers when you have to.

이 말은 왠지 가능한 레퍼런스 변수를 사용하고, 어쩔수 없이 반드시 써야 할때만 포인터 변수를 쓰라는 말인 것 같습니다. 레퍼런스 변수는 초기화에 대한 강제성이 있고, 포인터의 보완을 위해 등장했다고도 보여지기 때문에... 사용하기 전에 한번 고민해볼 필요는 있을 것 같습니다.

 

- Ayotera Lab -

댓글