본문 바로가기

C++/C.S 필요한 C++ 문법!

[중요] C++ 0.06 - Reference Operator &

Reference Operator 란?


Refercece Operator는 C의 Pointer 와 동일한 역할을 하는 것으로 

A Reference allows to declare an alias to another variable.

 

C++ 에서는 함수가 value를 리턴할 때, return 할 value를 stack에 카피하는데 calling function은 stack에 저장된 변수를 읽어서 그걸 다시 변수에 copy 한다.

 

하지만 Reference Operator는 함수를 부를 때 스택으로 Parameter를 Copy 하는 비용을 줄여준다!!

 

예제로 살펴보도록 하자!

 

Code ( Pointer vs Reference )


// using pointer
void swap(int *a, int *b) {
  int temp = *a;
  *a = *b;
  *b = temp;
}

// using reference
void swap(int& a, int& b) { 
  int temp = a;
  a = b;
  b = temp;
}

// find max elements
int& max(int a[], int n){
  int x = 0;
  for(int i = 0; i < n; i++)
    if ( a[i] > a[x] ) x = i;
  return a[x];
}

int main(){
  int a[] = {12, 42, 33, 99, 63};
  int n = 5;
  max(a,5) = 0;
  for(int i = 0; i < n; i++){
    cout << a[i] << " ";
  }
}

Potiner와 같은 기능을 하지만 Reference의 경우가 훨씬 간단한 것을 알 수 있다.

 

max(a,5) = 0; 부분을 보면 변수에 함수의 리턴 값을 따로 할당하지 않고 Reference Operator를 이용해서 비용을 줄인 것을 알 수 있다.

 

✔️ 만약 참조형을 변수로 선언할 경우 선언과 동시에 초기화 해줘야한다.

- 참조형 변수는 pointer 변수와 다르게 null 참조를 할 수 없기 때문이다.

 

const Reference


struct Person {
  char name[40];
  int age;
};

void print(const Person& k) {
  cout << "Name: " << k.name << endl;
  cout << "Age: " << k.age << endl; 
}

int 
main(){
  Person man{"Adam", 316}; print(man);
  return 0;
}

const 형을 같이 넘겨줌으로써 바뀌지 않아야 할 변수를 보호할 수 있다.

또한 44 bytes 대신 오직 4 btyes 만 function으로 보낼 수 있다. ( 메모리 아낄 수 있음 )

 

 

Detail about Reference Operator


#include <iostream>

int main() {
  int a = 3;
  int& another_a = a;

  another_a = 5;
  std::cout << "a : " << a << std::endl;
  std::cout << "another_a : " << another_a << std::endl;

  return 0;
}

// a : 5
// another_a : 5

int& another_a = a;

a 의 참조자 another_a를 정의하였다. 위와 같이 선언함으로써 another_a 는 a의 또 다른 이름이라고 컴파일러에게 알려주는 것과 동일하다. 즉, another_a 는 a의 별명인 셈이다. 

 

고로 레퍼런스는 반드시 처음에 누구의 별명이 될 것인지를 지정해줘야한다.

int& another_a;  // 불가능
int* p           // 가능

이러한 것은 불가능하다!!

 

반면에 pointer의 경우는 null을 참조할 수 있어서 가능하다!

 

예를 들어서 아래와 같은 코드를 살펴보자!

int a = 10;
int &another_a = a; // another_a 는 이제 a 의 참조자!

int b = 3;
another_a = b; // ??

another_a = b 를 뭘 의미할까?

여기서 another_a 는 a의 별명이 됐기 때문에 이는 a = b 와 동치라고 보면 된다.

Reference가 한 번 별명이 된다면 *절대로* 다른 이의 별명이 될 수 없다. (중요)

 

다음 코드를 한번 봐보자!

// 참조자 이해하기

#include <iostream>

int main() {
  int x;
  int& y = x;
  int& z = y;

  x = 1;
  std::cout << "x : " << x << " y : " << y << " z : " << z << std::endl;

  y = 2;
  std::cout << "x : " << x << " y : " << y << " z : " << z << std::endl;

  z = 3;
  std::cout << "x : " << x << " y : " << y << " z : " << z << std::endl;
}

