本文章分為知識點(diǎn)旨椒、例子和心得,交流群728483370,一起學(xué)習(xí)加油堵漱!
7.組合综慎、繼承與多態(tài)性
7.1 組合
7.2 繼承
7.3繼承和組合
7.4構(gòu)造與析構(gòu)順序
7.5派生類重載基類函數(shù)的訪問
7.6多態(tài)性&虛函數(shù)
7.7純虛函數(shù)和抽象基類
7.8 多重繼承
7.9派生類成員的標(biāo)識與訪問
7.組合、繼承與多態(tài)性
面向?qū)ο笤O(shè)計(jì)的重要目的之一就是代碼重用勤庐,這也是C++重要性能之一示惊。軟件的重用性鼓勵(lì)人們使用已有的好港、得到認(rèn)可并經(jīng)過測試的高質(zhì)量代碼。多態(tài)性可以以常規(guī)方式書寫程序來訪問多種現(xiàn)有的且專門化了的相關(guān)類米罚。繼承和多態(tài)是面向?qū)ο蟪绦蛟O(shè)計(jì)方法的兩個(gè)最主要的特征钧汹。繼承可以將一群相關(guān)的類組織起來,并共享期間的相同數(shù)據(jù)和操作行為录择;多態(tài)使得設(shè)計(jì)者在這些類上編程時(shí)拔莱,可以如同操作一個(gè)單一體,而非相互獨(dú)立的類隘竭,且給設(shè)計(jì)者更多靈活性來加入或刪除類的一些屬性或方法塘秦。
7.1 組合
對于比較簡單的類,其數(shù)據(jù)成員可能都是基本數(shù)據(jù)類型动看,但對于某些復(fù)雜的類來說尊剔,其某些數(shù)據(jù)成員可能又是另一些類類型,這就形成了類的組合弧圆。實(shí)際上赋兵,C語言中一直在使用組合,如C語言中的結(jié)構(gòu)體就可以看成是不同類型數(shù)據(jù)的組合搔预,前面提及的類也是不同類型數(shù)據(jù)及操作的組合霹期,只不過是基本數(shù)據(jù)類型的組合而已。
#include <iostream>
class Myclass
{
enum{NUM = 50};
char cName[20];
int iNum;
Student stulist[Num];
public:
const char* Getclassname();
const char* Getstuname(int ino);
};
Myclass::Myclass()
{
iNum = 0;
inline const char* Myclass::Getclassname()
{
return cName;
}
const char* Myclass::GetStuName(int iNo)
{
for(int i = 0; i < iNum; i++)
{
if(stulist[i].Getno() == iNo)
return stulist[i].GetName();
}
return NULL;
}
7.2 繼承
類的繼承就是新類從已有類哪里獲得已有的屬性和行為拯田,或者說是基類派生了具有基類特征又有新特征的派生類历造。繼承是軟件可重用型的一種形式,新類通過繼承從現(xiàn)有類中吸取其屬性和行為船庇,并對其進(jìn)行覆蓋或改寫吭产,產(chǎn)生新類所需要的功能。同樣鸭轮,新類也可以派生出更新的類臣淤。創(chuàng)建新類,但不是從頭創(chuàng)建窃爷∫亟可以使用其他人已經(jīng)創(chuàng)建并調(diào)試過的類。關(guān)鍵是使用類按厘,而不是更改已存在的代碼医吊。
類繼承語法:
class 子類名:[public|private|protected] 父類名
{
...
}
子類具有父類的所有屬性和行為,且可以增加新的行為和屬性逮京。
C++提供了三種繼承的方式:公有繼承(public)卿堂、私有繼承(private)、保護(hù)繼承(protected)懒棉。
公有繼承
(1)基類的private草描、public览绿、protected成員的訪問屬性在派生類中保持不變。
(2)派生類中繼承的成員函數(shù)可以直接訪問基類中所有成員陶珠,派生類中新增的成員函數(shù)只能訪問基類的public和protected成員挟裂,不能訪問基類的private成員。
(3)通過派生類的對象只能訪問基類的public成員揍诽。
矩形移動(dòng):
#include <iostream>
using namespace std;
class Point
{
private:
float x,y;
public:
void InitP(float xx = 0, float yy = 0)
{
x = xx;
y = yy;
}
void Move(float a, float b)
{
x += a;
y += b;
}
float Getx(){return x;}
float Gety(){return y;}
};
class Rectangle:public Point
{
private:
float w,h;
public:
void InitR(float x, float y, float w, float h)
{
InitP(x, y);
this->w = w;
this->h = h;
}
float Geth(){return h;}
float Getw(){return w;}
};
int main()
{
Rectangle rect;
rect.InitR(2,3,20,10);
//rect.h;
//rect.w; //錯(cuò)誤诀蓉,父類私有成員
rect.Move(3,2);
cout << rect.Getx() << ',' <<rect.Gety() << ',' <<rect.Geth() << ',' <<rect.Getw() <<endl;
return 0;
}
保護(hù)繼承
(1)基類的public、protected成員都以protected身份出現(xiàn)在派生類中暑脆。
(2)派生類中新增的成員函數(shù)可以直接訪問基類中的public和protected成員渠啤,但不能訪問基類的private成員。
(3)通過派生類的對象不能訪問基類的任何成員添吗。
私有繼承
(1)基類的public沥曹、protected成員都以private身份出現(xiàn)在派生類中。
(2)派生類中新增的成員函數(shù)可以直接訪問基類中的public和protected成員碟联,但不能訪問基類的private成員妓美。
(3)通過派生類的對象不能訪問基類的任何成員。
例如:把上述程序的繼承改為私有繼承鲤孵。
class Rectangle:private Point
{
private:
float w,h;
public:
void InitR(float x, float y, float w, float h)
{
InitP(x, y);
this->w = w;
this->h = h;
}
float geth(){retrun h;}
float getw(){return w;}
};
當(dāng)聲明的子對象調(diào)用父類的成員函數(shù)時(shí)壶栋,會(huì)發(fā)生錯(cuò)誤:
Rectangle rect;
Rect.InitP(); //錯(cuò)誤,通過私有繼承普监,父類的InitP()在子類中變成私有的
如果確實(shí)需要在子類里對私有繼承父類的公有成員變?yōu)楣械墓笫裕恍枰谧宇愔新暶鳛楣屑纯?
class Rectangle:private Point
{
...
public:
Point::InitP;
Point::getx;
...
};
注意:在派生類中聲明基類的函數(shù)時(shí),只需要給出函數(shù)的名稱凯正,函數(shù)的參數(shù)和返回值類型不應(yīng)出現(xiàn)毙玻。
class Rectangle:private Point
{
...
public:
Point::InitP;
Point::getx;
void Point::InitR(float x, float y, float w, float h); //錯(cuò)誤
Point::InitR(float x, float y, float w, float h); //錯(cuò)誤
Point::InitR(); //錯(cuò)誤
...
};
關(guān)于繼承,我將專門用一篇來介紹廊散,這里只是先了解基本概念桑滩。
7.3繼承和組合
實(shí)際工作中往往需要在定義一個(gè)新類時(shí)這個(gè)新類的一部分內(nèi)容時(shí)從已有類中繼承的,還有一部分內(nèi)容則是需要由其他類組合的允睹,這就需要把組合和繼承放在一起使用施符。例如,在定義Z類時(shí)它的一部分內(nèi)容需要從X類繼承擂找,另一部分內(nèi)容則是Y類型的,具體定義:
class X
{
int i;
};
class Y
{
float f;
};
class Z:public X
{
double d;
Y y;
};
組合通常在希望新類內(nèi)部有已存在類性能時(shí)使用浩销,而不希望已存在類作為它的接口贯涎。這就是說,嵌入一個(gè)計(jì)劃用于實(shí)現(xiàn)新類性能的對象慢洋,而新類的用戶看到的是新定義的接口,而不是來自父類的接口。
繼承是取一個(gè)已存在的類姻锁,并制作它的一個(gè)專門的版本谷醉。通常,這意味著取一個(gè)一般目的的類舆吮,并為了特殊的需要對它進(jìn)行專門化。
建立班級類:
#include <iostream>
#include <string.h>
using namespace std;
class Person
{
char strname[20];
int iage;
char csex;
public:
Person(const char* cpname = NULL, int age = 0, char sex = 'm');
const char* Getname(){return strname;}
int Getage(){return iage;}
char Getsex(){return csex;}
void Setname(const char *cpname);
void Setage(int age){iage = age;}
void Setsex(char sex){csex = sex;}
};
class Teacher:public Person
{
int wid;
public:
Teacher(const char* cpname = NULL, int age = 0, char sex = 'm', int no = 0):Person(cpname, age, sex),wid(no){}
int Getwid(){return wid;}
void Setwid(int no){wid = no;}
};
class Student:public Person
{
int ino;
float score[5];
float ave;
public:
Student(const char* cpname = NULL,int age = 0, char sex = 'm', int no = 0):Person(cpname, age, sex),ino(no){}
int Getno(){return ino;}
void Setno(int no){ino = no;}
void Setscore(const float fdata[]);
float Getavescore(){return ave;}
void print();
};
void Studentprint(Student &a)
{
cout << "Student's infomation:";
cout << "Ino:" << a.Getno() << " " << "Age:" << a.Getage() << " " << "Name:" << a.Getname() << "Sex:" << a.Getsex() <<endl;
}
Person::Person(const char* cpname, int age, char sex)
{
csex = sex;
iage = age;
if(strlen(cpname) < 20)
{
strcpy(strname, cpname);
}
else
strname[0] = '\0';
}
void Person::Setname(const char* cpname)
{
if(strlen(cpname) < 20)
strcpy(strname, cpname);
else
strname[0] = '\0';
}
void Student::Setscore(const float fdata[])
{
float fsum = 0;
for(int i = 0; i < 5; i++)
{
score[i] = fdata[i];
fsum += fdata[i];
}
ave = fsum/5;
}
int main()
{
Student a("Tyler", 23, 'm', 00001);
Studentprint(a);
return 0;
}
7.4構(gòu)造與析構(gòu)順序
當(dāng)采用繼承方式創(chuàng)建子類對象時(shí),首先從父類開始執(zhí)行構(gòu)造函數(shù)酸员,父類的成員,然后再執(zhí)行子類的構(gòu)造函數(shù)讳嘱,子類成員幔嗦;當(dāng)撤銷子類對象時(shí),執(zhí)行相反的順序沥潭,即首先撤銷子類的成員邀泉,執(zhí)行子類的析構(gòu)函數(shù),在撤銷父類成員钝鸽,執(zhí)行父類的析構(gòu)函數(shù)汇恤。
#include <iostream>
using namespace std;
class A
{
int a;
public:
A(int i = 0):a(i)
{
cout << "A is constructed" <<endl;
}
~A()
{
cout << "A is destructed" <<endl;
}
};
class B:public A
{
int b;
public:
B(int j = 0):b(j)
{
cout << "B is constructed" <<endl;
}
~B()
{
cout << "B is destructed" <<endl;
}
};
int main()
{
B b;
return 0;
}
構(gòu)造順序:
(1)調(diào)用基類的構(gòu)造函數(shù)。
(2)根據(jù)類中聲明的順序構(gòu)造組合對象拔恰。
(3)派生類中構(gòu)造函數(shù)的執(zhí)行因谎。
派生類中構(gòu)造函數(shù)的格式:
class 派生類名:[public|private|protected]基類名
{
public:
派生類名(參數(shù)列表1):基類名(參數(shù)列表2),組合對象列表{...}
};
組合類中構(gòu)造函數(shù)與析構(gòu)函數(shù)的調(diào)用順序:
#include <iostream>
using namespace std;
class X
{
public:
X()
{
cout << "X is constructed" <<endl;
}
~X()
{
cout << "X is destructed" <<endl;
}
};
class A
{
int a;
X x;
public:
A(int i = 0)
{
cout << "A is constructed" <<endl;
}
~A()
{
cout << "A is destructed" <<endl;
}
};
class Y
{
int y;
public:
Y(int i = 0)
{
y = i;
cout << "Y is constructed" <<endl;
}
~Y()
{
cout << "Y is destructed" <<endl;
}
};
class Z
{
int z;
public:
Z(int i = 0)
{
z = i;
cout << "Z is constructed" <<endl;
}
~Z()
{
cout << "Z is destructed" <<endl;
}
};
class B:public A
{
int b;
Y y;
Z z;
public:
B(int j = 0):A(1),z(j),b(j),y(j)
{
cout << "B is constructed" <<endl;
}
~B()
{
cout << "B is destructed" <<endl;
}
};
int main()
{
B b;
return 0;
}
如果基類的構(gòu)造函數(shù)沒有參數(shù),或者沒有顯示定義構(gòu)造函數(shù)時(shí)仁连,派生類構(gòu)造時(shí)可以不向基類傳遞參數(shù)蓝角,甚至可以不定義構(gòu)造函數(shù)。
注意:
(1)派生類不能繼承基類的構(gòu)造函數(shù)和析構(gòu)函數(shù)饭冬。當(dāng)基類帶有參數(shù)的構(gòu)造函數(shù)時(shí)使鹅,派生類必須定義構(gòu)造函數(shù),以便把參數(shù)傳遞給基類構(gòu)造函數(shù)昌抠。
(2)當(dāng)派生類也作為基類使用時(shí)患朱,則各派生類只負(fù)責(zé)其直接的基類的構(gòu)造。
(3)因?yàn)槲鰳?gòu)函數(shù)不帶參數(shù)炊苫,派生類中析構(gòu)函數(shù)的存在不依賴于基類裁厅,基類中析構(gòu)函數(shù)的存在也不依賴于派生類。
繼承類中構(gòu)造與析構(gòu)函數(shù)的調(diào)用順序:
#include <iostream>
using namespace std;
class A
{
int a;
public:
A(int i = 0)
{
cout << "A is constructed" <<endl;
}
~A()
{
cout << "A is destructed" <<endl;
}
};
class Y
{
int y;
public:
Y(int i = 0)
{
y = i;
cout << "Y is constructed" <<endl;
}
~Y()
{
cout << "Y is destructed" <<endl;
}
};
class B:public A
{
int b;
Y y;
public:
B(int j = 0):b(j),y(j)
{
cout << "B is constructed" <<endl;
}
~B()
{
cout << "B is destructed" <<endl;
}
};
class C:public B
{
int c;
public:
C(int i):B(1),c(i)
{
cout << "C is constructed" <<endl;
}
~C()
{
cout << "C is destructed" <<endl;
}
};
int main()
{
C c(2);
return 0;
}
7.5派生類重載基類函數(shù)的訪問
如果在基類中有一個(gè)函數(shù)名被重載幾次侨艾,在派生類中又重定義了這個(gè)函數(shù)名执虹,則在派生類中會(huì)覆蓋這個(gè)函數(shù)的所有基類定義。也就是說唠梨,通過派生類來訪問該函數(shù)時(shí)袋励,由于采用就近匹配的原則,只會(huì)調(diào)用在派生類中所定義的該函數(shù),基類中所定義的函數(shù)都變得不再可用茬故。
派生類中重載基類函數(shù):
#include <iostream>
using namespace std;
class Number
{
public:
void print(int i) {cout << i;}
void print(char c) {cout << c;}
void print(float f){cout << f;}
};
class Data:public Number
{
public:
void print(){}
void f()
{
//print(5); //錯(cuò)誤盖灸,因?yàn)樾露x的print()函數(shù)不帶參數(shù)
}
};
int main()
{
Data data;
data.print(); //正確
data.print(1); //錯(cuò)誤
//data.print(12.3); //錯(cuò)誤
//data.print('a'); //錯(cuò)誤
return 0;
}
因?yàn)镈ata類中對print()函數(shù)做了重定義,所以沒有找到一個(gè)匹配的函數(shù)給Data類對象調(diào)用磺芭。如果要訪問基類中聲明的函數(shù)赁炎,則用以下方法:
(1)使用作用域標(biāo)識符限定。
Data.Number::print(5); //正確钾腺,被調(diào)用的函數(shù)是基類Number的print(int)
(2)避免名稱覆蓋徙垫。
class Number
{
public:
void print(int i) {cout << i;}
void print(char c) {cout << c;}
void print(float f) {cout << f;}
};
class Data:public Number
{
public:
void print2(){}
void f()
{
print(1);
}
};
函數(shù)f()中調(diào)用基類函數(shù)print()的方法有稱為”向上映射”,即派生類向上調(diào)用基類的函數(shù)垮庐。向上映射是派生類中在不引用歧義的情形下才有松邪,即沒有名稱覆蓋發(fā)生。
7.6多態(tài)性&虛函數(shù)
面向?qū)ο蟪绦蛟O(shè)計(jì)的真正優(yōu)勢不僅僅在于繼承哨查,還在于將派生類對象當(dāng)基類對象一樣處理的功能逗抑。支持這種功能的機(jī)制就是多態(tài)和動(dòng)態(tài)綁定。
多態(tài)是指同樣的消息被不同類型的對象接收時(shí)導(dǎo)致不同的行為寒亥。所謂消息是指對類的成員函數(shù)的調(diào)用邮府,不同的行為是指不同的實(shí)現(xiàn),也就是調(diào)用了不同的函數(shù)溉奕。最簡單的例子就是運(yùn)算符褂傀,使用同樣的加號”+”,就可以實(shí)現(xiàn)整型數(shù)之間加勤、浮點(diǎn)數(shù)之間仙辟、雙精度浮點(diǎn)數(shù)之間的加法,以及這幾種數(shù)據(jù)類型混合的加法運(yùn)算鳄梅。同樣的相加操作叠国,被不同類型的對象接收后,不同類型的變量采用不同的方式進(jìn)行加法運(yùn)算戴尸。如果是不同類型的變量相加粟焊,例如浮點(diǎn)數(shù)和整型數(shù),則要先將整型數(shù)轉(zhuǎn)換為浮點(diǎn)數(shù)孙蒙,然后再進(jìn)行加法運(yùn)算项棠,這就是典型的多態(tài)現(xiàn)象。
多態(tài)的類型:面向?qū)ο蟮亩鄳B(tài)性可以分為4類挎峦,重載多態(tài)香追、強(qiáng)制多態(tài)、包含多態(tài)和參數(shù)多態(tài)坦胶。前面兩種統(tǒng)稱為專用多態(tài)翅阵,而后面的兩種稱為通用多態(tài)歪玲。之前所學(xué)習(xí)的普通函數(shù)、類的成員函數(shù)的重載以及運(yùn)算符重載都屬于重載多態(tài)掷匠。上述加法運(yùn)算符在進(jìn)行浮點(diǎn)數(shù)與整型數(shù)相加時(shí),首先進(jìn)行類型強(qiáng)制轉(zhuǎn)換岖圈,把整數(shù)變?yōu)楦↑c(diǎn)數(shù)再相加的情況讹语,就是強(qiáng)制多態(tài)的實(shí)例。包含多態(tài)是類族定義與不同類中的同名成員函數(shù)的多態(tài)行為蜂科,主要是通過虛函數(shù)來實(shí)現(xiàn)的顽决。參數(shù)多態(tài)與類模板相關(guān)聯(lián),在使用時(shí)必須賦予實(shí)際的類型才可以實(shí)例化导匣。這樣才菠,由類模板實(shí)例化的各個(gè)類都具有相同的操作,而操作對象的類型卻各不相同贡定。
多態(tài)性是面向?qū)ο蟪绦蛟O(shè)計(jì)的重要特征赋访,重載和虛函數(shù)是體現(xiàn)多態(tài)性的兩個(gè)重要手段。虛函數(shù)體現(xiàn)了多態(tài)的靈活性缓待,進(jìn)一步減少冗余信息蚓耽,顯著提高了軟件的可擴(kuò)充性。通過學(xué)習(xí)函數(shù)重載與繼承方法后旋炒,經(jīng)常會(huì)遇到以下問題:在派生類中存在對基類函數(shù)的重載步悠,當(dāng)通過派生類對象調(diào)用重載函數(shù)時(shí)卻調(diào)用了基類的原函數(shù)。
通過派生類對象間接調(diào)用重載函數(shù):
#include <iostream>
using namespace std;
class Ins
{
public:
void play() const
{
cout << "Ins::play" <<endl;
}
};
class Piano:public Ins
{
public:
void play() const
{
cout << "Piano::play" <<endl;
}
};
void tune(Ins &i) //由參數(shù)類型決定
{
i.play();
}
int main()
{
Piano a;
tune(a);
return 0;
}
7.6.1靜態(tài)綁定與動(dòng)態(tài)綁定
綁定瘫镇,又稱聯(lián)編鼎兽,是使一個(gè)計(jì)算機(jī)程序的不同部分彼此關(guān)聯(lián)的過程。根據(jù)進(jìn)行綁定所處階段的不同铣除,有兩種不同的綁定方法谚咬,靜態(tài)綁定和動(dòng)態(tài)綁定。
(1)靜態(tài)綁定在編譯階段完成通孽,所有綁定過程都在程序開始之前完成序宦。靜態(tài)綁定具有執(zhí)行速度快的特點(diǎn),因?yàn)樵诔绦蜻\(yùn)行前背苦,編譯程序能夠進(jìn)行代碼優(yōu)化互捌。
函數(shù)重載(包括成員函數(shù)重載和派生類對基類函數(shù)的重載)就是靜態(tài)綁定。而上例中的問題根源在于:通過指針或引用引起的對普通成員函數(shù)的調(diào)用行剂,由參數(shù)的類型決定秕噪,而在指針或引用實(shí)際指向的對象無關(guān)。這也是靜態(tài)綁定的限定性厚宰。
(2)如果編譯器在編譯階段不確切地知道把發(fā)送到對象的消息和實(shí)現(xiàn)消息的哪段代碼具體聯(lián)系到一起腌巾,而是運(yùn)行時(shí)才把函數(shù)調(diào)用與函數(shù)具體聯(lián)系在一起遂填,就稱作動(dòng)態(tài)綁定。相對于靜態(tài)綁定澈蝙,動(dòng)態(tài)綁定是在編譯后綁定吓坚,也稱晚綁定,又稱運(yùn)行時(shí)識別灯荧。動(dòng)態(tài)綁定具有靈活性好礁击、更高級更自然的問題抽象、易于擴(kuò)充和易于維護(hù)等特點(diǎn)逗载。通過動(dòng)態(tài)綁定哆窿,可以動(dòng)態(tài)地根據(jù)指針或引用指向的對象實(shí)際類型來選擇調(diào)用的函數(shù)。
7.6.2虛函數(shù)
虛函數(shù)定義格式:
class 基類名
{
...
virtual 返回值類型 要在派生類中重載的函數(shù)名(參數(shù)列表);
};
用虛函數(shù)方法實(shí)現(xiàn)對派生類中重載函數(shù)的調(diào)用:
#include <iostream>
using namespace std;
class Ins
{
public:
virtual void play() const
{
cout << "Ins::play" <<endl;
}
};
class Piano:public Ins
{
public:
void play() const
{
cout << "Piano::play" <<endl;
}
};
class Newpiano:public Piano
{
public:
void play() const
{
cout << "Newpiano::play" <<endl;
}
};
void tune(Ins &i) //由參數(shù)類型決定
{
i.play();
}
int main()
{
Piano a;
tune(a);
Newpiano b;
tune(b);
Ins *p = &a;
tune(*p);
p = &b;
tune(*p);
Ins c;
tune(c);
return 0;
}
使用虛函數(shù)時(shí)需要注意:
(1)必須在基類中聲明虛函數(shù)厉斟,即需要在派生類中重載的函數(shù)挚躯,必須在基類中聲明為虛函數(shù)。
(2)虛函數(shù)一經(jīng)聲明擦秽,在派生類中重載的基類中的函數(shù)即是虛函數(shù)码荔,不需要再加virtual。
(3)只有非靜態(tài)成員函數(shù)可以聲明為虛函數(shù)号涯。靜態(tài)成員函數(shù)和全局函數(shù)不能聲明為虛函數(shù)目胡。
(4)編譯器把名稱相同、參數(shù)不同的函數(shù)看做不同的函數(shù)链快∮海基類和派生類中有相同名字但是參數(shù)列表不同的函數(shù),不需要聲明為虛函數(shù)域蜗。
(5)普通對象調(diào)用虛函數(shù)巨双,系統(tǒng)仍然以靜態(tài)綁定方式調(diào)用函數(shù)。因?yàn)榫幾g器編譯時(shí)能確切知道對象的類型霉祸,能確切調(diào)用其成員函數(shù)筑累。
Piano x;
Newpiano y;
X = y;
tune(x);
運(yùn)行結(jié)果是Piano::play
7.6.3虛析構(gòu)函數(shù)
通過學(xué)習(xí)虛函數(shù),可以掌握虛函數(shù)在繼承和派生中的調(diào)用方式丝蹭。
(1)構(gòu)造函數(shù)不能聲明為虛函數(shù)慢宗。因?yàn)闃?gòu)造函數(shù)有特殊的工作,它處在對象創(chuàng)建初期奔穿,首先調(diào)用基類構(gòu)造函數(shù)镜沽,然后調(diào)用按照繼承順序派生的派生類的構(gòu)造函數(shù)。
(2)析構(gòu)函數(shù)能夠且常常必須是虛函數(shù)贱田。析構(gòu)函數(shù)調(diào)用順序與構(gòu)造函數(shù)完全相反缅茉,從最晚派生類開始,依次向上到基類男摧。析構(gòu)函數(shù)確切地知道它是從哪個(gè)類派生而來的蔬墩。
虛析構(gòu)函數(shù)聲明格式:
virtual ~析構(gòu)函數(shù)名稱();
虛析構(gòu)函數(shù)定義:
class Ins
{
public:
virtual void play()
{
cout << “Ins::play” <<endl;
}
virtual ~Ins();
};
虛函數(shù)的目的是讓派生類去做”自己想做的事”译打。所以應(yīng)該在基類中聲明虛析構(gòu)函數(shù)。當(dāng)類中存在虛函數(shù)時(shí)拇颅,也應(yīng)該使用虛析構(gòu)函數(shù)奏司。這樣保證類對象銷毀時(shí)得到”完整”空間。如果某個(gè)類不包含虛函數(shù)蔬蕊,一般是表示它將不作為一個(gè)基類來使用结澄。當(dāng)一個(gè)類不準(zhǔn)備作為基類使用時(shí),建議不要將析構(gòu)函數(shù)聲明為虛函數(shù)岸夯,以保證程序執(zhí)行的高效性。
7.7純虛函數(shù)和抽象基類
工作有時(shí)需要定義這么一個(gè)類们妥,對這個(gè)類中的處理函數(shù)只需要說明函數(shù)的名稱猜扮、參數(shù)列表,以及返回值的類型监婶,也就是只提供一個(gè)接口旅赢,以說明和規(guī)范其他程序?qū)Υ朔?wù)的調(diào)用。至于這個(gè)函數(shù)如何實(shí)現(xiàn)惑惶,則根據(jù)具體需要在派生類中定義即可煮盼。通常把這樣的類稱為抽象基類,而把這樣的函數(shù)稱為純虛函數(shù)带污。純虛函數(shù)定義格式:
virtual 返回值類型 函數(shù)名稱(參數(shù)列表) = 0;
當(dāng)一個(gè)類中存在純虛函數(shù)時(shí)僵控,這個(gè)類就是抽象類。
class Ins
{
public:
virtual void play()const = 0; //純虛函數(shù)
};
抽象類的主要作用是通過它為一個(gè)類建立一個(gè)公共的接口鱼冀,使他們能夠更有效地發(fā)揮多態(tài)特性报破。使用抽象類時(shí)要注意:
(1)抽象類只能用做其他類的基類,不能建立抽象類對象千绪。抽象類處于繼承層次結(jié)構(gòu)的較上層充易,抽象類自身無法實(shí)例化,只能通過繼承機(jī)制荸型,生成抽象類的非抽象派生類盹靴,然后再實(shí)例化(抽象類提供了若干函數(shù)接口,當(dāng)有其他類需要時(shí)瑞妇,可以通過多重繼承來使用這些函數(shù)接口稿静,而新派生類不是一個(gè)抽象類,所以可以實(shí)例化)踪宠。
(2)抽象類不能用做參數(shù)類型自赔、函數(shù)返回值或顯式轉(zhuǎn)換的類型。
(3)可以聲明一個(gè)抽象類的指針和引用柳琢。通過指針或引用绍妨,可以指向并訪問派生類對象润脸,以訪問派生類的成員。
(4)抽象類派生出新的類之后他去,如果派生類給出所有純虛函數(shù)的函數(shù)實(shí)現(xiàn)毙驯,這個(gè)派生類就可以聲明自己的對象,因而不再是抽象類灾测;反之爆价,如果派生類沒有給出全部純虛函數(shù)的實(shí)現(xiàn),這時(shí)的派生類仍然是一個(gè)抽象類媳搪。
用抽象類的方法:
#include <iostream>
using namespace std;
class Ins
{
public:
virtual void play() const = 0;
};
class Piano:public Ins
{
public:
void play() const
{
cout << "Piano::play" <<endl;
}
};
class Newpiano:public Piano
{
public:
void play() const
{
cout << "Newpiano::play" <<endl;
}
};
void tune(Ins &i) //由參數(shù)類型決定
{
i.play();
}
int main()
{
Piano a;
tune(a);
Newpiano b;
tune(b);
Ins *p = &a;
tune(*p);
p = &b;
tune(*p);
//Ins c; //錯(cuò)誤铭段,Ins是抽象類,不能實(shí)例化
return 0;
}
純虛函數(shù)非常有用秦爆,因?yàn)樗沟妙愑忻黠@的抽象性序愚,并告訴用戶和編譯器希望如何使用。在基類中等限,對純虛函數(shù)提供定義時(shí)有可能的爸吮,告訴編譯器不允許純抽象基類聲明對象,而且純虛函數(shù)在派生類中必須定義望门,以便于創(chuàng)建對象形娇。然而,如果希望一塊代碼對于一些或所有派生類定義能共同使用筹误,而不希望在每個(gè)函數(shù)中重復(fù)這段代碼桐早,具體實(shí)現(xiàn)方法如下:
#include <iostream>
using namespace std;
class Ins
{
public:
virtual void play() const = 0;
virtual void showmsg() const
{
cout << "Ins::showmsg()" <<endl;
}
};
class Piano:public Ins
{
public:
void play() const
{
cout << "Piano::play" <<endl;
}
void showmsg()const
{
Ins::showmsg();
}
};
class Newpiano:public Piano
{
public:
void play() const
{
cout << "Newpiano::play" <<endl;
}
void showmsg()const
{
Ins::showmsg();
}
};
void tune(Ins &i) //由參數(shù)類型決定
{
i.play();
}
int main()
{
Piano a;
tune(a);
a.showmsg();
Newpiano b;
tune(b);
b.showmsg();
return 0;
}
7.8 多重繼承
在派生類的聲明中,基類名可以有一個(gè)纫事,也可以有多個(gè)勘畔。如果只有一個(gè)基類名,則這種繼承方式稱為單繼承丽惶;如果基類名有多個(gè)炫七,則這種繼承方式稱為多繼承,這時(shí)的派生類同時(shí)得到了多個(gè)已有類的特征钾唬。
7.8.1多繼承語法
多繼承允許派生類有兩個(gè)或多個(gè)基類的能力万哪,這是為了使多個(gè)類以這種方式組合起來,使得派生類對象的行為具有多個(gè)基類對象的特征抡秆。多繼承聲明語法:
class 派生類名:[繼承方式]基類名1奕巍,[繼承方式]基類名2,...[繼承方式]基類名n
多繼承的使用:
#include <iostream>
using namespace std;
class A
{
int a;
public:
void SetA(int x)
{
a = x;
}
};
class B
{
int b;
public:
void SetB(int x)
{
b = x;
}
};
class C:public A, private B
{
int c;
public:
void SetC(int , int, int);
};
void C::SetC(int x, int y, int z)
{
SetA(x);
SetB(y);
c = z;
}
int main()
{
C obj;
obj.SetA(5);
obj.SetC(6, 7, 9);
//obj.setB(6); //錯(cuò)誤儒士,不能訪問私有繼承的基類成員
return 0;
}
7.8.2多繼承中的二義性
多繼承中的二義性例子:
#include <iostream>
using namespace std;
class A
{
int a;
public:
void f(int x)
{
a = x;
}
};
class B
{
int b;
public:
void f(int x)
{
b = x;
}
};
class C:public A, private B
{
int c;
public:
void SetC(int);
};
void C::SetC(int x)
{
c = x;
}
int main()
{
C obj;
//obj.f(5); //錯(cuò)誤的止,不能訪問私有繼承的基類成員
return 0;
}
對編譯器來說,不知道是調(diào)用基類A的f()着撩,還是調(diào)用基類B的f()诅福。解決的辦法是使用域名控制來解決:
obj.A::f(5);
obj.B::f(5);
增加作用域分辨符可以消除二義性匾委,但降低了程序的可讀性。因此氓润,盡量避免在基類中使用同名函數(shù)赂乐。
多繼承里還有這樣的情況:有相同基類帶來的二義性。例如:
class base
{
...
public:
void show();
};
class c1:public base
{
...
public:
void show();
};
class c2:public base
{
...
public:
void show();
};
class a:public c1,public c2
{
...
public:
void show();
};
這種情況被稱為菱形繼承咖气。類a的父類c1挨措、c2繼承了基類base的成員函數(shù)show(),當(dāng)然類a也繼承了成員函數(shù)show()崩溪,a調(diào)用show()則產(chǎn)生了二義性浅役;類a繼承c1、c2的同時(shí)也包含了兩份基類base伶唯,菱形繼承使得子對象重疊担租,增加了額外的空間開銷谣旁。為了解決此類問題涩嚣,C++引入了虛基類莺债。
如把一個(gè)基類定義為虛基類,必須在派生子類時(shí)在父類的名字前加關(guān)鍵字virtual反惕。
Class 派生類名:virtual 訪問權(quán)限修飾符 父類名{};
虛基類使用方法:
#include <iostream>
using namespace std;
class base
{
public:
virtual char* show()const = 0;
};
class d1:virtual public base
{
public:
char* show()const{return (char*)"d1";}
};
class d2:virtual public base
{
public:
char* show()const{return (char*)"d2";}
};
class m:public d1,public d2
{
public:
char* show()const
{
return d2::show();
}
};
int main()
{
m m1;
cout << m1.show() <<endl;
return 0;
}
7.8.3最終派生類
在上一個(gè)例子中,各類沒有構(gòu)造函數(shù)演侯,使用的是默認(rèn)構(gòu)造函數(shù)姿染。如果類里有了構(gòu)造函數(shù),情形將有所不同秒际。如在上面的base基類里增加構(gòu)造函數(shù):
Base(int){}
則用派生類聲明對象時(shí)悬赏,編譯器報(bào)錯(cuò):沒有合適的構(gòu)造函數(shù)調(diào)用。為解決此類問題娄徊,首先引入最終派生類的概念闽颇。
最終派生類,又稱為最晚輩派生類寄锐,指當(dāng)前所在的類兵多。例如,在基類base的構(gòu)造函數(shù)里橄仆,base就是最終派生類剩膘;在派生類a里,a就稱為最終派生類盆顾;在類c1里怠褐,類c1就成為最終派生類。當(dāng)使用虛基類時(shí)您宪,尤其是帶有參數(shù)的構(gòu)造函數(shù)的虛基類時(shí)奈懒,最終派生類的構(gòu)造函數(shù)必須對虛基類初始化奠涌。不管派生類離虛基類多遠(yuǎn),都必須對虛基類初始化筐赔。
含虛基類的構(gòu)造函數(shù)使用方法:
#include <iostream>
using namespace std;
class base
{
int i;
public:
base(int x):i(x){}
int geti(){return i;}
virtual char* show()const
{
return (char*)"base";
}
};
class d1:virtual public base
{
int id1;
public:
d1(int x = 1):base(0),id1(x){}
char* show()const{return (char*)"d1";}
};
class d2:virtual public base
{
int id2;
public:
d2(int x = 2):base(1),id2(x){}
char* show()const{return (char*)"d2";}
};
class m:public d1,public d2
{
int im;
public:
m(int x = 0):base(3),im(x){}
char* show()const
{
return d2::show();
}
};
int main()
{
m m1;
cout << m1.show() <<endl;
cout << m1.geti() <<endl;
d1 d11;
cout << d11.geti() <<endl;
cout << d11.show() <<endl;
return 0;
}
使用虛基類時(shí)要注意:
(1)必須在派生類的構(gòu)造函數(shù)中調(diào)用初始化虛基類的構(gòu)造函數(shù)铣猩。
(2)給虛基類安排默認(rèn)構(gòu)造函數(shù)。使得虛基類的使用者變得簡單易行茴丰。
7.8.4多繼承的構(gòu)造順序
#include <iostream>
using namespace std;
class base
{
int i;
public:
base(int x):i(x)
{
cout << "base is cnostructed" <<endl;
}
virtual ~base()
{
cout << "base is destructed" <<endl;
}
int geti(){return i;}
virtual char* show()const = 0;
};
class d1:virtual public base
{
int id1;
public:
d1(int x = 1):base(0),id1(x)
{
cout << "d1 is constructed" <<endl;
}
virtual ~d1()
{
cout << "d1 is destructed" <<endl;
}
char* show()const{return (char*)"d1";}
};
class d2:virtual public base
{
int id2;
public:
d2(int x = 2):base(1),id2(x)
{
cout << "d2 is constructed" <<endl;
}
virtual ~d2()
{
cout << "d2 is destructed" <<endl;
}
char* show()const{return (char*)"d2";}
};
class m:public d1,public d2
{
int im;
public:
m(int x = 0):base(3),im(x)
{
cout << "m is constructed" <<endl;
}
virtual ~m()
{
cout << "m is destructed" <<endl;
}
char* show()const
{
return d2::show();
}
};
int main()
{
m m1;
cout << m1.show() <<endl;
return 0;
}
多繼承構(gòu)造順序與單繼承構(gòu)造順序類似达皿,從基類開始,沿著派生順序逐層向下贿肩,當(dāng)同一層次派生同一個(gè)類時(shí)峦椰,按照聲明繼承的順序自左到右。例如汰规,c1汤功、c2派生a時(shí),先構(gòu)造c1溜哮,再構(gòu)造c2滔金。析構(gòu)順序與構(gòu)造順序相反。
7.9派生類成員的標(biāo)識與訪問
經(jīng)過類的派生茂嗓,就形成了一個(gè)具有層次結(jié)構(gòu)的類族餐茵。接下來就看看派生類使用過程中的一些問題,也就是標(biāo)識和訪問派生類及其對象的成員問題述吸。
派生類中忿族,成員可以按訪問屬性劃分為以下4種:
(1)不可訪問的成員。這是從基類私有成員繼承而來的蝌矛,派生類或是建立派生類對象的模塊都沒有辦法訪問到它們道批,如果從派生類繼續(xù)派生新類,也是無法訪問的入撒。
(2)私有成員隆豹。包括從基類繼承過來的成員以及新增加的成員,在派生類內(nèi)部可以訪問衅金,但是建立派生類對象的模塊中無法訪問噪伊,繼續(xù)派生,就成為了新的派生類中的不可訪問成員氮唯。
(3)保護(hù)成員鉴吹。可能是新增也可能是從基類繼承過來的惩琉,派生類內(nèi)部成員可以訪問豆励,建立派生類對象的模塊無法訪問,進(jìn)一步派生,在新的派生類中可以成為私有成員或保護(hù)成員良蒸。
(4)公有成員技扼。派生類、建立派生類的模塊都可以訪問嫩痰,繼續(xù)派生剿吻,可能是新派生類中的私有、保護(hù)或者共有成員串纺。
7.9.1作用域分辨符
作用域分辨符丽旅,就是我們經(jīng)常見到的”::”,它可以用來限定要訪問的成員所在的類的名稱纺棺。一般使用形式是:
類名::成員名 //數(shù)據(jù)成員
類名::成員名(參數(shù)表) //函數(shù)成員
對于在不同的作用域聲明的標(biāo)識符榄笙,可見性原則是:如果存在兩個(gè)或多個(gè)具有包含關(guān)系的作用域,外層聲明了一個(gè)標(biāo)識符祷蝌,而內(nèi)層沒有再次聲明同名標(biāo)識符茅撞,那么外層標(biāo)識符在內(nèi)層仍然可見;如果在內(nèi)層聲明了同名標(biāo)識符巨朦,則外層標(biāo)識符在內(nèi)層不可見米丘,這時(shí)稱內(nèi)層標(biāo)識符隱藏了外層同名標(biāo)識符,這種現(xiàn)象稱為隱藏規(guī)則糊啡。
在類的派生層次結(jié)構(gòu)中蠕蚜,基類的成員和派生類新增的成員都具有類作用域。二者的作用范圍不同悔橄,是相互包含的兩個(gè)層,派生類在內(nèi)層腺毫。這是如果派生類聲明了一個(gè)和某個(gè)基類成員同名的新成員癣疟,派生的新成員就隱藏了外層同名成員,直接使用成員名只能訪問到派生類的成員潮酒。如果派生類中聲明了與基類成員函數(shù)同名的新函數(shù)睛挚,即使函數(shù)的參數(shù)表不同,從基類繼承的同名函數(shù)的所有重載形式也都會(huì)被隱藏急黎。如果要訪問被隱藏的成員扎狱,就需要使用作用域分辨符和基類名來限定。
對于多繼承情況勃教,首先要考慮各個(gè)基類之間有沒有任何繼承關(guān)系淤击,同時(shí)考慮有沒有共同基類的情況。最經(jīng)典的情況就是所有基類都沒有上級基類故源。如果某派生類的多個(gè)基類擁有同名的成員污抬,同時(shí),派生類有新增這樣的同名成員,在這種情況下印机,派生類成員將隱藏所有基類的同名成員矢腻。這時(shí)使用”對象名.成員名”或”對象指針->成員名”方式可以唯一標(biāo)識和訪問派生類新增成員,基類的同名成員也可以使用基類名和作用域分辨符訪問射赛。但是多柑,如果派生類沒有聲明同名成員,”對象名.成員名”或”對象指針->成員名”方式就無法唯一標(biāo)識成員楣责。這時(shí)竣灌,從不同基類繼承過來的成員具有相同的名稱,同時(shí)具有相同的作用域腐魂,系統(tǒng)僅僅根據(jù)這些信息根本無法判斷到底是調(diào)用哪個(gè)基類的成員帐偎,這時(shí)就必須通過基類名和作用域分辨符來標(biāo)識成員。
細(xì)節(jié):如果子類中定義的函數(shù)與父類的函數(shù)同名但是具有不同的參數(shù)數(shù)量或參數(shù)類型蛔屹,不屬于函數(shù)重載削樊,這時(shí)子類中的函數(shù)將使父類中的函數(shù)隱藏,調(diào)用父類中的函數(shù)必須使用父類名稱來限定兔毒。只有在相同的作用域中定義的函數(shù)才可以重載漫贞。
#include <iostream>
using namespace std;
class Base1
{
public:
int var;
void fun(){cout << “Member of Base1” <<endl;}
};
class Base2
{
public:
int var;
void fun(){cout << “Member of Base2” <<endl}
};
class Derived:public Base1,public Base2
{
public:
int var;
void fun(){cout << “Member of Derived” <<endl;}
};
int main()
{
Derived d;
Derived *p = &d;
d.var = 1;
d.fun();
d.Base1::var = 2;
d.Base2::fun();
p->Base2::var = 3;
p->Base2::fun();
return 0;
}
在主函數(shù)中,創(chuàng)建了一個(gè)派生類的對象d育叁,根據(jù)隱藏規(guī)則迅脐,如果通過成員名稱來訪問該類成員,就只能訪問到派生類新增加的兩個(gè)成員豪嗽,從基類繼承過來的成員由于處于外層作用域而被隱藏谴蔑。這時(shí)要想訪問從基類繼承來的成員,就必須使用類名和作用域分辨符龟梦。程序中后兩組語句就是分別訪問由基類Base1隐锭,Base2繼承來的成員。通過作用域分辨符计贰,就明確地唯一標(biāo)識了派生類中由基類所繼承來的成員钦睡,達(dá)到了訪問的目的,解決了成員被隱藏的問題躁倒。
如果將類Derived寫成這樣:
class Derived:public Base1,public Base2{};
此時(shí)在主函數(shù)中寫:
d.var = 1; //錯(cuò)誤荞怒,這樣會(huì)有二義性
d.fun(); //錯(cuò)誤,這樣會(huì)有二義性
如果希望d.var和d.fun()的用法不產(chǎn)生二義性秧秉,可以使用using關(guān)鍵字加以澄清褐桌。例如:
class Derived:public Base1,public Base2
{
public:
using Base1::var;
using Base2::fun;
};
這樣,d.var和d.fun()都可以明確表示對Base1中相關(guān)成員的引用了象迎。using的一般功能是將一個(gè)作用域中的名字引入到另一個(gè)作用域中撩嚼,它還有一個(gè)非常有用的用法:將using用于基類中的函數(shù)名,這樣派生類中如果定義同名但參數(shù)不同的參數(shù),基類的函數(shù)不會(huì)被隱藏完丽,兩個(gè)重載的函數(shù)將會(huì)并存在派生類的作用域中恋技。例如:
class Derived2:public Base1
{
public:
using Base1::fun;
void fun(int i){...}
};
這時(shí),使用Derived2的對象逻族,既可以直接調(diào)用無參數(shù)的fun()函數(shù)蜻底,又可以直接調(diào)用帶int型參數(shù)的fun函數(shù)。
發(fā)布于 19:44
?贊同?添加評論
?分享
?收藏
?設(shè)置
?投稿
推薦閱讀
高曉松一句話暴露情商:會(huì)聊天的人聘鳞,從不說這3句話
槽值發(fā)表于槽值
楊超越是直男斬薄辅?看她穿很少女的裙子就知道了
曉文視角
我,一個(gè)直男抠璃,在親眼目睹了一次女人P圖后站楚,頓悟了
行尸走肥肉發(fā)表于直男實(shí)驗(yàn)室
細(xì)思極恐小故事系列
七味Ay
還沒有評論
寫下你的評論...
發(fā)布