C++ — 繼承、重載&多態(tài)

timg.jpg

C++ 繼承

面向?qū)ο蟪绦蛟O(shè)計(jì)中最重要的一個(gè)概念是繼承。繼承允許我們依據(jù)另一個(gè)類來定義一個(gè)類,這使得創(chuàng)建和維護(hù)一個(gè)應(yīng)用程序變得更容易。這樣做,也達(dá)到了重用代碼功能和提高執(zhí)行時(shí)間的效果。

文中示例代碼gayhub傳送門

當(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、protectedprivate 其中的一個(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、protectedprivate 幾種類型。繼承類型是通過上面講解的訪問修飾符 access-specifier 來指定的。

我們幾乎不使用 protectedprivate 繼承,通常使用 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)算符列表:

雙目算術(shù)運(yùn)算符 + (加),-(減),*(乘),/(除),% (取模)
關(guān)系運(yùn)算符 ==(等于),!= (不等于),< (小于),> (大于>,<=(小于等于),>=(大于等于)
邏輯運(yùn)算符 ||(邏輯或),&&(邏輯與),!(邏輯非)
單目運(yùn)算符 + (正),-(負(fù)),*(指針),&(取地址)
自增自減運(yùn)算符 ++(自增),--(自減)
位運(yùn)算符 | (按位或),& (按位與),~(按位取反),^(按位異或),,<< (左移),>>(右移)
賦值運(yùn)算符 =, +=, -=, *=, /= , % = , &=, |=, ^=, <<=, >>=
空間申請與釋放 new, delete, new[ ] , delete[]
其他運(yùn)算符 ()(函數(shù)調(diào)用),->(成員訪問),,(逗號(hào)),

下面是不可重載的運(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ù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容