目录
virtual 在C++中用于三个部分:虚函数、虚基类、纯虚函数。
一.虚函数
一般会在基类中,将派生类会重新定义的函数方法,声明为虚方法;
且在派生类中,该虚方法会自动成为虚方法;
在前面类继承中,多态共有继承 brass 示例如下:
// brass.h -- bank account classes
#ifndef BRASS_H_
#define BRASS_H_
#include <string>
// Brass Account Class
class Brass
{
private:
std::string fullName;
long acctNum;
double balance;
public:
Brass(const std::string & s = "Nullbody", long an = -1,
double bal = 0.0);
void Deposit(double amt);
//虚函数
virtual void Withdraw(double amt);
double Balance() const;
//虚函数
virtual void ViewAcct() const;
//虚析构函数
virtual ~Brass() {}
};
//Brass Plus Account Class
class BrassPlus : public Brass
{
private:
double maxLoan;
double rate;
double owesBank;
public:
BrassPlus(const std::string & s = "Nullbody", long an = -1,
double bal = 0.0, double ml = 500,
double r = 0.11125);
BrassPlus(const Brass & ba, double ml = 500,
double r = 0.11125);
virtual void ViewAcct()const;
virtual void Withdraw(double amt);
void ResetMax(double m) { maxLoan = m; }
void ResetRate(double r) { rate = r; };
void ResetOwes() { owesBank = 0; }
};
#endif
(1)虚函数
//虚函数
virtual void Withdraw(double amt);
//虚函数
virtual void ViewAcct() const;
可以看到,在派生类同样也声明了这两个虚函数,这说明同一个函数有不同的行为,不同行为取决于具体对象,如下所示:
Brass dom;
BrassPlus dot;
dom.ViewAcct(); //use Brass::ViewAcct()
dot.ViewAcct(); //use BrassPlus::ViewAcct()
如果方法是通过引用、指针,而不是对象调用的:
- 如果没有关键字 virtual ,程序将根据引用、指针类型选择方法;
- 如果有关键字 virtual ,程序将根据引用、指针指向的对象类型选择方法;
Brass dom;
BrassPlus dot;
Brass & b1_ref = dom;
Brass & b2_ref = dot;
# if unuse virtual
b1_ref.ViewAcct(); //use Brass::ViewAcct()
b2_ref.ViewAcct(); //use Brass::ViewAcct()
# if use virtual
b1_ref.ViewAcct(); //use Brass::ViewAcct()
b2_ref.ViewAcct(); //use BrassPlus::ViewAcct()
(2)虚析构函数
//虚析构函数
virtual ~Brass() {}
如果方法是通过引用、指针,而不是对象调用的:
- 如果没有关键字 virtual ,程序将根据引用、指针类型选择析构函数;
- 如果有关键字 virtual ,程序将根据引用、指针指向的对象类型选择析构函数;
比如前面继承的例子:
// usebrass2.cpp -- polymorphic example
// compile with brass.cpp
#include <iostream>
#include <string>
#include "brass.h"
const int CLIENTS = 4;
int main()
{
using std::cin;
using std::cout;
using std::endl;
Brass * p_clients[CLIENTS];
std::string temp;
long tempnum;
double tempbal;
char kind;
for (int i = 0; i < CLIENTS; i++)
{
cout << "Enter client's name: ";
getline(cin,temp);
cout << "Enter client's account number: ";
cin >> tempnum;
cout << "Enter opening balance: $";
cin >> tempbal;
cout << "Enter 1 for Brass Account or "
<< "2 for BrassPlus Account: ";
while (cin >> kind && (kind != '1' && kind != '2'))
cout <<"Enter either 1 or 2: ";
if (kind == '1')
p_clients[i] = new Brass(temp, tempnum, tempbal);
else
{
double tmax, trate;
cout << "Enter the overdraft limit: $";
cin >> tmax;
cout << "Enter the interest rate "
<< "as a decimal fraction: ";
cin >> trate;
p_clients[i] = new BrassPlus(temp, tempnum, tempbal,
tmax, trate);
}
while (cin.get() != '\n')
continue;
}
cout << endl;
for (int i = 0; i < CLIENTS; i++)
{
p_clients[i]->ViewAcct();
cout << endl;
}
for (int i = 0; i < CLIENTS; i++)
{
delete p_clients[i]; // free memory
}
cout << "Done.\n";
/* code to keep window open
if (!cin)
cin.clear();
while (cin.get() != '\n')
continue;
*/
return 0;
}
通过 new 创建对象后,当通过 delete 释放时:
- 如果不是虚析构函数,因为指针 p_clients 的类型是 Brass 类,所以这里只会调用 Brass 类的析构函数;
- 如果是虚析构函数,即使指针 p_clients 的类型是 Brass 类,这里也会调用 相应对象的析构函数;
二.虚基类
虚基类也叫抽象基类(ABC),该基类包含多个派生类的共性;
一般在虚函数中,当至少有一个纯虚函数时,这个类就叫 虚基类/抽象基类;
抽象基类不能创建该类的对象,必须是其派生类才能创建对象;
三.纯虚函数
纯虚函数是指在虚函数尾部加上 = 0,用于提供为实现的函数:
virtual void ViewAcct() const = 0; // pure virtual function
包含纯虚函数的类只能作为基类,且不能创建该类的对象;
示例:
// acctabc.h -- bank account classes
#ifndef ACCTABC_H_
#define ACCTABC_H_
#include <iostream>
#include <string>
// Abstract Base Class
class AcctABC
{
private:
std::string fullName;
long acctNum;
double balance;
protected:
struct Formatting
{
std::ios_base::fmtflags flag;
std::streamsize pr;
};
const std::string & FullName() const {return fullName;}
long AcctNum() const {return acctNum;}
Formatting SetFormat() const;
void Restore(Formatting & f) const;
public:
AcctABC(const std::string & s = "Nullbody", long an = -1,
double bal = 0.0);
void Deposit(double amt) ;
virtual void Withdraw(double amt) = 0; // pure virtual function
double Balance() const {return balance;};
virtual void ViewAcct() const = 0; // pure virtual function
virtual ~AcctABC() {}
};
// Brass Account Class
class Brass :public AcctABC
{
public:
Brass(const std::string & s = "Nullbody", long an = -1,
double bal = 0.0) : AcctABC(s, an, bal) { }
virtual void Withdraw(double amt);
virtual void ViewAcct() const;
virtual ~Brass() {}
};
//Brass Plus Account Class
class BrassPlus : public AcctABC
{
private:
double maxLoan;
double rate;
double owesBank;
public:
BrassPlus(const std::string & s = "Nullbody", long an = -1,
double bal = 0.0, double ml = 500,
double r = 0.10);
BrassPlus(const Brass & ba, double ml = 500, double r = 0.1);
virtual void ViewAcct()const;
virtual void Withdraw(double amt);
void ResetMax(double m) { maxLoan = m; }
void ResetRate(double r) { rate = r; };
void ResetOwes() { owesBank = 0; }
};
#endif
说明:
- 可以看到程序声明了一个基类 AcctABC ,因为该基类包含了两个纯虚函数,所以该基类也叫虚基类/抽象基类。
- 两个纯虚函数:
virtual void Withdraw(double amt) = 0; // pure virtual function
virtual void ViewAcct() const = 0; // pure virtual function
是两个派生类(Bras、BrassPlus)公有的方法,只是方法实现各有不同,所以在派生类中要对这两种方法重新定义,在虚基类中不用定义;
- 对于虚基类的其他方法,派生类可以正常使用;
在继承中,ABC常被看作一种必须实施的接口。