열혈 강의 10장

static복습하고 오기. 전역함수로 선언(friend 선언 이해하기)


왜 연산자 오버로딩인가

함수 오버로딩이란 이르이 같아도 전달 인자에 따라 호출되는 함수가 달라지는 것

int a = 1+2;             --> 정수형 변수

Point p3 = p1+p2;  --> 객체

--> 같은 +인데도 피연산자의 종류에 따라서 다른 +기능을 한다.


1. 객체를 리턴하는 함수

1. AAA& GetName(){ return a; }

2.AAA GetName() { return a; }

--> 차이가 뭐지


class Point {

private:

int x, y;


public:

Point(int _x=0, int _y=0) : x (_x), y(_y) P{}

void show();

Point& operator++();

friend Point& operato--r(Point & _p);

}


void Point::show() {

cout <<x<<"  "<<y<<endl;

}


Point& Point::operator++() {  --> 왜 레퍼런스로 리턴을 받는가? 

x++;

y++;

return *this;  --> this는 포인터 *가 붙었기 때문에 자기자신을 리턴

}


Point& operator--(Point& _p){  --> 왜 참조로 리턴을 하는가?

_p.x--;

_p.y--;

return _p;

}


int main(){

Point p(1,2);

++p;

p.show();   ///2,3


--p

p.show();    ///1,2


++(++p);    ///3,4

p.show();


--(--p);

p.show();    ///1,2

}

----------------------------------------------------------------------------------문제 제기


Point Point::operator++(){

x++;

y++;

return *this;

}


--> 참조로 리턴을 받지 않는다면?? --> 나 자신을 복사해서 리턴을 한다.  --> 리턴과 동시에 새로운 객체를 만들게 된다. (복사본)

--> ++(++p)  --> 밑줄 친 부분이 p자기 자신이 아니라 복사본이 오게 된다.

--> 그 결과 p.show()에는 2,3 즉, p는 ++연산을 한번만 하게 되고, 괄호 밖의 ++연산은 p의 복사본에 하게된다. 

--> 복사생성자 호출조건 case3 참고


--> 조로 리턴을 받는다면?? --> ++(++p) --> 밑줄 친 부분에 복사본이 아닌 p의 레퍼런스가 위치하게 된다.

--> 그 결과 p에 ++연산이 두번 실행된다.


--> 함수를 void로 선언하지않고 Point&로 선언하는 이유이다.


check!! 

--> p3    =  p1   +   p1;

      p3    =  리턴된 객체  ---> 복사생성자에 의해 멤버 대 멤버 복사가 일어난다.


2. 단항 연산자 오버로딩 전위 후위


++p --> p.operator++();

p++ --> p.operator++(int);   --> int는 단순히 구분하기 위한 용도


Point operator++(int){

Point temp(x,y);            --> 증가시키기 전의 값을 저장

x++;

y++;

return temp;

}                           --> 참조를 리턴하지 않는 이유 --> 지역객체이기 때문에 레퍼런스로 리턴할 수 없다.


int main(){

Point p(1,2);

(p++).show();    --> temp를 리턴받았기 때문에 1 2 출력

p.show();           --> 증가된 x, y

}

--> 참조가 불가능 하기 때문에 (p++)++ 는 불가능



3. 교환 법칙


Point p(1,2);

p+3; ---> 가능

3+p; ---> 불가능

가능하게 하려면?


class Point{

private:

int x,y;

public:

Point operator+(int val);

friend Point operator+(int val, Point& p);   ---> friend는 연산자 오버로딩 외에는 사용하지 말자.

}


Point::Point operator+(int val){

Point temp(x+val, y+val);

return temp;

}


Point operator(int val, Point& p){                        Point operator(int val, Point& p){

Point temp(p.x+val, p.y+val);        ---->          return p+val;                                   --> 바꾸어주는 기능으로도 구현할 수 있다.

return temp;                                                 }

}


전역함수로 선언해줌으로써 해결된다.


4. 임시 객체


Point p(1,2);

Point (1,2);   ---> 임시 객체 생성방법  --> 임시 객체는 생성된 후 다음 줄에서 바로 소멸된다.


임시 객체를 만드는 이유는 뭐냐?

-->임시 객체는 생성과 소멸을 최적화 시킨다. 임시 객체를 쓸 수 있는 부분에서는 써주는 것이 좋다.

교환법칙의 예에서 임시 객체를 사용할 수 있는 부분을 찾아보자.


Point Point::operator+(int val){

return Point(x+val, y+val);  ---> 소멸이 되기 전에 리턴!!

}


5. 대입 연산자 오버로딩


class Point{ ... };

int main(){

Point b(1,2);

Point a = b   --->  AAA a(b);  ---> 복사생성자에 의한 대입연산은 생성될 땐 가능하지만


Point a(1,2);

Point b(10,20);


a=b;   ----> a(b) ---> 객체 생성 문장이 아님 --> 복사생성자에 의한 대입 연산 불가능

but 디폴트 대입 연산자가 제공되기 때문에 가능하다.

}


------------------------------------------------------------------디폴트 대입연산자 ex


Point& operator=(const Point& p){

x=p.x;

y=p.y;

return *this;

}


대입 연산자의 문제점


class Person{

private:

char* name;

public:

Person(char* _name){

name = new char[strlen(_name)+1];

strcpy(name,_name);

}

Person (const Person& p){

name = new char[strlen(p.name)+1];

strcpy(name,p.name);

}

~Person(){ delete[] name }

};


int main(){                                        스택           힙

Person a("ddd");                          a ---->  ddd

Person b("fff");                            b ---->  fff


a=b;   --------------->> 문제 발생

}


b가 가진값을 a에 복사해준다. a가 b가 가리키는 fff를 가리키게 된다. (복사생성자의 문제와 유사)

디폴트 대입연산자는 얕은 복사가 가지지 않는 또 다른 문제 하나를 더 가지고 있다.

--> 메모리 유출이 발생한다.

--> a가 원래 가리켰던 ddd가 힙에 계속 남아있게 된다.


--> 올바르게 동작하도록 바꿔보자!

1. a가 가리키던 영역을 해제시켜준다.

2. 메모리 할당을 다시해서 b와 같은 문자열을 할당해준다.


Person& operator=(const Person& p){

delete []name;                                              --> 메모리 해제

name = new char[strlen(p.name)+1];            --> 깊은 복사에 의한 할당

strcpy(name,p.name);


return *this;

}


Point!!

--> Point&을 통해 자기자신을 리턴하는 이유 --> p3 = p1 + p2; 와 같은 연산을 수행하기 위해

--> 생성자에서 동적할당을 하게 되면  1. delete

    2. 깊은 복사를 하는 복사생성자.

    3. 해제, 깊은 복사를 하는 대입 연산자.

'C++' 카테고리의 다른 글

[C++] 복사생성자 정리  (0) 2016.12.07
[C++] 레퍼런스 정리  (0) 2016.12.07
[C++] virtual 소멸자, virtual table // 열혈강의  (0) 2016.12.05
[C++] 상속에 대하여 // 열혈강의  (0) 2016.12.01
[C++] 기초4 // 열혈강의  (0) 2016.12.01

열혈 강의 9장


함수 포인터 공부하자.


oop 프로젝트 6단계

--> AAA 클래스 내의 함수 virtual void show() { ... }

BBB 클래스 내의 함수 virtual void show() { 


AAA::show();

....

....


}


---> 유용하게 쓸 일이 많으니 꼭 기억할 것!!



1. virtual 소멸자의 필요성


/////////////////////////////////////////// Base 클래스 AAA 선언


class AAA{


private:

char* name1;


public:

AAA(char* _name1) {


name = new char[strlen(_name1)+1];

strcpy(name, _name);

}

~AAA(){

delete []name1;

}

}


////////////////////////////////////////// AAA를 상속하는 Derived 클래스 BBB


class BBB : public AAA{


private:

char* name2;


public:

BBB(char* _name1, char* _name2) : AAA(_name){


name2 = new char(strlen(_name2)+1];

strcpy(name2, _name2);


}

~BBB(){

delete []name1;

}

}


////////////////////////////////////////////// 메인 함수


int main(){


AAA* a = new BBB("aaa", "bbb");  ///// 1번

BBB* b = new BBB("aaa","bbb");  ///// 2번


}



2번의 경우 :


소멸의 주체가 되는 포인터가 BBB 객체 포인터이다.

BBB의 소멸자 호출 후 AAA의 소멸자도 호출

--> 메모리 반환이 정상적으로 일어난다.


1번의 경우 :


소멸의 주체가 되는 포인터가 AAA 객체 포인터이다.

AAA의 소멸자만 호출하고 BBB의 소멸자는 호출하지 않는다.

--> 메모리 유출이 일어난다.


--> 가리키는 객체가 무엇이든 AAA의 포인터이기 때문에 AAA의 객체로 바라본다.



