7. 함수의 활용(2) - 참조 변수(lvalue, rvalue)

WanJu Kim·2022년 10월 25일
0

C++

목록 보기
31/81

참조 변수는 일반 변수에 별명을 붙여주는 거와 다름없다. 왜 그런 행동을 할까? 참조의 주된 목적은 매개변수에 사용하는 것이다. 그러면 그 함수는 복사 데이터가 아니라, 원본 데이터를 가지고 작업을 하는 효과가 생긴다. 참조는 &기호를 붙여서 사용한다.

	int rats = 101;
	int& rodents = rats;	// rodents는 참조 변수이다.

	cout << "rats : " << rats << endl;
	cout << "rodents" << rodents << endl;
	rodents++;
	cout << "rats : " << rats << endl;
	cout << "rodents" << rodents << endl;

	cout << "rats의 주소 : " << &rats << endl;
	cout << "rodents의 주소 : " << &rodents << endl;

실행 결과.

참조 변수를 바꾸면 원본이 바뀌고, 그 둘은 같은 주소를 공유하는 것을 알 수 있다.

참조 변수는 포인터 변수와 비슷하고도 달라서 헷갈릴 수 있다. 우선 같은 점은 선언과 초기화를 동시에 해주어야 한다. 포인터 변수와 다른 점은, 초기화 후 다른 주소로 초기화 할 수 없다.

	int rats = 100;
	int& rodents = rats;	// rodents는 참조 변수이다.

	cout << "int rats = 100" << endl;
	cout << "int& rodents = rats" << endl;

	cout << "rats : " << rats << endl;
	cout << "rodents : " << rodents << endl << endl;
	rodents++;
	cout << "rodents++" << endl;
	cout << "rats : " << rats << endl;
	cout << "rodents : " << rodents << endl << endl;
	rats++;
	cout << "rats++" << endl;
	cout << "rats : " << rats << endl;
	cout << "rodents : " << rodents << endl << endl;

	cout << "rats의 주소 : " << &rats << endl;
	cout << "rodents의 주소 : " << &rodents << endl << endl;

	int bunnies = 50;
	rodents = bunnies;

	cout << "int bunnies = 50" << endl;
	cout << "rodents = bunnies" << endl;
	
	rodents++;
	cout << "rodents++" << endl;
	cout << "bunnies : " << bunnies << endl;
	cout << "rats : " << rats << endl;
	cout << "rodents : " << rodents << endl << endl;

	rats++;
	cout << "rats++" << endl;
	cout << "bunnies : " << bunnies << endl;
	cout << "rats : " << rats << endl;
	cout << "rodents : " << rodents << endl << endl;

	bunnies++;
	cout << "bunnies++" << endl;
	cout << "bunnies : " << bunnies << endl;
	cout << "rats : " << rats << endl;
	cout << "rodents : " << rodents << endl << endl;

	cout << "bunnies의 주소 : " << &bunnies << endl;
	cout << "rodents의 주소 : " << &rodents << endl;

실행 결과.

확실하게 알기 위해 다 적어보았다. 요점은, 참조 변수 rodents에 새로운 변수 bunnies의 주소를 대입하려고 했는데 값만 바뀌고 주소는 안바뀐다. 그 후 값을 조정했을 땐, 주소를 공유하는 변수끼리만 값이 같이 바뀌었다. 이거 포인터는 가능했었다.

	int rats = 101;
	int* rodents = &rats;
	int bunnies = 50;

	cout << "rats의 주소 : " << &rats << endl;
	cout << "rodents의 주소 : " << rodents << endl << endl;

	rodents = &bunnies;

	cout << "rats의 주소 : " << &rats << endl;
	cout << "rodents의 주소 : " << rodents << endl;
	cout << "bunnies의 주소 : " << &bunnies << endl << endl;

실행 결과.

함수 매개변수로서의 참조

위에서 말했듯이, 참조 변수는 함수에 많이 쓰인다. 원본으로 작업할 수 있기 때문이다. 다음 코드는 각각 일반, 포인터, 참조 변수를 이용해 두 변수를 맞바꾸는 작업을 보여준다.

void Swapr(int& a, int& b);	// 참조로 변환.
void Swapp(int* a, int* b);	// 포인터로 변환.
void Swapv(int a, int b);	// 일반 변환.

int main()	
{
	int wallet1 = 1000;
	int wallet2 = 10000;

	cout << "지갑1 = " << wallet1 << "원";
	cout << ", 지갑2 = " << wallet2 << "원" << endl << endl;

	cout << "참조를 이용해 내용들을 교환 : " << endl;
	swapr(wallet1, wallet2);
	cout << "지갑1 = " << wallet1 << "원";
	cout << ", 지갑2 = " << wallet2 << "원" << endl << endl;

	cout << "포인터를 이용해 내용들을 교환 : " << endl;
	swapp(&wallet1, &wallet2);
	cout << "지갑1 = " << wallet1 << "원";
	cout << ", 지갑2 = " << wallet2 << "원" << endl << endl;

	cout << "값을 이용해 내용들을 교환 : " << endl;
	swapv(wallet1, wallet2);
	cout << "지갑1 = " << wallet1 << "원";
	cout << ", 지갑2 = " << wallet2 << "원" << endl;
}

void Swapr(int& a, int& b)
{
	int temp;

	temp = a;
	a = b;
	b = temp;
}

void Swapp(int* a, int* b)
{
	int temp;

	temp = *a;	// 값을 넣어야 하기 때문에 *를 사용했다.
	*a = *b;
	*b = temp;
}

void Swapv(int a, int b)
{
	int temp;

	temp = a;
	a = b;
	b = temp;
}

실행 결과.

참조와 포인터는 원본을 사용해 값이 바뀌었다. 하지만 일반은 복사본을 교환 했기 때문에 값이 바뀌지 않고 사기당했다.

참조의 특성

참조 매개변수를 사용할 때는 몇 가지 흥미 있는 일이 일어난다. 다음 코드를 보자.

double Cube(double a);
double Refcube(double& ra);

int main()	
{
	double x = 3.0;
	cout << cube(x) << " = " << x << "의 세제곱" << endl;
	cout << refcube(x) << " = " << x << "의 세제곱" << endl;
}

double Cube(double a)
{
	a *= a * a;
	return a;
}

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

실행 결과.

cube 함수는 변수 자체를 변경하지 않지만, refcube는 변경시킨다. 왤까? 매개변수로 참조 연산자를 썼기 때문이다. 원본을 사용해서 그 자체가 바뀐 것이다. 만약 원본은 참조하고 싶지만 값은 바꾸면 안된다면 어떻게 해야 할까?

double Refcube(const double& ra)
{
	ra *= ra * ra;
	return ra;
}

자료형 앞에다가 const를 붙이면 된다.

임시 변수, 참조 매개변수, const

참조는 주소를 필요로 한다. 근데 만약에 참조할 수 없는 상황에 참조하려고 하면 어떻게 될까? 그럴 경우 컴파일러는 임시 변수를 생성하고 그걸 참조한다. 근데 여기서 문제가 생긴다. 참조의 의미는 원본에 접근하겠다는 의미이다. 하지만 임시 변수를 생성하면 그 의미가 사라진다. 예를 들어, 다음의 코드를 보자.

함수 Swapr은 int 참조 매개변수를 받는다. 하지만 이 경우에는, long형 변수를 넣으려고 한다. 데이터형이 다르므로, 참조가 불가능해서 이 경우에는 임시 변수를 만든다. 그러면 함수 내용에 따라, 임시 변수끼리 교환하므로 실제 원본을 교환하는 Swapr의 기능은 일어나지 않는다. 이런 일을 방지하는 방법은, 임시 변수 자체를 만들지 않는 것이다. 최근의 C++는 위의 사진과 같이 그렇게 하고 있다.

그럼 임시 변수를 생성하는 길은 완전히 막혔는가? 그렇지 않다. const 참조 변수일 때만 생겨난다. 어짜피 원본을 못 바꿀터이니, const를 명시해서 그 사실을 알리는 것이다. 다음은 컴파일러가 임시 변수를 생성하는 예이다.

double Refcube(const double& ra)
{
	return ra * ra * ra;
}

double side = 3.0;
double* pd = &side;
double& rd = side;
long edge = 5L;
double lens[4] = {2.0, 5.0, 10.0, 20.0};
double c1 = Refcube(edge);	// 임시 변수. 매개변수 데이터형이 다름.
double c2 = Refcube(7.0);	// 임시 변수. 7.0의 주소가 없음.
double c3 = Refcube(side + 10.0);	// 임시 변수. 마찬가지로 주소가 없음.

여기서 참조가 가능한 데이터 객체를 lvalue라고 부른다. 예를 들어 변수, 배열의 원소, 구조체의 멤버, 참조 또는 역참조 포인터다. 일반 상수와 여러 개의 항으로 이루어진 표현식은 lvalue가 아니다. 정리하자면 컴파일러는 다음과 같은 상황에서 임시 변수를 생성한다.
1) 실제 매개변수가 올바른 데이터형이지만 lvalue가 아닐 때
2) 실제 매개변수가 잘못된 데이터형이지만 올바른 데이터형으로 바뀔 수 있을 때

lvalue가 있는가 하면 rvalue가 있다. lvalue가 아닌, 즉 일반 상수와 여러 개의 항으로 이루어진 표현식이 rvalue이다. rvalue는 &&를 이용한다.

double j = 15.0;
double && jref = 2.0*j + 18.5;

rvalue는 나중에 move semantic을 배울 때 더 알아볼 것이다.

참조 변수 리턴

int& Add(int& a)와 같이 함수에서 참조 변수를 그대로 리턴하는 방법이 있다. 이건 함수에서 일반 변수를 리턴하는 것과 어떤 차이가 있을까? 일반 변수가 함수에서 리턴될 때는 그 값을 임시 장소에 복사한 다음에 복사된 값을 가져온다.

double m = sqrt(16.0);	// sqrt 함수에서 리턴된 값은 임시 장소에 저장된 후, 다시 변수 m으로 저장된다.

만약 이게 더 큰 자료형인 구조체인 경우면 어떨까? 최적화와는 더욱 멀어질 것이다. 반면 참조 변수 리턴은 원본을 직접적으로 복사하기 때문에 더 효율적이다.

참조를 리턴할 때 주의할 점은, 지역 변수를 반환하면 안된다는 것이다. 지역 변수는 함수가 끝날 때 사라지는 변수를 말한다.

const free_throws& clone2(free_throws& fit)
{
	free_throws newguy;
	newguy = {"vanish, 6, 66"};
	return newguy;
}

newguy 변수는 clone2 함수에서 선언한 변수라 함수가 끝나면 사라진다. 이 함수는 원본을 반환하는 함수라, 사라진 변수를 반환하면 사라진 데이터만 남는다. 마찬가지로 그러한 임시 변수를 가리키는 포인터를 반환하는 것도 안된다.

profile
Question, Think, Select

0개의 댓글