이 코드를 돌렸을 때 x, y, z 의 값은 어떻게 될까??

x : 1 , y : 1 , z : 1 그 다음은 2 , 3 으로 전부 동일 할 것이다.

 

왜 이런 결과가 나오는 것일까?

여기서 z 는 y의 참조자인데 그럼 z는 참조자의 참조자가 되는 셈이다. 하지만 C++ 문법 상 참조자의 참조자를 만드는 것은 금지되어 있다.

고로 z, y 는 전부 x 의 참조자라고 볼 수 있다.

 

다음 코드를 한번 봐보자!

#include <iostream>

int main() {
  int &ref = 4;        // wrong
  const int &ref = 4;  // okay
  
  int a = ref;          // a = 4;

  std::cout << ref << std::endl;
}

Reference는 상수 값을 참조할 수 없다. 상수 값 자체는 literal 이기 때문이다.

고로 상수 참조자로 선언하면 참조할 수 있다.

 

 

배열과 Reference


이러한 코드를 컴파일했다고 하자. 어떤 결과가 나올까? 

컴파일을 해보면 컴파일 오류가 뜰 것이다. 왜?

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

C++ 에서 배열은 첫 번째 원소의 주소값에 + 되어서 다음 Element를 참조하는 구조인데,, Reference 같은 경우 특별한 경우가 아니면 메모리 상에서 공간을 차지 하지 않기 때문에 이같은 선언은 불가능한 것입니다.

 

""공간을 차지 하지 않는 이유"" 는 굳이 별명을 위해서 메모리 상에 공간을 할당할 필요가 있을까?

왜냐면 별명이 쓰이는 자리를 원래 주인으로 전부 교체하면 되니까! 물론 그렇다고 해서 항상 존재하지 않는 것은 아닙니다!

 

그럼 어떻게 배열을 참조하지??

#include <iostream>

int main() {
  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;
}

이와 같이 참조를 하면 가능합니다.

이때 중요한 것은 배열을 Reference 하려면 반드시 배열의 길이를 명시해야 합니다!

 

 

Reference를 리턴하는 함수


// 지역변수 리턴
int& function() {
  int a = 2;
  return a;
}

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

만약 다음과 같은 Function을 int 타입 b로 받는다면 어떻게 될까요?? 

아마도 런타임 에러가 날 것입니다.

 

왜? Function 안의 a는 지역변수라서 함수가 종료되면 메모리 상에서 사라지게 됩니다. 

그런데 a의 별명을 b 에 복사하려고 했더니 a 는 사라지고 결국 a 의 별명만 남은 셈입니다.

이를 Dangling 이라고 하는데 위 처럼 레퍼런스를 리턴하는 함수에서 지역변수를 리턴하지 않도록 해야합니다.

 

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

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

이와 같은 코드를 볼까요?

눈치를 채셨겠지만 " c = b " 와 동치라는 것을 알 수 있습니다.

이렇게 참조자를 리턴할 때 좋은 점은 만약에 데이터의 크기가 엄청 큰 구조체가 있을 때 해당 구조체 변수를 리턴하면

전체 복사가 발생해서 시간이 오래걸리지만 레퍼런스를 리턴해주면 그냥 포인터 주소 한 번 복사로 매우 빠르게 끝낼 수 있는 장점이 있습니다.

 

마지막 코드는 바로 상수를 리턴 받는 레퍼런스 변수입니다. 다음 코드를 함께 보시죠!

#include <iostream>

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

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

 

function의 리턴값은 상수이고, 본래 reference는 상수 리터럴을 참조할 수 없지만 const 키워드를 붙여줌으로써 참조할 수 있습니다.

 

여기까지 C++ 의 중요한 문법 중 하나인 Reference 를 다뤘습니다. 

'C++ > C.S 필요한 C++ 문법!' 카테고리의 다른 글

[중요] C++ 0.07 - new, delete  (0) 2021.10.26
C++ 0.05 - Default Function Arguments  (0) 2021.10.26
C++ 0.04 - Inline functions  (0) 2021.10.26
C++ 0.03 - Input & Output  (0) 2021.10.25
C++ 0.02 - Namespaces  (0) 2021.10.25