////////////////////////////////////////////////////////////// 해결책


---> 해결책 : virtual 소멸자를 이용한다!!


virtual ~AAA(){

delete[] name1;

}


소멸자를 호출하려 들어갔는데, 가상함수로 선언 되어있다,

때문에 AAA를 상속하는 BBB의 소멸자를 호출한다.

그리고 또 다시 AAA의 소멸자를 호출하게 된다.

--> 특성 그 자체로 이해하기!!



2. virtual table


멤버함수는 객체 내에 존재하지 않는다.

코드영역이란?


객체를 여러개 만들 경우 여러개의 멤버함수 또한 만들어지게 된다. --> 비효율적이다.


때문에 코드영역에 멤버함수를 단 하나만 만들어 놓고

객체들이 공유하며 사용한다


////////////////////////////////////////////////// ex


class AAA{


int n;


public:

void Add(){

n++;


}

}


Add() 함수가 코드 영역에 들어가게 된다.


단,

--> Add(A* a){

(a->n)++;

}


이러한 형태로 변형되어 저장되며, 호출할 경우 Add의 인자로 객체의 포인터가 전달되어진다.


////////////////////////////////////////////////virtual table


virtual table은 key 값과 value 값이 있다.

key값은 어떠한 함수인지 구분해주는 역할을 하고, value값은 그 함수의 주소값을 지닌다.



객체 내에서 함수가 단 하나라도 virtual로 선언될 경우 (1개 이상)

그 클래스 내의 모든 함수(virtual이 아니라도)의 정보를 가지는 virtual table이 만들어진다.


때문에 함수가 호출 될 때에는 virtual table을 거쳐서 코드영역에 있는 함수를 찾게 된다.


virtual을 선언하지 않을 경우 바로 코드영역의 함수를 찾았지만 virtual table을 거쳐서 함수호출을 하게 된다.(virtual이든 아니든) ---> 단점.

따라서 메모리 또한 추가된다. (4byte)


AAA클래스의 멤버함수 virtual void fct()

virtual void show();


AAA를 상속하는 BBB클래스의 멤버함수 virtual void fct()

void fct2();


BBB클래스에서 virtual이 선언 되었기 때문에 virtual  table이 만들어지게 된다.

그 테이블에는 fct(), fct2() 그리고 AAA를 상속했기 때문에 show()의 정보 또한 저장된다.


AAA클래스의 virtual table에는 fct()와 show()의 정보


AAA의 fct() BBB의 fct()는 virtual table에 저장된 주소값이 다르다 --> 오버라이딩 되었지만 다른 함수이기 때문


--> 때문에 fct()는 AAA의 virtual table에도 BBB의 virtual table에도 저장되게 된다.


다만 BBB의 virtual table에는 재정의 되기전의 fct()의 정보가 전혀 없다. --> 가상함수의 원리


AAA* a = new BBB(); 의 가상함수 원리

'C++' 카테고리의 다른 글

[C++] 레퍼런스 정리  (0) 2016.12.07
[C++] 연산자 오버로딩 // 열혈강의  (0) 2016.12.07
[C++] 상속에 대하여 // 열혈강의  (0) 2016.12.01
[C++] 기초4 // 열혈강의  (0) 2016.12.01
[C++] 기초3 // 열혈강의  (0) 2016.11.30

열혈강의 8장


객체 리스트를 만들 때

포인터를 사용하는 이유

: 미리 생성되어져 버림.


포인터 배열로 선언할 경우 할당될 시에 객체가 생성됨.


단순 오버라이딩 했을 때  a b c 선언에 따라 함수가 어떻게 나오나

버추얼로 했을 때 버추얼된 함수를 나타내는방법



1. 객체 포인터의 권한


class Character(){


public:

void fct1();


}


class Gunner() : public Character{


public:

void fct2();


}


class Sniper() : public Gunner{


public:

void fct3();


}


Base 클래스의 객체 포인터는 자기 자신의 객체 뿐만 아니라

Derived 클래스의 주소값을 가질 수도 있다.


IS - A 관계에 의한 이해

-->  스나이퍼와 건너는 캐릭터이다. 라고 말할 수 있다. 때문에 

Base클래스인 Character의 포인터는 건너와 스나이퍼를 포함 할 수 있다.


--> 반대로 캐릭터는 스나이퍼이다.

캐릭터는 건너이다. 라고 말할 수 없다.

게임에서 존재하는 캐릭터가 모두 스나이퍼일리는 없다.


따라서 스나이퍼 클래스와 건너 클래스의 객체는 캐릭터 클래스의 객체이다. 라고 말할 수 있다.


1 . Character* c = new Character();

2. Character* c = new Gunner();

3. Character* c = new Sniper();

///// Base 클래스의 객체 포인터는 하위 클래스의 주소값을 모두 저장할 수 있기 때문에 문제없는 선언이 된다.


4. Gunner* c = new Gunner();

5. Gunner* c = new Sniper();


6. Sniper* c = new Sniper();



1. Character 객체 타입으로 선언된 포인터가 Character 객체를 가르키고 있다.

--> 따라서 Character 객체 내의 fct1(); 함수만 호출가능하다.


2. 이번에는 Gunner객체를 가르키고 있다.

--> Gunner객체의 fct2() 함수의 호출이 가능할 것 같지만, Character 객체의 멤버 함수 fct1()만 호출 된다.


왜?? Character 객체 포인터는 가르키는 대상이 무엇이건 Character 객체 내의 멤버와 Character가 상속한 멤버에만 접근이 가능하다.


3. 위와 똑같은 이유 때문에 Character 객체의 멤버 함수 fct1()만 호출된다.


4. 이번 같은 경우는 Gunner객체의 포인터가 Gunner의 객체를 가르키고 있다. 

Character에 의해 상속되어지고 있기 때문에 Character의 fct1()와 Gunner의 fct2() 에 모두 접근이 가능하다.


5. 이번 경우에도 Gunner 객체의 포인터로 선언이 되었기 때문에

Gunner이 가지고 있는 fct2()와 상위 클래스 fct1()의 호출이 가능하다.


6. Sniper객체 포인터가 자기 자신을 가르키고 있다.

이 경우 Sniper 클래스는 Character 과 Gunner에 모두 상속되어지고 있기 때문에

fct1(), fct2(), fct3() 모두 접근 가능하다.




2. 함수 오버라이딩


class Character(){


public:

void fct();


}


class Gunner() : public Character{


public:

void fct();


}


class Sniper() : public Gunner{


public:

void fct();


}


1 . Character* c = new Character();

2. Character* c = new Gunner();

3. Character* c = new Sniper();

4. Gunner* c = new Gunner();

5. Gunner* c = new Sniper();

6. Sniper* c = new Sniper();


c -> fct();


1. Character 객체 내의 fct()가 호출된다. (당연한 말)

2. Character 객체 내의 fct()가 호출된다.

3. 마찬가지

4. Gunner의 멤버함수 fct()가 호출된다.

왜?? --> 이름이 같기 때문에 오버라이딩에 의해 Character의 fct()가 은닉되어 Gunner 객체의 fct()가 호출된다.


5. Gunner의 멤버함수 fct()가 호출

6. Sniper의 멤버함수 fct() 호출

--> 마찬가지로 함수 오버라이딩에 의해 Character, Gunner 의 fct()가 은닉되어 Sniper의 fct()가 호출 된다.




3. virtual


class Character(){


public:

virtual void fct();


}


class Gunner() : public Character{


public:

virtual void fct();


}


class Sniper() : public Gunner{


public:

virtual void fct();


}


1 . Character* c = new Character();

2. Character* c = new Gunner();

3. Character* c = new Sniper();

4. Gunner* c = new Gunner();

5. Gunner* c = new Sniper();

6. Sniper* c = new Sniper();


c -> fct();



1. Character의 fct();

2. Gunner의 fct();

--> Character의 객체 포인터임에도 불구하고 Gunner의 멤버함수가 호출된다. 왜??

가상함수로 선언되었기 때문에 어떠한 객체를 가르키던 오버라이딩된 함수만 호출되어지게 된다.

말그대로 '가상'함수이기 때문에


3. Sniper의 fct();   --->  fct는 가상함수이기 때문에 오버라이딩된 Sniper의 fct()가 호출된다.

4. Gunner의 fct();

5. sniper의 fct();

6. sniper의 fct();



--> 함수오버라이딩의 은닉의 기능과 더불어 virtual을 사용할 경우 재정의의 기능까지 할 수있다.


'C++' 카테고리의 다른 글

[C++] 연산자 오버로딩 // 열혈강의  (0) 2016.12.07
[C++] virtual 소멸자, virtual table // 열혈강의  (0) 2016.12.05
[C++] 기초4 // 열혈강의  (0) 2016.12.01
[C++] 기초3 // 열혈강의  (0) 2016.11.30
[C++] 기초2 // 열혈강의  (0) 2016.11.30

+ Recent posts