
C++ 繼承
面向?qū)ο蟪绦蛟O(shè)計(jì)中最重要的一個(gè)概念是繼承。繼承允許我們依據(jù)另一個(gè)類來定義一個(gè)類,這使得創(chuàng)建和維護(hù)一個(gè)應(yīng)用程序變得更容易。這樣做,也達(dá)到了重用代碼功能和提高執(zhí)行時(shí)間的效果。
當(dāng)創(chuàng)建一個(gè)類時(shí),您不需要重新編寫新的數(shù)據(jù)成員和成員函數(shù),只需指定新建的類繼承了一個(gè)已有的類的成員即可。這個(gè)已有的類稱為基類,新建的類稱為派生類。
繼承代表了 is a 關(guān)系。例如,哺乳動(dòng)物是動(dòng)物,狗是哺乳動(dòng)物,因此,狗是動(dòng)物,等等。
基類 & 派生類
一個(gè)類可以派生自多個(gè)類,這意味著,它可以從多個(gè)基類繼承數(shù)據(jù)和函數(shù)。定義一個(gè)派生類,我們使用一個(gè)類派生列表來指定基類。類派生列表以一個(gè)或多個(gè)基類命名,形式如下:
class derived-class: access-specifier base-class
其中,訪問修飾符 access-specifier 是 public、protected 或 private 其中的一個(gè),base-class 是之前定義過的某個(gè)類的名稱。如果未使用訪問修飾符 access-specifier,則默認(rèn)為 private。
假設(shè)有一個(gè)基類 Shape,Rectangle 是它的派生類,如下所示:
class Shape {
public:
void setWidth(int w) {
width = w;
}
void setHeight(int h) {
height = h;
}
protected:
int width;
int height;
};
// 派生類
class Rectangle: public Shape
{
public:
int getArea()
{
return (width * height);
}
};
void set_print_inherit(){
Rectangle rectangle;
rectangle.setHeight(20);
rectangle.setWidth(12);
int area=rectangle.getArea();
cout<<"rectangle total area is :"<<area<<endl;
}
打印如下:
rectangle total area is :240
訪問控制和繼承
派生類可以訪問基類中所有的非私有成員。因此基類成員如果不想被派生類的成員函數(shù)訪問,則應(yīng)在基類中聲明為 private。
我們可以根據(jù)訪問權(quán)限總結(jié)出不同的訪問類型,如下所示:
| 訪問 | public | protected | private |
|---|---|---|---|
| 同一個(gè)類 | yes | yes | yes |
| 派生類 | yes | yes | no |
| 外部的類 | yes | no | no |
一個(gè)派生類繼承了所有的基類方法,但下列情況除外:
- 基類的構(gòu)造函數(shù)、析構(gòu)函數(shù)和拷貝構(gòu)造函數(shù)。
- 基類的重載運(yùn)算符。
- 基類的友元函數(shù)。
繼承類型
當(dāng)一個(gè)類派生自基類,該基類可以被繼承為 public、protected 或 private 幾種類型。繼承類型是通過上面講解的訪問修飾符 access-specifier 來指定的。
我們幾乎不使用 protected 或 private 繼承,通常使用 public 繼承。當(dāng)使用不同類型的繼承時(shí),遵循以下幾個(gè)規(guī)則:
- 公有繼承(public):當(dāng)一個(gè)類派生自公有基類時(shí),基類的公有成員也是派生類的公有成員,基類的保護(hù)成員也是派生類的保護(hù)成員,基類的私有成員不能直接被派生類訪問,但是可以通過調(diào)用基類的公有和保護(hù)成員來訪問。
- 保護(hù)繼承(protected): 當(dāng)一個(gè)類派生自保護(hù)基類時(shí),基類的公有和保護(hù)成員將成為派生類的保護(hù)成員。
- 私有繼承(private):當(dāng)一個(gè)類派生自私有基類時(shí),基類的公有和保護(hù)成員將成為派生類的私有成員。
多繼承
多繼承即一個(gè)子類可以有多個(gè)父類,它繼承了多個(gè)父類的特性。
C++ 類可以從多個(gè)類繼承成員,語法如下:
class <派生類名>:<繼承方式1><基類名1>,<繼承方式2><基類名2>,… { <派生類類體> };
//基類
class Shape {
public:
void setWidth(int w) {
width = w;
}
void setHeight(int h) {
height = h;
}
protected:
int width;
int height;
};
// 基類 PaintCost
class PaintCost
{
public:
int getCost(int area)
{
return area * 12;
}
};
// 派生類
class Rectangle: public Shape,public PaintCost
{
public:
int getArea()
{
return (width * height);
}
};
void set_print_inherit(){
Rectangle rectangle;
rectangle.setHeight(20);
rectangle.setWidth(12);
int area=rectangle.getArea();
cout<<"rectangle total area is :"<<area<<endl;
cout<<"rectangle getCost is :"<<rectangle.getCost(area)<<endl;
}
當(dāng)上面的代碼被編譯和執(zhí)行時(shí),它會(huì)產(chǎn)生下列結(jié)果:
rectangle total area is :240
rectangle getCost is :2880
另外多繼承(環(huán)狀繼承),A->D, B->D, C->(A,B),例如:
class D{......};
class B: public D{......};
class A: public D{......};
class C: public B, public A{.....};
這個(gè)繼承會(huì)使D創(chuàng)建兩個(gè)對象,要解決上面問題就要用虛擬繼承格式
格式:class 類名: virtual 繼承方式 父類名
class D{......};
class B: virtual public D{......};
class A: virtual public D{......};
class C: public B, public A{.....};
虛繼承--(在創(chuàng)建對象的時(shí)候會(huì)創(chuàng)建一個(gè)虛表)在創(chuàng)建父類對象的時(shí)候
A:virtual public D
B:virtual public D
C++ 重載運(yùn)算符和重載函數(shù)
C++ 允許在同一作用域中的某個(gè)函數(shù)和運(yùn)算符指定多個(gè)定義,分別稱為函數(shù)重載和運(yùn)算符重載。
重載聲明是指一個(gè)與之前已經(jīng)在該作用域內(nèi)聲明過的函數(shù)或方法具有相同名稱的聲明,但是它們的參數(shù)列表和定義(實(shí)現(xiàn))不相同。
當(dāng)您調(diào)用一個(gè)重載函數(shù)或重載運(yùn)算符時(shí),編譯器通過把您所使用的參數(shù)類型與定義中的參數(shù)類型進(jìn)行比較,決定選用最合適的定義。選擇最合適的重載函數(shù)或重載運(yùn)算符的過程,稱為重載決策。
C++ 中的函數(shù)重載
在同一個(gè)作用域內(nèi),可以聲明幾個(gè)功能類似的同名函數(shù),但是這些同名函數(shù)的形式參數(shù)(指參數(shù)的個(gè)數(shù)、類型或者順序)必須不同。您不能僅通過返回類型的不同來重載函數(shù)。
下面的實(shí)例中,同名函數(shù) print() 被用于輸出不同的數(shù)據(jù)類型:
#include <iostream>
using namespace std;
class printData
{
public:
void print(int i) {
cout << "整數(shù)為: " << i << endl;
}
void print(double f) {
cout << "浮點(diǎn)數(shù)為: " << f << endl;
}
void print(char c[]) {
cout << "字符串為: " << c << endl;
}
};
int main(void)
{
printData pd;
// 輸出整數(shù)
pd.print(5);
// 輸出浮點(diǎn)數(shù)
pd.print(500.263);
// 輸出字符串
char c[] = "Hello C++";
pd.print(c);
return 0;
}
C++ 中的運(yùn)算符重載
您可以重定義或重載大部分 C++ 內(nèi)置的運(yùn)算符。這樣,您就能使用自定義類型的運(yùn)算符。
重載的運(yùn)算符是帶有特殊名稱的函數(shù),函數(shù)名是由關(guān)鍵字 operator 和其后要重載的運(yùn)算符符號(hào)構(gòu)成的。與其他函數(shù)一樣,重載運(yùn)算符有一個(gè)返回類型和一個(gè)參數(shù)列表。
Box operator+(const Box&);
聲明加法運(yùn)算符用于把兩個(gè) Box 對象相加,返回最終的 Box 對象。大多數(shù)的重載運(yùn)算符可被定義為普通的非成員函數(shù)或者被定義為類成員函數(shù)。如果我們定義上面的函數(shù)為類的非成員函數(shù),那么我們需要為每次操作傳遞兩個(gè)參數(shù),如下所示:
Box operator+(const Box&, const Box&);
下面的實(shí)例使用成員函數(shù)演示了運(yùn)算符重載的概念。在這里,對象作為參數(shù)進(jìn)行傳遞,對象的屬性使用 this 運(yùn)算符進(jìn)行訪問,如下所示(借用上節(jié)代碼并做修改):
class Box {
public:
double length; // 長度
double breadth; // 寬度
double height; // 高度
friend void printBreadth(Box box) {// 請注意:printWidth() 不是任何類的成員函數(shù)
/** 因?yàn)?printWidth() 是 Box 的友元,它可以直接訪問該類的任何成員 */
cout << "breadth of box : " << box.breadth << endl;
}
int compare(Box box) {
return this->getVolume() > box.getVolume();
}
double getVolume(void) // 返回體積
{
return length * breadth * height;
}
double getLength() const {
return length;
}
void setLength(double length) {
Box::length = length;
}
double getBreadth() const {
return breadth;
}
void setBreadth(double breadth) {
Box::breadth = breadth;
}
double getHeight() const {
return height;
}
void setHeight(double height) {
Box::height = height;
}
// 重載 + 運(yùn)算符,用于把兩個(gè) Box 對象相加
Box operator+(const Box& b)
{
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
}
};
void set_print_boxes() {
Box Box1; // 聲明 Box1,類型為 Box
Box Box2; // 聲明 Box2,類型為 Box
Box Box3; // 聲明 Box3,類型為 Box
double volume = 0.0; // 用于存儲(chǔ)體積
Box *ptrBox; // Declare pointer to a class.
// box 1 詳述
Box1.setLength(6.0);
Box1.setBreadth(7.0);
Box1.setHeight(5.0);
// box 2 詳述
Box2.setLength(12.0);
Box2.setBreadth(13.0);
Box2.setHeight(10.0);
ptrBox = &Box1;
cout << "ptrBox getVolume is :" << ptrBox->getVolume() << endl;
// box 1 的體積
volume = Box1.getVolume();
cout << "Box1 getVolume is :" << volume << endl;
printBreadth(Box1);
// box 2 的體積
volume = Box2.getVolume();
cout << "Box2 getVolume is :" << volume << endl;
if (Box1.compare(Box2)) {
cout << "Box2 is smaller than Box1" << endl;
} else {
cout << "Box2 is equal to or larger than Box1" << endl;
}
Box3 = Box1 + Box2;
// box 3 的體積
volume = Box3.getVolume();
cout << "Box3 getVolume is :" << volume << endl;
}
打印結(jié)果如下;
ptrBox getVolume is :210
Box1 getVolume is :210
breadth of box : 7
Box2 getVolume is :1560
Box2 is equal to or larger than Box1
Box3 getVolume is :5400
可重載運(yùn)算符/不可重載運(yùn)算符
下面是可重載的運(yùn)算符列表:
下面是不可重載的運(yùn)算符列表:
- .:成員訪問運(yùn)算符
- ., ->:成員指針訪問運(yùn)算符
- :::域運(yùn)算符
- sizeof:長度運(yùn)算符
- ?::條件運(yùn)算符
- #: 預(yù)處理符號(hào)
值得注意的是:
- 1、運(yùn)算重載符不可以改變語法結(jié)構(gòu)。
- 2、運(yùn)算重載符不可以改變操作數(shù)的個(gè)數(shù)。
- 3、運(yùn)算重載符不可以改變優(yōu)先級(jí)。
- 4、運(yùn)算重載符不可以改變結(jié)合性。
C++ 多態(tài)
多態(tài)按字面的意思就是多種形態(tài)。當(dāng)類之間存在層次結(jié)構(gòu),并且類之間是通過繼承關(guān)聯(lián)時(shí),就會(huì)用到多態(tài)。
C++ 多態(tài)意味著調(diào)用成員函數(shù)時(shí),會(huì)根據(jù)調(diào)用函數(shù)的對象的類型來執(zhí)行不同的函數(shù)。
下面的實(shí)例中,基類 Shape 被派生為兩個(gè)類,如下所示:
//基類
class Shape {
public:
void setWidth(int w) {
width = w;
}
void setHeight(int h) {
height = h;
}
public:
Shape(int a = 0, int b = 0) {
width = a;
height = b;
}
virtual int getArea() {
cout << "Parent class area :" << endl;
return 0;
}
protected:
int width;
int height;
};
// 基類 PaintCost
class PaintCost {
public:
int getCost(int area) {
return area * 12;
}
};
// 派生類
class Rectangle : public Shape, public PaintCost {
public:
Rectangle(int a = 0, int b = 0) : Shape(a, b) {}
int getArea() {
cout << "Rectangle class area :" << (width * height) << endl;
return (width * height);
}
};
class Triangle : public Shape {
public:
Triangle(int a = 0, int b = 0) : Shape(a, b) {}
int getArea() {
cout << "Triangle class area :" << (width * height / 2) << endl;
return (width * height / 2);
}
};
void set_print_polymorphism() {
Shape *shape;
Rectangle rec(10, 7);
Triangle tri(10, 5);
// 存儲(chǔ)矩形的地址
shape = &rec;
// 調(diào)用矩形的求面積函數(shù) area
shape->getArea();
// 存儲(chǔ)三角形的地址
shape = &tri;
// 調(diào)用三角形的求面積函數(shù) area
shape->getArea();
}
打印結(jié)果:
Rectangle class area :70
Triangle class area :25
如果不加virtual,會(huì)導(dǎo)致錯(cuò)誤輸出,調(diào)用函數(shù) area() 被編譯器設(shè)置為基類中的版本,這就是所謂的靜態(tài)多態(tài),或靜態(tài)鏈接 - 函數(shù)調(diào)用在程序執(zhí)行前就準(zhǔn)備好了。有時(shí)候這也被稱為早綁定,因?yàn)?area() 函數(shù)在程序編譯期間就已經(jīng)設(shè)置好了。
虛函數(shù)
虛函數(shù) 是在基類中使用關(guān)鍵字 virtual 聲明的函數(shù)。在派生類中重新定義基類中定義的虛函數(shù)時(shí),會(huì)告訴編譯器不要靜態(tài)鏈接到該函數(shù)。
我們想要的是在程序中任意點(diǎn)可以根據(jù)所調(diào)用的對象類型來選擇調(diào)用的函數(shù),這種操作被稱為動(dòng)態(tài)鏈接,或后期綁定。
純虛函數(shù)
您可能想要在基類中定義虛函數(shù),以便在派生類中重新定義該函數(shù)更好地適用于對象,但是您在基類中又不能對虛函數(shù)給出有意義的實(shí)現(xiàn),這個(gè)時(shí)候就會(huì)用到純虛函數(shù)。
我們可以把基類中的虛函數(shù) area() 改寫如下:
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
// pure virtual function
virtual int area() = 0;
};
= 0 告訴編譯器,函數(shù)沒有主體,上面的虛函數(shù)是純虛函數(shù)。
1、純虛函數(shù)聲明如下: virtual void funtion1()=0; 純虛函數(shù)一定沒有定義,純虛函數(shù)用來規(guī)范派生類的行為,即接口。包含純虛函數(shù)的類是抽象類,抽象類不能定義實(shí)例,但可以聲明指向?qū)崿F(xiàn)該抽象類的具體類的指針或引用。
2、虛函數(shù)聲明如下:virtual ReturnType FunctionName(Parameter) 虛函數(shù)必須實(shí)現(xiàn),如果不實(shí)現(xiàn),編譯器將報(bào)錯(cuò),錯(cuò)誤提示為:
error LNK****: unresolved external symbol "public: virtual void __thiscall ClassName::virtualFunctionName(void)"3、對于虛函數(shù)來說,父類和子類都有各自的版本。由多態(tài)方式調(diào)用的時(shí)候動(dòng)態(tài)綁定。
4、實(shí)現(xiàn)了純虛函數(shù)的子類,該純虛函數(shù)在子類中就編程了虛函數(shù),子類的子類即孫子類可以覆蓋該虛函數(shù),由多態(tài)方式調(diào)用的時(shí)候動(dòng)態(tài)綁定。
5、虛函數(shù)是C++中用于實(shí)現(xiàn)多態(tài)(polymorphism)的機(jī)制。核心理念就是通過基類訪問派生類定義的函數(shù)。
6、在有動(dòng)態(tài)分配堆上內(nèi)存的時(shí)候,析構(gòu)函數(shù)必須是虛函數(shù),但沒有必要是純虛的。
7、友元不是成員函數(shù),只有成員函數(shù)才可以是虛擬的,因此友元不能是虛擬函數(shù)。但可以通過讓友元函數(shù)調(diào)用虛擬成員函數(shù)來解決友元的虛擬問題。
8、析構(gòu)函數(shù)應(yīng)當(dāng)是虛函數(shù),將調(diào)用相應(yīng)對象類型的析構(gòu)函數(shù),因此,如果指針指向的是子類對象,將調(diào)用子類的析構(gòu)函數(shù),然后自動(dòng)調(diào)用基類的析構(gòu)函數(shù)。