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.繼承的意義
- 面向對象的四大特徵:抽象 繼承 封裝 多態
- 封裝->繼承->多態
- 繼承是多態的基礎