728x90

황기태 저자의 명품 C++ Programming 개정판을 읽고 학습한 내용을 정리한 포스트입니다!

https://search.shopping.naver.com/book/catalog/32436115747

 

C++ Programming : 네이버 도서

네이버 도서 상세정보를 제공합니다.

search.shopping.naver.com

 

함수의 인자 전달 방식

  • 값에 의한 호출(call by value)
    • 값에 의한 호출은 호출하는 코드에서 넘겨주는 실인자 값이 함수의 매개 변수에 복사되어 전달되는 방식
  • 주소에 의한 호출(call by address)
    • 주소를 직접 포인터 타입의 매개 변수에 전달받는 방법
  • 아래와 같이 매개 변수로 복사한 경우 변경되지 않지만, 포인터 매개 변수로 전달 시 main 스택의 m, n이 교환된다.
#include <iostream>
using namespace std;

void swap(int a, int b) {
	int tmp;
	tmp = a;
	a = b;
	b = tmp;
}

int main() {
	int m = 2, n = 9;
	swap(m, n);
	cout << m << " " << n;
}

// 2 9

#include <iostream>
using namespace std;

void swap(int *a, int *b) {
	int tmp;
	tmp = *a;
	*a = *b;
	*b = tmp;
}

int main() {
	int m = 2, n = 9;
	swap(&m, &n);
	cout << m << " " << n;
}

// 9 2

 

  • 값에 의한 호출은 실인자의 값을 복사하여 전달하므로, 함수 내에서 실인자를 손상 시킬 수 없는 장점
    • 즉, 함수 호출에 따른 부작용(side-effect)가 없다.

함수 호출 시 객체 전달

값에 의한 호출
#include <iostream>
using namespace std;

class Circle {
private:
	int radius;
public:
	Circle();
	Circle(int r);
	~Circle();
	double getArea() { return 3.14 * radius * radius; }
	int getRadius() { return radius; }
	void setRadius(int radius) { this->radius = radius; }
};

Circle::Circle() {
	radius = 1;
}
Circle::Circle(int radius) {
	this->radius = radius;
}
Circle::~Circle() {

}
void increase(Circle c) {
	int r = c.getRadius();
	c.setRadius(r + 1);
}


int main() {
	Circle waffle(30);
	increase(waffle);
	cout << waffle.getRadius() << endl;
}

 

위의 코드는 아래와 같은 순서로 실행된다.

  • 반지름이 30인 waffle 객체를 생성하고 increase(Cicle c) 함수에 전달한다.
    • 함수에 객체를 전달할 때 객체 이름만 적는다.
  • increase()가 호출되면 waffle 객체가 현재 상태 그대로 매개 변수 c에 복사된다.
  • 이후 increase()는 객체 c의 반지름을 1 증가시켜 31이 되지만, waffle 객체의 반지름은 변하지 않는다.
  • increase()가 종료하면 매개 변수 객체 c가 함께 소멸되고, main()의 waffle 객체는 increase()를 호출하기 전과 동일하게 반지름이 30이다.

'값에 의한 호출'은 함수 안에서 매개 변수 객체에 어떤 변화가 일어나도 실인자를 훼손시키지 않는 장점이 있지만, 실인자 객체의 크기가 크면 객체를 복사하는 시간이 커지는 단점

 

값에 의한 호출로 객체 전달 시 문제점
  • 표면적으로 나타나지 않는 약간의 문제가 수반된다.
  • 객체가 함수에 전달되면, 함수의 매개 변수 객체가 생성되고, 함수가 종료하면 매개 변수 객체가 소멸된다.
  • 이 때객체를 매개 변수로 가지는 함수의 경우 생성자는 실행되지 않고 소멸자만 실행되도록 컴파일
    • 위의 코드에서도 생성자 실행 없이 c의 객체 공간에 waffle 객체가 그대로 복사된다.
    • 따라서, 매개 변수 객체의 생성자가 실행되지 않고 소멸자만 실행되는 비대칭 구조가 발생
왜 생성자가 실행되지 않도록 컴파일??
  • increase 함수의 매개 변수 c에 waffle 객체가 전달된 후, 만일 객체 c의 생성자 Circle()이 실행된다면, 객체 c의 반지름이 1로 초기화되어, 전달 받은 원본의 상태를 잃어버리게 되어 소멸자만 호출한다.
  • 이러한 비대칭 구조는 함수 호출 시 원본 객체의 상태를 그대로 매개 변수 객체에 전달되도록 하기 위한 것이지만, 중대한 문제가 발생하는데 이 문제와 해결 방법인 복사 생성자에 대해서는 나중에 알아보자.

 

