【C++總複習】 第5章----繼承與派生

1.繼承

  • 繼承:在一個已經存在的類的基礎上建立一個新的類。已存在的類稱爲父類、基類;新建立的類稱爲子類、派生類
  • 一個派生類只從一個基類派生稱爲單繼承
  • 一個派生類有兩個或多個基類稱爲多重繼承
  • 圖解單繼承在這裏插入圖片描述
  • 圖解多繼承在這裏插入圖片描述- 派生類是基類的具體化,而基類則是派生類的抽象
    在這裏插入圖片描述

從上圖中可以看出:
1.小學生、中學生、大學生、研究生、留學生是學生的具體化,都是從學生的共性基礎上加上某些特點形成的子類
2.學生則是各類學生共性的提取形成的一個抽象的類
3.基類綜合了派生類的公共特徵,派生類則在基類的基礎上增加某些特性,把抽象變的具體

  • 派生類的聲明形式
  • class 派生類名 : [繼承方式] 基類名{ 子類新增成員 };
  • 繼承方式:public(公用的) private(私有的) protected(受保護的)
  • 如果不寫繼承方式則默認爲private
  • 派生類的成員有:(1)從父類繼承來的成員 (2)修改從父類繼承來的成員 (3)在派生類中增加的新成員
  • 繼承優點:繼承性提供了重複利用程序資源的一種途徑。通過繼承機制,可以擴充和完善舊的程序設計以適應新的需求。這樣不僅可以節省程序開發的時間和資源,並且爲未來程序增添了新的資源。

2.子類成員的訪問屬性

  • 無論是單繼承還是多繼承,都符合以下訪問屬性的限制規律

2.1公用繼承

  • 子類從父類的繼承方式爲public,稱爲公用繼承
  • 公用繼承後父類成員訪問屬性表格
    在這裏插入圖片描述
class A{
	private:
		int y;	//private訪問權限 
	protected:
		int z;	//protected訪問權限 
	public:
		int x;	//public訪問權限 
	
};
class B:public A{
	public:
		void Change(){
			x = 1;	
//合法,基類中public成員在public繼承之後依然是public訪問權限 
			y = 1;	
//不合法,基類中private成員在public繼承之後無法訪問 
			z = 1;	
//合法,基類中protected成員在public繼承之後依然是protected訪問權限 
		} 
};

2.2私有繼承

  • 子類從父類的繼承方式爲private,稱爲私有繼承
  • 私有繼承後父類成員訪問屬性表格
    在這裏插入圖片描述
class A{
	private:
		int y;	//private訪問權限 
	protected:
		int z;	//protected訪問權限 
	public:
		int x;	//public訪問權限 
	
};
class B:private A{
	public:
		void Change(){
			x = 1;	
//合法,基類中public成員在private繼承之後是private訪問權限 
			y = 1;
//不合法,基類中private成員在private繼承之後無法訪問 
			z = 1;	
//合法,基類中protected成員在private繼承之後是private訪問權限 
		} 
};

2.3保護繼承

  • 子類從父類的繼承方式爲protected,稱爲保護繼承
  • 保護成員對於類的用戶角度看,等價於私有成員,但是保護成員可以在子類的成員函數中使用
  • 保護繼承後父類成員訪問屬性表格
    在這裏插入圖片描述
class A{
	private:
		int y;	//private訪問權限 
	protected:
		int z;	//protected訪問權限 
	public:
		int x;	//public訪問權限 
	
};
class B:protected A{
	public:
		void Change(){
			x = 1;	
//合法,基類中public成員在protected繼承之後是protected訪問權限 
			y = 1;	
//不合法,基類中private成員在protected繼承之後無法訪問 
			z = 1;	
//合法,基類中protected成員在protected繼承之後是protected訪問權限 
		} 
};

2.4類中成員訪問屬性

在這裏插入圖片描述

3.子類構造函數和析構函數

3.1構造函數

  • 子類構造函數不僅僅需要爲子類中特有的屬性傳值,還要爲父類中繼承過來的屬性傳值(即調用父類構造函數)

子類構造函數格式:
子類名(總參數列表) : 父類名(父類參數表){ 新增數據成員初始化 }

例:B(int x,int y,int z) : A(x,y){ this->z = z;}
B繼承A類,A中有x,y兩個參數,B中新增一個z參數

class A{
	private:
		int x;
		int y;	
		int z;	
	public:
		A(){
			x = 0;
			y = 0;
			z = 0;
		}
		A(int x,int y,int z){
			this->x = x;
			this->y = y; 
			this->z = z;
		}
};
class B:protected A{
	private:
		int m;
		int n;
	public:
		//子類無參構造函數 
		B():A(){
			m = 0;
			n = 0;
		}
		//子類有參構造函數 
		B(int x,int y,int z,int m,int n):A(x,y,z){
			this->m = m;
			this->n = n;
		}	
};
  • 無論繼承結構多麼複雜,無論繼承了多少層,子類一定要負責對直接父類進行初始化

構造函數執行順序:先基類後子類

