1. C++ & C 스타일 초기화
int a=10;
int b=20;
int a(10);
int b(20);
C++은 저런 두가지 형태의 초기화 방법을 제공하고 있다.
위 와 아래는 완전히 똑같은 코드이다.
#include <iostream>
using std::cout;
using std::endl;
class AAA
{
int val;
public:
AAA(int n){
val=n;
}
void ShowData(){
cout<<val<<endl;
}
};
int main()
{
AAA a1(10);
a1.ShowData();
AAA a2=20; // 객체생성시도 적용이된다 그러나..밑의 설명으로...
a2.ShowData();
return 0;
}
그러나 객체생성시에는 AAA a2=20;은 묵시적인 변환이 일어난다 AAA a2(20); 으로..
2. 복사생성자의 형태
#include <iostream>
using std::cout;
using std::endl;
class AAA{
public:
AAA(){
cout<<"AAA()호출"<<endl;
}
AAA(int i){
cout<<"AAA(int i)호출"<<endl;
}
AAA(const AAA& a){
cout<<"AAA(const AAA& a)호출"<<endl;
}
};
int main()
{
AAA obj1;
AAA obj2(10);
AAA obj3(obj2);
return 0;
}
세번째의 생성자의 형태를 가리켜 복사 생성자라고 한다. 메인 함수에서 obj3객체를
만들면서 obj2 객체를 인자로 전달하고 있다. 그렇기 때문에 객체를 받을수 있는 생성자가
필요하다. 그것이 바로 복사생성자인 AAA(const AAA& a) 이다.
레퍼런스로 접근 하기때문에 const키워드를 붙여주지 않으면 내용변경이 가능하기때문에
상수화(const)를 시켜준다.
3. 디폴트 복사 생성자
#include <iostream>
using std::cout;
using std::endl;
class Point{
int x,y;
public:
Point(int _x,int _y)
{
x=_x;
y=_y;
}
void ShowData(){
cout<<x<<' '<<y<<endl;
}
};
int main()
{
Point p1(10,20);
Point p2(p1);
p1.ShowData();
p2.ShowData();
return 0;
}
메인 함수의 두번째 p2객체를 생성하면서 p1객체를 인자로 전달하고 있다.
그러나 Point 클래스에서는 객체p1을 받을수 있는 생성자가 정의 되어있지 않다..
결과는 제대로 나온다..
이게 무슨 조화인가? 그렇다..저렇다는 얘기는 p1을 객체로 받아줄수 있는 생성자가
있다는 소리다.. 그것이 바로 디폴트 복사생성자 이다.
즉....
#include <iostream>
using std::cout;
using std::endl;
class Point{
int x,y;
public:
Point(int _x,int _y){
x=_x;
y=_y;
}
Point(const Point& p1){
x=p1.x;
y=p1.y;
}
void ShowData(){
cout<<x<<' '<<y<<endl;
}
};
int main()
{
Point p1(10,20);
Point p2(p1);
p1.ShowData();
p2.ShowData();
return 0;
}
파란색 글씨 형태의 생성자가 하나가 추가되었단 소리다..저것은 디폴트 복사생성자와
같은 형태이다.저렇게 써주지 않아도 디폴트로 저런 형태의 복사생성자가 삽입이된다.
4. 깊은 복사
우선 아주 긴 예문 하나를 보자..
#include <iostream>
using std::cout;
using std::endl;
class Person
{
char* name;
char* phone;
int age;
public:
Person(char* _name, char* _phone, int _age);
~Person();
void ShowData();
};
Person::Person(char* _name, char* _phone, int _age)
{
name=new char[strlen(_name)+1];
strcpy(name, _name);
phone=new char[strlen(_phone)+1];
strcpy(phone,_phone);
age=_age;
}
Person::~Person()
{
delete []name;
delete []phone;
}
void Person::ShowData()
{
cout<<"name : "<<name<<endl;
cout<<"phone: "<<phone<<endl;
cout<<"age : "<<age<<endl;
}
int main()
{
Person p1("jslee","010-1010-1010",25);
Person p2=p1;
return 0;
}
너무길다..ㅠㅠ 여기서 하고픈 이야기는 뭘까?까먹었어 ㅅㅂ..
메인함수에서 p1이라는 객체를 생성함과 동시에 초기화 시켜주고 있고..
p2라는 객체를 만들면서 p1객체를 인자로 넘겨주고 있다.
p1을 받을수 있는 생성자가 없다고 생각하는가?
조기 위에서 배운 디폴트 복사생성자가 호출된다.. ㅋㅋ바보 ㅋㅋㅋㅋㅋㅋㅋㅋㅋ
그럼.생각해봅시다..
p1이나 p2나 두개다 heap에 올라가있는 데이터를 가리키는 포인터가 있다..그런데 p1의 포인터 값. 즉p1이 가리키는 데이터의 주소값을 고대로~~p2로 복사했으니까..
p1이나 p2나 가리키는 대상은 같을것이라고..맞지? 그림 하나 보자
자..우리는 여기서 스택의 구조를 다시한번 상기해야될듯 하다 ㅋ
스택은 First in Last out. Last in First out. 똑같은 말인데 어떤걸쓰야되나??후입선출 이래.
암튼 저래..먼저 들어간건 나중에 나오고 나중에 들어간게 젤 먼저 나와. ㅋㅋ
그럼..위에서 보면 p2가 나중에 생겼으니까..먼저 사라질꺼라고..근데 저기 그림봐봐.
p2도 p1에서 동적메모리 할당해서 생성된 데이터를 가리키고 있다고..
p2객체가 소멸하면서 소멸자를 부른다? 그럼 소멸자에선 저기 heap의 메모리를 반환해..
delete 키워드로 반환했으니까..자.. 그럼p1은 도대체 누굴 가리켜야 하지? 미쳤군 ㅋㅋ
이게 문제야..디폴트 복사생성자는.. 그럼 어떻게 해줘야 저 문제를 해결할수 있을까?
또 그림하나 보자..
저딴식으로 해주면 문제가 없겠지? 그럼 어떻게 해야되? 그렇지 복사생성자를 하나 정의해.
ㅇㅋㅂㄹ??
그럼 자 간다. 두눈 똑바로 뜨고 쳐다봐라 ㅋㅋ
Person::Person(const Person& p)
{
name=new char[strlen(p.name)+1];
strcpy(name,p.name);
phone=new char[strlen(p.phone)+1];
strcpy(phone,p.phone);
age=p.age;
}
이딴식으로 복사생성자의 정의를 해주면되..자 그럼 p2도 복사생성자를 호출하면서
heap새로운 영역을 할당하고 그놈을 가리키겠지..
머리 잘굴려라 헷갈릴라..내가 볼거 내가 쓰면서 내가 헷갈리네 ㅋㅋ아놔
이게 바로 DEEP COPY 깊은 복사다..아까 그건 얕은 복사~~
Do u Understand? ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
아...전반적인건 마무리 됐다..여기까지만 이해하고 있어도 된데. 우리 윤성우 사부님이.
자..이제 좀 빡신거 들어가보자!
5. 복사생성자가 호출되는 시점
복사생성자가 호출되는 시점은 세가지의 형태로 분류할수 있다.
저기 위에처럼 기존에 생성된 객체로 새로운 객체를 초기화 하는 경우.
두번째로 함수 호출시 객체를 값에 의해 전달 하는 경우
세번째는 함수 내에서 객체를 값에 의해 리턴 하는 경우..
아..저거만 봐도 오바이트 쏠려 ㅅㅂ..ㅋㅋㅋㅋ
첫번째 경우는 안써놔도 알겠고...그럼 두번째 부터 가자..자 긴장해라..
일단 함수호출과정을 세분화 시켜보재..책에서...나도 해야지..나중에까먹을라.
함수도 호출이 되면서 매개변수로 전달되는 인자값의 메모리 공간을 할당을 하겠고.
그다음에 전달된 인자를 복사하겠지..call-by-value의경우에 말이야..
자..그럼 들어간다 두번째 함수 호출시 객체를 값에 의해 전달 하는 경우
#include <iostream>
using std::cout;
using std::endl;
class AAA{
int val;
public:
AAA(int i){
val=i;
}
AAA(const AAA& a){
cout<<"AAA(const AAA& a)호출"<<endl;
val=a.val;
}
void ShowData(){
cout<<"val : "<<val<<endl;
}
};
void function(AAA a)
{
a.ShowData();
}
int main()
{
AAA obj(30);
function(obj);
return 0;
}
메인 함수에서 function함수 호출하면서 만들어진 obj객체를 인자로 전달했어..
그럼 전역함수 function에서 그 함수를 a로 받어..저건 call-by-value야..
그럼. function함수 내에 객체 obj와 똑같은 메모리가 할당이 되었을껄?그지..
자 그럼 객체 생성과정에선 1)메모리할당 ,2)생성자의호출 이되어야 비로소 객체라는 이름을 가질수 있다고 배웠다 기억안나?ㅅㅂ.. 아무튼 메모리는 할당되었는데..생성자를 호출해야되..어떻게해?그냥 function함수에 단순히 객체만 복사되어 전달이 되었는데 말여..
객체obj를 복사하는 과정에서 단순히 값만 복사하는게 아니고 복사생성자의 호출을 통해
이 과정을 처리한데...자 그럼 먼 소리냐? 일단 메모리 공간을 할당받았으니까 저것들이 들어있을거란 말야..변수의 값은 아직몰라..근데 거기서 복사생성자를 호출하면서~!
복사의 대상이 되는 객체obj를 인자로 전달해...아 먼소리야 젠장..그림 하나 보고 넘어가.
저렇게 된다고..아 골치아프다..좀 생각더해보자..
자 그다음에..세번째 함수 내에서 객체를 값에 의해 리턴 하는경우.
리턴되는 값은 받아주는 변수가 없더라도 함수를 호출한 영역으로 복사되어넘어간다.
뜬금없이 뭔소리..책(203쪽)참고해라..
#include <iostream>
using std::cout;
using std::endl;
class AAA{
int val;
public:
AAA(int i){
val=i;
}
AAA(const AAA& a){
cout<<"AAA(const AAA& a)호출"<<endl;
val=a.val;
}
void ShowData(){
cout<<"val : "<<val<<endl;
}
};
AAA function(void)
{
AAA a(10);
return a;
}
int main()
{
function(); //.ShowData();
return 0;
}
음..자..AAA클래스형 전역함수 function을 보자..메인함수에서 함수를 호출한다.
그럼 function에서 a객체를 생성하면서 10으로 초기화해주고 그 값을 복사해서
리턴해주겠지..복사해서 리턴해준데..그럼 복사본이 어떻게 객체냐?
메모리 공간은 할당받았을테고..생성자호출은??
복사본이 리턴이 되면서 복사생성자를 호출해서 함수function에서 생성된 객체의 값을
인자로 전달받지.아..ㅠㅠ복잡하다..결국 2번째와 3번째 케이스는 비슷한 케이스라고..
할수가 있지 마지막 그림 하나 더 보자..
자 여기까지 5장 마무리..좀더 고민해보자..흠..
이거쓰는데만 1시간걸렸다 ㅠㅠㅠㅠ