繼承與組合
- 繼承
一、基本概念
1 基類和派生類
1.1 只有兩層的繼承關係中,被繼承者稱為基類(父類);繼承者稱為派生類(子類)
1.2 多層繼承關係中,類A通過類B間接派生出類C,則類A和類B稱為類C的祖先類;類B和類C是類A的後代類;
一個類的祖先類包含了該類的基類和基類的祖先類;一個類的後代類包含了該類的派生類和派生類的後代類;
2 繼承形式
單重繼承、多重繼承、重複繼承;
3 繼承成員的訪問控制
問:派生類B繼承基類A後,基類A中的資料成員和成員函式在類B中是否可以訪問?其他類能否訪問類B繼承的類A的成員?
答:取決於由類A中成員的訪問控制方式和類B在繼承類A時指定的繼承控制方式共同決定。
基類中private成員以完全封閉的形式存在,只容許本類內部成員及友元對其訪問,因此不論繼承控制方式如何,在派生類中都無法訪問該成員。
基類中protected成員以半封閉的形式存在,容許派生類的成員對其訪問,因此不論繼承控制方式如何,在派生類中都可訪問該成員;但這些成員在派生類中對外表現形式有些變化,如繼承控制方式是private,基類中的protected成員在派生類中變成protected成員,在派生類中可以訪問這些成員,但派生類以外的其他不可訪問該成員;
基類中public成員以開放的形式存在,容許其他類的成員對其進行訪問,因此不論在繼承是繼承控制方式如何,在派生類中都可訪問這些成員,但這些成員在派生類中對外表現形式有些變化。
4 繼承語法class <派生類名>: <基類名列表>
{<資料成員和成員函式的宣告>};
基類名列表表示為:<繼承控制方式> <基類名1>, <繼承控制方式> <基類名2>, ......
繼承控制方式預設為private;
一般使用公有繼承,使基類中所有公有成員在派生類中也保持公有;
舉例:小汽車和跑車之間的關係用繼承描述。
其中,小汽車是基類,跑車是派生類,通過公有繼承方式從小汽車類中派生得到。
在跑車類中新增資料成員——顏色,同時增加對這個資料成員操作的成員函式。
#include <iostream>
#include <string>
using namespace std;
class Car{
public:
Car(int theweight, int thespeed)
{
weight = theweight;
speed = thespeed;
}
void setWeight(int theweight)
{
weight = theweight;
}
void setSpeed(int thespeed)
{
speed = thespeed;
}
int getWeight()
{
return weight;
}
int getSpeed()
{
return speed;
}
private:
int weight;
int speed;
};
class sportCar :public Car
{
public:
sportCar(int theweight, int thespeed, string thecolor) :Car(theweight,thespeed)
{
color = thecolor;
}
void setColor(string thecolor)
{
color = thecolor;
}
string getColor()
{
return color;
}
private:
string color;
};
int main()
{
Car car(100, 100);
sportCar sportcar(100, 200, "white");
cout << car.getWeight() << "\t" << car.getSpeed() << endl;
cout << sportcar.getWeight() << "\t" << sportcar.getSpeed() << "\t" << sportcar.getColor() << endl;
return 0;
}
二、派生類中繼承成員函式的重定義
繼承目的是在一般性的類的基礎上生成具有特殊性的類。
若在派生類中定義一個函式原型和繼承成員函式一樣的成員函式,則該成員函式實現對繼承成員函式的重定義。
當通過派生類物件呼叫成員函式,編譯器對函式呼叫的處理方法:
1 在派生類中查詢該函式,若在派生類中有該函式的定義,則呼叫派生類中定義的函式;否則進入2;
2 在基類中查詢該函式的定義,找到則呼叫;否則進入3;
3 繼續2,若查詢完所有的祖先類都未找到該函式的定義,則報未定義錯誤。
這體現派生類中定義的函式有優先權,即派生類中的重定義函式遮蔽基類中的相應函式。
三、繼承層次中的建構函式和解構函式
1 派生類物件的儲存空間
在類物件的儲存空間中,只存放從基類繼承的非靜態資料成員和派生類中定義的非靜態資料成員;
類的靜態資料成員和成員函式由類的所有物件公用,只需儲存一份。
2 派生類並不繼承基類的建構函式和解構函式派生類必須定義自己的建構函式和解構函式;
在生成派生類物件時,由派生類的建構函式呼叫其直接基類的建構函式,對從基類繼承的資料成員進行初始化;
若自己未定義建構函式和解構函式,系統會自動生成一個預設的建構函式,該建構函式的函式體為空。
3 派生類的建構函式通常,一個類中包含預設的建構函式和帶引數的建構函式;
3.1 派生類建構函式作用
a 通過初始化列表給基類建構函式傳遞引數,呼叫基類的帶引數建構函式初始化基類的資料成員;或呼叫基類的預設建構函式初始化基類的資料成員;
b 初始化派生類中定義的資料成員
3.2 帶引數列表的建構函式的語法:
<派生類名> (<形參列表>):<基類名>(<傳遞給基類的建構函式的實參列表>)
4 建構函式和解構函式的呼叫次序生成派生類物件時,建構函式的呼叫次序是:首先呼叫直接基類的狗在函式,再呼叫派生類的建構函式;
解構函式的呼叫次序正好相反。
當建立一個後代類物件,需追溯到其最遠祖先,由最遠祖先開始逐級呼叫建構函式初始化該後代類繼承得到的資料,最後呼叫後代類自己的建構函式。
舉例:
#include <iostream>
#include <string>
using namespace std;
class A{
public:
A()
{
cout << "Construct Base Object.\n";
}
~A()
{
cout << "Destruct Base Object.\n";
}
};
class B :public A{
public:
B()
{
cout << "Construct Derived Level 1 Object.\n";
}
~B()
{
cout << "Destruct Derived Level 1 Object.\n";
}
};
class C :public B{
public:
C()
{
cout << "Construct Derived Level 2 Object.\n";
}
~C()
{
cout << "Destruct Derived Level 2 Object.\n";
}
};
int main()
{
C c;
return 0;
}
輸出:
Construct Base Object.
Construct Derived Level 1 Object.
Construct Derived Level 2 Object.
Destruct Derived Level 2 Object.
Destruct Derived Level 2 Object.
Destruct Base Object.
- 組合
一、組合的語法表示和圖形表示
class Wheel{
... ... // 成員定義省略
};
class Car {
public:
... ...
private:
int weight;
int speed;
Wheel wheel[4]; // 一個Car物件中包含4個Wheel物件
};
class SportCar:public Car{
... ...
};
其中,被包含物件稱為嵌入物件。
通常情況下,將嵌入物件作為私有成員;但若想保留嵌入物件的共有介面,也可將嵌入物件作為共有成員。
二、組合與建構函式和解構函式
1 建立一個包含嵌入物件的物件時,建構函式的呼叫次序是:
首先按類宣告中嵌入物件出現的次序,分別呼叫各嵌入物件的建構函式;
執行本類的建構函式;
2 當一個類既是派生類,又組合其他類,建立該類物件時建構函式的呼叫次序是:
呼叫基類的建構函式;
按類宣告中嵌入物件出現的次序,分別呼叫各嵌入物件的建構函式;
最後執行派生類的建構函式;
三、繼承與組合的比較
繼承表示一般性和特殊性的關係,使用繼承方法可建立已存在類的特殊版本;
組合表示組成關係,當一個物件是另一個物件的組成部分時,使用組合方法可用已存在的類組裝新的類;
- 多重繼承與重複繼承
一、多重繼承
1 多重繼承中,派生類有多個基類,派生類與每個基類之間關係仍看作是單繼承關係;
class A{
... ...
};
class B{
... ...
};
class C:public A,public B{
... ...
};
通過多重繼承,派生類C具有兩個基類(類A和類B),在類C物件的儲存空間中除了存放C類中定義的非靜態資料成員,還存放從類A和類B繼承下來的非靜態資料成員;
多重繼承的應用背景是有時描述一個概念C,該概念具有雙重特性,即可以說是A,也可以說是B;
2 舉例
Device1定義一個裝置類,具有音量、開關等屬性作為被保護的資料成員,在資料上的操作包括:建構函式、顯示開關狀態、顯示音量;
Device2定義另一個裝置類,具有待機時間、通話時間、電池電量等屬性作為被保護的資料成員,在資料上的操作包括:建構函式、顯示裝置屬性、顯示電池電量;
有一個新裝置類DeviceNew,既是Device1定義的裝置,也是Device2定義的裝置;
通過共有多重繼承,定義DeviceNew類,在該類中定義屬性重量及顯示重量的函式。
#include <iostream>
using namespace std;
class Device1{
public:
Device1();
Device1(int vol, bool onORoff);
void showPower();
void showVol();
protected:
int volume;
bool powerOn;
};
class Device2{
public:
Device2();
Device2(int newTalkTime, int newStandbyTime, float powerCent);
void showProperty();
void showPower();
protected:
int talkTime;
int standbyTime;
float power;
};
class DeviceNew:public Device1, public Device2{
public:
DeviceNew();
DeviceNew(float newWeight, int vol, bool onORoff, int newTalkTime, int newStandbyTime, float powerCent):Device2(newTalkTime,newStandbyTime),Device1(vol, onORoff);
float getWeight();
private:
float weight;
};
Device1::Device1()
{
cout<<"Initialize device 1 by default constructor in Device1."<<enel;
volume=5;
powerOn=false;
}
Device1::Device1(int vol, bool onORoff)
{
cout<<"Initialize device 1 by constructor with parameters in Device1."<<endl;
volume=vol;
powerOn=onORoff;
}
void Device1::showPower()
{
cout<<"The status of power is: ";
switch(powerOn)
{
case true: cout<<"Power on."<<endl;break;
case false: cout<<"Power off."<<endl;break;
}
}
void Device1::showVol()
{
cout<<"Volume is "volume<<endl;
}
Device2::Device2()
{
cout<<"Initialize device 2 by default constructor in Device2"<<endl;
talkTime=10;
standbyTime=300;
power=100;
}
Device2::Device2(int newTalkTime, int newStandbyTime, float powerCent)
{
cout<<"Initialize device 2 by constructor with parameters in Device2."<<endl;
talkTime=newTalkTime;
standbyTime=newStandbyTime;
power=powerCent;
}
void Device2::showProperty()
{
cout<<"The property of the device:"<<endl;
cout<<"talk time: "<<talkTime<<"hours"<<endl;
cout<<"standbyTime: "<<standbyTime<<"hours"<<endl;
}
void Device2::showPower()
{
cout<<"Power: "<<power<<endl;
}
DeviceNew::DeviceNew()
{
cout<<"Initialize device new by default constructor in DeviceNew."<<endl;
weight=0.56;
}
DeviceNew::DeviceNew(float newWeight, int vol, bool onORoff, int newTalkTime, int newStandbyTime, float powerCent):Device2(newTalkTime, newStandbyTime, powerCent),Device1(vol, onORoff)
{
cout<<"Initialize device new by constructor with parameters in DeviceNew."<<endl;
weight=newWeight;
}
float DeviceNew::getWeight()
{
return weight;
}
int main()
{
DeviceNew device; // 生命一個派生類
cout<<"The weight of the device is "<<device.getWeight()<<endl; // getWeight()函式是DEVICE_NEW類自身定義的
device.showVol(); // showVol()函式是從DEVICE1類繼承下來的
device.showProperty(); // showProperty()函式是從DEVICE2類繼承下來的
return 0;
}
執行結果:
Initialize device 1 by default constructor in Device1.
Initialize device 2 by default constructor in Device2.
Initialize device new by default constructor in DeviceNew.
The weight of the device: 0.56
Volume is 5
The property of the device:
talk time: 10 hours
standbyTime: 300 hours
二、多重繼承的建構函式
多重繼承下派生類建構函式和單繼承下派生類建構函式相似,必須同時負責呼叫該派生類所有基類的建構函式;
派生類建構函式的形參表必須滿足所有基類建構函式所需引數(或者使用常量表示式呼叫基類建構函式);
若基類的建構函式帶有引數,則由派生類建構函式通過初始化列表的方式將引數傳遞給基類建構函式;
派生類建構函式格式如下:
<派生類名> (<形參表>): <初始化列表>
{
<派生類建構函式>
}
其中,若派生類呼叫基類的預設建構函式,則初始化列表為空;若不為空,則初始化列表的語法形式為:<基類名1>(<參數列1>),<基類名2>(<參數列2>),...
派生類建構函式的執行順序是先執行所有基類的建構函式,再執行派生類本身的建構函式,處於同一層次的各基類建構函式的執行順序取決於定義派生類時所指定的各基類順序,與派生類建構函式中所定義的初始化列表的順序無關。即執行基類建構函式的順序取決於定義派生類時指定的基類的順序。
如上例,在定義派生類時,採用以下繼承方式:class DeviceNew: public Device1, public Device2 ,它決定了基類建構函式的呼叫次序是先Device1,後Device2;
修改main函式為:
int main()
{
DeviceNew device(0.7,3,false,10,250,80);
cout<<"The weight of the device: "<<device.getWeight()<<endl;
device.showVol();
device.showProperty();
return 0;
}
執行結果:
Initialize device 1 by constructor with parameters in Device1.
Initialize device 2 by constructor with parameters in Device2.
Initialize device new by constructor with parameters in DeviceNew.
The weight of the device: 0.7
Volume is 3
The property of the device:
talk time: 10 hours
standbyTime: 250 hours
三、多重繼承中存在的問題:名字衝突
名字衝突指在多個基類中具有相同名字的成員時,在派生類中這個名字會產生二義性。
如,在執行device.showPower();語句時,編譯器無法確定要呼叫的是從哪個基類繼承下來的showPower()函式(是Device1類還是Device2類?),因此產生二義性;
解決辦法有二:
1 用作用於操作符::明確派生類物件要訪問的是從哪個基類繼承下來的成員
device.Device1::showPower();
device.Device2::showPower();
2 在派生類中重定義有名字衝突的成員
在派生類DeviceNew中重定義showPower()函式:
woid showPower()
{
Device1::showPower();
Device2::showPower();
}
當通過派生類物件呼叫showPower()函式時,首先檢查在派生類中是否定義了該函式,若定義了,呼叫派生類中的showPower()函式;
四、重複繼承
當派生類的多個基類具有相同的祖先時,會出現重複繼承的情形,即一個類重複多次繼承了某個祖先類;
如,Derived類通過其兩個基類Base1和Base2重複繼承了Base類兩次,Derived類物件的儲存空間中會包含從Base1和Base2中繼承下來的資料成員,而Base1和Base2中又包含從Base類中繼承下來的資料成員,故Derived類物件的儲存儲存空間中包含了Base類中非靜態資料成員的兩個副本,致使二義性。
#include <iostream>
using namespace std;
class Base{
public:
void setData(int newData)
{
data=newData;
}
protected:
int data;
};
class Base1: public Base{
public:
void setData1(int newData, int newData1)
{
data=newData;
data1=newData1;
}
protected:
int data1;
};
class Base2: public Base{
public:
void setData2(int newData, int newData2)
{
data=newData;
data2=newData2;
}
protected:
data2=newData2;
};
class Derived: public Base1, public Base2{
public:
void setData3(int newData, int newData1, int newData2)
{
data=newData; // 對data訪問有二義性
data1=newData1;
data2=newData2;
}
};
int main()
{
Derived dObj;
dObj.setData3(3,4,5);
return 0;_
}
解決該問題方法有二:
1 採用作用域運算髮::明確選擇哪個副本中的資料
class Derived: public Base1, public Base2{
public:
void setData3(int newData, int newData1, int newData2)
{
Base1::data=newData;
Base2::data=newData;
data1=newData1;
data2=newData2;
}
};
2 採用虛基類方法,使派生類物件的儲存空間中只保留被重複繼承的祖先類的一個物件副本
虛基類和普通基類的區別是在繼承控制保留字之前加virtual,當用virtual限定的基類被重複繼承時,只在派生類物件的儲存空間中保留其資料的一個副本;
class Base1: virtual public Base{ // Base是其虛基類
public:
void setData1(int newData, int newData1)
{
data=newData;
data1=newData1;
}
protected:
int data1;
};
class Base2: virtual public Base{ // Base是其虛基類
public:
void setData2(int newData, int newData2)
{
data=newData;
data2=newData2;
}
protected:
data2=newData2;
};
相關文章
- prefer 組合 to 繼承繼承
- 類的組合與繼承——作業繼承
- 組合優於繼承繼承
- 【Java】繼承、抽象、組合Java繼承抽象
- js 組合繼承詳解JS繼承
- JavaScript中的繼承和組合JavaScript繼承
- Docker的組合優於繼承 - frankelDocker繼承
- python物件導向的繼承-組合-02Python物件繼承
- 【設計模式】如何用組合替代繼承設計模式繼承
- javascript組合繼承的基本原理JavaScript繼承
- 多繼承 與 多重繼承繼承
- 類的組合、繼承、模板類、標準庫繼承
- C++ 多級繼承與多重繼承:程式碼組織與靈活性的平衡C++繼承
- odoo 繼承(owl繼承、web繼承、view繼承)Odoo繼承WebView
- 繼承與多型繼承多型
- Maven 聚合與繼承Maven繼承
- 實驗4 類的組合、繼承、模板類、標準庫繼承
- 實驗四 類的組合、繼承、模板類、標準庫繼承
- 實驗四 類的組合,繼承,模板類,標準庫繼承
- 為什麼更推薦使用組合而非繼承關係?繼承
- 菱形繼承,虛繼承繼承
- 原型,繼承——原型繼承原型繼承
- 34_繼承的綜合案例繼承
- javascript - 繼承與原型鏈JavaScript繼承原型
- java繼承與多型Java繼承多型
- class語法與繼承繼承
- 原型、原型鏈與繼承原型繼承
- 繼承(extends)與介面( implements)繼承
- Java 繼承與多型:程式碼重用與靈活性的巧妙結合Java繼承多型
- Golang物件導向程式設計之繼承&虛基類【組合&介面】Golang物件程式設計繼承
- Javascript繼承2:建立即繼承—-建構函式繼承JavaScript繼承函式
- Javascript繼承4:潔淨的繼承者—-原型式繼承JavaScript繼承原型
- JavaScript 的繼承與多型JavaScript繼承多型
- ES6 - 類與繼承繼承
- Javascript的繼承與多型JavaScript繼承多型
- 菱形繼承與虛基類繼承
- 繼承 基類與派生類繼承
- Javascript物件導向與繼承JavaScript物件繼承
- JavaScript基礎: 類與繼承JavaScript繼承