面向對象程式設計中最重要的一個概念是繼承。
繼承允許我們依據另一個類來定義一個類,這使得建立和維護一個應用程式變得更容易。這樣做,也達到了重用代碼功能和提高執行效率的效果。
當建立一個類時,不需要重新編寫新的資料成員和成員函數,隻需指定建立的類繼承了一個已有的類的成員即可。這個已有的類稱為基類,建立的類稱為派生類。
基類 & 派生類
一個類可以派生自多個類,這意味着,它可以從多個基類繼承資料和函數。定義一個派生類,我們使用一個類派生清單來指定基類。類派生清單以一個或多個基類命名,形式如下:
其中,通路修飾符
access-specifier
是
public
、
protected
或
private
其中的一個,
base-class
是之前定義過的某個類的名稱。如果未使用通路修飾符
access-specifier
,則預設為
private
。
假設有一個基類
Shape
,
Rectangle
是它的派生類,如下所示:
#include <iostream>
using namespace std;
// 基類
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);
}
};
int main(void)
{
Rectangle Rect;
Rect.setWidth(5);
Rect.setHeight(7);
// 輸出對象的面積
cout << "Total area: " << Rect.getArea() << endl;
return 0;
}
當上面的代碼被編譯和執行時,它會産生下列結果:
通路控制和繼承
派生類可以通路基類中所有的非私有成員。是以基類成員如果不想被派生類的成員函數通路,則應在基類中聲明為
private
。
我們可以根據通路權限總結出不同的通路類型,如下所示:
通路 | public | protected | private |
---|---|---|---|
同一個類 | yes | yes | yes |
派生類 | yes | yes | no |
外部的類 | yes | no | no |
一個派生類繼承了所有的基類方法,但下列情況除外:
- 基類的構造函數、析構函數和拷貝構造函數。
- 基類的重載運算符。
- 基類的友元函數。
繼承類型
當一個類派生自基類,該基類可以被繼承為
public
、
protected
或
private
幾種類型。繼承類型是通過上面講解的通路修飾符
access-specifier
來指定的。
我們幾乎不使用
protected
或
private
繼承,通常使用 public 繼承。當使用不同類型的繼承時,遵循以下幾個規則:
- 公有繼承(
):當一個類派生自公有基類時,基類的公有成員也是派生類的公有成員,基類的保護成員也是派生類的保護成員,基類的私有成員不能直接被派生類通路,但是可以通過調用基類的公有和保護成員來通路。public
- 保護繼承(
): 當一個類派生自保護基類時,基類的公有和保護成員将成為派生類的保護成員。protected
- 私有繼承(
):當一個類派生自私有基類時,基類的公有和保護成員将成為派生類的私有成員。private
多繼承
多繼承即一個子類可以有多個父類,它繼承了多個父類的特性。
C++
類可以從多個類繼承成員,文法如下:
class <派生類名>:<繼承方式1><基類名1>,<繼承方式2><基類名2>,…
{
<派生類類體>
};
其中,通路修飾符繼承方式是
public
、
protected
或
private
其中的一個,用來修飾每個基類,各個基類之間用逗号分隔,如上所示。現在讓我們一起看看下面的執行個體:
執行個體
#include <iostream>
using namespace std;
// 基類 Shape
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 * 70;
}
};
// 派生類
class Rectangle: public Shape, public PaintCost
{
public:
int getArea()
{
return (width * height);
}
};
int main(void)
{
Rectangle Rect;
int area;
Rect.setWidth(5);
Rect.setHeight(7);
area = Rect.getArea();
// 輸出對象的面積
cout << "Total area: " << Rect.getArea() << endl;
// 輸出總花費
cout << "Total paint cost: $" << Rect.getCost(area) << endl;
return 0;
}
當上面的代碼被編譯和執行時,它會産生下列結果:
Total area: 35
Total paint cost: $2450
另外多繼承(環狀繼承),
A->D
,
B->D
,
C->(A,B)
,例如:
class D{......};
class B: public D{......};
class A: public D{......};
class C: public B, public A{.....};
這個繼承會使
D
建立兩個對象,要解決上面問題就要用虛拟繼承格式
格式:
class 類名: virtual 繼承方式 父類名
class D{......};
class B: virtual public D{......};
class A: virtual public D{......};
class C: public B, public A{.....};
虛繼承–(在建立對象的時候會建立一個虛表)在建立父類對象的時候
A:virtual public D
B:virtual public D
執行個體:
#include <iostream>
using namespace std;
//基類
class D
{
public:
D(){cout<<"D()"<<endl;}
~D(){cout<<"~D()"<<endl;}
protected:
int d;
};
class B:virtual public D
{
public:
B(){cout<<"B()"<<endl;}
~B(){cout<<"~B()"<<endl;}
protected:
int b;
};
class A:virtual public D
{
public:
A(){cout<<"A()"<<endl;}
~A(){cout<<"~A()"<<endl;}
protected:
int a;
};
class C:public B, public A
{
public:
C(){cout<<"C()"<<endl;}
~C(){cout<<"~C()"<<endl;}
protected:
int c;
};
int main()
{
cout << "Hello World!" << endl;
C c; //D, B, A ,C
cout<<sizeof(c)<<endl;
return 0;
}
1、與類同名的函數是構造函數。
2、~ 類名的是類的析構函數。
C++ 重載運算符和重載函數
C++ 允許在同一作用域中的某個函數和運算符指定多個定義,分别稱為函數重載和運算符重載。
重載聲明是指一個與之前已經在該作用域内聲明過的函數或方法具有相同名稱的聲明,但是它們的參數清單和定義(實作)不相同。
當您調用一個重載函數或重載運算符時,編譯器通過把您所使用的參數類型與定義中的參數類型進行比較,決定選用最合适的定義。選擇最合适的重載函數或重載運算符的過程,稱為重載決策。
C++
中的函數重載
在同一個作用域内,可以聲明幾個功能類似的同名函數,但是這些同名函數的形式參數(指參數的個數、類型或者順序)必須不同。您不能僅通過傳回類型的不同來重載函數。
下面的執行個體中,同名函數
print()
被用于輸出不同的資料類型:
執行個體
#include <iostream>
using namespace std;
class printData
{
public:
void print(int i) {
cout << "整數為: " << i << endl;
}
void print(double f) {
cout << "浮點數為: " << f << endl;
}
void print(char c[]) {
cout << "字元串為: " << c << endl;
}
};
int main(void)
{
printData pd;
// 輸出整數
pd.print(5);
// 輸出浮點數
pd.print(500.263);
// 輸出字元串
char c[] = "Hello C++";
pd.print(c);
return 0;
}
當上面的代碼被編譯和執行時,它會産生下列結果:
整數為: 5
浮點數為: 500.263
字元串為: Hello C++
C++ 中的運算符重載
您可以重定義或重載大部分 C++ 内置的運算符。這樣,您就能使用自定義類型的運算符。
重載的運算符是帶有特殊名稱的函數,函數名是由關鍵字
operator
和其後要重載的運算符符号構成的。與其他函數一樣,重載運算符有一個傳回類型和一個參數清單。
聲明加法運算符用于把兩個
Box
對象相加,傳回最終的
Box
對象。大多數的重載運算符可被定義為普通的非成員函數或者被定義為類成員函數。如果我們定義上面的函數為類的非成員函數,那麼我們需要為每次操作傳遞兩個參數,如下所示:
下面的執行個體使用成員函數示範了運算符重載的概念。在這裡,對象作為參數進行傳遞,對象的屬性使用
this
運算符進行通路,如下所示:
執行個體
#include <iostream>
using namespace std;
class Box
{
public:
double getVolume(void)
{
return length * breadth * height;
}
void setLength( double len )
{
length = len;
}
void setBreadth( double bre )
{
breadth = bre;
}
void setHeight( double hei )
{
height = hei;
}
// 重載 + 運算符,用于把兩個 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;
}
private:
double length; // 長度
double breadth; // 寬度
double height; // 高度
};
// 程式的主函數
int main( )
{
Box Box1; // 聲明 Box1,類型為 Box
Box Box2; // 聲明 Box2,類型為 Box
Box Box3; // 聲明 Box3,類型為 Box
double volume = 0.0; // 把體積存儲在該變量中
// Box1 詳述
Box1.setLength(6.0);
Box1.setBreadth(7.0);
Box1.setHeight(5.0);
// Box2 詳述
Box2.setLength(12.0);
Box2.setBreadth(13.0);
Box2.setHeight(10.0);
// Box1 的體積
volume = Box1.getVolume();
cout << "Volume of Box1 : " << volume <<endl;
// Box2 的體積
volume = Box2.getVolume();
cout << "Volume of Box2 : " << volume <<endl;
// 把兩個對象相加,得到 Box3
Box3 = Box1 + Box2;
// Box3 的體積
volume = Box3.getVolume();
cout << "Volume of Box3 : " << volume <<endl;
return 0;
}
當上面的代碼被編譯和執行時,它會産生下列結果:
Volume of Box1 : 210
Volume of Box2 : 1560
Volume of Box3 : 5400
值得注意的是:
- 1、運算重載符不可以改變文法結構。
- 2、運算重載符不可以改變操作數的個數。
- 3、運算重載符不可以改變優先級。
- 4、運算重載符不可以改變結合性。
類重載、覆寫、重定義之間的差別:
重載指的是函數具有的不同的參數清單,而函數名相同的函數。重載要求參數清單必須不同,比如參數的類型不同、參數的個數不同、參數的順序不同。如果僅僅是函數的傳回值不同是沒辦法重載的,因為重載要求參數清單必須不同。(發生在同一個類裡)
覆寫是存在類中,子類重寫從基類繼承過來的函數。被重寫的函數不能是
static
的。必須是
virtual
的。但是函數名、傳回值、參數清單都必須和基類相同(發生在基類和子類)
重定義也叫做隐藏,子類重新定義父類中有相同名稱的非虛函數 ( 參數清單可以不同 ) 。(發生在基類和子類)
C++ 多态
多态按字面的意思就是多種形态。當類之間存在層次結構,并且類之間是通過繼承關聯時,就會用到多态。
C++ 多态意味着調用成員函數時,會根據調用函數的對象的類型來執行不同的函數。
下面的執行個體中,基類
Shape
被派生為兩個類,如下所示:
執行個體
#include <iostream>
using namespace std;
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
int area()
{
cout << "Parent class area :" <<endl;
return 0;
}
};
class Rectangle: public Shape{
public:
Rectangle( int a=0, int b=0):Shape(a, b) { }
int area ()
{
cout << "Rectangle class area :" <<endl;
return (width * height);
}
};
class Triangle: public Shape{
public:
Triangle( int a=0, int b=0):Shape(a, b) { }
int area ()
{
cout << "Triangle class area :" <<endl;
return (width * height / 2);
}
};
// 程式的主函數
int main( )
{
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
// 存儲矩形的位址
shape = &rec;
// 調用矩形的求面積函數 area
shape->area();
// 存儲三角形的位址
shape = &tri;
// 調用三角形的求面積函數 area
shape->area();
return 0;
}
當上面的代碼被編譯和執行時,它會産生下列結果:
Parent class area
Parent class area
導緻錯誤輸出的原因是,調用函數
area()
被編譯器設定為基類中的版本,這就是所謂的靜态多态,或靜态連結 - 函數調用在程式執行前就準備好了。有時候這也被稱為早綁定,因為
area()
函數在程式編譯期間就已經設定好了。
但現在,讓我們對程式稍作修改,在
Shape
類中,
area()
的聲明前放置關鍵字
virtual
,如下所示:
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
virtual int area()
{
cout << "Parent class area :" <<endl;
return 0;
}
};
修改後,當編譯和執行前面的執行個體代碼時,它會産生以下結果:
Rectangle class area
Triangle class area
此時,編譯器看的是指針的内容,而不是它的類型。是以,由于
tri
和
rec
類的對象的位址存儲在
*shape
中,是以會調用各自的
area()
函數。
正如您所看到的,每個子類都有一個函數
area()
的獨立實作。這就是多态的一般使用方式。有了多态,您可以有多個不同的類,都帶有同一個名稱但具有不同實作的函數,函數的參數甚至可以是相同的。
虛函數
虛函數 是在基類中使用關鍵字
virtual
聲明的函數。在派生類中重新定義基類中定義的虛函數時,會告訴編譯器不要靜态連結到該函數。
我們想要的是在程式中任意點可以根據所調用的對象類型來選擇調用的函數,這種操作被稱為動态連結,或後期綁定。
純虛函數
您可能想要在基類中定義虛函數,以便在派生類中重新定義該函數更好地适用于對象,但是您在基類中又不能對虛函數給出有意義的實作,這個時候就會用到純虛函數。
我們可以把基類中的虛函數
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
告訴編譯器,函數沒有主體,上面的虛函數是純虛函數。
1、純虛函數聲明如下:
virtual void funtion1()=0
; 純虛函數一定沒有定義,純虛函數用來規範派生類的行為,即接口。包含純虛函數的類是抽象類,抽象類不能定義執行個體,但可以聲明指向實作該抽象類的具體類的指針或引用。
2、虛函數聲明如下:
virtual ReturnType FunctionName(Parameter)
虛函數必須實作,如果不實作,編譯器将報錯,錯誤提示為:
error LNK****: unresolved external symbol "public: virtual void __thiscall
ClassName::virtualFunctionName(void)"
3、對于虛函數來說,父類和子類都有各自的版本。由多态方式調用的時候動态綁定。
4、實作了純虛函數的子類,該純虛函數在子類中就程式設計了虛函數,子類的子類即孫子類可以覆寫該虛函數,由多态方式調用的時候動态綁定。
5、虛函數是C++中用于實作多态(
polymorphism
)的機制。核心理念就是通過基類通路派生類定義的函數。
6、在有動态配置設定堆上記憶體的時候,析構函數必須是虛函數,但沒有必要是純虛的。
7、友元不是成員函數,隻有成員函數才可以是虛拟的,是以友元不能是虛拟函數。但可以通過讓友元函數調用虛拟成員函數來解決友元的虛拟問題。
8、析構函數應當是虛函數,将調用相應對象類型的析構函數,是以,如果指針指向的是子類對象,将調用子類的析構函數,然後自動調用基類的析構函數。
虛函數這裡說的有些亂,因為
C++
寫法奇葩略多。其實可以簡單了解。
虛函數可以不實作(定義)。不實作(定義)的虛函數是純虛函數。
在一個類中如果存在未定義的虛函數,那麼不能直接使用該類的執行個體,可以了解因為未定義
virtual
函數,其類是抽象的,無法執行個體化。将報錯誤:
undefined reference to `vtable for xxx'
這和其它語言的抽象類,抽象方法是類似的——我們必須實作抽象類,否則無法執行個體化。(
virtual
和
abstract
還是有些差別的)
也就是說,如果存在以下代碼:
using namespace std;
class Base {
public:
virtual void tall();
};
class People : Base {
public:
void tall() {
cout << "people" << endl;
};
};
那麼,在
main
方法中,我們不能使用
Base base
; 這行代碼,此時的
tall
沒有實作,函數表(
vtable
)的引用是未定義的,故而無法執行。但我們可以使用
People people
; 然後
people.tall()
; 或
(&people)->tall()
; 因為People實作或者說重寫、覆寫了 Base 的純虛方法
tall()
,使其在 People 類中有了定義,函數表挂上去了,于是可以誕生執行個體了。
int main() {
// Base base;//不可用
People people;//可用
people.tall();
(&people)->tall();
return 0;
}
上述的是針對虛函數而言,普通的函數,即使我們隻聲明,不定義,也不會産生上述不可用的問題。
父類的虛函數或純虛函數在子類中依然是虛函數。有時我們并不希望父類的某個函數在子類中被重寫,在
C++11
及以後可以用關鍵字
final
來避免該函數再次被重寫。
例:
#include<iostream>
using namespace std;
class Base
{
public:
virtual void func()
{
cout<<"This is Base"<<endl;
}
};
class _Base:public Base
{
public:
void func() final//正确,func在Base中是虛函數
{
cout<<"This is _Base"<<endl;
}
};
class __Base:public _Base
{
/* public://不正确,func在_Base中已經不再是虛函數,不能再被重寫
void func()
{
cout<<"This is __Base"<<endl;
}*/
};
int main()
{
_Base a;
__Base b;
Base* ptr=&a;
ptr->func();
ptr=&b;
_Base* ptr2=&b; ptr->func();
ptr2->func();
}
以上程式運作結果:
This is _Base
This is _Base
This is _Base
如果不希望一個類被繼承,也可以使用
final
關鍵字。
格式如下:
class Class_name final
{
...
};
則該類将不能被繼承。
C++ 資料抽象
資料抽象是指,隻向外界提供關鍵資訊,并隐藏其背景的實作細節,即隻表現必要的資訊而不呈現細節。
資料抽象是一種依賴于接口和實作分離的程式設計(設計)技術。
讓我們舉一個現實生活中的真執行個體子,比如一台電視機,您可以打開和關閉、切換頻道、調整音量、添加外部元件(如喇叭、錄像機、DVD 播放器),但是您不知道它的内部實作細節,也就是說,您并不知道它是如何通過纜線接收信号,如何轉換信号,并最終顯示在螢幕上。
是以,我們可以說電視把它的内部實作和外部接口分離開了,您無需知道它的内部實作原理,直接通過它的外部接口(比如電源按鈕、遙控器、聲量控制器)就可以操控電視。
現在,讓我們言歸正傳,就 C++ 程式設計而言,C++ 類為資料抽象提供了可能。它們向外界提供了大量用于操作對象資料的公共方法,也就是說,外界實際上并不清楚類的内部實作。
例如,您的程式可以調用
sort()
函數,而不需要知道函數中排序資料所用到的算法。實際上,函數排序的底層實作會因庫的版本不同而有所差異,隻要接口不變,函數調用就可以照常工作。
在 C++ 中,我們使用類來定義我們自己的抽象資料類型(
ADT
)。您可以使用類
iostream
的
cout
對象來輸出資料到标準輸出,如下所示:
執行個體
#include <iostream>
using namespace std;
int main( )
{
cout << "Hello C++" <<endl;
return 0;
}
在這裡,您不需要了解
cout
是如何在使用者的螢幕上顯示文本。您隻需要知道公共接口即可,cout 的底層實作可以自由改變。
通路标簽強制抽象
在
C++
中,我們使用通路标簽來定義類的抽象接口。一個類可以包含零個或多個通路标簽:
使用公共标簽定義的成員都可以通路該程式的所有部分。一個類型的資料抽象視圖是由它的公共成員來定義的。
使用私有标簽定義的成員無法通路到使用類的代碼。私有部分對使用類型的代碼隐藏了實作細節。
通路标簽出現的頻率沒有限制。每個通路标簽指定了緊随其後的成員定義的通路級别。指定的通路級别會一直有效,直到遇到下一個通路标簽或者遇到類主體的關閉右括号為止。
資料抽象的好處
資料抽象有兩個重要的優勢:
- 類的内部受到保護,不會因無意的使用者級錯誤導緻對象狀态受損。
-
類實作可能随着時間的推移而發生變化,以便應對不斷變化的需求,或者應對那些要求不改變使用者級代碼的錯誤報告。
如果隻在類的私有部分定義資料成員,編寫該類的作者就可以随意更改資料。如果實作發生改變,則隻需要檢查類的代碼,看看這個改變會導緻哪些影響。如果資料是公有的,則任何直接通路舊表示形式的資料成員的函數都可能受到影響。
資料抽象的執行個體
C++
程式中,任何帶有公有和私有成員的類都可以作為資料抽象的執行個體。請看下面的執行個體:
執行個體
#include <iostream>
using namespace std;
class Adder{
public:
// 構造函數
Adder(int i = 0)
{
total = i;
}
// 對外的接口
void addNum(int number)
{
total += number;
}
// 對外的接口
int getTotal()
{
return total;
};
private:
// 對外隐藏的資料
int total;
};
int main( )
{
Adder a;
a.addNum(10);
a.addNum(20);
a.addNum(30);
cout << "Total " << a.getTotal() <<endl;
return 0;
}
當上面的代碼被編譯和執行時,它會産生下列結果:
上面的類把數字相加,并傳回總和。公有成員
addNum
和
getTotal
是對外的接口,使用者需要知道它們以便使用類。私有成員
total
是使用者不需要了解的,但又是類能正常工作所必需的。
設計政策
抽象把代碼分離為接口和實作。是以在設計元件時,必須保持接口獨立于實作,這樣,如果改變底層實作,接口也将保持不變。
在這種情況下,不管任何程式使用接口,接口都不會受到影響,隻需要将最新的實作重新編譯即可。
C++ 資料封裝
所有的 C++ 程式都有以下兩個基本要素:
- 程式語句(代碼):這是程式中執行動作的部分,它們被稱為函數。
-
程式資料:資料是程式的資訊,會受到程式函數的影響。
封裝是面向對象程式設計中的把資料和操作資料的函數綁定在一起的一個概念,這樣能避免受到外界的幹擾和誤用,進而確定了安全。資料封裝引申出了另一個重要的
概念,即資料隐藏。OOP
資料封裝是一種把資料和操作資料的函數捆綁在一起的機制,資料抽象是一種僅向使用者暴露接口而把具體的實作細節隐藏起來的機制。
C++ 通過建立類來支援封裝和資料隐藏(
public
、
protected
、
private
)。我們已經知道,類包含私有成員(
private
)、保護成員(
protected
)和公有成員(
public
)成員。預設情況下,在類中定義的所有項目都是私有的。例如:
class Box
{
public:
double getVolume(void)
{
return length * breadth * height;
}
private:
double length; // 長度
double breadth; // 寬度
double height; // 高度
};
變量
length
、
breadth
和
height
都是私有的(
private
)。這意味着它們隻能被
Box
類中的其他成員通路,而不能被程式中其他部分通路。這是實作封裝的一種方式。
為了使類中的成員變成公有的(即,程式中的其他部分也能通路),必須在這些成員前使用 public 關鍵字進行聲明。所有定義在 public 辨別符後邊的變量或函數可以被程式中所有其他的函數通路。
把一個類定義為另一個類的友元類,會暴露實作細節,進而降低了封裝性。理想的做法是盡可能地對外隐藏每個類的實作細節。
資料封裝的執行個體
C++
程式中,任何帶有公有和私有成員的類都可以作為資料封裝和資料抽象的執行個體。請看下面的執行個體:
執行個體
#include <iostream>
using namespace std;
class Adder{
public:
// 構造函數
Adder(int i = 0)
{
total = i;
}
// 對外的接口
void addNum(int number)
{
total += number;
}
// 對外的接口
int getTotal()
{
return total;
};
private:
// 對外隐藏的資料
int total;
};
int main( )
{
Adder a;
a.addNum(10);
a.addNum(20);
a.addNum(30);
cout << "Total " << a.getTotal() <<endl;
return 0;
}
當上面的代碼被編譯和執行時,它會産生下列結果:
上面的類把數字相加,并傳回總和。公有成員
addNum
和
getTotal
是對外的接口,使用者需要知道它們以便使用類。私有成員
total
是對外隐藏的,使用者不需要了解它,但它又是類能正常工作所必需的。
設計政策
通常情況下,我們都會設定類成員狀态為私有(
private
),除非我們真的需要将其暴露,這樣才能保證良好的封裝性。
這通常應用于資料成員,但它同樣适用于所有成員,包括虛函數。
C++中, 虛函數可以為
privat
e, 并且可以被子類覆寫(因為虛函數表的傳遞),但子類不能調用父類的
private
虛函數。虛函數的重載性和它聲明的權限無關。
一個成員函數被定義為
private
屬性,标志着其隻能被目前類的其他成員函數(或友元函數)所通路。而
virtual
修飾符則強調父類的成員函數可以在子類中被重寫,因為重寫之時并沒有與父類發生任何的調用關系,故而重寫是被允許的。
編譯器不檢查虛函數的各類屬性。被
virtual
修飾的成員函數,不論他們是
private
、
protect
或是
public
的,都會被統一的放置到虛函數表中。對父類進行派生時,子類會繼承到擁有相同偏移位址的虛函數表(相同偏移位址指,各虛函數相對于
VPTR
指針的偏移),則子類就會被允許對這些虛函數進行重載。且重載時可以給重載函數定義新的屬性,例如
public
,其隻标志着該重載函數在該子類中的通路屬性為
public
,和父類的
private
屬性沒有任何關系!
純虛函數可以設計成私有的,不過這樣不允許在本類之外的非友元函數中直接調用它,子類中隻有覆寫這種純虛函數的義務,卻沒有調用它的權利。