C++ 참조 변수

프로그래밍/C++ 2019.08.15 댓글 Plorence

먼저 참조란?

참조(reference)는 미리 정의된 어떤 변수의 실제 이름 대신 쓸 수 있는 대용 이름입니다.

A을 B변수의 참조로 만들면, A와 B는 같은 변수를 나타내는 것으로 사용할 수 있습니다.
참조의 주된 용도는 힘수의 매개변수에서 사용합니다.
참조를 매개변수로 사용하면 복사본이 아닌 원본 데이터를 가지고 작업을 합니다.
크기가 큰 구조체를 처리해야 하는 함수에서 포인터 대신에 참조를 사용할 수 있습니다.

참조 선언은 '&'기호로 합니다.(C, C++에서 변수의 주소를 나타내기 위해 &사용한다. C++에서는 하나의 의미를 더 추가한 것이다.)
※C에서는 참조 선언을 못한다.

 

참조 변수 선언 하기

int A = 10;
int & B = A; //이때 B는 참조변수이다

간단한 사용법입니다.

이때 &는 주소 연산자가 아니라 참조 선언을 의미합니다.

#include <iostream>
int main(void) {
    int A = 10;
    int & B = A;
    std::cout << "A: " << A << std::endl;
    std::cout << "B: " << B << std::endl;
    B++;
    std::cout << "A: " << A << std::endl;
    std::cout << "B: " << B << std::endl;
    std::cout << "A var Add:"<< &A << std::endl;
    std::cout << "b var Add:" << &B << std::endl;
    /*출력결과
    A: 10
    B: 10
    A: 11
    B: 11
    A var Add:00D5FE98
    B var Add:00D5FE98
    */
}

간단한 예시입니다.
참조 변수를 사용하면 이름만 다를뿐이지 사실상 똑같습니다.
어찌 보면 포인터와 비슷할 수도 있습니다. 하지만 포인터는 선언과 동시에 굳이 초기화를 안 해도 되지만, 참조 변수는 선언과 동시에 반드시 초기화를 해야 합니다.

참조 변수는 선언과 동시에 참조했던 변수를 다른 변수로 바꿀수 없습니다.

참조는 대입문이 아니라 초기화 선언에 의해서만 설정이 가능합니다

#include <iostream>
int main(void) {
    int A = 10;
    int & B = A;
    B++; //A++와 같다.
    int C = 100;
    B = C; // A=C와 같다.
    std::cout << B;
    /*출력결과
    100
    */
}

다음 예제를 보면 이해가 갈것입니다.
참조 변수를 선언하고 난뒤 참조 변수는 참조하고 있는 변수의 이름만 다를뿐 사실상 같습니다.

결론은 참조 변수는 초기화 선언에서 참조한 변수 이후에 참조 대상 변경이 불가능합니다.

함수 매개변수로서의 참조도 다를 빠가 없습니다.

C에서는 값을 복사하여 넘겨주지만 C++의 참조 변수에서는 변수 그 자체를 넘겨줍니다.

(이름이 하나 더 생길 뿐 원래 매개변수로 넘겨준 변수와의 차이점은 없다는 것이다.)

일반 변수(위에서 말하는 참조가 아닌 매개변수)와 참조 변수의 차이점

 

참조의 특성

2개의 함수가 있다고 가정할 때, 파라미터가 하나는 참조 변수 하나는 일반 변수입니다.
일반 변수에는 함수에서 값을 바꿔도 호출할 때 넘겨준 변수까지 바뀌지 않습니다.
하지만 참조 변수는 함수 내부에서 값을 바꾸면 인수로 전달한 변수의 값까지 바뀝니다.(메인에 있는 변수)
만약 그 파라미터가 참조 변수인 함수가 값을 변경하지 않고 사용만 한다면 앞에 'const'를 붙여야 합니다.
이렇게 하면 값을 바꾸려고 시도할 때 컴파일러가 에러를 알려줍니다.
굳이 이런 실수를 방지할 수 있다는 점 말고도 다양한 이점이 있습니다.
또한 간단한 함수는 참조 변수 사용이 부적합. 데이터가 큰 구조체나 클래스에 적합합니다.

그리고 참조 변수를 파라미터로 사용할 때 참조를 전달할 때 몇 가지 제약이 따릅니다.
파라미터에 참조 변수는 무엇보다도 어떤 변수의 대용 이름이라면, 그 변수를 실제 파라미터로 써야 합니다.

rvalue과 lvalue인것을 인수로 넘겼을 때

 

참조의 특성 - 참조 매개변수, const

C++은 실제 매개변수와 참조 매개변수가 일치하지 않을 때 임시 변수를 생성할 수 있습니다.

최근의 C++에서는 매개변수가 const 참조일 때만 허용하고 있습니다.

 

임시 변수는 언제 생성되는가?

  1. 실제 매개변수가 올바른 데이터형이지만 lvalue이 아닐 때

  2. 실제 매개변수가 잘못된 데이터형이지만 올바른 데이터형으로 변환할 수 있을 때

 

이때 lvalue이란?

lvalue 매개변수는 참조가 가능한 데이터 객체입니다.

