프로그래밍 지식/C++

C++문법/ 참조 변수, reference + 참조의 특성

게임이 더 좋아 2021. 11. 29. 21:41
반응형
728x170

 

C++에서는 참조 변수라는 새로운 복합 타입을 언어에 추가했다.

여기서 참조는 미리 정의된 어떤 변수의 실제 이름 대신 쓸 수 있는 대용 이름을 말한다.

 

예를 들어 ace 를 trees 변수의 참조로 만들면

ace와 trees는 같은 변수를 나타내는 것으로 사용할 수 있다.

 

그런데 생각해보면 굳이 다른 이름을 만들어야 하는가? 라는 의문이 든다.

참조를 하는 주된 이유는 바로 함수의 형식 매개변수에 사용하기 위함이다.

-> 해당 변수의 주소를 직접 가리키는 다른 이름의 변수 == 참조 변수

 

참조를 매개변수로 사용하면 그 함수는 복사본이 아니라 원본 데이터를 가지고 작업한다.

이는 복사본을 만들기 위한 오버헤드가 사라지는 효과도 있다.

 

그래서 참조는 클래스를 설계할 때 필수적으로 사용된다.

 

 

우선 참조가 어떻게 동작하는지를 보면서 더 알아보자

 


 

1. 참조 변수를 만들어보자

C와 C++ 에서는 변수의 주소를 앰퍼샌드(&)을 이용한다.

C++은 & 기호에 또 하나의 의미를 추가하여 참조 선언을 나타내게 하였다.

쓰임은 조금 다르게 쓰인다.

변수의 주소를 얻어내는 것이 아니라 데이터 형 식별자 같이 쓴다.

-> 즉, 데이터 형이랑 같이 나오는 &는 참조변수를 만드려고 쓰는 것이다.

 

서론이 길었다.

코드로 보자.

#include  <iostream>

