본문 바로가기

개발공부/C++

래퍼런스의 도입

#include <iostream>

int change_val(int *p) {
  *p = 3;

  return 0;
}
int main() {
  int number = 5;

  std::cout << number << std::endl;
  change_val(&number);
  std::cout << number << std::endl;
}
5
3

함수의 인자 p에 number의 주소값을 전달하여, *p를 통해 number를 참조하여 number의 값을 3으로 바꾸었다.

 C언어에서는 &키를 계속 눌러줘야했지만 C++에는 새로운 개념이 생겼다.

바로 '래퍼런스'

#include <iostream>

int change_val(int &p) {
  p = 3;

  return 0;
}
int main() {
  int number = 5;

  std::cout << number << std::endl;
  change_val(number);
  std::cout << number << std::endl;
}
5
3

위와 같은 결과 때문에 흔히 C++에서 래퍼런스를 참조자라고도 한다.

p가 number변수의 다른 이름이 되는 것이다.  따라서 p=3; 이란 명령은 number=3;과 정확히 일치하는 명령이다.

래퍼런스를 정의하는 방법은 아래와 같다.

int& ref = number;

즉, int 타입의 변수의 레퍼런스를 만들기 위해서는 int& 로 하면 되고, 그 오른쪽에 참고하고 싶은 것을 써주면 된다.

참조자의 가장 중요한 특성은 반드시 정의시 초기화되어야 한다는 것이다. 아래와 같이 쓸 수 없다.

//int &ref;

JVM의 garbage Collector가 해당 변수의 reference-count가 0이 되면 지우는 알고리즘과 같다.

여기서 그러면 "포인터도 다른 어떤 변수를 가리켜야 하는데 왜 초기화하지 않고 정의 할 수 있냐?"라고 물을 수 있다.

하지만 포인터 자체는 '메모리값을 보관하는 변수'자체로 활용될 수  있지만 참조자는 그렇지 않다.

참조자는 포인터처럼 어떠한 메모리공간에 할당되어서 자신을 참조하는 주소값을 보관하는 것이 아니다.

컴파일시에 원래 가리키던 변수의 주소값으로 다 치환되버린다.( *(주소값) 으로)

래퍼런스는 한 번 초기화되면 다른 변수의 별명이 될 수 없다.

int a = 10;
int &ref = a;
int b = 3;
ref = b;

참조대상이 바뀌는 것이 아니라 ref는 a를 참조하니까 a=b라는 결과가 나온다.

//&ref = b;

와 같은 문장은 &a = b; 즉, "a 의 주소값을 3 으로 변경한다?" 라는 말이 안되는 문장이다.

ref &= b;

 ref = ref &b;, 즉 a = a & b; 와 같은 문장으로 역시 전혀 의미가 다르다.

아무튼 레퍼런스는 포인터로 치면 int* const 와 같은 형태라 말할 수 있다.

즉 한 번 별명이 된다면 영원히 바뀔 수 없다. (물론 포인터와 레퍼런스는 엄연히 다른 것이다)

int number = 10;
int& ref = number;
int* p = &number;

ref++;
p++;

p ++ 의 경우 C 언어를 배운 사람이라면 p 의 주소값이 4 만큼 증가되어서 (int 의 크기가 4 이니까) 아마 이상한 것을

가리키고 있겠고. 반면에 ref++ 의 경우 ref 가 아까 'number 의 다른 이름' 이라고 했으므로 number++ 과 동일하다.

 number 가 11 이다.

이렇게 참조자를 사용한다면 귀찮았던 포인터 관련 연산들을 모두 생략할 수 있게 된다.

원래코드를 다시 보자.

change_val(number);

인자로 number를 전달하였다. 따라서

int change_val(int &p) {
  p = 3;

  return 0;
}

int &p = number; 와 같으므로 p가 number의 별명이 된다.

따라서 p = 3; 은 number = 3; 과 동일한 작업이다.

// 참조자 이해하기

#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 : 1 y : 1 z : 1
x : 2 y : 2 z : 2
x : 3 y : 3 z : 3

C언어의 포인터와 대응시키지말자. 포인터와 래퍼런스는 다르다.

int x;
int& y = x;

여기서 포인터 개념으로 보면 의문이 생길 수 있다.

int& y = x;
int& z = y;

y가 int&이므로 z는 int&&가 되야하는데 왜 int&이지? 

이것은 포인터였다면,

int x;
int* y = &x;
int** z = &y;

와 같이 소스를 작성했어야 맞다. 이로써 포인터와 래퍼런스는 다르다.

정리하자면 래퍼런스의 경우 x, y, z가 모두 같은 것이여야 한다. x의 다른 이름인 y와 z를 만든 것 뿐이다.

case1:

std::cin >> user_input;

case2:

scanf("%d", &user_input);

case2에서는 어떤 변수의 값을 다른 함수에서 바꾸기 위해서는 항상 포인터로 전달했다.

하지만 여기서 cin에서는 래퍼런스로 전달하였기 때문에 앞에 &를 붙일 필요가 없게 된다.

'개발공부 > C++' 카테고리의 다른 글

포인터 복습  (0) 2019.10.10
상수에 대한 참조자  (0) 2019.10.10
std::cin  (0) 2019.10.10
c++공부를 시작합니다.  (0) 2019.10.10
Namespace  (0) 2019.10.10