1. 程式人生 > 實用技巧 >C++04類的繼承與多型

C++04類的繼承與多型

目錄

一、繼承(Inherit)

1.1、定義

繼承是一種機制,在已有類的基礎之上,重新封裝一個新的類,這種方式叫做繼承.
1> 父類中的成員變數和成員函式會被子類繼承
2> 子類有父類中所有的方法和屬性
3> 子類是特殊的父類

eg:
學生類:姓名,年齡,性別,學習
老師類:姓名,年齡,性別,授課,工資
以上寫法程式碼冗餘
	
Human:姓名,年齡,性別
學生類:繼承Human類,學習
教師類:繼承Human類,授課,工資

	人   類      》基類 (父類)
	/     \
學生類     教師類  》派生類 (子類)

1.2、繼承的語法格式

class 子類類名:繼承表{};
class 子類類名:繼承屬性 基類類名1,
			 繼承屬性 基類類名2,......{};
繼承屬性: 
	public 
	protected
	private 

1.3、子類繼承基類中的成員屬性的變化

行---->父類訪問許可權
列---->子類繼承方式

public(公有) protected(受保護) private(私有)
public(公有的繼承) public(公有) protected(受保護) private(私有)
protected(受保護繼承) protected(受保護) protected(受保護) private(私有)
private(私有的繼承) private(私有) private(私有) private(私有)
公有的繼承: 
	子類物件會繼承基類中的所有的屬性和行為,
	子類物件訪問從基類中繼承的公有的和保護的成員,如同訪問自己的一樣。
	基類中私有的成員在子類中無法訪問,子類繼承了基類中私有的成員,
	只是繼承的私有的成員在子類中被隱藏了。

受保護繼承:

私有的繼承:

//參考案例: 04inher.cpp
#include <iostream>
using namespace std;

class Basic{
public: 
	Basic(int data):m_data1(data),m_data2(data),m_data3(data){}
	Basic(void){}
	void show(){
		cout << "m_data3 = " << m_data3 << endl;
	}
	void print(){
		cout << "m_data3 = " << m_data3 << endl;	
	}
public:
	int m_data1;
protected:
	int m_data2;
private:
	int m_data3;
};
class Child:public Basic{
public:
	Child(int data):Basic(data),m_data(data){}
	void show()
	{
		// 基類公有的成員子類可以訪問
		cout << "m_data1 = " << m_data1 << endl;
		// 基類保護的成員子類可以訪問
		cout << "m_data2 = " << m_data2 << endl;
		// 基類私有的成員子類不可以訪問
		// cout << "m_data3 = " << m_data3 << endl;
		Basic::show();
		cout << "m_data = " << m_data << endl;
	}
private:
	int m_data;
};


int main(int argc, const char *argv[])
{
	Child  c(100);
	c.show();

	// 通過子類物件呼叫基類中在子類中隱藏的函式
	c.Basic::show();
	c.print();
	return 0;
}
	
//結果:
m_data1 = 100
m_data2 = 100
m_data3 = 100
m_data = 100
m_data3 = 100
m_data3 = 100

練習題: 測試保護繼承和私有繼承
A -> B:public 
A -> B:protected -> C:public B
A -> B:private   -> C:public B

1.4、基類中的建構函式,子類是無法繼承的

1> 	但是可以通過初始化表的方式顯示的呼叫基類中的建構函式,完成對基類中成員的初始化。	
語法格式:
	類名(形參表):基類類名(形參名),...,子類成員(形參名),...{}
	
2> 如果子類的建構函式沒有顯示的在初始化表中呼叫基類的建構函式,預設會呼叫基類的無參建構函式。
3> 如果子類想呼叫基類中的建構函式,必須在初始化表中顯示的呼叫基類的建構函式。
4> 建構函式的呼叫順序
	基類的建構函式 
	子類的建構函式