int main()
{
     using naespace std;
     int rats = 101;
     int & rodents = rats; // rodents가 참조변수다. & 가 변수에 있는 것이 아니라 중간에 있다.
     
     cout << "rats = " << rats;
     cout << ", rodents = "<< rodents << endl;
     
     rodents++;
     
     cout << "rats = " << rats;
     cout << ", rodents = " << rodents << endl;

 

앰퍼샌드를 썼지만 주소를 얻기 위해서 쓴 것이 아니라 참조를 위해서 쓴 것이다.

하지만 결국 같은 주소를 같게 됨은 당연한 것이 참조를 했기 때문이다.

 


 

흠.. 근데 이거 포인터랑 다른가???

다르긴 하다.

int rats = 101;
int & rodents = rats; // 참조
int * prats = &rats; // 포인터

 

같은 결과를 나오게 할 수 있지만 조금 다른 점이 있다.

우선 앞선 예제에서는 참조를 선언할 때 그 참조를 초기화해야 했다.

포인터를 선언할 때 처럼, 참조를 먼저 선언하고 나중에 값을 지정할 수는 없다.

 

int & rodents; //미리 참조변수 선언 (초기화 전)
rodent = rat; // 값 지정(초기화)
// -> 불가능

 

포인터는 가능한데 반해 참조 변수는 할 수 없다.

즉, 참조를 선언할 때 참조 변수를 함께 초기화를 같이 해야 한다.

 


 

또한 구조체를 접근할 때!!! 다르다.★★(21.12.05 업데이트)

 

포인터로 받으면 멤버를 읽으려면 ->로 읽어야 하지만

 

참조 변수로 받으면 . 으로 읽는다.

 

예를 들면

 

typedef struct Position{
	int x_;
    int y_;
}Pos;

//구조체

Pos a;

void func1(Pos *a){
    a->x_ = 1;
    //동치인 문장 (*a).x_ = 1;
}

void func2(Pos &a){
    a.x_ = 0;
}

 

실행해보자.

 

결과는??

 

그렇다면 동치인 문장으로 바꿔도 결과가 잘 나올까??

잘나온다.

 

 

 


 

그렇다.  만들 때는 위와 같이 만들고

2.  주 목적인 함수 매개변수로서의 참조는 어떻게 할까??

 

즉, 어떤 함수에서 사용하는 변수의 이름을 그 함수를

호출한 프로그램(호출 함수)에 있는 어떤 변수의 대용 이름으로 만드는 것이다.

 

이러한 방식으로 매개변수를 전달하는 것을 "참조로 전달"한다고 한다.

참조로 전달하면 피호출 함수가 호출 함수의 변수를 사용할 수 있다.

C++에서 새로 추가된 이 기능은 오로지 값으로만 전달하는 C를 확장시켜놓기 위함이다.

값으로 전달하는 C에서 참조로 전달하는 C++이 된 것이다.

C에서는 피호출 함수는 호출 함수가 건네주는 값의 복사본을 대상으로 작업했다.

(물론 포인터로 직접 참조해서 사용할 수는 있긴 했다.)

 

*값으로 전달의 예

void sneezy(int x);

int main()
{
    int times = 20; // times라는 변수에 값 20 할당
    sneezy(times); //함수의 원형을 보면 x라는 변수를 만들고 전달된 값 20을 대입하는 것이다.
    ...
    
}

void sneezy(int x)
{
	.... 
}

//결국 x도 만들어주고 times도 만들어서 둘다 20을 갖게됨

 

*참조로 전달의 예

void grumpy(int &x);

int main()
{
    int times = 20;
    grumpy(times);
    ...
}

void grumpy(int &x) // ->x를 times의 대용 이름 (참조로 만듬)
{
	....
}

// 결국 x, times도 같은 주소를 가리키며 하나의 변수이면서 2개의 이름만 가지는 것이다.

 

결국 메모리에서 유리한 면을 가져갈 수 있다는 말이다.

 


 

참고로

 

또한 이준 포인터는 만들 수 있지만

이중 레퍼런스라는 것은 없다.

 

포인터에 대한 레퍼런스는 만들 수 있지만레퍼런스에 대한 포인터는 만들 수 없다.

 

레퍼런스의 배열도 만들 수 없다.어차피 레퍼런스에 대한 포인터를 만들지 못하기 때문이다.

 

 


 

참조 매개변수를 사용하는 것은 좋다.

하지만 참조의 특성을 알고 사용하는 것이 매우 좋다.

알아보자


예를 들어보자

//그냥 앞에 없어도 알아듣자.
double cube(double a){

    a *= a * a;
    return a;
}

double refcube(double &ra){
    
    ra *= ra * ra;
    return ra;
}

int main(){

    ...


    double x = 3.0;

    cout << cube (x) ;
    cout << refcube(x);
    
}

 

x를 세제곱 하고

해당 값을 다시 세제곱 했으니

결과는 이렇게 나오지 않을까?

27
729

??

 

실제 결과

27
27

 

뭐야???

 

refcube( )는 main에 있는 x를 변경도 하면서 반환을 하는 것이고

cube( ) 는 main에 있는 x 값을 받아와서 해당 값을 반환하는 것이 목적인 것이다.

즉, 참조를 하지 않은 함수는 값을 빌려와서 반환하는 데 목적이 있기 때문에

원본을 변경하지 못한다.

 

?? 아까 참조를 쓰는 것이 값 복사하는 것보다 값을 참조하는 것이 좋다고 했잖아

무조건 그럼 참조하면 원본이 바뀌어야 하는건가????  라는 의문이 들 수 있다.

 

하지만 괜찮다 우리는 const 키워드가 있다.

 

아래와 같이 쓰면

refcube( const double &ra){

    ...

}

const 선언 후에 원본을 변경하려고 시도하면 에러메세지를 보낸다.

다시 const 참조에 대해서 좀 더 알아보자면??

 


 

함수는 거의 const 키워드가 붙여져있는 채로 남아있는데

무엇때문일까?????

 

참조 매개변수를 상수 데이터에 대한 참조로 선언하는 이유는 3가지 이점이 있기 때문이다.

 

1. const를 사용하면 실수로 원본이 수정되는 것을 막을 수 있다.

 

2. 원형에 const를 사용하면 함수가 const와 const가 아닌 실제 매개변수를 모두 처리할 수 있다.

 

하지만 원형에 const를 생략하면 const가 아닌 데이터만 처리할 수 있다.

-> const로 하면 둘 다 되니까 웬만하면 붙이라 이말이다.

 

3. const 참조를 사용하면, 함수가 자신의 필요에 따라 임시변수도 생성하여 사용 가능하다.

 

따라서 참조 형식 매개변수는 const로 선언하는 것을 지향하자.

 


그렇다고해서 const가 만능이냐????

아니다.

 

사실 간단한 함수를 작성할 때는 쓸데없이 참조로 전달하지 말고 값으로 전달해야 한다.

참조 매개변수는 구조체나 클래스 같이 덩치 큰 데이터를 다룰 때나 유용한 것이다.

-> 다만 임시변수로 써져서 여전히 쓸만하다.

 

왜냐면 우리에게는.. 맞아보이는 것들이 참조변수를 이용하는 순간 틀린 경우가 있기 때문이다.

 

위에 이어서

double z1 = cube(x + 2.0);

double arr[3] = {1.1, 2.2, 3.3};

double z2 = cube(arr[2]);

 

z1, z2 둘다 정상적으로 값이 계산된다.

하지만 참조로 사용한다면

double z = refcube(x + 3.0);
//오류가 생긴다.

 

컴파일조차 되지 않는다.

x + 3.0 자체가 변수가 아니니 연산도 되지 않는 것이다.

 

하지만 C++에서는 너그럽게도 임시 변수라는 것을 만들어서

임시변수를 x + 3.0의 값으로 초기화해서 ra가 해당 임시변수의 참조로 만들기도 한다.

 

즉, C++에서는 실제 매개변수와 참조 매개변수가 일치하지 않을 때

임시변수를 생성해줄 수 있다.

**최근 C++은 const 참조일 경우에만 임시변수를 생성하게 허용한다.

 

왜 C++은 const 참조일 때만 임시 변수를 생성시켜주는 것일까??

 

알아보자 

 


 

우선 임시변수가 언제 생긴다고???

 

우선 참조 매개변수가 const여야 한다고 말했다.

const 참조할 때 컴파일러는 2가지 상황에서 임시변수를 생성한다.

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

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

 

** lvalue란 참조가 가능한 데이터 객체를 말한다.

(예를 들어 변수, 배열의 원소, 구조체의 멤버, 참조 또는 역참조 포인터를 lvalue 라고 말할 수 있다)

일반 상수와 여러 개의 항으로 이루어진 표현식은 lvalue가 아니다.

 

다시 refcube( )로 돌아가보자

 

double side = 3.0; // 변수
double * pd = &side; // side를 가리키는 포인터 pd
double & rd = side; //side를 가리키는 대용이름 rd (주소연산자 &가 아니다) -> 참조 변수
long edge = 5;

double lens[4] = {2.0, 5.0, 10.0, 12.0};

double c1 = refcube(side); // ra는 side가 되겠다.
double c2 = refcube(lens[2]); // ra는 lens[2]
double c3 = refcube(rd); // ra는 rd이면서 side
double c4 = refcube(*pd); // ra는 *pd 이면서 side

double c5 = refcube(edge); // 임시변수 생성
double c6 = refcube(7.0); // 임시변수 생성
double c7 = refcube(side + 10.0); // 임시변수 생성

 

위의 4개는 double형 데이터 객체라고 할 수 있다.

그래서 해당 객체에 대한 참조 매개변수를 알아서 생성할 수 있으므로 임시변수는 필요가 없다.

**edge는 변수지만 데이터 타입이 다르기 때문 나머지 아래 2개는 데이터 형만 일치하지 데이터 객체는 아니다.

 

즉, 아래 3개는 임시변수를 생성하고

ra가 해당 임시변수를 참조하게 만든다.

 

이것이 바로 const 참조에만 임시변수를 생성할 수 있는 이유다.

const가 아니라고 해보자

 

아래 swapr 함수는 값을 참조를 통해 바꾸는 함수다.

void swapr(int &a, int &b){
    int temp;
    
    temp = a;
    a = b;
    b = temp;
}

 

const가 아니고 데이터 타입이 달라보자

long a = 3, b = 5;

swapr(a, b);

데이터형이 일치하지 않으므로 컴파일러는 int 형 임시변수를 만든다.

그것들을 3,5로 초기화한 후 임시 변수의 내용을 서로 교환한다.

 

하지만 여전히 a의 값은 3, b의 값은 5를 유지한다.

정상적으로 작동했는가? -> 절대 아니다.

 

즉, 전달받은 매개변수를 변형시키기 위해서 만든 참조함수라면

임시변수를 만들어봤자 원본인 매개변수는 변경이 되지 않는다는 말이다.

 

다시 말하면 const일 때나 임시변수를 만드는 것이 의미가 생긴다는 말과 동치이다.

즉, 단순히 그 값만 참조해서 사용할 것이라면 임시변수는 조금 더 다양한 매개변수를 사용할 수 있도록 해주는 것이다.

 

 

 

반응형
그리드형