[C++] 레퍼런스(reference, 참조자)

·2023년 8월 11일
1

C++

목록 보기
1/17
post-custom-banner

참조자의 도입

C 언어에서는 어떠한 변수를 가리키고 싶을 때 반드시 포인터를 사용해야만 함
C++에서는 참조자도 사용 가능

// 포인터 사용
#include <iostream>

int ChangeValue(int* p)
{
	*p = 3;
    
    return 0;
}

int main()
{
	int number = 5;
    
    std::cout << number << std::endl;
    ChangeValue(&number);
    std::cout << number << std::endl;
}

실행 결과
5
3

// 레퍼런스 사용
#include <iostream>

int main()
{
	int a = 3;
    int& anotherA = a;
    
    anotherA = 5;
    
    std::cout << "a : " << a << std::endl;
    std::cout << "anotherA : " << anotherA << std::endl;
    
    return 0;
}

실행 결과
a : 5
anotherA : 5

레퍼런스의 특징

반드시 처음에 누구의 별명이 될 것인지를 지정해야 함


int& anotherA; // 불가
int* p; // 포인터는 가능

레퍼런스가 한 번 별명이 되면 절대로 다른 이의 별명이 될 수 없다.

int a = 10;
int& anotherA = a; // anotherA는 a의 참조자

int b = 3;
anotherA = b; // a = b 대입과 같음

// 포인터는 자유롭게 변경 가능
int* p = &a;
p = &b;

레퍼런스는 메모리 상에 존재하지 않을 수도 있다.

int a = 10;
int& anotherA = a; // anotherA가 자리를 차지할 필요가 있을까? -> 없음

함수 인자로 레퍼런스 받기

#include <iostream>

// int& p = number, 즉 호출 시 초기화가 되므로 int& p 가능
int ChangeValue(int& p)
{
	p = 3;
    return 0;
}

int main()
{
	int number = 5;
    
    std::cout << number << std::endl;
    ChangeValue(number); // 포인터와 달리 & 붙일 필요 없음
    std::cout << number << std::endl;
}

실행 결과
5
3

cin과 scanf 비교

std::cin >> userInput;

scanf("%d", &userInput);

cin이 레퍼런스로 userInput을 받기 때문에 &를 userInput 앞에 붙일 필요가 없어짐

상수에 대한 참조자

int& ref = 4 // 불가
const int &ref = 4; // 가능

상수 값 자체는 리터럴이기 때문에 첫번째 문장이 만약 가능하다면
ref = 5;와 같이 리터럴의 값을 바꾸는 말도 안되는 행위가 가능하게 됨
-> 따라서 C++ 문법 상 상수 리터럴을 일반적인 레퍼런스가 참조하는 것은 불가능 함

그러나 상수 참조자로 선언하면 리터럴도 참조 가능함 (값을 변경할 수 없으므로)

레퍼런스의 배열

int a, b;
int& arr[2] = { a, b }; // 불가

There shall be no references to references, no arrays of references, and no pointers to references
레퍼런스의 레퍼런스,레퍼런스의 배열, 레퍼런스의 포인터는 존재할 수 없다.

C++ 문법 상 배열의 이름은 첫 번째 원소의 주소값으로 변환이 될 수 있어야 함

arr[1] = *(arr + 1)

주소값이 존재한다는 의미는 해당 원소가 메모리 상에 존재한다는 의미
그러나 레퍼런스는 특별한 경우가 아닌 이상 메모리 상에서 공간을 차지하지 않음
-> 레퍼런스의 배열은 불가

배열의 레퍼런스

int arr[3] = { 1, 2, 3 };
int(&ref)[3] = arr;

ref[0] = 2;
ref[1] = 3;
ref[2] = 1;

std::cout << arr[0] << arr[1] << arr[2] << std::endl;
return 0;

실행 결과
231

배열의 레퍼런스는 가능
포인터와 달리 배열의 레퍼런스의 경우 참조하기 위해선 반드시 배열의 크기를 명시해야 함

즉, int(&ref)[3]은 반드시 크기가 3인 int 배열의 별명이 되어야 함
2차원 배열도 동일하게 가능

int arr[3][2] = { 1, 2, 3, 4, 5, 6 };
int (&ref)[3][2] = arr;

레퍼런스를 리턴하는 함수

int function()
{
	int a = 2;
    return a;
}

int main()
{
	int b = function();
    return 0;
}

int& function() // 런타임 오류 발생
{
  int a = 2;
  return a;
}

int main() 
{
  int b = function();
  b = 3;
  return 0;
}

function의 리턴 타입은 int&로 참조자를 리턴하게 됨
리턴하는 function 안에 정의되어 있는 a는 함수의 리턴과 함께 사라짐
int b = function();은 int& ref = a; int b = ref; -> 근데 a가 사라짐을 의미
레퍼런스가 참조하고 있던 변수가 사라졌으므로 오류가 발생됨
즉, 본체는 사라졌지만 별명만 남아있는 상황이 발생됨

레퍼런스는 있는데 원래 참조 하던 것이 사라진 레퍼런스를 "댕글링 레퍼런스"라고 부름
(Dangling은 약하게 결합돼서 달랑거리는 것을 뜻함)

-> 레퍼런스를 리턴하는 함수에서 지역 변수의 레퍼런스를 리턴하지 않도록 조심해야 함

int& function(int& a) 
{
  a = 5;
  return a;
}

int main() 
{
  int b = 2;
  int c = function(b);
  return 0;
}

아까와 달리 인자로 받은 레퍼런스를 그대로 리턴하고 있음 -> 문제 없음

레퍼런스는 참조하는 타입의 크기와 상관없이 주소값 복사로 전달이 끝나게 되므로 매우 효율적임

참조자가 아닌 값을 리턴하는 함수를 참조자로 받기

int function() 
{
  int a = 5;
  return a;
}

int main() // 오류 발생
{
  int& c = function();
  return 0;
}

참조자로 참조자가 아닌 값을 리턴 받으면 문제가 생김
위에서 봤던 케이스처럼 댕글링 레퍼런스가 되어버리기 때문임

#include <iostream>

int function() 
{
  int a = 5;
  return a;
}

int main() 
{
  const int& c = function();
  std::cout << "c : " << c << std::endl; // 5
  return 0;
}

const 참조자로 받으면 문제 없음 -> 리턴값도 원하는 값이 제대로 출력됨

원칙상 함수의 리턴값은 해당 문장이 끝나면 소멸되는 것이 정상
하지만 예외적으로 상수 레퍼런스로 리턴값을 받으면 해당 리턴값의 생명이 연장됨
-> 연장되는 기간은 레퍼런스가 사라질 때 까지

post-custom-banner

1개의 댓글

comment-user-thumbnail
2023년 8월 11일

좋은 글 감사합니다.

답글 달기