본문 바로가기
Dev Language/C++

참조 변수 & 복사 생성자

by 미티치 2016. 12. 6.

참조 변수

 

 C++에 추가된 새로운 복합형, 참조(reference)는 미리 정의된 어떤 변수에 실제 이름대신 쓸 수 있는 대용 이름이다. 참조의 주된 용도는 함수의 형식 매개변수에 사용하는 것이다. 큰 구조체를 처리해야 하는 함수에서 포인터 대신 참조를 사용할 수 있다.

void swap(int x, int y){     // 두 매개변수의 값을 서로 바꿔주는 기능 구현    }
int a = 10, b = 20;
swap(a, b);

-      위와 같이 선언하면 메모리 상에서 swap 함수가 호출될 때, 스택에 int xy를 생성하고 ab에 들어있는 값 10,20을 복사해서 사용함. 따라서 swap 함수 호출 이후에
cout << a << “ , ”<< b << endl;
을 이용해서 값을 찍어봐도 값은 변해있지 않고 그대로 a = 10, b = 20을 출력함

 

void swap(int & x, int & y){          // 두 매개변수의 값을 서로 바꿔주는 기능 구현         }
int a = 10, b = 20;
swap(a, b);

-      하지만 위와 같이 매개변수를 참조 변수로 전달하면, 값을 복사하지 않고 메모리에 잡혀있는 변수 a, b 원본을 사용한다. 따라서 swap 함수 호출 이후에 a,b의 값을 cout으로 찍으면 a = 20, b =10 으로 서로 바뀌어있다.

-      여기서 swap(int *pa, int *pb);로 선언하여 포인터를 이용해서 교환한다면?
void swap(int *pa, int *pb){           //
두 매개변수의 값을 서로 바꿔주는 기능 구현         }
int a = 10, b = 20;
swap(&a, &b);

전달되는 매개변수의 주소를 전달하기 때문에 당연히 a,b의 값이 바뀐다.

-      정리하자면,
swap(int x, int y )
는 변수의 값을 전달

swap(int & x, int & y)
는 변수를 전달
swap(int *px, int *py)
는 변수의 주소를 전달

 

 참조 변수의 선언 방식은 다음과 같다

           int rats;
           int & ro = rats;

여기서 &는 주소 연산자가 아니라, 데이터 형 식별자의 일부로 사용된 것이다. ( 포인터 변수를 선언할 때 int *int형 데이터를 지시하는 포인터를 의미하는 것처럼 ) int &int에 대한 참조를 의미한다.

-      참조 변수를 선언할 때에는 선언과 동시에 초기화를 해주어야 한다. 포인터처럼 참조를 먼저 하고 나서 선언을 할 수 없다.

-      또한 한번 선언한 참조 변수는 다른 변수를 참조할 수 없다.
int a = 10;
int & ra = a;
int b = 50;
cout << a << “,” << ra << “,” << b << endl;          //  10, 10, 50
출력
cout << &a << “,” << &ra << “,” << &b << endl;   // 0067FD14 , 0067FD14, 0067FCFC

ra = b;                                                           //
참조를 바꿈?
cout << a << “,” << ra << “,” << b << endl;          // 50 , 50, 50
출력

cout << &a << “,” << &ra << “,” << &b << endl;   // 0067FD14 , 0067FD14, 0067FCFC
         

-      위와 같은 예제에서 중요한 것은, ara는 주소 값이 같다는 것이다. 참조 변수는 실제의 변수 이름 대신 쓸 수 있기 때문이다. 따라서 ra = b 에서 참조 변수 rab를 참조하게 되는 것이 아니라 실제로는 a = b로 작동한다. 따라서 마지막 행에서 값이 모두 50으로 찍히는 이유는 0067FD14에 위치한 a의 값이 50으로 변했기 때문에 a를 참조하는 ra 50을 출력하는 것이다. b의 주소는 0067FCFC, 여기서 알 수 있듯이 참조는 대입문이 아니라 초기화 선언에 의해서만 설정할 수 있다.

-      참조는 내용 참조 연산자 *가 암묵적으로 받아들여지도록 가장한 포인터와 비슷해 보인다. 사실 어느 정도는 그것을 참조라고 할 수 있다. 그러나 포인터와 참조 사이에는 표기 방식 외에도 차이가 존재한다. 포인터는 선언 후 나중에 값을 지정할 수 있지만, 참조는 선언과 동시에 값을 지정해야 한다.

-      int x = 100;
int *px = &x;
int & rx = *px;               //
참조변수 rx는 포인터 px를 참조

int y = 200;
px = &y;                       //
포인터 pxy를 지시하도록 변경

이렇게 rx가 포인터를 참조하고 포인터가 마지막 행에서 y를 지시하도록 변경해도 참조 변수 rxx를 참조한다는 사실은 변하지 않는다.

-      참조 매개변수는 구조체나 클래스 같이 덩치 큰 데이터를 다룰 때에나 유익하다.

 

 

객체 리턴과 참조 리턴

 

 메서드에서 반드시 객체를 리턴해야 하는 경우가 아니라면, 참조를 리턴하는 것이 더 좋다. 코드 상의 차이는 함수 원형과 함수 머리에만 나타나며, 다음의 예시와 같다.

 

Star nova1 (const Star &);           // Star 객체를 리턴
Star & nova2 (const Star &);        // Star 객체에 대한 참조를 리턴

 

- 객체를 리턴하면 리턴된 객체의 임시 복사본을 생성하기 때문이다. 이렇게 임시 복사본을 생서하게 되면, 복사본 생성 시간 부담과, 나중에 파괴자를 호출하여 그 복사본을 없애는 시간 부담이 따른다. ( 객체를 직접 리턴하는 것은 객체를 값으로 전달하는 것과 비슷하다. ) 따라서 참조 리턴하면 시간과 메모리가 절약된다.

 

- 함수가 그 함수 안에서 생성된 임시 객체에 대한 참조를  리턴하면 안된다. 함수가 종료되면 그 객체가 파괴되어 더 이상 참조가 유효하지 않기 때문이다. 이런 경우에는 호출한 함수에서 사용할 수 있는 복사본을 생성할 수 있도록 객체를 직접 리턴해야 한다.

 

 

  • cpp에는 call by value , call by address, call by reference 가 있다.
  • call by value는 swap(int x, int y ) => 변수의 값을 전달
  • call by address는 swap(int *px, int *py) => 변수의 주소를 전달
  • call by reference는 swap(int & x, int & y) => 변수를 전달
  • address와 reference는 메모리에 4byte(포인터 변수의 크기)를 더 차지하고 안하고의 차이도 있지만 (reference는 차지 안함. int &b = a; 로 선언할 때 가인수 b를 선언한 것이므로) , address는 null로 초기화가 되지만 reference는 null로 초기화할 수 없음.
  • 따라서 구조체나 크기가 큰 변수의 경우, 또한 null 값이 들어가면 안되는 경우 call by reference를 사용

 

 

 

복사 생성자

 

 어떤 클래스를 위한 복사 생성자는, 그 클래스 형의 객체를 매개변수로 사용하는 생성자이다. 예를 들어 복사 생성자는 다음과 같은 원형을 가진다.

           Star (const Star &);

 복사 생성자는 다음과 같은 상황에서 사용된다.

-       새 객체를 동일한 클래스의 다른 객체로 초기화 할 때

-       객체가 함수에 값으로 전달될 때

-       함수가 객체를 값으로 리턴 할 때

-       컴파일러가 임시 객체를 생성할 때

 

프로그램이 객체의 복사본을 생성할 때마다 컴파일러는 복사 생성자를 사용한다. 특히 함수가 객체를 값으로 전달하거나, 객체를 리턴할 때 복사 생성자가 사용된다. 값으로 전달한다는 것은, 오리지널 변수의 복사본이 만들어진다는 것을 의미한다. 또한 컴파일러는 임시 객체를 생성할 때마다 복사 생성자를 사용한다.

 

그런데 객체를 값으로 전달하면 복사 생성자가 호출되기 때문에, 참조로 전달하는 것이 더 좋다. 참조로 전달하면 생성자는 호출하는 시간과, 새로운 객체를 저장하는 메모리 공간이 절약된다.

 

 

StringBad ditto(motto);        // StringBad(const StringBad&) 호출

 

디폴트 복사 생성자는 static  멤버를 제외한 멤버들을 멤버별로 복사한다. (멤버별 복사 또는 얕은 복사)

 

 

생성자에 new를 사용할 때 주의 사항

 

- 생성자에서 new를 사용하여 포인터 멤버를 초기화 하면, 파괴자에 반드시 delete를 사용해야 함

 

- new와 delete의 사용은 서로 어울려야 한다. new는 delete와 짝을 이루고, new []는 delete[]와 짝을 이루어야 한다. ( new char와 new char[]의 차이점은, 두 형식 모두 같은 크기의 메모리를 대입하지만, 후자는 클래스 파괴자와 호환이 된다는 것 )

 

- 생성자가 여러 개일 경우에는, 모두 대괄호를 사용하던지 아니면 아예 다 없이 사용하던지, 모든 생성자가 같은 방법으로 new를 사용해야 한다. 파괴자는 하나밖에 없으므로 모든 생성자가 그 파괴자와 어울려야 한다.

 

- 깊은 복사를 통해 하나의 객체를 다른 객체로 초기화하는,, 복사 생성자를 정의해야 한다. 일반적으로 그러한 복사 생성자는 다음과 같다.

 

String :: String ( const String & st)

{

num_strings++;

len = st.len;

str = new char[len+1];

std::strcpy(str, st.tr);

}

 

 

 

'Dev Language > C++' 카테고리의 다른 글

정적 결합과 동적 결합 & 가상함수  (0) 2016.12.23
Is a, Has a 관계 & 상속  (0) 2016.12.23
if문과 switch문  (0) 2016.12.06
함수 오버로딩  (0) 2016.12.06
배열과 포인터  (0) 2016.12.05