5> 子類物件被創建出來的時候父類建構函式先於子類建構函式被呼叫
6> 類內封裝的其它類的構造順序和類內的宣告一致
7> 呼叫方法可以隱式呼叫和顯示呼叫	
//參考案例: 05inher.cpp
#include <iostream>
using namespace std;
class Human{
public:
	Human(void){
		cout << "基類的無參建構函式" << endl;
	}
	Human(const string& name, int age):m_name(name),m_age(age){
		cout << "基類有參建構函式" << endl;	
	}
	void show(void){
		cout << "姓名:" << m_name << endl;
		cout << "年齡:" << m_age << endl;
	}
	~Human(void){
		cout << "基類的解構函式" << endl;
	}
private:
	string m_name;
	int m_age;
};

// 如果省略public繼承屬性,預設是公有的繼承
class Student:public Human{
public:
	Student(const string& name, int age, int score):Human(name,age),m_score(score){
		cout << "子類的有參建構函式" << endl;	
	}
	// 子類中的建構函式沒有顯示的呼叫基類的建構函式,
	// 預設會呼叫基類的無參建構函式
	Student(void){
		cout << "子類的無參建構函式" << endl;		
	}
	~Student(void){
		cout << "子類的解構函式" << endl;
	}
	// 子類中隱藏基類中同名的成員,
	void show(void){
		Human::show();
		cout << "成績:" << m_score << endl;
	}

private:
		int m_score;
};

int main(int argc, const char *argv[])
{
	Student stu1("zhoukai", 18, 99);
	// 通過子類物件呼叫基類在子類中隱藏的成員
	// stu1.Human::show();
	// 呼叫的是子類中的show函式
	stu1.show();
	cout << sizeof(stu1) << endl;

	Student stu2;
	return 0;
}
//結果:
基類有參建構函式
子類的有參建構函式
姓名:zhoukai
年齡:18
成績:99
40
基類的無參建構函式
子類的無參建構函式
子類的解構函式
基類的解構函式
子類的解構函式
基類的解構函式

1.5、基類的解構函式,子類也無法繼承

1> 子類中的解構函式是否顯示的定義了,都會呼叫基類中的解構函式完成對基類的成員的析構。
	1. 如果說在全域性區或者棧區,子類物件中解構函式的執行順序和建構函式執行順序相反
   	2. 如果在堆區,和delete的執行順序有關
2> 解構函式的呼叫順序 
	子類的解構函式
	基類的解構函式 
//參考案例: 05inher.cpp
//在上面

1.6、子類中隱藏基類中同名的成員

1> 子類和基類具有相同名字的成員,在子類中會被隱藏,
2> 子類中隱藏基類中的成員函式在子類中的成員函式和基類中的成員函式,及時具有相同的函式名,形參列表不同,也不可以構成過載的關係,原因是函式的作用域不同。
3> 如果想在子類隱藏的基類成員:(這些成員是保護或者公有)
	基類類名::成員函式名(實參表);
	基類類名::變數名;
		
	如果通過子類物件訪問在子類中隱藏的基類的公有的成員
	物件名.基類類名::函式名(實參表);
	物件名.基類類名::變數名;
參考案例: 06inher.cpp
#include <iostream>
using namespace std;
class Human{
public:
	Human(void){
		cout << "基類的無參建構函式" << endl;
	}
	Human(const string& name, int age):
		m_name(name),m_age(age){
		cout << "基類有參建構函式" << endl;	
	}
	void show(int){
		cout << "姓名:" << m_name << endl;
		cout << "年齡:" << m_age << endl;
	}
	~Human(void){
		cout << "基類的解構函式" << endl;
	}

private:
	string m_name;
	int m_age;
};

// 如果省略public繼承屬性,預設是公有的繼承
class Student:public Human{
public:
	Student(const string& name, int age, int score):
		Human(name,age),m_score(score){
		cout << "子類的有參建構函式" << endl;	
	}
	// 子類中的建構函式沒有顯示的呼叫基類的建構函式,
	// 預設會呼叫基類的無參建構函式
	Student(void){
		cout << "子類的無參建構函式" << endl;		
	}
	~Student(void){
		cout << "子類的解構函式" << endl;
	}
	// 子類中隱藏基類中同名的成員,
	void show(void){
		Human::show(1);
		cout << "成績:" << m_score << endl;
	}

private:
	int m_score;
};