多層繼承:A派生出B,B派生出A
A: int x ; A構造函數:A(int x){ this-> x = x;}
B: int y ; B構造函數:B(int x,int y) : A(x){ this->y = y; }
C: int z ; C構造函數:C(int x,int y,int z):B(x,y){ this->z = z; }
調用C構造函數初始化對象時成員變量初始化順序:
①初始化A的x
②初始化B的y
③初始化C的z

多重繼承:A、B共同派生出C
A:int x; A構造函數:A(int x){ this-> x = x; }
B:int y ;B構造函數:B(int y) { this->y = y; }
C:int z;C構造函數:C(int x,int y,int z) : A(x),B(y){ this->z = z; }
調用C構造函數初始化對象時成員變量初始化順序:
①初始化A的x
②初始化B的y
③初始化C的z
提醒:雖然A和B之間並無繼承關係,本應初始化順序並無先後,但是C中的構造函數是先初始化的A中的x,所以按照代碼順序執行初始化

3.2析構函數

  • 子類是無法繼承父類的析構函數的
  • 子類必須通過自己的析構函數來調用父類的析構函數,來處理從父類繼承來的成員,同時在自己的析構函數中處理自己新增的成員
class A{
	private:
		int x;
	public:
		A(){x = 0;}
		A(int x){this->x = x;}
		~A(){}		//父類析構函數
};
class B:public A{
	private:
		int m;
	public:
		B():A(){m = 0;}
		B(int x,int m):A(x){this->m = m;}
		~B(){}		//子類析構函數
};

5.虛基類

Q:看下面這段代碼,你發現了什麼問題?

class A{
	public:
		m(){}
};
class B : public A{
};
class C : public A{
};
class D : public B,public C{
	public:
		n(){
			m();
		}
}

在這裏插入圖片描述

Q:很顯然:在多層繼承加多重繼承中,一個子類同時繼承了兩個父類,這兩個父類又同時繼承了同一個類,此時,中間層的兩個類存在相同的方法或者屬性,這時,如何在最底層的子類中區分相同的方法或屬性呢?
A:由於同名方法或屬性造成了二義性,這時解決方法有兩個:第一個方法是使用作用域運算符進行指明,例如:將上述程序中的n方法修改成:n(){ B::m();},就明確指出了調用的是父類B中的m方法,這樣便消除了二義性。第二個方法是使用虛基類

  • 虛基類的作用:讓繼承間接基類時只保留一份成員
  • 聲明虛基類的形式:class 子類名 : virtual 繼承方式 父類名
class B : virtual public A{
};
class C : virtual  public A{
};

此時A被聲明爲虛基類,D類同時繼承了B和C類,D中只會出現一個A中的屬性與方法,並不會出現二義性

  • 爲了保證虛基類中的成員在子類中只被繼承一次,需要將該基類的所有直接子類中聲明爲虛基類,否則,仍然會出現對基類的多次繼承,如圖:
    在這裏插入圖片描述

6.父類對象與子類對象之間的轉換

class A {
	public:
		int x;
};
class B : public A{
	public:
		int y;
};

(1)用子類對象給父類對象賦值

A a;
B b;
a = b;

在這裏插入圖片描述
(2)父類對象引用可以引用子類對象

B b;
A &a = b
  • 引用對象a只是b對象中x部分的名字,只可以訪問b對象中的x,無法訪問y.

在這裏插入圖片描述
(3)父類類型指針變量指向子類對象

A *p;
B b;
p = &b;

p->x = 10;	//合法
p->y = 10;	//不合法
  • 通過指向父類對象的指針來指向子類對象,此指針只能訪問子類從父類繼承來的屬性和方法,無法通過此指針間接訪問子類特有的屬性和方法
    在這裏插入圖片描述

7.類的組合

  • 簡單來說,類的組合就是類中屬性包含其他類的對象
  • 讀懂以下程序就可理解類的組合、屬性爲對象時如何初始化
#include <iostream>
using namespace std;

class A{
	private:
		int x;
	public:
		A(){
			x = 0;
		}
		A(int x){
			this->x = x;
		}
		int getX(){
			return x;
		}
};
class B{
	private:
		int y;
		A a;
	public:
		B(){	//對象a默認調用無參構造函數 
			y = 0;
		}
		B(int y,A &ra){	
			this->y = y;
			a = ra;		//爲對象a初始化 
		}
		void display(){
			cout<<this->a.getX()<<" "<<this->y<<endl;
		}
};

int main(){
	A a(1);
	B b1;
	B b2(2,a);
	
	b1.display();// 0 0	
	b2.display();// 1 2
	
	return 0;
}

8.類的繼承和組合

  • 繼承關係可以理解爲 is a 關係:鬥牛犬是一隻狗
  • 組合關係可以理解爲 has a 關係:張三有一隻鬥牛犬
  • 繼承是縱向的,組合是橫向的

9.繼承的意義

  • 面向對象的四大特徵:抽象 繼承 封裝 多態
  • 封裝->繼承->多態
  • 繼承是多態的基礎
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章