예를 들어 변수, 배열의 원소, 구조체의 멤버, 참조 또는 역참조 포인터는 lvalue입니다.

일반 상수와 여러 개의 항으로 이루어진 표현식은 lvalue이 아닙니다.(위 사진의 Num + 3과 같은 인자)

※C에서 lvalue는 본래 대입문 왼편에 나타날 수 있는 독립체들을 의미했었다.

각 lvalue과 rvalue인 것을 인수로 넘겼을 때에 대한 설명

만약 참조 매개변수를 가진 함수의 목적이 매개변수로 전달되는 변수의 값을 변경하는 것이라면 임시 변수의 생성은 그 목적을 방해합니다.

해결방법은 임시 변수의 생성을 허용하지 않으면 됩니다.(const를 안 쓰면 된다.)

하지만 그렇지 않은 경우에는 가능하면 const를 써주는 게 좋습니다. 왜냐?

  1. 실수로 데이터 변경을 일으키는 에러를 막을 수 있다.

  2. 원형에 const를 사용하면 const와 const이 아닌 매개변수를 모두 처리를 할 수 있지만, const를 생략한 함수는 const가 아닌 데이터만 처리가 가능하다.

  3. const를 쓰면 함수가 필요에 따라 임시 변수를 생성한다.

 

참조의 특성 - 구조체에 대한 참조

참조는 사용자 정의 데이터형을 다루는데 유용하게 쓰입니다.

원래 참조는 built-in 데이터 타입보다는 주로 사용자 정의 데이터형에 사용하기 위해 도입됐습니다.

함수가 구조체의 대한 참조를 리턴하는 것과 구조체를 리턴하는것과 다릅니다.

#include <iostream>
struct A {
    char name[20];
    int age;
};
int main(void) {
    A a = { "Hello",1 };
    A a_b = { "World",2 };
    Display(a) = a_b;
    
}
A & Display(A & Ar) {
    printf("%s", Ar.name);
    return Ar;
}

먼저 Display함수는 매개변수로 구조체 A 변수 자체를 넘겨받고, 넘겨받은 변수가 Ar이 됩니다.

그리고 구조체 A의 멤버 name을 출력하고 구조체 A 참조 Ar를 반환합니다.

그리고 구조체 A 변수 a와 구조체 A 변수 a_b를 각각 선언과 동시에 초기화시켜줬습니다.

Display함수 호출에 인자로 구조체 A 변수 a를 넘겨줬으며, 호출 이후에 Display함수가 반환하는 참조 변수에 a_b를 대입합니다.

이게 Display함수가참조 변수를 반환해서 정상적으로 작동하는 코드입니다.

하지만 참조 변수가 아닌 값반환한다면 값이 변수(lvalue)는 아니기 때문에 컴파일 에러가 발생합니다..

(Display함수의 리턴 값은 구조체 A 변수 a에 대한 참조이기 때문이다.)

 

참조를 리턴하는 이유

전통적인 리턴은 값을 기준으로 함수 매개변수를 통해 값을 전달하는 방식으로 동작하고 함수가 종료되면 리턴에 의해 그 값은 다시 호출한 곳으로 전달됩니다.

이 값은 임시 장소에 복사되고 호출 프로그램은 이 값을 사용합니다.

참조인 매개변수와 참조가 아닌 매개변수일 때의 차이점

값을 반환하면 임시장소에 한번 거쳐서 복사하지만, 참조를 반환하게 되면 임시장소를 거치지 않고 바로 복사하기 때문에 값을 반환하는 것보다는 효율적입니다.

 

※참조를 반환할 때 주의해야 할 점

참조를 리턴할 때 주의해야 할 것은 함수가 종료할 때 같이 메모리 공간에서 할당 해제되는 참조를 리턴하지 않도록 조심해야 합니다.

만약 함수 내에 변수가 있고 그 변수를 참조로 반환한다면, 함수가 종료될 때 변수도 메모리 공간에서 없어지니 문제가 된다는 것입니다.

 

문제가 발생하는 코드의 대한 설명

위 코드를 실행시켜보면 컴파일 에러는 발생하지 않습니다.

하지만 컴파일 에러가 발생하지 않는다고 문제가 없는 건 아닙니다.

위 사진처럼 함수가 종료할 때 temp변수가 없어지니 런타임 에러가 발생합니다.

 

그럼 해결법은?

  1. 함수에 매개변수로 전달된 참조를 리턴한다.

  2. new를 사용하여 새로운 기억공간을 만드는 것이다.

하지만 new에 의해 대입된 메모리를 반드시 delete로 삭제해줘야 한다는 것은 잊지 말아야 합니다.

 

참조를 리턴할 때 const를 사용하는 이유

const 참조 리턴일 때의 설명

6번째 줄은 리턴형이 const이라서 변경 불가능한 lvalue입니다.

그래서 아래와 같은 에러가 발생합니다.(Visual Studio 2017 기준)

visual studio 2017에서 발생하는 컴파일 에러

리턴형에 const를 쓰지 않아도 가능하지만, 더 모호한 코드가 작성되는 문제가 있습니다.

모호한 코드는 에러를 일으킬 확률이 높기 때문에, 모호한 에러를 발생시키지 않기 위해 const를 써준 것입니다.

댓글