주소에 의한 호출로 객체 전달
void increase(Circle *p) {
	int r = p->getRadius();
	p->setRadius(r + 1);
}


int main() {
	Circle waffle(30);
	increase(&waffle);
	cout << waffle.getRadius() << endl;
}
  • waffle 객체의 주소가 포인터 p에 전달
  • p는 객체가 아니므로 생성자나 소멸자와 상관이 없다.
  • 원본 객체를 복사하는 시간 소모가 없으며, 매개 변수가 단순 포인터이므로, '값에 의한 호출' 시에 발생하는 생성자 소멸자의 비대칭 문제도 없다.
  • 하지만, 의도하지 않게 원본 객체를 훼손할 가능성이 있기 때문에 주의해야 한다.

객체 치환 및 객체 리턴

  • 객체 치환 시 객체의 모든 데이터가 비트 단위로 복사된다.
Circle c1(5);
Circle c2(30);
c1 = c2; // c2 객체를 c1 객체에 비트 단위로 복사한다.
  • c1과 c2의 내용이 같아 졌지만, 하나의 객체가 되는 것은 아니다. c1과 c2는 별개이며 내용물만 같고, 객체 치환(assignment)는 동일한 클래스 타입에 대해서만 적용된다.
  • 아래는 객체를 리턴하는 예시이다.
#include <iostream>
using namespace std;

class Circle {
	int radius;
public:
	Circle() { radius = 1; }
	Circle(int radius) { this->radius = radius; }
	void setRadius(int radius) { this->radius = radius; }
	double getArea() { return 3.14*radius*radius; }
};

Circle getCircle() {
	Circle tmp(30);
	return tmp; // 객체 tmp을 리턴한다.
}

int main() {
	Circle c; // 객체가 생성된다. radius=1로 초기화된다.
	cout << c.getArea() << endl;

	c = getCircle();
	cout << c.getArea() << endl;
}

 


참조와 함수

C++에서는 C언어에 없는 참조(reference) 개념을 도입하였다. 포인터 변수를 사용하기 위해서는 *를 사용하지만, 참조 변수를 사용하기 위해서는 & 기호를 사용한다.

  • 참조 변수는 이미 선언된 변수에 대한 별명(alias)이다.
  • 참조는 참조 변수, 참조에 의한 호출, 함수의 참조 리턴에서 활용된다.

 

참조 변수
  • &를 이용하여 선언
  • 선언 시 반드시 원본 변수로 초기화하여야 한다.
int n = 2;
int& refn = n; // 참조 변수 refn 선언. refn은 n에 대한 별명. refn과 n은 동일한 변수

Circle circle;
Circle& refc = circle; // 참조 변수 refc 선언. refc는 circle에 대한 별명. refc와 circle은 동일한

 

  • refn과 refc는 따로 변수 공간을 가지지 않고, 각각 n과 circle을 공유
  • 참조 변수가 선언되면, 이름만 생성되는 대신, 원본 변수의 공간을 공유

 

사용
  • 보통 변수와 동일하게 사용하면 된다.
refn = 3;
n = 5; // n = 5, refn = 5가 됨
refn++; // n=6, refn = 6이 됨

 

  • 포인터가 아니므로 아래와 같이 사용하면 안되며, 참조 변수에 대한 포인터를 만들 수 있다.
refc->setRadius(30); // 컴파일 오류. refc.setRadius(30)으로 해야됨

int *p = &refn; // p는 refn의 주소를 가짐. p는 n의 주소
*p = 20; // n=20, refn=20

 

주의 사항

 

참조 변수 선언시 몇 가지 주의할 점이있다.

  • 초기화가 없다면 컴파일 오류가 발생한다.
int n=2;
int &ref2; // 컴파일 오류
  • 참조자 &의 위치에 무관하다.
int &refn = n;
int & refn = n;
int& refn = n;
  • 참조 변수의 배열을 만들 수 없다.
char &n[10]; // 컴파일 오류. 참조의 배열을 만들 수 없다.
  • 참조 변수에 대한 참조 선언이 가능하다.
    • r과 refn은 모두 n의 공간을 공유하며 구분 없이 사용 가능하다.
int &r = refn; // 참조변수 refn에 대한 참조 변수 r 선언 가능
728x90