함수와 참조, 복사 생성자
함수의 인자 전달 방식 리뷰
인자 전달 방식
•
값에 의한 호출, call by value
◦
함수가 호출되면 매개변수가 스택에 생성된다
◦
호출하는 코드에서 값을 넘겨준다
◦
호출하는 코드에서 넘어온 값이 매개변수에 복사된다
int add(int a, int b) {
return a * b;
}
add(100, 200);
C++
복사
•
주소에 의한 호출, call by address
◦
함수의 매개변수는 포인터 타입이다
▪
함수가 호출되면 포인터 타입의 매개변수가 스택에 생성된다
◦
호출하는 코드에서는 명시적으로 주소를 넘겨준다
▪
기본 타입 변수나 객체의 경우, 주소를 전달한다
▪
배열의 경우, 배열의 이름
int add(int *a, int *b) {
return a * b;
}
add(100, 200);
C++
복사
◦
호출하는 코드에서 넘어온 주소 값이 매개변수에 저장된다
예제)
1.
값에 의한 호출
#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;
}
C++
복사
2.
주소에 의한 호출
#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;
}
C++
복사
‘값에 의한 호출' 로 객체 전달
•
함수를 호출하는 쪽에서 객체를 전달
◦
객체 이름만 사용
•
함수의 매개변수 객체 생성
◦
매개변수 객체의 공간이 스택에 할당
◦
호출하는 쪽의 객체가 매개변수 객체에 그대로 복사된다
◦
매개변수 객체의 생성사는 호출되지 않는다
•
함수 종료
◦
매개변수 객체의 소멸자를 호출한다
•
값에 의한 호출 시 매개변수 객체의 생성자가 실행되지 않는 이유?
→ 호출되는 순가의 실인자 객체 상태를 매개변수 객체에 그대로 전달하기 위함
#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;
cout << "생성자 실행 radius = " << radius << endl;
}
Circle::Circle(int radius) {
this->radius = radius;
cout << "생성자 실행 radius = " << radius << endl;
}
Circle::~Circle() {
cout << "소멸자 실행 radius = " << radius << endl;
}
void increase(Circle c) {
int r = c.getRadius();
c.setRadius(r + 1);
}
int main() {
Circle waffle(30);
increase(waffle);
cout << waffle.getRadius() << endl;
}
C++
복사
함수에 객체 전달 - ‘주소에 의한 호출'로
•
함수 호출시 객체의 주소만 전달
◦
함수의 매개변수는 객체에 대한 포인터 변수로 선언
◦
함수 호출 시 생성자 소멸자가 실행되지 않는 구조이다
객체 치환 및 객체 리턴
•
객체 치환
◦
동일한 클래스 타입의 객체끼리 치환이 가능하다
◦
객체의 모든 데이터가 비트 단위로 복사
Circle c1(5);
Circle c2(30);
c1 = c2; //c2 객체를 c1 객체에 비트 단위 복사, c1의 반지름 30 됨
C++
복사
◦
치환된 두 객체는 현재 내용물만 같을 뿐 독립적인 공간 유지한다
•
객체 리턴
◦
객체의 복사본 리턴
Circle getCircle() {
Circle tmp(30);
return tmp; //객체 tmp 리턴
}
Circle c; //c의 반지름 1
c = getCircle(); //tmp 객체의 복사본이 c에 치환, c의 반지름은 30이 됨
C++
복사
#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;
cout << c.getArea() << endl;
c = getCircle();
cout << c.getArea() << endl;
}
C++
복사
참조 (reference)
참조란 가리킨다는 뜻으로, 이미 존재하는 객체나 변수에 대한 별명
참조활용
•
참조 변수
•
참조에 의한 호출
•
참조 리턴
참조 변수
•
참조 변수 선언
◦
참조자 &의 도입
◦
이미 존재하는 변수에 대한 다른 이름(별명)을 선언
▪
참조 변수는 이름만 생기며
▪
참조 변수에 새로운 공간을 할당하지 않는다
▪
초기화로 지정된 기존 변수를 고유한다
int n = 2;
int &refn = n;
refn = 3;
C++
복사
Circle circle;
Circle &refc = circle;
refc.setRadius(40);
C++
복사
refc → setRadius(30); 으로 하면 안된다
#include <iostream>
using namespace std;
int main() {
cout << "i" << '\t' << "n" << '\t' << "refn" << endl;
int i = 1;
int n = 2;
int &refn = n; //참조변수 refn 선언, refn은 n에 대한 별명
n = 4;
refn++;
cout << i << '\t' << n << '\t' << refn << endl;
refn = i; //refn = 1, n = 1
refn++; //refn = 2, n = 2
cout << i << '\t' << n << '\t' << refn << endl;
int *p = &refn; // p는 n의 주소를 가짐
*p = 20; // refn=20, n=20
cout << i << '\t' << n << '\t' << refn << endl;
}
C++
복사
참조에 의한 호출
•
참조를 가장 많이 활용하는 사례
•
call by reference 라고 부름
•
함수형식
◦
함수의 매개변수를 참조 타입으로 선언
▪
참조 매개변수 (reference parameter) 라고 부른다
•
참조 매개변수는 실인자 변수를 참조한다
▪
참조 매개변수의 이름만 생기고 공간이 생기지 않음
▪
참조 매개변수는 실인자 변수 공간을 공유한다
▪
참조 배개변수에 대한 조작은 실인자 변수 조작 효과
#include <iostream>
using namespace std;
void swap(int &a, int &b) { //참조 매개변수 a, b
int tmp;
tmp = a;
a = b; // 참조 매개변수를 보통 변수처럼 사용
b = tmp;
}
int main() {
int m = 2, n = 9;
swap(m, n);
// 함수가 호출되면 m, n에 대한 참조변수 a, b 가 생긴다
cout << m << "" << n;
}
C++
복사
참조 매개변수가 필요한 사례
#include <iostream>
using namespace std;
bool average(int a[], int size, int& avg) {
// 참조 매개 변수 avg에 평균값 전달
if (size <= 0)
return false;
int sum = 0;
for (int i = 0; i < size; i++)
sum += a[i];
avg = sum / size;
return true;
}
int main() {
int x[] = {0,1,2,3,4,5};
int avg;
if (average(x, 6, avg))
cout << "평균은 "<< avg <<endl;
else
cout << "매개변수 오류" << endl;
if (average(x, -2, avg))
cout << "평균은 " << avg << endl;
else
cout << "매개변수 오류 " << endl;
}
C++
복사
예제)
#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;
cout << "생성자 실행 radius = " << radius << endl;
}
Circle::Circle(int radius) {
this->radius = radius;
cout << "생성자 실행 radius = " << radius << endl;
}
Circle::~Circle() {
cout << "소멸자 실행 radius = " << radius << endl;
}
void increaseCircle(Circle &c) {
int r = c.getRadius();
c.setRadius(r + 1);
}
int main() {
Circle waffle(30);
increaseCircle(waffle);
cout << waffle.getRadius() << endl;
}
C++
복사
참조 리턴
•
C 언어의 함수 리턴
◦
함수는 반드시 값만 리턴
▪
기본 타입 값 : int, char, double 등
▪
포인터 값
•
C++ 의 함수 리턴
◦
함수는 값 외에 참조 리턴 가능
◦
참조 리턴
▪
변수 등과 같이 현존하는 공간에 대한 참조 리턴
•
변수의 값을 리턴하는 것이 아니다
#include <iostream>
using namespace std;
char& find(char s[], int index) {
return s[index]; // s[index] 공간의 참조 리턴
}
int main() {
char name[] = "Mike";
cout << name << endl;
find(name, 0) = 'S'; //name[0] = "S" 로 변경
// find()가 리턴한 위치에 문자 'm' 저장
cout << name << endl;
char& ref = find(name, 2); //ref는 name[2] 참조
ref = 't';
cout << name << endl;
}
// 결과
//Mike
//Sike
//Site
C++
복사
C++ 에서 얕은 복사와 깊은 복사
얕은 복사 (shallow copy)
◦
객체 복사 시, 객체의 멤버를 1:1로 복사
◦
객체의 멤버 변수에 동적 메모리가 할당된 경우
▪
사본은 원본 객체가 할당 받은 메모리를 공유하는 문제 발생
깊은 복사 (deep copy)
◦
객체 복사 시, 객체의 멤버를 1:1로 복사
◦
객체의 멤버 변수에 동적 메모리가 할당된 경우
▪
사본은 원본이 가진 메모리 크기 만큼 별도로 동적 할당
▪
원본의 동적 메모리에 있는 내용을 사본에 복사
◦
완전한 형태의 복사
▪
사본과 원본은 메모리를 공유하는 문제 없음
복사 생성자
•
복사 생성자 (copy constructor) 란?
◦
객체의 복사 생성시 호출되는 특별한 생성자
•
특징
◦
한 클래스에 오직 한 개만 선언 가능
◦
복사 생성자는 보통 생성자와 클래스 내에 중복 선언 가능
◦
모양
▪
클래스에 대한 참조 매개변수를 가지는 톡특한 생성자
•
복사 생성자 선언
복사 생성 과정
디폴트 복사 생성자
•
복사 생성자가 선언되어 있지 않는 클래스
◦
컴파일러는 자동으로 디폴트 복사 생성자 삽입
예)
예제) 얕은 복사 생성자를 사용하여 프로그램이 비정상 종료되는 경우
#define _CRT_SECURE_NO_WARNINGS
#include <cstring>
#include <iostream>
using namespace std;
class Person {
char *name;
int id;
public:
Person(int id, const char *name); // 생성자
~Person();
void changeName(const char *name);
void show() { cout << id << ',' << name << endl; }
};
Person::Person(int id, const char *namee) { // 생성자
this->id = id;
int len = strlen(name); // name의 문자 개수
this->name = new char[len + 1]; // name 문자열 공간 할당
strcpy(this->name, name); // name에 문자열 복사
}
Person::~Person() {
if (name) // 만약 name에 동적 할당된 배열이 있으면
delete[] name;
}
void Person::changeName(const char *name) { // 이름 변경
if (strlen(name) > strlen(this->name))
return;
strcpy(this->name, name);
}
int main() {
Person father(1, "Kitae"); // father 객체 생성
Person daughter(father); // daughter 객체 복사 생성, 복사 생성사 호출
// 컴파일러가 삽입한 디폴트 복사 생성자 호출
cout << "daughter 객체 생성 직후 ---" << endl;
father.show(); // father 객체 출력
daughter.show(); // daughter 객체 출력
daughter.changeName("Grace"); // daughter 의 이름ㅇ르 grace 로 변경
cout << "daughter 이름을 Grace로 변경한 후 ---" << endl;
father.show();
daughter.show();
return 0;
}
C++
복사
ㅇ