int main(int argc, const char *argv[])
{
	Student stu1("zhoukai", 18, 99);
	// 通過子類物件呼叫基類在子類中隱藏的成員
	stu1.Human::show(1);
	// 呼叫的是子類中的show函式
	stu1.show();
	cout << sizeof(stu1) << endl;

	Student stu2;
	return 0;
}
//結果:
基類有參建構函式
子類的有參建構函式
姓名:zhoukai
年齡:18
姓名:zhoukai
年齡:18
成績:99
40
基類的無參建構函式
子類的無參建構函式
子類的解構函式
基類的解構函式
子類的解構函式
基類的解構函式

1.7、子類中的拷貝建構函式

1》子類如果沒有顯示的定義拷貝建構函式,編譯器預設會提供一個預設的拷貝建構函式,預設的拷貝建構函式預設會呼叫基類中的拷貝建構函式,完成對基類中成員的初始化。 
	
2》 子類中顯示的定義了拷貝建構函式,編譯器不在提供預設的拷貝建構函式,此時必須通過初始化表的方式顯示的呼叫基類中的拷貝建構函式,否則會呼叫基類中的無參建構函式,對基類中的成員進行無參初始化。
	
//參考案例:07inher.cpp
#include <iostream>
using namespace std;

class Shape{
public:
	Shape(int x,int y):m_x(x),m_y(y){}
	Shape(){
		cout << "基類中的無參建構函式" << endl;
	}
	~Shape(void){}
	Shape(const Shape& other):
		m_x(other.m_x),m_y(other.m_y){
		cout << "基類拷貝建構函式" << endl;	
	}
	void show(){
		cout << "座標:" << m_x << ":" << m_y << endl;
	}
private:
	int m_x;
	int m_y;
};

class Rect:public Shape{
public:
	Rect(int x,int y,int w,int h):
		m_w(w),m_h(h),Shape(x,y){}
	~Rect(void){}
#if 0   
	// 拷貝建構函式沒有顯示呼叫基類的拷貝建構函式
	// 預設呼叫基類無參建構函式,
	// 完成對基類中的成員無參初始化
	Rect(const Rect& other):m_w(other.m_w),m_h(other.m_h){}
#else 
	// Rect --> Shape  : 隱士的完成型別轉換
	Rect(const Rect& other):
		Shape(other),m_w(other.m_w),m_h(other.m_h){}
#endif
	void show(){
		Shape::show();
		cout << "寬高:" << m_w << ":" << m_h << endl;
	}
private:
	int m_w;
	int m_h;
};

int main(int argc, const char *argv[])
{
	Rect r1(1,2,3,4);
	Rect r2(r1); // 呼叫拷貝建構函式
	r2.show();
	return 0;
}
//結果:
基類拷貝建構函式
座標:1:2
寬高:3:4

1.8、子類中的拷貝賦值函式

1》子類如果沒有顯示的定義拷貝賦值函式,編譯器預設會提供一個預設的拷貝賦值函式,預設的拷貝賦值函式預設會呼叫基類中的拷貝賦值函式,完成對基類中成員的初始化。 
	
2》 子類中顯示的定義了拷貝賦值函式,編譯器不在提供預設的拷貝賦值函式,此時必須通過在拷貝賦值函式中顯示的呼叫基類中的拷貝賦值函式,否則基類中的成員保持不變。
//參考案例:08inher.cpp
#include <iostream>
using namespace std;

class Shape{
public:
	Shape(int x,int y):m_x(x),m_y(y){}
	Shape(){
		cout << "基類中的無參建構函式" << endl;
	}
	~Shape(void){}
	Shape(const Shape& other):m_x(other.m_x),m_y(other.m_y){
		cout << "基類拷貝建構函式" << endl;	
	}
	Shape& operator=(const Shape& other){
		cout << "基類拷貝賦值函式" << endl;
		if(&other != this){
			m_x = other.m_x;
			m_y = other.m_y;
		}
		return *this;
	}
	void show(){
		cout << "座標:" << m_x << ":" << m_y << endl;
	}
private:
	int m_x;
	int m_y;
};

class Rect:public Shape{
public:
	Rect(int x,int y,int w,int h):
		m_w(w),m_h(h),Shape(x,y){}
	~Rect(void){}
#if 0   
	// 拷貝建構函式沒有顯示呼叫基類的拷貝建構函式
	// 預設呼叫基類無參建構函式,
	// 完成對基類中的成員無參初始化
	Rect(const Rect& other):m_w(other.m_w),m_h(other.m_h){}
#else 
	// Rect --> Shape  : 隱士的完成型別轉換
	Rect(const Rect& other):Shape(other),m_w(other.m_w),m_h(other.m_h){}
#endif
	// 子類顯示的定義拷貝賦值函式 
#if 0
	// 子類的拷貝賦值函式不顯示的呼叫基類的拷貝賦值函式
	Rect& operator=(const Rect& other){
		cout << "子類的拷貝賦值函式" << endl;
		if(&other != this){
			m_w = other.m_w;
			m_h = other.m_h;
		}
		return *this;
	}
#else 		
	// 子類的拷貝賦值函式顯示的呼叫基類的拷貝賦值函式
	Rect& operator=(const Rect& other){
		cout << "子類的拷貝賦值函式" << endl;
		if(&other != this){
			// Rect --> Shape : 隱士型別轉換
			Shape::operator=(other);
			m_w = other.m_w;
			m_h = other.m_h;
		}
		return *this;
	}
#endif
	void show(){
		Shape::show();
		cout << "寬高:" << m_w << ":" << m_h << endl;
	}
private:
	int m_w;
	int m_h;
};

int main(int argc, const char *argv[])
{
	Rect r1(1,2,3,4);
	Rect r2(5,6,7,8); 
	r1 = r2;  // 呼叫拷貝賦值函式 
	r1.show();

	return 0;
}

//結果:
子類的拷貝賦值函式
基類拷貝賦值函式
座標:5:6
寬高:7:8

1.9、基類和子類之間的型別轉換

1> 子類---》基類  : 向上造型  (常用,安全)

2> 基類---》子類  : 向下造型  (不常用,不安全)

重點內容:

運算子的過載
繼承 
向上造型

二、多重繼承

A   B
 \ /
  C

2.1、定義

一個子類繼承多個基類,這種繼承方式叫做多重繼承

2.2、語法格式

class 子類類名:繼承屬性 基類類名1,繼承屬性 基類類名2,......{};

2.3、問題

1> 名字衝突的問題 
	在多個基類中具有相同名字的成員,通過子類物件去訪問基類中的成員時,就會存在名字衝突的問題。
	注意:不能和子類中的名字相同,否則會呼叫子類中的成員。
2> 可以通過"基類類名::"限定符的方式訪問基類類中的成員。
3> 多重繼承向上造型的問題:
	將繼承自多個基類的子類物件,隱式的轉換成基類物件,編譯器會根據每個基類在記憶體中	的排布不同自動的完成地址偏移,讓每個基類指向跟自己型別匹配的地址空間。
 
#include <iostream>
using namespace std;
class A{
	int m_a;
public:
	A(int a):m_a(a){}
	void show(){
		cout << m_a << "-" << &m_a << endl;
	}
};
class B{
	float m_b;
public:
	B(float b):m_b(b){}
	void show(){
		cout << m_b << "-" << &m_b <<endl;
	}
};
//多重繼承
class C:public B,public A{
	string m_name;
public:
	C(const string& name, int a, float b):m_name(name),A(a),B(b){}
	void print()
	{
		cout << m_name << "-" << &m_name << endl;
		A::show();
		B::show();
	}
};

int main(int argc, const char *argv[])
{
	C c("sdfas", 12, 3.14);
	c.print();
	
	A a = c;
	a.show();

	B b = c;
	b.show();
	
	return 0;
}
//結果:
sdfas - 0x7fffa169abd8
12 - 0x7fffa169abd4
3.14 - 0x7fffa169abd0
12 - 0x7fffa169abcc
3.14 - 0x7fffa169abc8

三、虛繼承《鑽石繼承》

3.1、定義

一個子類的多個基類來自於共同的祖先基類,這種繼承方式需要使用虛繼承的語法格式。
	 A
	/ \
   B   C   : 需要採用虛繼承的語法格式
    \ /
	 D

3.2、語法格式

關鍵字: virtual

class A{}  // 虛基類
class B:virtual public A{} 虛繼承
class C:virtual public A{} 虛繼承
class D:public B, public C{}

3.3、多個公共基類問題

派生多箇中間子類 (B,C) 的 公共基類(A) ,在繼承自多箇中間子類的 匯聚子類(D),如果 中間子類 (B,C)不採用虛繼承的語法格式,會在 匯聚子類(D)例項化物件時, 公共基類(A)會存在多個例項物件。如果在一個 匯聚子類(D)物件中存在多個 公共基類(A)的物件時,根據不同的訪問路徑,訪問不同的地址空間
	A   A   // 公共基類 
	|   |
	B   C   // 中間子類
	 \ / 
	  D     // 匯聚子類
		
#include <iostream>
using namespace std;
class A{  // 公共基類
public:
	A(int a):m_a(a){}
	void show(){
		cout << "A::m_a = " << m_a << endl;
		cout << "&A::m_a = " << &m_a << endl;
	}
private:
		int m_a;
};
class B:public A{
public:
	B(int a, int b):A(a),m_b(b){}
	void show(){
		A::show();
		cout << "B::m_b = " << m_b << endl;
	}
private:
	int m_b;
};
class C:public A{
public:
	C(int a, int c):A(a),m_c(c){}
	void show(){
		A::show();
		cout << "C::m_c = " << m_c << endl;
	}
private:
	int m_c;
};
class D:public B, public C{
public:
	D(int a, int b, int c,int d):B(a, b),C(a,c),m_d(d){}
	void show(){
		cout << "D -> B -> A -> m_a" << endl;
		B::show();
		cout << "D -> C -> A -> m_a" << endl;
		C::show();
		cout << "D::m_d = " << m_d << endl;
	}
private:
	int m_d;
};

int main(int argc, const char *argv[])
{
	D d(1111,2222,3333,4444);
	d.show();

	return 0;
}
//結果:
D -> B -> A -> m_a
A::m_a = 1111
//&A::m_a = 0x7ffff0ebcf20
B::m_b = 2222
D -> C -> A -> m_a
A::m_a = 1111
//&A::m_a = 0x7ffff0ebcf28
C::m_c = 3333
D::m_d = 4444
// 公共基類的地址不一樣,即 存在多個公共基類

3.4、虛繼承

可以使用虛繼承的語法格式解決以上問題。在繼承鏈的最末端的匯聚子類負責構造虛基類物件。
虛基類的所有的子類(B,C,D)都必須在其建構函式的初始化表中顯示的呼叫虛基類的建構函式。

關鍵字: virtual

class A{}  // 虛基類
class B:virtual public A{} 虛繼承
class C:virtual public A{} 虛繼承
class D:public B, public C{}
#include <iostream>
using namespace std;
class A{  // 公共基類
public:
	A(int a):m_a(a){}
	void show(){
		cout << "A::m_a = " << m_a << endl;
		cout << "&A::m_a = " << &m_a << endl;
	}
private:
	int m_a;
};
// 使用 virtual 關鍵字進行虛繼承 基類
class B:virtual public A{
public:
	B(int a, int b):A(a),m_b(b){}
	void show(){
		A::show();
		cout << "B::m_b = " << m_b << endl;
	}
private:
	int m_b;
};

// 使用 virtual 關鍵字進行虛繼承 基類
class C:virtual public A{
public:
	C(int a, int c):A(a),m_c(c){}
	void show(){
		A::show();
		cout << "C::m_c = " << m_c << endl;
	}
private:
	int m_c;
};
class D:public B, public C{
public:
	D(int a, int b, int c,int d):
		A(a), B(a, b),C(a,c),m_d(d){}
	void show(){
		cout << "D -> B -> A -> m_a" << endl;
		B::show();
		cout << "D -> C -> A -> m_a" << endl;
		C::show();
		cout << "D::m_d = " << m_d << endl;
	}
private:
	int m_d;
};

int main(int argc, const char *argv[])
{
	D d(1111,2222,3333,4444);
	d.show();

	return 0;
}
//結果:
D -> B -> A -> m_a
A::m_a = 1111
//&A::m_a = 0x7fff68ce27d0
B::m_b = 2222
D -> C -> A -> m_a
A::m_a = 1111
//&A::m_a = 0x7fff68ce27d0
C::m_c = 3333
D::m_d = 4444
// 公共基類的地址一樣,即 只存在一個公共基類 

