C/C++中虛函數詳解及其作用介紹

概述

虛函數 (virtual function) 指可以被子類繼承和覆蓋的函數.

在這裡插入圖片描述

使用方法

基類聲明成員函數為虛函數的方法:

virtual [類型] 函數名([參數表列])

註: 在類外定義虛函數時, 不需再加 virtual.

虛函數的特點:

  • 提高程序擴充性: 派生類根據需要可以進行函數覆蓋
  • 成員函數被聲明為虛數後, 其派生類中覆蓋函數自動稱為虛函數
  • 若虛函數在派生類中未重新定義, 則派生類簡單繼承其直接基類的虛函數
  • 指向基類的指針, 當指向派生類對象時, 可以嗲用派生類的方法

關聯

通過關聯 (binding), 我們可以把一個標識符和一個存儲地址聯系起來, 或者把一個函數名與一個類對象捆綁在一起.

在這裡插入圖片描述

靜態關聯

靜態關聯 (static binding) 指通過對象名調用虛函數. 在編譯時即可確定其調用的虛函數屬於哪一類

動態關聯

動態關聯 (dynamic binding) 是指通過基類指針與虛函數, 在運行階段確定關聯關系. 動態關聯提供動態的多態性, 即運行階段的多態性.

案例1

未使用虛函數

Square 類:

#ifndef PROJECT6_SQUARE_H
#define PROJECT6_SQUARE_H

class Square {
protected:
    int length;
public:
    Square(int l) : length(l) {};
    int area() const {
        return length *length;
    }
};

#endif //PROJECT6_SQUARE_H

Rectangle 類:

#ifndef PROJECT6_RECTANGLE_H
#define PROJECT6_RECTANGLE_H

#include "Square.h"

class Rectangle : public Square{
private:
    int height;
public:
    Rectangle(int l, int h) : Square(l), height(h) {};
    int area() const {
        return Square::area() * 2 + length * height * 4;  // 兩個底加四個邊
    }
};

#endif //PROJECT6_RECTANGLE_H

main:

#include <iostream>
#include "Square.h"
#include "Rectangle.h"
using namespace std;

int main() {
    // 創建對象
    Square s1(2), *pt;
    Rectangle r1(3, 3);

    pt = &s1;
    cout << pt->area() << endl;
    pt = &r1;
    cout << pt->area() << endl;

    return 0;
}

輸出結果:

4
9 // 輸出的是底面積

此時調用的是 Square 類的area()函數.

使用虛擬類

Square 類:

#ifndef PROJECT6_SQUARE_H
#define PROJECT6_SQUARE_H

class Square {
protected:
    int length;
public:
    Square(int l) : length(l) {};
    virtual int area() const {
        return length *length;
    }
};

#endif //PROJECT6_SQUARE_H

Rectangle 類:

#ifndef PROJECT6_RECTANGLE_H
#define PROJECT6_RECTANGLE_H

#include "Square.h"

class Rectangle : public Square{
private:
    int height;
public:
    Rectangle(int l, int h) : Square(l), height(h) {};
    int area() const {
        return Square::area() * 2 + length * height * 4;  // 兩個底加四個邊
    }
};

#endif //PROJECT6_RECTANGLE_H

main:

#include <iostream>
#include "Square.h"
#include "Rectangle.h"
using namespace std;

int main() {
    // 創建對象
    Square s1(2), *pt;
    Rectangle r1(3, 3);

    pt = &s1;
    cout << pt->area() << endl;
    pt = &r1;
    cout << pt->area() << endl;

    return 0;
}

輸出結果:

4
54 // 長方體的面積

此時調用的是 Rectangle 類的area()函數.

案例2

Animal 類:

#ifndef PROJECT6_ANIMAL_H
#define PROJECT6_ANIMAL_H

#include <iostream>
using namespace std;

class Animal {
public:
    virtual void bark(){
        cout << "咋叫?" << endl;
    }
};

#endif //PROJECT6_ANIMAL_H

Dog 類:

#ifndef PROJECT6_DOG_H
#define PROJECT6_DOG_H

#include "Animal.h"

class Dog : public Animal{
public:
    void bark() {
        cout << "汪汪!" << endl;
    }
};

#endif //PROJECT6_DOG_H

Cat 類:

#ifndef PROJECT6_CAT_H
#define PROJECT6_CAT_H

#include "Animal.h"

class Cat : public Animal{
public:
    void bark() {
        cout << "喵喵!" << endl;
    }
};

#endif //PROJECT6_CAT_H

Pig 類:

#ifndef PROJECT6_PIG_H
#define PROJECT6_PIG_H

#include "Animal.h"

class Pig : public Animal {
public:
    void bark() {
        cout << "哼哼!" << endl;
    }
};

#endif //PROJECT6_PIG_H

main:

#include <iostream>
#include "Animal.h"
#include "Dog.h"
#include "Cat.h"
#include "Pig.h"
using namespace std;

int main() {
    // 創建對象
    Animal a, *pt;
    Dog d;
    Cat c;
    Pig p;

    pt = &a;
    pt -> bark();  // 調用基類的bark()
    pt = &d;
    pt -> bark();  // 調用狗的bark()
    pt = &c;
    pt -> bark();  // 調用貓的bark()
    pt = &p;
    pt -> bark();  // 調用豬的bark()

    return 0;
}

輸出結果:

咋叫?
汪汪!
喵喵!
哼哼!

總結

虛函數隻能是類的成員函數, 而不能將類外的普通函數聲明為虛函數. 虛函數的作用是允許在派生類中對基類的虛函數重新定義 (函數覆蓋), 隻能用於類的繼承層次結構中.

虛函數能有效減少空間開銷. 當一個類帶有虛函數時, 編譯系統會為該類構造一個虛函數表 (一個指針數組), 用於存放每個虛函數的入口地址.

什麼時候應該使用虛函數:

  • 判斷成員函數所在的類是不是基類, 非基類無需使用虛函數
  • 成員函數在類被繼承後有沒有可能被更改的功能, 如果希望修改成員函數功能, 一般在基類中將其聲明為虛函數
  • 我們會通過對象名還是基類指針訪問成員函數, 如果通過基類指針過引用去訪問, 則應當聲明為虛函數

有時候在定義虛函數的時候, 我們無需定義其函數體. 它的作用隻是定義瞭一個虛函數名, 具體的功能留給派生類去添加, 也就是純虛函數. 例如我們在上面的 Animal 類的bark()函數就應該聲明為純虛函數, 因為 Animal 為基類, 定義bark()函數實體並無意義.

推薦閱讀: