출처 :
- 명품 C++ Programming (저자 황기태)
- 객체지향프로그래밍
- 노션 공유를 원하시는 분은 댓글에 이메일 남겨주세요
함수의 인자 전달 방식
- 값에 의한 호출 call by value
- 주소에 의한 호출 call by address
- 참조에 의한 호출 call by reference
값에 의한 호출 call by value
- 함수가 호출되면 매개 변수가 stack에 생성됨
- 호출하는 코드에서 값을 넘겨줌
- 호출하는 코드에서 넘어온 값이 매개 변수에 복사됨
| #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); |
| } |
값에 의한 호출로 객체 전달
- 함수를 호출하는 쪽에서 객체 전달 - 객체 이름만 사용
- 함수의 매개 변수 객체 생성
- 매개 변수 객체의 공간이 stack에 할당
- 호출하는 쪽의 객체가 매개 변수 객체에 그대로 복사됨
- 매개 변수 객체의 생성자는 호출되지 않음 → 호출되는 순간의 실인자 객체 상태를 매개 변수 객체에 그대~로 복사하여 전달하기 위해서
| int main() { |
| |
| Circle waffle(30); |
| |
| increase(waffle); |
- 함수 종료
- 매개 변수 객체의 소멸자 호출
- 매개 변수 객체의 생성자, 소멸자의 비대칭 실행 구조
| void increase(Circle c) { |
| int r = c.getRadius(); |
| c.setRadius(r + 1); |
| } |
| |
| int main() { |
| |
| Circle waffle(30); |
| |
| increase(waffle); |
| cout << waffle.getRadius() << endl; |
| |
| } |
값에 의한 호출의 비대칭 구조 정리!
- increase ()의 매개 변수 c 가 생성될 때 생성자 실행 없이 c의 객체 공간에 waffle 객체가 그대로 복사된다.
- 하지만 increase()가 종료될 때 객체 c의 소멸자는 실행된다.
- 왜? 만일 c의 생성자 Circle()이 실행된다면, 객체 c의 반지름이 1로 초기화되어, 전달 받은 원본의 상태를 잃어버린다.
주소에 의한 호출 call by address
- 함수가 호출되면 “포인터 타입”의 매개 변수가 stack에 생성됨
- 호출하는 코드에서는 명시적으로 주소를 넘겨줌
- 변수나 객체의 경우 주소 전달, 배열은 배열의 이름
- 호출하는 코드에서 넘어온 주소 값이 매개 변수에 저장됨
| #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); |
| } |
주소에 의한 호출로 객체 전달
- 함수 호출시 객체 주소만 전달
- 함수의 매개 변수는 객체에 대한 포인터 변수로 선언
- 함수 호출시 생성자, 소멸자가 실행되지 않는 구조
- 왜? 포인터는 객체가 아니므로 생성자나 소멸자와 상관없다.
| void increase(Circle *p) { |
| int r = p->getRadius(); |
| p->setRadius(r + 1); |
| } |
| |
| int main() { |
| Circle waffle(30); |
| increase(&waffle); |
| cout << waffle.getRadius(); |
| } |
객체 치환 및 객체 리턴
객체 치환
- 동일한 클래스 타입의 객체까지 치환 가능
- 객체의 모든 데이터가 비트 단위로 복사
- 치환된 두 객체는 복사 당시 내용물만 같을 뿐 독립적인 공간 유지
| Circle c1(5); |
| Circle c2(30); |
| c1 = c2; |
객체 리턴
| Circle getCircle() { |
| Circle tmp(30); |
| return tmp; |
| } |
| |
| Circle c; |
| c = getCircle(); // tmp 객체의 복사본이 c에 치환 - c의 반지름 30으로 |
참조
- reference 가리킨다는 뜻으로 이미 존재하는 객체나 변수에 대한 별명
- 참조 변수, 참조에 의한 호출, 참조 리턴
참조 변수
참조 변수 선언
- 참조자 & 기호 사용
- 이미 존재하는 변수에 대한 다른 이름(별명)을 선언
- 참조 변수는 이름만 생기고 새로운 공간을 “할당하지 않는다.”
- 초기화로 지정된 기본 변수를 공유한다.
참조 변수 선언 시 주의!
- 반드시 원본 변수로 초기화해야 한다.
- 초기화가 없다면 컴파일 오류가 발생한다. int &refn;
- 참조 변수의 배열은 만들 수 없다. char &n[10];
- 참조 변수에 대한 참조 선언은 가능하다.
| Circle circle; |
| Circle &refc = circle; |
| |
| refc.setRadius(30); |
예제 - 기본 타입 변수에 대한 참조
| #include <iostream> |
| using namespace std; |
| |
| int main() { |
| int i = 1; |
| int n = 2; |
| int &refn = n; |
| n = 4; |
| refn++; |
| |
| refn = i; |
| refn++; |
| |
| int * p = &refn; |
| *p = 20; |
| } |
예제 - 객체에 대한 참조
| #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; } |
| }; |
| |
| int main() { |
| Circle circle; |
| Circle &refc = circle; |
| refc.setRadius(10); |
| |
| } |
참조에 의한 호출 call by reference
call by address와 다르다!!!
- 참조를 가장 많이 활용하는 사례
- 함수의 매개 변수를 참조 타입으로 선언
- 참조 매개 변수 reference parameter
- 참조 매개 변수의 이름만 생기고 공간이 생기지 않음
- 참조 매개 변수는 실인자 변수 공간 공유
- 참조 매개 변수에 대한 조작은 실인자 변수 조작 효과
*참조는 그냥 별명, 이름
*포인터와 달리 swap() 스택에 공간을 할당받지 않는게 키포인트!
*함수 내에서 사용할 때는 일반 변수처럼 사용한다.
| void swap(int &a, int &b) { |
| int tmp; |
| |
| tmp = a; |
| a = b; |
| b = tmp; |
| } |
주의!
- 참조 매개 변수로 이루어진 모~든 연산은 원본 객체에 대한 연산이 된다.
- 이름만 생성되므로, 생성자와 소멸자는 아예 실행되지 않는다.
참조 매개변수가 필요한 사례
참조 매개 변수로 평균 리턴하기 (중요 문제!!)
| bool average(int a[], int size, int& 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 << "\\n"; |
| else |
| cout << "매개 변수 오류"; |
참조에 의한 호출로 Circle 객체에 참조 전달
| void increaseCircle(Cirel &c) { |
| int r = c.getRadius(); |
| c.setRadius(r + 1); |
| } |
| |
| increaseCircle(waffle); |
참조 리턴
- C++의 함수 리턴
- 함수는 값 외에 참조 리턴 가능
- 변수 등과 같이 현존하는 공간에 대한 참조 리턴
- 변수의 값을 리턴하는 것이 아님
- 참조는 공간이 없음
| |
| char c = 'a'; |
| |
| char get() { |
| return c; |
| } |
| |
| char a = get(); |
| get() = 'b' |
| |
| |
| char c = 'a'; |
| |
| char& find() { |
| return c; |
| } |
| |
| char &ref = find(); |
| ref = 'M'; |
| find() = 'b'; |
| char& find(char s[], int index) { |
| return s[index]; |
| } |
| |
| find(name, 0) = 'S'; |
| |
| char& ref = find(name, 2); |
| ref = 't'; |
교재 문제 245p - 3
| int ar[] = {0,1,3,5,7}; |
| int& f(int n) { |
| return ar[n]; |
| } |
| |
| (1) f(0) = 100; |
| (2) f(0) = f(1) + f(2) + f(3) + f(4); |
| (3) int& v = f(2); v++ |
얕은 복사와 깊은 복사
얕은 복사 shallow copy
- 객체 복사 시, 객체의 멤버를 1:1로 복사
- 객체의 멤버 변수에 동적 메모리가 할당된 경우
- 사본은 원본 객체가 할당 받은 메모리를 공유하는 문제 발생
깊은 복사 deep copy
- 객체 복사 시, 객체의 멤버를 1:1로 복사
- 객체의 멤버 변수에 동적 메모리가 할당된 경우
- 사본은 원본이 가진 메모리 크기만큼 별도로 동적 할당
- 원본의 동적 메모리에 있는 내용을 사본에 복사
- 완전한 형태의 복사
복사 생성자 copy constructor (4/8 강의 참고)
- 객체의 복사 생성시 호출되는 특별한 생성자
- 한 클래스에 오직 한 개만 선언 가능
- 보통 생성자와 클래스 내에 중복 선언 가능
- 클래스에 대한 참조 매개 변수를 가지는 독특한 생성자
| class Circle { |
| Circle(const Circle& c); |
| } |
| |
| Circle::Circle(const Circle& c) { |
| this->radius = c.radius; |
| } |
디폴트 복사 생성자
- 복사 생성자가 선언되어 있지 않는 클래스
- 컴파일러는 자동으로 디폴트 복사 생성자 삽입
- 동적으로 할당된 메모리 주소만 복사된다. → 얕은 복사 실행
| |
| class Book { |
| double price; |
| char *title; |
| public: |
| Book(double pr, char* t); |
| ~Book(); |
| }; |
| |
| |
| Book(const Book& book) { |
| this->price = book.price; |
| this->title = book.title; |
| } |
얕은 복사 생성자를 사용하여 프로그램이 비정상 종료되는 경우
| class Person { |
| char* name; |
| int id; |
| public: |
| Person(int id, const char* name); |
| ~Person(); |
| |
| |
| |
| |
| |
| void changeName(const char *name); |
| }; |
| |
| Person::Person(int id, const char* name) { |
| this->id = id; |
| int len = strlen(name); |
| this->name = new char [len+1]; |
| strcpy(this->name, name); |
| } |
| |
| Person::~Person() { |
| if (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"); |
| Person daughter(father); |
| |
| dauther.changeName("Grace"); |
| |
| return 0; |
| } |
깊은 복사 생성자를 가진 정상적인 Person 클래스
| class Person { |
| char* name; |
| int id; |
| public: |
| Person(int id, const char* name); |
| ~Person(); |
| Person(const Person& person) |
| void changeName(const char *name); |
| }; |
| |
| Person::Person(const Person& person) { |
| this->id = person.id; |
| int len = strlen(person.name); |
| this->name = new char [len+1]; |
| strcpy(this->name, person.name); |
| } |
| |
| int main() { |
| Person father(1, "Kitae"); |
| Person daugther(father); |
| |
| daughter.changeName("Grace"); |
| |
| return 0; |
| } |
묵시적 복사 생성에 의해 복사 생성자가 자동 호출되는 경우