모두의 코드 내용을 공부하고 정리한 내용입니다.



Reference(참조자)

  • 타입뒤에 &를 붙여서 가리키고자 하는 주소를 받아낸다.
  • C언어와 다르게 선언할 때 초기화를 해야하며 한 번 어떤 변수의 참조자가 되면 더 이상 다른 참조를 할 수 없다
    int a = 10;
    int &another_a = a; // another_a 는 이제 a 의 참조자!
    
    int b = 3;
    another_a = b; // 잘못된 방법
    


  • 레퍼런스는 특별한 경우가 아니면 메모리를 차지하지 않는다
    • 위 코드에서 another_a가 쓰이는 자리는 모두 a로 바꿔치기 하면되어 another_a 공간 할당이 필요없어진다.


참조자의 참조자
  • 어떤 참조자에 대한 참조자는 만들 수 있을까?
    • 불가능하다. 궅이 별명의 별명을 만들 필요가 없다.
    • 실제로 C++ 문법 상 참조자의 참조자를 만드는 것은 금지되어 있다.


상수에 대한 참조자
int &ref = 4;
  • 컴파일 오류가 날 것이다.
    • 상수는 리터럴이기 때문에 레퍼런스로 참조를 한다면 리터럴 값을 바꾸는 말도 안되는 행위가 가능하기 때문이다.
      • 하지만 다음과 같은 문법은 가능하다.
           const int &ref = 4;
        



배열의 레퍼런스

배열의 레퍼런스는 불가능
int & arr[2] = {a, b}; // 의미: 2개의 int형 레퍼런스의 배열


  • C++에서 배열은 배열의 이름은 첫번째 주소값으로 변환될 수 있어야한다.
    • 그렇기에 *(arr+1)이 가능하게 하지.


  • 주소값을 가지면 해당 원소가 메모리에 존재한다는 의미.
    • 하지만 레퍼런스는 특별한 경우가 아니면 메모리 상에서 공간을 차지하지 않는다.
      • 위에서 말했듯이 컴파일러 입장에서는 a와 b는 둘다 같은 것으로 인식하여 a는 메모리상에 존재하지 않는다.(a 자리에 b를 사용하기 떄문에)
      • 따라서 메모리 상에 없으니 배열이 될 수 없다


하지만 항상 메모리 상에 존재하지 않는 것은 아니다.
  • 예를 들어 함수 인자를 레퍼런스로 받으면 메모리가 할당된다.
    • 함수에 전달된 주소값은 stack 메모리 공간에 저장되어 사용된다. ==> 함수의 공간에서만 사용
  • 이렇게 함수에 레퍼런스를 넘겼을 경우 main에서 할당된 주소를 사용하는게 아니라 함수에서 할당된 메모리 주소를 사용한다.



레퍼런스의 배열

  • 레퍼런스의 배열은 가능하다
int arr[3] = {1, 2, 3}; 
int (&ref)[3] = arr; // 의미: 3개의 int형으로 이루어진 배열의 레퍼런스
                     // ==> ref[0] 부터 ref[2] 가 각각 arr[0] 부터 arr[2] 의 레퍼런스
  • 포인터와는 다르게 배열의 레퍼런스의 경우 참조하기 위해선 반드시 배열의 크기를 명시해야 한다.



Dangling Reference(댕글링 레퍼런스)

int& function() {
  int a = 2;
  return a;
}
  • 레퍼런스는 있는데 원래 참조 하던 것이 사라진 레퍼런스
    • 어떤 지역변수를 참조자를 만든다면(어떤 함수에서) 그 지역을 벗어나는 순간 참조자가 가르키는 주소가 없어진다.


외부 변수의 레퍼런스를 리턴
int& function(int& a) {
  a = 5;
  return a;
}

int main() {
  int b = 2;
  int c = function(b);
  return 0;
}
  • 아까와 다른 점은 인자로 받은 레퍼런스를 그대로 리턴하고 있다.
    • function(b)를 실행한 시점에서는 a는 main의 b를 참조하고 있다.
    • 따라서 function이 리턴한 참조자는 아직 존재하는 b를 계속 참조한다.


참조자를 리턴하는 경우의 장점
  • C 언어에서 엄청나게 큰 구조체가 있을 때 해당 구조체 변수를 그냥 리턴하면 전체 복사가 발생해야 해서 시간이 오래걸리지만, 해당 구조체를 가리키는 포인터를 리턴한다면 그냥 포인터 주소 한 번 복사로 매우 빠르게 끝난다.

  • 마찬가지로 레퍼런스를 리턴하게 된다면 레퍼런스가 참조하는 타입의 크기와 상관 없이 딱 한 번의 주소값 복사로 전달이 끝나게 됩니다. 따라서 매우 효율적!


참조자가 아닌 값을 리턴하는 함수를 참조자로 받기
int function() {
  int a = 5;
  return a;
}

int main() {
  int& c = function();
  return 0;
}     // 컴파일 오류!!!
  • 아까전 상황과 마찬가지로 함수의 리턴값은 해당 문장이 끝난 후 바로 사라지는 값이기 때문에 참조자를 만들게 되면 바로 다음에 댕글링 레퍼런스가 되버리기 때문이다.
  • 해당 코드에서 다음과 같은 코드를 작성하면 런타임 오류가 발생한다.
int& c = function();
c = 2;


  • 하지만 예외적인 상황이 있다.
int function() {
  int a = 5;
  return a;
}

int main() {
  const int& c = function();
  std::cout << "c : " << c << std::endl;
  return 0;
}
  • 원칙상 함수의 리턴값은 해당 문장이 끝나면 소멸되는 것이 정상이다.
    • 따라서 기존에 int& 로 받았을 때에는 컴파일 자체가 안됨
  • 하지만 예외적으로 상수 레퍼런스로 리턴값을 받게 되면 해당 리턴값의 생명이 연장된다.
    • 연장되는 기간은 레퍼런스가 사라질 때 까지…


  함수에서 값 리턴(int F()) 함수에서 참조자 리턴(int& F())
값 타입으로 받음
(int a = f())
값 복사됨 값 복사됨. 다만 지역 변수의 레퍼런스를 리턴하지 않도록 주의
참조자 타입으로 받음
(int& a = f())
컴파일 오류 가능. 다만 마찬가지로 지역 변수의 레퍼런스를 리턴하지 않도록 주의
상수 참조자 타입으로 받음 (const int& a = f()) 가능 가능. 다만 마찬가지로 지역 변수의 레퍼런스를 리턴하지 않도록 주의


개인 공부 기록용 블로그입니다.
잘못된 곳은 댓글 혹은 메일을 지적해주시면 감사하겠습니다!

카테고리:

업데이트:

댓글남기기