虚函数是应在派生类中重新定义的成员函数。 当使用指针或对基类的引用来引用派生的类对象时,可以为该对象调用虚函数并执行该函数的派生类版本。
虚函数确保为该对象调用正确的函数,这与用于进行函数调用的表达式无关。
假定基类包含声明为 virtual 的函数,并且派生类定义了相同的函数。 为派生类的对象调用派生类中的函数,即使它是使用指针或对基类的引用来调用的。 以下示例显示了一个基类,它提供了 PrintBalance 函数和两个派生类的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
// deriv_VirtualFunctions.cpp
// compile with: /EHsc
#include <iostream>
using namespace std;
class Account {
public :
Account( double d ) { _balance = d; }
virtual double GetBalance() { return _balance; }
virtual void PrintBalance() { cerr << "Error. Balance not available for base type." << endl; }
private :
double _balance;
};
class CheckingAccount : public Account {
public :
CheckingAccount( double d) : Account(d) {}
void PrintBalance() { cout << "Checking account balance: " << GetBalance() << endl; }
};
class SavingsAccount : public Account {
public :
SavingsAccount( double d) : Account(d) {}
void PrintBalance() { cout << "Savings account balance: " << GetBalance(); }
};
int main() {
// Create objects of type CheckingAccount and SavingsAccount.
CheckingAccount *pChecking = new CheckingAccount( 100.00 ) ;
SavingsAccount *pSavings = new SavingsAccount( 1000.00 );
// Call PrintBalance using a pointer to Account.
Account *pAccount = pChecking;
pAccount->PrintBalance();
// Call PrintBalance using a pointer to Account.
pAccount = pSavings;
pAccount->PrintBalance();
}
|
在前面的代码中,对 PrintBalance 的调用是相同的,pAccount 所指向的对象除外。 由于 PrintBalance 是虚拟的,因此将调用为每个对象定义的函数版本。 派生类 PrintBalance 和 CheckingAccount 中的 SavingsAccount 函数“重写”基类 Account 中的函数。
如果声明的类不提供 PrintBalance 函数的重写实现,则使用基类 Account 中的默认实现。
派生类中的函数仅在基类中的虚函数的类型相同时重写这些虚函数。 派生类中的函数不能只是与其返回类型中的基类的虚函数不同;参数列表也必须不同。
当使用指针或引用调用函数时,以下规则将适用:
根据为其调用的对象的基本类型来解析对虚函数的调用。
根据指针或引用的类型来解析对非虚函数的调用。
以下示例说明在通过指针调用时虚函数和非虚函数的行为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
// deriv_VirtualFunctions2.cpp
// compile with: /EHsc
#include <iostream>
using namespace std;
class Base {
public :
virtual void NameOf(); // Virtual function.
void InvokingClass(); // Nonvirtual function.
};
// Implement the two functions.
void Base::NameOf() {
cout << "Base::NameOf\n" ;
}
void Base::InvokingClass() {
cout << "Invoked by Base\n" ;
}
class Derived : public Base {
public :
void NameOf(); // Virtual function.
void InvokingClass(); // Nonvirtual function.
};
// Implement the two functions.
void Derived::NameOf() {
cout << "Derived::NameOf\n" ;
}
void Derived::InvokingClass() {
cout << "Invoked by Derived\n" ;
}
int main() {
// Declare an object of type Derived.
Derived aDerived;
// Declare two pointers, one of type Derived * and the other
// of type Base *, and initialize them to point to aDerived.
Derived *pDerived = &aDerived;
Base *pBase = &aDerived;
// Call the functions.
pBase->NameOf(); // Call virtual function.
pBase->InvokingClass(); // Call nonvirtual function.
pDerived->NameOf(); // Call virtual function.
pDerived->InvokingClass(); // Call nonvirtual function.
}
|
输出
1
2
3
4
|
Derived::NameOf
Invoked by Base
Derived::NameOf
Invoked by Derived
|
请注意,无论 NameOf 函数是通过指向 Base 的指针还是通过指向 Derived 的指针进行调用,它都会调用 Derived 的函数。 它调用 Derived 的函数,因为 NameOf 是虚函数,并且 pBase 和 pDerived 都指向类型 Derived 的对象。
由于仅为类类型的对象调用虚函数,因此不能将全局函数或静态函数声明为 virtual。
在派生类中声明重写函数时可使用 virtual 关键字,但它不是必需的;虚函数的重写始终是虚拟的。
必须定义基类中的虚函数,除非使用 pure-specifier 声明它们。 (有关纯虚函数的详细信息,请参阅抽象类。)
可通过使用范围解析运算符 (::) 显式限定函数名称来禁用虚函数调用机制。 考虑先前涉及 Account 类的示例。 若要调用基类中的 PrintBalance,请使用如下所示的代码:
1
2
3
4
5
6
7
|
CheckingAccount *pChecking = new CheckingAccount( 100.00 );
pChecking->Account::PrintBalance(); // Explicit qualification.
Account *pAccount = pChecking; // Call Account::PrintBalance
pAccount->Account::PrintBalance(); // Explicit qualification.
|
在前面的示例中,对 PrintBalance 的调用将禁用虚函数调用机制。
单个继承
在“单继承”(继承的常见形式)中,类仅具有一个基类。考虑下图中阐释的关系。
简单单继承关系图
注意该图中从常规到特定的进度。在大多数类层次结构的设计中发现的另一个常见特性是,派生类与基类具有“某种”关系。在该图中,Book 是一种 PrintedDocument,而 PaperbackBook 是一种 book。
该图中的另一个要注意的是:Book 既是派生类(来自 PrintedDocument),又是基类(PaperbackBook 派生自 Book)。此类类层次结构的框架声明如下面的示例所示:
1
2
3
4
5
6
7
8
9
|
// deriv_SingleInheritance.cpp
// compile with: /LD
class PrintedDocument {};
// Book is derived from PrintedDocument.
class Book : public PrintedDocument {};
// PaperbackBook is derived from Book.
class PaperbackBook : public Book {};
|
PrintedDocument 被视为 Book 的“直接基”类;它是 PaperbackBook 的“间接基”类。差异在于,直接基类出现在类声明的基础列表中,而间接基类不是这样的。
在声明派生的类之前声明从中派生每个类的基类。为基类提供前向引用声明是不够的;它必须是一个完整声明。
在前面的示例中,使用访问说明符 public。 成员访问控制中介绍了公共的、受保护的和私有的继承的含义。
类可用作多个特定类的基类,如下图所示。
注意
有向非循环图对于单继承不是唯一的。它们还用于表示多重继承关系图。 多重继承中对本主题进行了说明。
在继承中,派生类包含基类的成员以及您添加的所有新成员。因此,派生类可以引用基类的成员(除非在派生类中重新定义这些成员)。当在派生类中重新定义了直接或间接基类的成员时,范围解析运算符 (::) 可用于引用这些成员。请看以下示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
// deriv_SingleInheritance2.cpp
// compile with: /EHsc /c
#include <iostream>
using namespace std;
class Document {
public :
char *Name; // Document name.
void PrintNameOf(); // Print name.
};
// Implementation of PrintNameOf function from class Document.
void Document::PrintNameOf() {
cout << Name << endl;
}
class Book : public Document {
public :
Book( char *name, long pagecount );
private :
long PageCount;
};
// Constructor from class Book.
Book::Book( char *name, long pagecount ) {
Name = new char [ strlen ( name ) + 1 ];
strcpy_s( Name, strlen (Name), name );
PageCount = pagecount;
};
|
请注意,Book 的构造函数 (Book::Book) 具有对数据成员 Name 的访问权。在程序中,可以创建和使用类型为 Book 的对象,如下所示:
1
2
3
4
5
6
7
8
|
// Create a new object of type Book. This invokes the
// constructor Book::Book.
Book LibraryBook( "Programming Windows, 2nd Ed" , 944 );
...
// Use PrintNameOf function inherited from class Document.
LibraryBook.PrintNameOf();
|
如前面的示例所示,以相同的方式使用类成员和继承的数据和函数。如果类 Book 的实现调用 PrintNameOf 函数的重新实现,则只能通过使用范围解析 (Document) 运算符来调用属于 :: 类的函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// deriv_SingleInheritance3.cpp
// compile with: /EHsc /LD
#include <iostream>
using namespace std;
class Document {
public :
char *Name; // Document name.
void PrintNameOf() {} // Print name.
};
class Book : public Document {
Book( char *name, long pagecount );
void PrintNameOf();
long PageCount;
};
void Book::PrintNameOf() {
cout << "Name of book: " ;
Document::PrintNameOf();
}
|
如果存在可访问的明确基类,则可以隐式将派生类的指针和引用转换为其基类的指针和引用。下面的代码使用指针演示了此概念(相同的原则适用于引用):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// deriv_SingleInheritance4.cpp
// compile with: /W3
struct Document {
char *Name;
void PrintNameOf() {}
};
class PaperbackBook : public Document {};
int main() {
Document * DocLib[10]; // Library of ten documents.
for ( int i = 0 ; i < 10 ; i++)
DocLib[i] = new Document;
}
|
在前面的示例中,创建了不同的类型。但是,由于这些类型都派生自 Document 类,因此存在对 Document * 的隐式转换。因此,DocLib 是“异类列表”(其中包含的所有对象并非属于同一类型),该列表包含不同类型的对象。
由于 Document 类具有一个 PrintNameOf 函数,因此它可以打印库中每本书的名称,但它可能会忽略某些特定于文档类型的信息(Book 的页计数、HelpFile 的字节数等)。
注意
强制使用基类来实现函数(如 PrintNameOf)通常不是最佳设计。 虚函数提供其他设计替代方法。