참조
C++에서 참조란 선언된 변수에 대한 별명, 지칭하는 새로운 이름으로 "& 기호"를 사용합니다.
int a=1;
int ref_a = a; // 참조 변수 ref_a를 선언하면, ref_a와 a는 동일한 변수가 됩니다.
Rectangle b;
Rectangle &ref_b = b; // 객체 변수에 대해서도 참조를 사용할수 있습니다.
참조 변수는 원본 변수와 동일한 공간을 사용하므로 아예 같다고 생각하시면 됩니다. 코드상 작동 구조가 포인터와 유사하기에 "->"같은 연산자를 사용하지 않도록 주의해야 합니다.
또한 원본 변수를 초기화하지 않으면 쓰레기값 때문에 참조를 생성할 수 없으며 (컴파일 오류 발생) 참조 배열은 만들 수 없습니다.
참조를 사용하는 이유는?
하나의 변수를 여러 개의 이름으로 사용하는 것인데 이는 변수의 동작을 분리해서 코드를 작성할 때 유용합니다.
예를 들어 특정 조건에 따라 여러 방향으로 이동하는 변수가 있다면 조건에 따라 변수를 주석 없이 처리할 수 있습니다. 또한 이루에 설명할 참조에 의한 호출에 주로 사용합니다.
참고로 많이 쓰이지는 않지만 참조를 참조하는 것도 가능합니다.
- 참조에 의한 호출 (Call by reference)
요약하면 값에 의한 호출형식으로 주소에 의한 호출로 표현하는 것입니다.
void swap(int &a,int &b){
int temp;
a = b;
b = temp;
}
int main(){
int c=1; d=2;
swap(c,d);
return 0;
}
다음과 같은 코드를 실행한다면 swap 함수의 a는 c, b는 d 변수의 동작을 수행합니다.
참조 매개변수는 원본 함수와 동일시되기에 별도의 스택(공간)을 낭비하지 않고 값을 교환할 수 있는 장점이 있습니다. 결과는 c=2, d=1로 포인터를 이용한 참조에 의한 호출과 동일합니다.
- 리턴값 구분
보통 함수가 제대로 동작하지 않을 경우 return 0; 이나 return false;를 반환하도록 하지만, 그럴 경우 함수가 내보낸 0의 값이 함수의 결괏값인지 오류값인지 구분하기 어려울 때가 있습니다.
이때 함수가 참조값에 결괏값을 받게 설정하면, 결괏값과 오류값을 구분하기 쉬워집니다.
int sum(int x[],int size,int &s){
if(size <= 0) return false; //오류 발생시 0 리턴
...
s = 결과값
}
int main(){
int a[] = {0,1,2,3,4,5};
int sum_number;
if(sum(a,-1,sum_number)) cout << sum_number << endl; // sum_number은 결과값
else cout << "오류" << endl;
...
}
물론 포인터를 이용하거나 전역변수를 사용하거나 리턴값이 0이 아니게 만들면 되지만, 다음과 같이 쓸 수도 있다고 기억합시다.
값에 의한 호출로 객체를 매개 변수로 전달할 때 비대칭 구조를 설명했었는데요. 참조에 의한 호출은 객체가 새로 만들어지는 것이 아니므로 생성자와 소멸자를 실행하지 않습니다.
- 참조 리턴
문자를 리턴할 때 리턴한 문자를 바꾸고 싶을 때가 있습니다. 그래서 다음과 같은 코드를 작성했는데
char str_1 = 'a';
char get(){
return str_1; //변수 str_1의 값 리턴
}
char str_2 = get() // get은 'a'를 리턴하므로 str_2은 'a'로 초기화 됩니다.
get() = 'b'; // get이 반환하는 str_1의 값을 'b'로 바꾸고 싶지만 컴파일 오류가 발생합니다.
컴파일 오류가 발생하는 이유는 get( )이 반환하는 str_1 변수는 get( )내에서만 유효하기 때문입니다. 이 경우 참조를 리턴하도록 하면 str_1을 자유자재로 사용할 수 있습니다.
char str_1 = 'a';
char& get(){
return str_1; //변수 str_1에 대한 '참조'리턴
}
char str_2 = get() // get은 'a'를 리턴하므로 str_2은 'a'로 초기화 됩니다.
char &ref = get() // 이제 get()을 벗어나도 ref변수로 str_1을 변경할수 있습니다.
get() = 'b'; // get이 반환하는 str_1의 참조가 'b'로 바뀌어 str_1값도 'b'가 됩니다.
ref = 'b'; // 위와 동일한 결과가 나옵니다.
복사 생성자
" = 연산자 "를 이용해 값을 복사하는 것이 아닌 말 그대로 원본과 동일반 별개의 사본을 만드는 것입니다.
- 객체의 얕은 복사와 깊은 복사
복사는 했지만 복사한 객체가 가지고 있는 멤버들은 복사하지 못해 복사된 객체가 원본의 멤버를 자기 것이라고 생각하는 경우를 '얕은 복사'라고 합니다.
간단한 코드는 문제가 없을 수 있지만 코드의 동작이 많을 경우 각 객체가 공유하게 된 멤버들을 서로가 사용하려고 해 '충돌'이 발생하고 이때 코드의 문제를 발견하기 어려워 얕은 복사는 되도록 피해야 합니다.
객체뿐만 아니라 멤버까지 전부 복사한 것을 '깊은 복사'라고 합니다.
- 복사 생성 및 복사 생성자
복사 생성 시 '복사 생성자'를 사용하고 다음 규칙을 따릅니다.
- 복사 생성자의 매개 변수는 오직 하나입니다.
- 자기 클래스에 대한 참조로 선언됩니다. ('참조'로 복사 생성자 구현)
복사 생성자로 얕은 복사를 피하고 깊은 복사를 하기 위해 복사 생성자 구현 부분을 최대한 신경 씁시다.
class Classname{
Classname(const Classname& c); //복사 생성자
};
Classname::Classname(const Classname& c) { //복사 생성자 구현
...
}
복사 생성자를 사용하지 않고 복사 생성을 할 경우 디폴트로 얕은 복사를 실행하도록 만들어진 코드가 실행됩니다. 때문에 깊은 복사를 위한 코드를 선언해 주도록 해야 합니다.
'프로그래밍 > C++' 카테고리의 다른 글
[C++] 5장. #1 함수 호출 시 객체 전달 (2) | 2023.12.08 |
---|---|
[C++] 4장. #2 this 포인터와 string 클래스 (0) | 2023.12.08 |
[C++] 4장. #1 객체 포인터와 객체 배열 (0) | 2023.12.04 |
[C++] 3장. #2 접근 지정자와 인라인 함수 (1) | 2023.12.03 |
[C++] 3장. #1 클래스와 객체 (0) | 2023.12.02 |