四、多型

4.1、定義:

函式的多種表現形態,
通過基類物件呼叫子類中的成員函式,及子類中的成員函式完成對基類中的成員函式的覆蓋,
這種形態叫做多型。

4.2、表現出多型特性的條件

1> 子類中的成員函式完成對基類中的成員函式的覆蓋。
2> 通過基類物件呼叫子類中的成員函式。

4.3、成員函式覆蓋的條件

1> 基類和子類中具有相同的函式原型
	函式的名字相同,形參列表相同,返回值相同具有相同的常屬性。
2> 基類中的成員函式使用virtual關鍵字修飾,
	則在子類中具有和基類中相同的函式也將變成虛擬函式。
3> 建構函式,拷貝建構函式、全域性函式、靜態成員函式、友元函式不可以是虛擬函式。
	解構函式可以是虛擬函式。

4.4、表現出多型的特性?

1> 滿足覆蓋的條件
2> 通過指向子類物件的基類指標 
	Shape *s1 = new Rect(1,2,3,4);
	或者通過引用目標為子類物件的基類引用
	Circle c1(5,6,7);
	Shape &s2 = c1;
	
	Shape s3 = c1; // 無法表現出多型的特性
#include <iostream>
using namespace std;
class Shape{
public:
	Shape(int x, int y):m_x(x),m_y(y){}
	virtual ~Shape(void){
		cout << "Shape::~Shape()" << endl;
	}
	// 在基類的函式前加virtual關鍵字,則此函式將變成虛擬函式,在子類中和該函式具有相同原型的函式也將變成虛擬函式,
    // 此時通過指向子類物件的基類指標呼叫此函式,將會呼叫到子類中對應的虛擬函式,
	// 可以表現出這種現象的叫做多型.

	// virtual關鍵字必須新增到基類中的函式前邊,載入子類中的函式前邊沒有任何的影響
	virtual void Painter(void){
		cout << "座標:" << m_x << ":" << m_y << endl;
	}
private:
	int m_x;
	int m_y;
};
class Rect:public Shape{
public:
	Rect(int x, int y, int w, int h):Shape(x,y),m_w(w),m_h(h){}
	~Rect(void){
		cout << "Rect::~Rect()" << endl;
	}
	void Painter(void){
		cout << "繪製矩形" << endl;
		Shape::Painter();
		cout << "寬高:" << m_w << ":" << m_h << endl;
	}
	private:
	int m_w;
	int m_h;
};

class Circle:public Shape{
public:
	Circle(int x,int y, int r):Shape(x,y),m_r(r){}
	~Circle(){
		cout << "Circle::~Circle()" << endl;
	}
	void Painter(void){
		cout << "繪製圓" << endl;
		Shape::Painter();
		cout << "半徑:" << m_r << endl;
	}
private:
	int m_r;
};
int main(int argc, const char *argv[])
{
	// 指向子類物件的基類指標
	Shape *S1 = new Rect(1,2,3,4);
	S1->Painter();  // 呼叫基類的Painter
	delete S1;

	Shape *S2 = new Circle(5,6,7);
	S2->Painter(); // 呼叫基類的Painter
	delete S2;
	
	Rect r1(11,22,33,44);
	Shape &S3 = r1;
	S3.Painter();

	// 無法表現出多型的特性
	// Shape S4 = r1;
	// S4.Painter();

	return 0;
}
//結果:
繪製矩形
座標:1:2
寬高:3:4
Rect::~Rect()
Shape::~Shape()
繪製圓
座標:5:6
半徑:7
Circle::~Circle()
Shape::~Shape()
繪製矩形
座標:11:22
寬高:33:44
Rect::~Rect()
Shape::~Shape()

4.5、虛解構函式

1> 通過指向子類物件的基類指標,當delete基類指標時,只會呼叫基類中的解構函式,而不呼叫子類中的解構函式,

2> 此時可以將基類中的解構函式宣告為虛擬函式,

3> 此時子類中的解構函式也將變成虛擬函式,	

4> 此時再delete基類指標時,呼叫的是子類中的解構函式對基類的解構函式的覆蓋版本,
	
5> 在通過子類的解構函式呼叫基類中的解構函式,完成對基類中成員的資源的釋放。

4.6、純虛擬函式

語法格式:
	virtual 返回型別 函式名 (形參表) = 0;
沒有函式體的虛擬函式,叫做純需函式。

4.7、虛類(抽象類)

類中包含純虛擬函式的類叫做虛類。

4.8、純虛類(純抽象類)

類中只包含純虛擬函式的類叫做純虛類。
#include <iostream>
using namespace std;
// 虛類  純虛類
class Shape{
public:
	Shape(int x, int y):m_x(x),m_y(y){}
	virtual ~Shape(void){
		cout << "Shape::~Shape()" << endl;
	}
	// 純虛擬函式
	virtual void Painter(void) = 0;
protected:
	int m_x;
	int m_y;
};
class Rect:public Shape{
public:
	Rect(int x, int y, int w, int h):Shape(x,y),m_w(w),m_h(h){}
	~Rect(void){
		cout << "Rect::~Rect()" << endl;
	}
	void Painter(void){
		cout << "繪製矩形" << endl;
		cout << "座標:" << m_x << ":" << m_y << endl;
		cout << "寬高:" << m_w << ":" << m_h << endl;
	}
private:
	int m_w;
	int m_h;
};

class Circle:public Shape{
public:
	Circle(int x,int y, int r):Shape(x,y),m_r(r){}
	~Circle(){
		cout << "Circle::~Circle()" << endl;
	}
	void Painter(void){
		cout << "繪製圓" << endl;
		cout << "座標:" << m_x << ":" << m_y << endl;
		cout << "半徑:" << m_r << endl;
	}
private:
	int m_r;
};

int main(int argc, const char *argv[])
{
	// 指向子類物件的基類指標
	Shape *S1 = new Rect(1,2,3,4);
	S1->Painter();  // 呼叫基類的Painter
	delete S1;

	Shape *S2 = new Circle(5,6,7);
	S2->Painter(); // 呼叫基類的Painter
	delete S2;
	
	Rect r1(11,22,33,44);
	Shape &S3 = r1;
	S3.Painter();

	// 無法表現出多型的特性
	// Shape S4 = r1;
	// S4.Painter();

	return 0;
}

//結果:
繪製矩形
座標:1:2
寬高:3:4
Rect::~Rect()
Shape::~Shape()
繪製圓
座標:5:6
半徑:7
Circle::~Circle()
Shape::~Shape()
繪製矩形
座標:11:22
寬高:33:44
Rect::~Rect()
Shape::~Shape()
  1. 父類中使用virtual關鍵字修飾的方法,可以在子類中被重寫,從而實現多型

4.2 虛擬函式表

  1. 虛擬函式表(virtual table),用來解決多型和覆蓋的問題
  2. 虛擬函式表是在物件開始的位置
  3. 只有類中有虛擬函式,才會有一個指標,指向虛擬函式表
  4. 虛擬函式表中存放的是這個物件中的虛擬函式的地址
  5. 一個類中如果有虛擬函式(不論多少個),會多出來四位元組,這個是指向虛擬函式表指標的位元組大小
  6. 虛擬函式表中的虛擬函式順序是根據類中虛擬函式的宣告順序來的
  7. 如果,重寫了父類的虛擬函式,虛擬函式表中將只體現一個虛擬函式

4.3 過載、隱藏、重寫的區別

  1. 過載

    是發生在同一個作用域

    函式名相同,形參列表不同

  2. 隱藏

    發生在不同的作用域

    函式名和形參列表完全相同的時候,不管有沒有virtual關鍵字子類都會把父類中的同名函式隱藏

    實際上函式指標指向的是兩個函式,預設訪問的是子類的同名函式,如果想要訪問父類的同名函式需要加域訪問運算子

  3. 重寫

    發生在不同的作用域

    父類中必須有virtual關鍵字,子類才能重寫父類的同名函式

4.4 面向物件的特點

​ 封裝 繼承 多型