目錄
1.虛函數的引入
2.虛函數作用
3.關于虛函數的幾點說明
4.純虛函數
5.抽象類
6.虛基類
1.虛函數的引入
先看如下程式,程式後有進一步的解釋。如果讀者對程式不懂請先複習基礎知識。
//
// VirtualFun.cpp
// virtual
//
// Created by 劉一丁 on 2019/8/26.
// Copyright © 2019年 LYD. All rights reserved.
//
//file1.cpp
#include <iostream>
using namespace std;
class Base //基類 Base
{
public:
Base(int x, int y)
{ a = x; b = y; }
//virtual void show() //采用虛函數解決同名函數調用問題
void show() //基類成員函數 show()
{
cout << "base++++++" << endl;
cout << a << " " << b << endl;
}
private:
int a, b;
};
class Derived:public Base //派生類 Derived
{
public:
Derived(int x, int y, int z):Base(x, y)
{ c = z; }
void show() //派生類成員函數 show()
{
cout << "Derived+++++++" << endl;
cout << " c= " << c << endl;
}
private:
int c;
};
int main()
{
Base mb(60, 60), *pc; //定義基類對象及指向基類的指針(簡稱基類指針)
Derived mc(10, 20, 30); //定義派生類對象
pc = &mb; //基類指針指向基類對象 mb
pc-> show();
pc = &mc; //基類指針指向派生類對象 mc
pc-> show();
return 0;
}
//output:
//base++++++++
//60 60
//base++++++++
//10 20
注:
C++中規定:基類的對象指針可以指向它公有派生的對象,但是當其指向公有派生類對象時,它隻能通路派生類中從基類繼承來的成員,而不能通路公有派生類中定義的成員,解決這個問題的方法就是引入虛函數。
虛函數通常和指針連用,來展現動态的多态性。使用指針的目的是為了表達一種動态的特性,即當指針指向不同對象(基類或派生類對象)時,分别調用不同類的成員函數。
2.虛函數作用
在上文中雖然基類指針 pc 已經指向了派生類對象 mc,但是它所調用的成員函數 show() 仍然是其基類對象的 show()。解決方法有三種。
①mc.show();
②((Derived*)pc)-> show();
③将基類中的同名函數聲明為虛函數,即在對應成員函數前加上關鍵字 virtual,并在派生類中被重載。
虛函數更該方法即是代碼中注釋的部分,virtual void show();函數輸出結果如下:
base++++++++
60 60
Derived+++++
c = 30
3.關于虛函數的幾點說明
(1)C++規定,如果在派生類中,沒有用 virtual 顯式地給出虛函數聲明,這是系統就會遵循以下規則來判斷一個成員函數是否是虛函數。
①該函數與基類的虛函數有相同的名稱。
②該函數與基類的虛函數有相同的參數個數及相同的對應參數類型。
③該函數與基類的虛函數有相同的傳回類型或者滿足指派相容規則的指針、引用型的傳回類型。
(2)通過虛函數來使用多态性機制,派生類必須從他的基類公有派生(指派相容規則)。
(3)虛函數必須是其所在類的成員函數,而不能是友元函數,也不能是靜态成員函數,因為虛函數調用哪個要靠特定的對象來決定該激活哪個函數。
(4)構造函數不能是虛函數,通常把析構函數聲明為虛函數(請讀者自行百度)。
4.純虛函數
基類往往表示一種抽象的概念,它并不與具體的事物相聯系。這時在基類中将某一成員函數定義為虛函數,并不是基類本身的要求,而是考慮到派生類的需要,在基類中預留了一個函數名,具體功能留給派生類根據需要去定義。
純虛函數是在聲明虛函數時被“初始化”為 0 的函數。例如
virtual void show() = 0;
純虛函數的作用是在基類中為其派生類保留一個函數的名字,以便派生類根據需要對他進行重新定義。純虛函數沒有函數體,它最後面的“ = 0” 并不表示函數的傳回值為 0,它隻起形式上的作用,告訴編譯系統“這是純虛函數”。純虛函數不具備函數的功能,不能被調用。
//
// VirtualFun.cpp
// Virtual
//
// Created by 劉一丁 on 2019/8/26.
// Copyright © 2019年 LYD. All rights reserved.
//
//file1.cpp
#include <iostream>
using namespace std;
class Circle
{
public:
void set(int x)
{ r = x; }
virtual void show() = 0; //定義純虛函數
protected:
int r;
};
class Area:public Circle
{
public:
void show()
{ cout << "Area is " << 3.14 * r * r << endl; }
};
class Perimeter:public Circle
{
public:
void show()
{ cout << "Primeter is " << 2 * 3.14 * r << endl; }
};
int main()
{
Circle *ptr;
Area ob1;
Perimeter ob2;
ob1.set(10);
ob2.set(10);
ptr = &ob1;
ptr-> show();
ptr = &ob2;
ptr-> show();
return 0;
}
//output:
//Area is 314
//Primeter is 62.8
5.抽象類
如果一個類至少有一個純虛函數,那麼就稱該類為抽象類。上述程式中定義的類 circle 就是一個抽象類。
①抽象類隻能作為其他類的基類來使用,不能建立抽象類的對象。
②不允許從具體類派生出抽象類。
③抽象類不能用作函數的參數類型、函數的傳回類型或顯示轉換的類型。
6.虛基類
虛基類是對多繼承來說的。如果一個類有多個直接基類,而這些直接基類又有一個共同的基類,則在最底層的派生類中會保留這個間接的共同基類資料成員的多份同名成員。
//
// VirtualClass.cpp
// VirtualClass
//
// Created by 劉一丁 on 2019/8/31.
// Copyright © 2019年 LYD. All rights reserved.
//
//file1.cpp
#include <iostream>
using namespace std;
class base
{
public:
base()
{
a = 5;
cout << "base a = " << a << endl;
}
protected:
int a;
};
class base1:public base
{
public:
base1()
{
a = a + 10;
cout << "base1 a = " << a << endl;
}
};
class base2:public base
{
public:
base2()
{
a = a + 20;
cout << "base2 a = " << a << endl;
}
};
class derived:public base1, public base2
{
public:
derived()
{
cout << "base1::a = " << base1::a << endl; //明确指出調用 base1 的資料成員 a
cout << "base2::a = " << base2::a << endl; //明确指出調用 base2 的資料成員 a
}
};
int main()
{
derived obj;
return 0;
}
//output:
//1.base a = 5
//2.base1 a = 15
//3.base a = 5
//4.base2 a = 25
//5.base1::a = 15
//6.base2::a = 25
//注:在通路這些同名成員時,必須在派生類對象名後增加直接基類名
//使其唯一地辨別一個成員,以免産生二義性。
上述程式中,在定義 derived 對象 obj 時,首先調用 base1 的構造函數,在調用 base2 的構造函數,最後調用 derived 構造函數。在調用 base1 構造函數時又會先調用 base 的構造函數,是以會輸出 1-2 語句;在調用 base2 構造函數時又會先調用 base 的構造函數是以輸出 3-4 語句。 雖然在類 base1 和 base2 中沒有定義資料成員 a ,但是他們在調用基類 base 構造函數時會分别繼承基類的資料成員 a。這樣就在 base1 和 base2 中同時存在着同名的資料成員 a ,他們都是類 base 成員的拷貝。但是類 base1 和類 base2 中的資料成員 a 分别具有不同的存儲單元,可以存放不同的資料(在這裡可以這樣了解,base1 公有繼承了 base ,是以相當于 base1 中也有protected 類型的資料成員 a )。下圖顯示了其中的層次關系。

由于在類derived 中同時存在着類 base1 和類base2 的資料成員 a ,是以在 derived 的構造函數中輸出 a 的值,必須加上“類名::”,指出是那一個資料成員 a ,否則就會出現二義性。例如
class derived:public base1, public base2
{
public:
derived()
{ cout << "derived a = " << a << endl; } //錯誤,二義性。
};
要想解決這個問題就需要用到虛基類。即将共同的基類base 聲明為虛基類。這就要求從 base 派生新類時,使用關鍵字 virtual 将類 base 聲明為虛基類。聲明形式如下:
class 派生類名:virtual 繼承方式 類名
{
...
}
用虛基類重寫上述代碼如下:
//
// VirtualClass.cpp
// VirtualClass
//
// Created by 劉一丁 on 2019/8/31.
// Copyright © 2019年 LYD. All rights reserved.
//
//file1.cpp
#include <iostream>
using namespace std;
class base
{
public:
base()
{
a = 5;
cout << "base a = " << a << endl;
}
protected:
int a;
};
class base1:virtual public base
{
public:
base1()
{
a = a + 10;
cout << "base1 a = " << a << endl;
}
};
class base2:virtual public base
{
public:
base2()
{
a = a + 20;
cout << "base2 a = " << a << endl;
}
};
class derived:public base1, public base2
{
public:
derived()
{ cout << "derived a = " << a << endl; }
};
int main()
{
derived obj;
return 0;
}
//output:
//base a = 5
//base1 a = 15
//base2 a = 35
//derived a = 35
在上述程式中,從類 base 派生出來 base1 和類 base2 時,使用了關鍵字 virtual,把類 base 聲明為類 base1 和 base2 的虛基類。這樣,從類 base1 和 base2 派生出的類 derived 隻繼承基類 base 一次,也就是說,基類 base 的資料成員 a 隻保留一份。具體層次關系如下圖。