title: “C++ 7.함수와 클래스(객체)” categories:

  • CspecialCharspecialChar

    #C++ 7.함수와 클래스(객체) : 네이버 블로그

참조란.. 선언된 변수의 별명입니다

즉 참조 변수가 가리키는 대상은 원본 변수입니다

또 참조 변수는 초기화로 지정된 원본 변수의 공간을 공유합니다

그렇다면 이 참조 변수는 어디에 쓰이느냐

함수에 있어 인자의 전달 방식은 값에 의한 호출과 주소에 의한 호출로 나뉩니다

값에 의한 호출은 실인자 값이 함수의 매개 변수로 복사되어 전달되는 방식이고

주소에 의한 호출은 주소가 함수의 매개 변수(포인터)로 들어감으로써 매개 변수가 직접 전달됩니다

예시를 들어보자면

int add(int a, int b){

…}

위 함수에서의 인자는 int a와 int b입니다

이들은 int형 a와 b의 을 가져옵니다

int add(int *a, int *b){

}

위 함수에서의 인자는 int*a와 int *b입니다

*은 포인터 타입 변수이므로 포인터 타입의 변수를 인자로 사용됩니다

즉 주소가 입력되어 add함수 안에서는 주소를 통해 인자로 사용됩니다

값에 의한 호출과 주소에 의한 호출의 차이를 아시겠나요?

자 이제 C++에 맞춰 객체를 인자로 전달해봅시다

name이라는 클래스가 있다고 합시다

그리고 name_size라는 함수가 있다고 합시다

name_size(name);를 하게 된다면 name의 값이 name_size 함수로 전달되겠지요

그렇게 값에 의한 호출이 됩나다

중요한 점은 값에 의한 호출을 할 때에는 매개 변수 객체의 생성자는 실행되지 않고 소멸자만 실행된다는 점이다

즉 객체가 값에 의해 전달될 때 객체가 복사된다. 복사된 객체는 새로이 만들어 진 것이 아니기 때문에 생성자가 실행되지 않는다. 하지만 복사된 객체는 소멸할 때 소별자가 생성된다

#include
#include
using namespace std;
class Circle {
 int radius;
public:
 Circle(int r) {
 cout << "생성자 실행" << endl;
 this->radius = r;
 }
 ~Circle() {
 cout << "소멸자 실행" << endl;
 }
 int getRadius() {
 return radius;
 }
 void setRadius(int r) {
 this->radius = r;
 }
};
void increase(Circle c) {
 int r = c.getRadius();
 c.setRadius(r + 1);
}
int main() {
 Circle waffle(30);
 increase(waffle);
}

위 코드를 실행시켜보자

실행 결과부터 말하자면

생성자 실행

소멸자 실행

소멸자 실행

이렇게 출력됩니다

이상하지 않나요? 왜 생성자는 한개고 소멸자는 두개일까요

이유는 전달 과정에 있습니다

코드를 보면서 설명하자면

Circle waffle(30); //Circle 클래스 waffle 객체 생성(생성자 실행)

increase(waffle); //waffle 객체를 인자로 받음으로서 복사 객체 생성

//함수 종료로 복사 객체 소멸

//프로그램이 종료되며 소멸자 실행

waffle을 인자로 받을 때 새로운 Circle 객체가 생성되는 것이 아닌 기존의 객체를 복사하기 때문에 생성자가 실행되지 않습니다.

다시 정리하면 함수 인자로 받은 객체는 복사된 객체이기 때문에 클래스 생성자가 생성되지 않고 객체만 복사됩니다. 함수가 끝나면 복사된 객체는 소멸하기에 소멸자가 실행됩니다

클래스 객체를 값에 의해 호출했으니 이번엔 주소에 의해 호출해봅시다

위의 소스를 바꿔 주소에 의한 호출로 변경했습니다

void increase(Circle \*c) { //Circle 클래스 형식의 주소를 인자로 받는다
 int r = c->getRadius(); //c는 포인터이므로 포인터 연산자 ->를 사용한다
 c->setRadius(r + 1);
}
int main() {
 Circle waffle(30); 
 increase(&waffle); Circle \*c형식을 받기 때문에 waffle의 주소를 전달한다
}

함수의 인자가 주소이기 때문에 클래스가 자체가 전달됩니다

즉 함수에 전달된 인자는 객체의 주소(원본)이기 때문에 함수 내에서 객체가 변경되면 함수 밖에서도 변경됩니다

이렇게 객체를 인수로 주소를 통해 복사하는 방식을 얕은 참조라 합니다

왜냐면 함수 내에서 객체의 맴버를 변경하면 함수 밖의 원본도 바뀌기 때문이죠

그렇다면 객체 a와 똑같은 맴버를 가진 객체 b를 만들기 위해서는 어떻게 해야 할까요?

우선 복사 생성자에 대해 알아야 합니다

복사 생성자란 객체를 만드는 생성자가 입력받는 값이 객체인 생성자입니다

즉 Circle 생성자가 있고

객체 A가 있다고 합시다

Circle B(A);는 A를 입력 인자로 갖는 생성자가 실행되어 A의 인자를 B로 옮기게 됩니다

아래 코드를 보면

#include
using namespace std;
class Circle {
 int radius;
public:
 Circle() {
 radius = 1;
 }
 Circle(int r) {
 this->radius = r;
 }
 Circle(Circle& c) {
 this->radius = c.radius;
 }
 double getArea() {
 return radius \* radius\*3.14;
 }
};
int main() {
 Circle a(10);
 Circle b(a);
 cout << "a(10)의 넓이 " << a.getArea()<<endl;
 cout << "b(a)의 넓이 " << b.getArea()<<endl;
}

객체에 추가된 코드는

Circle(Circle& c) {

this->radius = c.radius;

}

입니다

Circle() 은 생성자인데 인수를 Circle&로 받으므로 복사 생성자가 됩니다

Circle(Circle& c) c를 인자로 받고

c.radius를 현재 객체의 radius로 옮깁니다

사실 이러한 복사 생성자는 입력하지 않아도 컴파일러가 객체를 생성할 때 생성자가 객체를 인자로 받는다면 자동으로 실행됩니다

즉 객체를 생성할 때 객체를 인자로 받으면 자동으로 “디폴트 복사 생성자”가 만들어지고

인자로 받은 객체의 맴버를 새로 만든 객체로 옮깁니다

중요한 점은 디폴트 복사 생성자는 얕은 복사로 복사되고 맴버의 값을 그대로 옮기다는 점입니다

문제는 변수가 포인터 타입인 경우 포인터를 그대로 복사 하기 때문에 한쪽의 값이 변경되거나 메모리가 반환된다면 다른 한 쪽에서는 문제가 생기게 됩니다

그래서 객체에 포인터 변수 타입이 있을 경우에는 깊은 복사를 해야 합니다

깊은 복사를 위해서는 객체의 맴버 중 포인터 타입인 변수를 새로운 메모리를 할당하고 거기에 값을 복사해야 합니다

복사를 하는 인자가 무엇이냐(포인터)에 신경쓰면 될 듯 합니다

업데이트: