728x90
728x90
7장 복습

1. 상속의 문법적 특성
2. 컨트롤클래스

8장은 상속의 장점, 상속이 필요한 상황적인 부분 등을 다룬다.

 


 

 

8-1 상속의 조건

public 상속은 is-a 관계가 성립되게 하자 (상속은 95%가 public 상속)

학생은 사람이다 (0) 

 

is-a 관계

 

올라갈수록 일반화, 아래로 내려갈수록 구체화된다. (특수화된다) 
즉 범위는 좁아지고 특성(멤버함수, 멤버변수)과 기능은 많아진다.   →  C++ 상속적 특성

 

잘못된 상속의 예

사람은 학생이다 (X)

 

 

 

 HAS-A (소유) 관계에 의한 상속

- 경찰은 몽둥이를 소유한다

 

 

예제

// hasa1.cpp
#include <iostream>
using namespace std;

//몽둥이
class Cudgel {
public:
    void Swing()
    {
        cout << "몽둥이를 휘둘러라!" << endl;
    }
};

//몽둥이를 가진 경찰
class Police: public Cudgel 
{
public:
    void UseWeapon() {
        Swing();
    } 

}; 

int main()
{
    Police pol;
    pol.UseWeapon();
    return 0;
}

 

 

 

HAS-A  관계에 의한 상속 그리고 대안 (대안방법이 낫다)

- 포함 관계를 통해 소유 관계를 표현
- 객체 멤버에 의한 포함 관계의 형성
- 객체 포인터 멤버에 의한 포함 관계의 형성

 

 

 

예제 :: 객체 멤버에 의한 포함 관계
/*   hasa2.cpp */
class Cudgel  //몽둥이
{ 
public:
	void Swing(){ cout<<"Swing a cudgel!"<<endl; }
};
class Police //몽둥이를 소유하는 경찰
{
	Cudgel cud; //멤버변수로 객체가 옴    
    // 구조체 때 배웠던 것처럼
    // 클래스 멤버로 클래스 객체가 올 수 있다
public:
	void UseWeapon(){ cud.Swing(); }
};

int main()
{
	Police pol;
	pol.UseWeapon();
	return 0;
}

 

 

Police 클래스 객체의 메모리공간 할당되는 순간에
멤버변수 Cudgel 객체의 메모리공간 할당되는 순간에
Cudgel 객체의 void 생성자 호출
Police 객체의 생성자 호출

클래스 객체가 멤버로 포함되기 위해서는 반드시 인자값을 받지 않는 void 생성자를 지녀야 한다.
// Cudgel cud(10);  // (x) 구조체처럼 C++ 클래스 객체 멤버는 생성과 동시에 초기화 불가 (자바나 C#은 가능)

클래스의 멤버는 Cudgel클래스에 void생성자가 반드시 있어야 한다.

 

 

 

예제 :: 객체 포인터 멤버에 의한 포함 관계
// hasa3.cpp

#include <iostream>
using namespace std;

//몽둥이
class Cudgel {
public:
    void Swing()
    {
        cout << "몽둥이를 휘둘러라!" << endl;
    }
};

//몽둥이를 가진 경찰
class Police  
{
    Cudgel* cud; //객체의 포인터라 변경 가능하다.
public:
    Police()
    {
        cud = new Cudgel; //객체 동적 생성, 힙에 존재.. void 생성자 호출
    }
    ~Police()
    {
        delete cud;
    }
    void UseWeapon()
    {
        cud->Swing(); //스택에 존재
    }
};

int main()
{
    Police pol;
    pol.UseWeapon();
    return 0;
}

pol 이름으로 메모리공간 할당
멤버로 cud 포인터 메모리공간 4바이트 할당
Police 생성자 호출
cud 객체 동적 생성 (Cudgel 객체를 동적할당)
명시적인 게 없으므로 void 생성자(디폴트 생성자) 호출하면서 Cudgel 객체를 생성
Police 객체는 Cudgel객체를 가리킨다

 

예제 2, 3 결과는 같으나 3이 낫다.

=> 논리적인 포함관계는 상속보다는 대안 방법 (포함, 3번 예제의 사용) 이 낫다.
느슨하면서 확실한 관계가 좋고, 커플링(결합도)이 낮아지는 게 좋다

 

 

 

8-2 상속된 객체와 포인터 관계

객체포인터, 객체포인터 권한에 대해 알아본다.

 

객체 포인터

- 객체의 주소 값을 저장할 수 있는 포인터 (포인터인데 객체를 가리킬 수 있는 포인터)
- AAA(Person)클래스의 포인터는 AAA객체 뿐만 아니라,
AAA클래스를 상속하는 Derived 클래스(Student) 객체의 주소 값도 저장 가능

 

 

Student는 Person이다. Person 클래스 포인터 p로 Student 객체를 가리킬 수 있다.
Student 객체로는 Person을 가리킬 수 없다.

 

 

예제
// inherit8_2.cpp 

#include <iostream>
using namespace std;

class Person
{
public:
    void Sleep() {
        cout << "Sleep" << endl;
    }
};

class Student: public Person
{
public:
    void Study() {
        cout << "Study" << endl;
    }
};

class PartTimeStd : public Student
{
public:
    void Work() {
        cout << "Work" << endl;
    }
};

int main()
{
    Person* p1 = new Person;
    Person* p2 = new Student;
    Person* p3 = new PartTimeStd;

    p1->Sleep();
    p2->Sleep();
    p3->Sleep();

    return 0;
}

 

 

 

 객체 포인터의 권한

- 포인터를 통해서 접근할 수 있는 객체 멤버의 영역
- Base 클래스의 객체 포인터는 가리키는 대상에 상관없이 Base 클래스 내에 선언된 멤버에만 접근

B클래스 포인터로 접근하면 B클래스 내로 접근이 가능

 

 

 

#include <iostream>
using namespace std;

class A
{
public:
	void a(){
	    cout<<"a()"<<endl;
	}
};

class B : public A
{
public:
	void b(){
	    cout<<"b()"<<endl;
	}
};

class C : public B
{
public:
	void c(){
	    cout<<"c()"<<endl;
    } 
};

int main()
{
	C* c = new C(); //void 생성자 호출
	c->a();
	c->b();
	c->c();
	
	return 0;
}

 

 

#include <iostream>
using namespace std;

class A
{
public:
	void a(){
	    cout<<"a()"<<endl;
	}
};

class B : public A
{
public:
	void b(){
	    cout<<"b()"<<endl;
	}
};

class C : public B
{
public:
	void c(){
	    cout<<"c()"<<endl;
    } 
};

int main()
{
	C* c = new C(); //void 생성자 호출
    B* b = c;  // b클래스의 포인터, c클래스의 포인터로 포인터의 타입이 달라도 대입 가능하다
    // C클래스의 포인터는 C클래스의 포인터이자 B, A클래스의 포인터도 되는 것이기 떄문이다
    // 그래서 타입이 좌 우가 일치한다.
    //A* a = c; //그래서 이것도 가능하다.
    A* a = b; // 이것도 가능하다.
    
    //cout<<c<<endl;
    //cout<<b<<endl;
    //cout<<a<<endl;
    // 출력을 해보면 같은 위치를 가리키고 있다
    
    b->a();  // b라는 포인터가 가리키는 객체 안에는 a, b, c 함수가 있는데
    b->b(); // 호출할 수 있는 함수는 a, b로 제한되어진다
    // b클래스 내에 선언되어있는, 비클래스가 상속하고 있는 클래스의 멤버에만 접근이 가능하다.
    //b->c(); // 컴파일타임에 에러가 발생
    
    a->a();
    //a->b();
    //a->c();
    
	
	return 0;
}

 

 

 

예제
// CPointer2.cpp 
 
#include <iostream>
using namespace std;

class Person
{
public:
    void Sleep() {
        cout << "Sleep" << endl;
    }
};

class Student : public Person
{
public:
    void Study() {
        cout << "Study" << endl;
    }
};

class PartTimeStd : public Student
{
public:
    void Work() {
        cout << "Work" << endl;
    }
};

int main()
{
    Person* p3 = new PartTimeStd;
    p3->Sleep();
    //p3->Study(); //error
    //p3->Work();  //error

    // 컴파일과정에서는 판단이 안되고 런타임에 p3이 PartTimeStd객체라는게 판단이 되기 때문에 허용이 안된다

    return 0;
}

 

 

 

Employee 문제 해결 첫 번째 단계

무슨 문제였는지 참고 : 7장

Department 객체는 Permanent 객체를 저장, 관리한다 (컨트롤클래스라서)
저장하기 위한 객체 포인터배열이 존재하여 포함관계를 갖는다.

 

프로그램의 확장성을 위해  Department 클래스는 그대로 둔다.

 

 

 

class Employee
{
protected:
    char name[20];  //이름
public:
    Employee(char* _name);
    const char* GetName(); //이름참조
};
Employee::Employee(char* _name) //생성자
{
    strcpy(name, _name);
}
const char* Employee::GetName()
{
    return name;
}

 

salary가 빠진 이유는 고용직이 매달 가져가는 급여(봉급) 과 임시직 일당은 달리 계산해야 되어서

 

 

 

#include <iostream>
#include <cstring>
using namespace std;

/**** Employee *****/
class Employee
{
protected:
    char name[20];  //이름
public:
    Employee(char* _name);
    const char* GetName(); //이름참조
};

Employee::Employee(char* _name) //생성자
{
    strcpy(name, _name);
}
const char* Employee::GetName()
{
    return name;
}

/**** Permanent *****/
class Permanent : public Employee
{
private:
    int salary; //기본급여
public:
    Permanent(char* _name, int sal);
    int GetPay();
};

Permanent::Permanent(char* _name, int sal) 
    :Employee(_name)  { //생성자
    //Permanent 생성자는 생성자를 통해서 필요한 이름정보와 급여정보를 받아와야 한다
    //상속받고 있는 employee 생성자를 호출하는데
    //멤버이니셜라이저로 이름정보는 employee 클래스에 넘겨주고
    //Permanent 기본급여를 관리해야해서 salary에 대입
    
    salary=sal; //기본급여
    //생성자 내에서 Employee 생성자를 상속하는데
}
int Permanent::GetPay()
{
    return salary;
}

/**** Temporary ****/
class Temporary : public Employee
{
private:
    int time; //일한시간 //Employee의 멤버로 올리지 않았음..올려도 되고..상황에 따라 판단.
    int pay; //시간당급여
public:
     Temporary(char* _name, int _time, int _pay);
     int GetPay();
};

Temporary::Temporary(char* _name, int _time, int _pay)
: Employee(_name)
{
    // 생성자
    // 멤버이니셜라이저와 함께 초기화
    time=_time;
    pay=_pay;
}

int Temporary::GetPay()
{
    return time*pay; //시간당 시급
}
 


/**** Department ****/
// 콘트롤 클래스

class Department
{
private:
    //Permanent * empList[10];
    Employee* empList[10];
    int index;
public:
    Department(): index(0) { };
    void AddEmployee(Employee* emp); //Employee 객체를 상속하는 객체는 전달 가능할 것
    void ShowList(); //급여 리스트 출력
};

void Department::AddEmployee(Employee* emp){
    empList[index++]=emp;
}

void Department::ShowList(){
    for(int i=0; i<index; i++)
    {
        cout<<"name: "<<empList[i]->GetName()<<endl;
        
        //cout<<"salary: "<<empList[i]->GetPay()<<endl;  
        // empList[i]는 employee클래스의 포인터이고
        // 가리키는 대상은 Permanent가 될 수 있지만
        // 포인터 객체의 권한 특성으로
        // 컴파일 타임에 에러가 발생한다...
        
        cout<<endl;
    }
}


/**** main function ****/
int main()
{
    // 직원을 관리하는 control 클래스
    Department department;
    
    // 직원 등록
    // Permanent객체 생성이 되고 그 자리로 
    // Permanent객체가 저장된 위치의 포인터가 리턴되어 AddEmployee 인자로 전달이 된다
    department.AddEmployee(new Permanent("KIM", 1000));
    department.AddEmployee(new Permanent("LEE", 1500));
    department.AddEmployee(new Temporary("HAN", 10, 200));
    department.AddEmployee(new Temporary("JANG", 12, 300));
    
    // 이번 달에 지불해야할 급여
    department.ShowList();
    return 0;
}

 

 

 

 

이 부분의 주석을 풀면

 

 

 

8-3 상속된 객체와 참조 관계
객체 레퍼런스

-객체를 참조할 수 있는 레퍼런스
-클래스 포인터의 특성과 일치 (AAA를 상속하고 있는 클래스의 객체도 참조할 수 있다)

 

#include <iostream>

using namespace std;

class Person
{
public:
	void Sleep(){ 
		cout<<"Sleep"<<endl;
	}
};

class Student : public Person
{
public:
	void Study(){
		cout<<"Study"<<endl;
	}
};

class PartTimeStd : public Student
{
public:
	void Work(){
		cout<<"Work"<<endl;
	}
};


int main(void)
{
	PartTimeStd p;
	Student& ref1=p; //Student 참조를 가지고 PartTimeStd 객체를 참조하고 있다
	Person& ref2=p;
	
	p.Sleep();
	ref1.Sleep();
	ref2.Sleep();
	
	return 0;
}

 

참조를 쓰게 되면 Call by reference 인지 value인지 의미가 불분명하기 때문에 쓰기 꺼려하기도 한다.
따라서 둘 중 하나를 고르자면 포인터에 더 익숙해지는 게 낫다.

 

 

 

객체 포인터의 권한 

- 포인터를 통해서 접근할 수 있는 객체 멤버의 영역
- AAA객체와, AAA를 상속하고 있는 클래스의 객체도 가리킬 수 있다 (IS-A 관계) (Person 클래스의 p포인터로..)
- 그러나 AAA 클래스의 객체 포인터는 가리키는 대상에 상관없이 AAA클래스 내에 선언된 멤버에만 접근

 

객체 레퍼런스의 권한

-객체를 참조하는 레퍼런스의 권한
-클래스 포인터의 권한과 일치

 

#include <iostream>

using namespace std;

class Person
{
public:
	void Sleep(){ 
		cout<<"Sleep"<<endl;
	}
};

class Student : public Person
{
public:
	void Study(){
		cout<<"Study"<<endl;
	}
};

class PartTimeStd : public Student
{
public:
	void Work(){
		cout<<"Work"<<endl;
	}
};

int main(void)
{
	PartTimeStd p;
	p.Sleep();
	p.Study();
	p.Work();

	Person& ref=p;
	ref.Sleep();
//	ref.Study(); // Error의 원인
//	ref.Work();  // Error의 원인
// 호출 불가능하다. Person 클래스의 참조이기 때문에.

	return 0;
}

 

 

 

8-4 Static Binding & Dynamic Binding

*오버로딩과 비교해서 복습

오버라이딩(Overriding)

- Base 클래스에 선언된 멤버와 같은 형태의 멤버를 Derived 클래스에서 선언
- Base 클래스의 멤버를 가리는 효과
- 보는 시야(Pointer)에 따라서 달라지는 효과
- 오버라이딩 자체가 재정의는 아니다. 재정의를 포함할 뿐.

 

 

 

fct 함수는 B클래스의 fct에 의해 오버라이딩 되었다 = 가려졌다 / 은닉되었다

보는 시야를 달리하면 볼 수도 있다는 이야기.

#include <iostream>
using namespace std;

class AAA
{
public:
	void fct(){
		cout<<"AAA"<<endl;
	}
};

class BBB : public AAA
{
public: 
	void fct(){ //AAA 클래스의 fct() 함수를 오버라이딩.
		cout<<"BBB"<<endl;
	}
};

int main(void)
{
	BBB b;
	b.fct();  // BBB가 출력!!!!

	return 0;
}

 

두 번째 예제
#include <iostream>
using namespace std;

class AAA
{
public:
	void fct(){
		cout<<"AAA"<<endl;
	}
};

class BBB : public AAA
{
public: 
	void fct(){ //AAA 클래스의 fct() 함수를 오버라이딩.
		cout<<"BBB"<<endl;
	}
};

 
int main(void)
{
	
    //	BBB b;
    //	b.fct();  // BBB가 출력 
    
	BBB* b=new BBB;
	b->fct(); // BBB가 출력 !!!

	AAA* a=b;
	a->fct(); // AAA가 출력 !!!

	delete b;
	return 0;
}

 

 

멤버 함수를 가상으로 선언하기

- 오버라이딩 되는 경우의 특징은? : 과장해서 말하자면 가상...즉 없는 것으로 간주한다.
- 가상이니 대신 BBB의 fct()가 호출된다
- virtual의 특성도 상속된다

 

오버라이딩이 재정의가 아니고 virtual 키워드가 없으면 은닉, virtual 키워드를 넣어주면 재정의 특성을 갖게 된다

재정의 특성에 대한 단점도 있다. 9장에서 다룬다.

#include <iostream>
using namespace std;

class AAA
{
public:
	virtual void fct(){
		cout<<"AAA"<<endl;
	}
};

class BBB : public AAA
{
public: 
	void fct(){ //AAA 클래스의 fct() 함수를 오버라이딩.
		cout<<"BBB"<<endl;
	}
};

 
int main(void)
{
	
    //	BBB b;
    //	b.fct();  // BBB가 출력 
    
	BBB* b=new BBB;
	b->fct(); // BBB가 출력 !!!

	AAA* a=b;
	a->fct(); // BBB가 출력 !!!

	delete b;
	return 0;
}

 

virtual의 특성도 상속된다.

#include <iostream>
using namespace std;

class AAA
{
public:
	virtual void fct(){
		cout<<"AAA"<<endl;
	} //이 클래스만 있으면 의미가 없다
	// 상속될 때만 virtual의 의미가 있음.
	
	// A* p = new A
	// p-> fct();
	// A a;
	// a.fct()
};

class BBB : public AAA
{
public: 
	void fct(){ //AAA 클래스의 fct() 함수를 오버라이딩.
	// virtual void fct()
	// 자동으로 virtual 키워드가 들어가나 
	// 코드가독성을 위해 명시적으로 써주는게 좋다
		cout<<"BBB"<<endl;
	}
};

class CCC : public BBB
{
public: 
    //상속하면서 오버라이딩
	void fct(){ 	//  virtual 
		cout<<"CCC"<<endl;
		// 얘는 또 의미가 없다
		// 얘를 상속받는 다른 클래스가 없어서
	}
};


 
int main(void)
{
	
   	BBB* b=new CCC;
	b->fct(); //CCC출력

	AAA* a=b;
	a->fct(); //CCC출력

	delete b;
	return 0;
}

 

 

Static Binding vs. Dynamic Binding

스태틱 바인딩 : 호출될 함수가 정해진 것 

A a;
a.f();

 

다이나믹 바인딩: 포인터가 아니고 포인터가 가리키는 객체에 따라 호출되는 함수가 동적으로 달라진다.

A* a = new ???
A클래스가 f함수는 virtual B클래스에 f함수가 있고 상속, C클래스(f함수 있음) 에 의해 상속되어진다



 

 

오버라이딩 된 함수의 호출

오버라이딩 된 함수의 호출이 필요한 이유 (연습문제 8-3 관련)

 

#include <iostream>
using namespace std;

class AAA
{
public:
	virtual void fct(){
		cout<<"AAA"<<endl;
	}  
};

class BBB : public AAA
{
public: 
	void fct(){  
	    AAA::fct(); //방법 1. : 오버라이딩 되어있어도 AAA 호출 가능
	    // fct(); //재귀로 무한루프..사용ㄴㄴ
		cout<<"BBB"<<endl;
	}
}; 

 
int main(void)
{
	AAA* a= new BBB;
    cout<< "첫 번째 시도" <<endl;
	a->fct();
	
	cout<< "2 번째 시도" <<endl;
	a->AAA::fct(); // 방법 2. 
	 
	return 0;
}

 

 

8-5 Employee Problem 완전한 해결
활용해야 할 문법적 요소

- 함수 오버라이딩
- virtual 함수의 오버라이딩
- 상속되어지는 virtual 특성
- 오버라이딩 된 함수의 호출 방법

(이전 단계에서 GetPay()함수가 호출이 안되는 문제가 있었다)

 

 

Employee클래스의 포인터배열 이기 때문에 상속하는 모든 클래스의 객체는 참조가 가능하다
그러나 그대로 Employee클래스에 GetPay()를 선언하면 Employee클래스의 GetPay가 호출되니 virtual를 추가해줬다.

 

상속이 가져다주는 이점과 함께 생각해서 정리해본다.

#include <iostream>
#include <cstring>
using namespace std;

/**** Employee *****/
class Employee
{
protected:
    char name[20];  //이름
public:
    Employee(char* _name);
    const char* GetName(); //이름참조
    virtual int GetPay() //add
    {
        return 0;
    }
};

Employee::Employee(char* _name) //생성자
{
    strcpy(name, _name);
}
const char* Employee::GetName()
{
    return name;
}

/**** Permanent *****/
class Permanent : public Employee
{
private:
    int salary; //기본급여
public:
    Permanent(char* _name, int sal);
    int GetPay();
};

Permanent::Permanent(char* _name, int sal) 
    :Employee(_name)  { //생성자
    //Permanent 생성자는 생성자를 통해서 필요한 이름정보와 급여정보를 받아와야 한다
    //상속받고 있는 employee 생성자를 호출하는데
    //멤버이니셜라이저로 이름정보는 employee 클래스에 넘겨주고
    //Permanent 기본급여를 관리해야해서 salary에 대입
    
    salary=sal; //기본급여
    //생성자 내에서 Employee 생성자를 상속하는데
}
int Permanent::GetPay()
{
    return salary;
}

/**** Temporary ****/
class Temporary : public Employee
{
private:
    int time; //일한시간 //Employee의 멤버로 올리지 않았음..올려도 되고..상황에 따라 판단.
    int pay; //시간당급여
public:
     Temporary(char* _name, int _time, int _pay);
     int GetPay();
};

Temporary::Temporary(char* _name, int _time, int _pay)
: Employee(_name)
{
    // 생성자
    // 멤버이니셜라이저와 함께 초기화
    time=_time;
    pay=_pay;
}

int Temporary::GetPay()
{
    return time*pay; //시간당 시급
}
 


/**** Department ****/
// 콘트롤 클래스

class Department
{
private:
    //Permanent * empList[10];
    Employee* empList[10];
    int index;
public:
    Department(): index(0) { };
    void AddEmployee(Employee* emp); //Employee 객체를 상속하는 객체는 전달 가능할 것
    void ShowList(); //급여 리스트 출력
};

void Department::AddEmployee(Employee* emp){
    empList[index++]=emp;
}

void Department::ShowList(){
    for(int i=0; i<index; i++)
    {
        cout<<"name: "<<empList[i]->GetName()<<endl;
        cout<<"salary: "<<empList[i]->GetPay()<<endl;   
        
        cout<<endl;
    }
}


/**** main function ****/
int main()
{
    // 직원을 관리하는 control 클래스
    Department department;
    
    // 직원 등록
    // Permanent객체 생성이 되고 그 자리로 
    // Permanent객체가 저장된 위치의 포인터가 리턴되어 AddEmployee 인자로 전달이 된다
    department.AddEmployee(new Permanent("KIM", 1000));
    department.AddEmployee(new Permanent("LEE", 1500));
    department.AddEmployee(new Temporary("HAN", 10, 200));
    department.AddEmployee(new Temporary("JANG", 12, 300));
    
    // 이번 달에 지불해야할 급여
    department.ShowList();
    return 0;
}

 

 

순수 가상함수와 추상 클래스

위의 예제에서 Employee에서 GetPay() 함수는 호출되지 않았다.
호출되기 위해서 존재가 아니라 대신 호출되기 위해서 교량의 역할로 존재한다.

 virtual int GetPay();


호출은 되지만 실행되지 않으니 몸체를 생략해버리면 컴파일이 안 된다.

 

선언만 존재하는 순수 가상함수 
 virtual int GetPay() = 0; //선언만 하고 정의를 일부로 안한 것이라고 컴파일러에 알려주는 법

 

- 추상클래스 : 하나 이상 순수가상함수를 가지는 클래스
- 추상클래스는 객체화될 수 없다.
완전한 클래스가 아니다.  (Employee클래스는 불완전 클래스)
그래서 객체화 될 수 없다. 포인터 선언은 가능하다.

 

 

8-6 virtual 소멸자의 필요성
상속하고 있는 클래스 객체 소멸 문제점
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

class AAA
{
    char* str1;
public:
    AAA(const char* _str1)
    {
        str1 = new char[strlen(_str1) + 1];
        strcpy(str1, _str1);
    }
    ~AAA() //virtual ~AAA()
    {
        cout << "~AAA() call!" << endl;
        delete []str1;
    }
    virtual void ShowString() {
        cout << str1 << ' ';
    }
};

class BBB : public AAA
{
    char* str2;
public:
    BBB(const char* _str1, const char* _str2) :AAA(_str1)
    {
        str2 = new char[strlen(_str2) + 1]; //동적할당
        strcpy(str2, _str2);
    }
    ~BBB() //소멸자가 호출될 떄 A클래스 소멸자도 호출된다
    {
        cout << "~BBB() call!" << endl;
        delete []str2;        // 메모리 해제
    }
    virtual void ShowString() {
        AAA::ShowString();
        cout << str2 << endl;
    }
};

int main()
{
    //AAA* a = new BBB("Good", "evening");
     
    BBB* b = new BBB("Good", "morning");        //b클래스의 객체를 생성하고 b클래스 포인터로 참조
    // 생성과정에서 두 생성자가 호출된다
    // b클래스 생성자에 의해, a클래스 생성자에 의해서 메모리공간이 동적할당된다 

    //a->ShowString();
    b->ShowString();

    cout << "----객체 소멸 직전---" << endl;
    
    //delete a;
    delete b;

    return 0;
}

 

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

class AAA
{
    char* str1;
public:
    AAA(const char* _str1)
    {
        str1 = new char[strlen(_str1) + 1];
        strcpy(str1, _str1);
    }
    ~AAA() //virtual ~AAA()
    {
        cout << "~AAA() call!" << endl;
        delete []str1;
    }
    virtual void ShowString() {
        cout << str1 << ' ';
    }
};

class BBB : public AAA
{
    char* str2;
public:
    BBB(const char* _str1, const char* _str2) :AAA(_str1)
    {
        str2 = new char[strlen(_str2) + 1]; //동적할당
        strcpy(str2, _str2);
    }
    ~BBB() //소멸자가 호출될 떄 A클래스 소멸자도 호출된다
    {
        cout << "~BBB() call!" << endl;
        delete []str2;        // 메모리 해제
    }
    virtual void ShowString() {
        AAA::ShowString();
        cout << str2 << endl;
    }
};

int main()
{
    //AAA* a = new BBB("Good", "evening");
    
    // 1) 
    //BBB* b = new BBB("Good", "morning");        //b클래스의 객체를 생성하고 b클래스 포인터로 참조
    // 생성과정에서 두 생성자가 호출된다
    // b클래스 생성자에 의해, a클래스 생성자에 의해서 메모리공간이 동적할당된다

    // 2) 포인터의 타입만 바꿔봤다 
    // A클래스의 소멸자만 호출된다
    // 소멸의 주체가 되는 포인터가 A클래스였기 때문이다
    AAA* b = new BBB("Good", "morning");


    //a->ShowString();
    b->ShowString();

    cout << "----객체 소멸 직전---" << endl;
    
    //delete a;
    delete b;

    return 0;
}

 

 

 

 

 

상속하고 있는 클래스 객체 소멸 문제점 해결
virtual ~AAA(){			
cout<<"~AAA() call!"<<endl;
delete []str1;
}

 

앞의 오버라이딩은 대신 호출됐었고, 가상 소멸자는...소멸자 호출하러 갔더니 가상 소멸자라서 A클래스를 상속하고 있는 B클래스의 소멸자가 대신 호출된다. B클래스의 소멸자는 a객체를 상속하고 있어서 다시 A객체의 소멸자도 호출된다.

 

 

OOP 프로젝트 5단계

(7장내용) 전역함수를 하나의 control클래스로 묶는다.

OOP 프로젝트 6단계 (상속, 오버라이딩, virtual.. 다형성 적용)

계좌클래스를 상속하는 신용계좌 클래스 추가
- 입금 시 바로 1%의 이자가 추가로 더해짐

계좌클래스를 상속하는 기부계좌 클래스 추가
- 입금 시 바로 1%에 해당되는 금액이 사회에 기부

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,

v