解析C++编程中virtual声明的虚函数以及单个继承

虚函数

虚函数是应在派生类中重新定义的成员函数。 当使用指针或对基类的引用来引用派生的类对象时,可以为该对象调用虚函数并执行该函数的派生类版本。
虚函数确保为该对象调用正确的函数,这与用于进行函数调用的表达式无关。
假定基类包含声明为 virtual 的函数,并且派生类定义了相同的函数。 为派生类的对象调用派生类中的函数,即使它是使用指针或对基类的引用来调用的。 以下示例显示了一个基类,它提供了 PrintBalance 函数和两个派生类的实现

// 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 中的默认实现。
派生类中的函数仅在基类中的虚函数的类型相同时重写这些虚函数。 派生类中的函数不能只是与其返回类型中的基类的虚函数不同;参数列表也必须不同。
当使用指针或引用调用函数时,以下规则将适用:
根据为其调用的对象的基本类型来解析对虚函数的调用。
根据指针或引用的类型来解析对非虚函数的调用。
以下示例说明在通过指针调用时虚函数和非虚函数的行为:

// 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.
}

输出

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,请使用如下所示的代码:

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)。此类类层次结构的框架声明如下面的示例所示:

// 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。 成员访问控制中介绍了公共的、受保护的和私有的继承的含义。
类可用作多个特定类的基类,如下图所示。

注意
有向非循环图对于单继承不是唯一的。它们还用于表示多重继承关系图。 多重继承中对本主题进行了说明。
在继承中,派生类包含基类的成员以及您添加的所有新成员。因此,派生类可以引用基类的成员(除非在派生类中重新定义这些成员)。当在派生类中重新定义了直接或间接基类的成员时,范围解析运算符 (::) 可用于引用这些成员。请看以下示例:

// 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 的对象,如下所示:

// 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) 运算符来调用属于 :: 类的函数:

// 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();
}

如果存在可访问的明确基类,则可以隐式将派生类的指针和引用转换为其基类的指针和引用。下面的代码使用指针演示了此概念(相同的原则适用于引用):

// 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)通常不是最佳设计。 虚函数提供其他设计替代方法。

(0)

相关推荐

  • 深入解析C++中的虚函数与多态

    1.C++中的虚函数C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有"多种形态",这是一种泛型技术.所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法.比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议. 对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Tab

  • C++中的多态与虚函数的内部实现方法

    1.什么是多态 多态性可以简单概括为"一个接口,多种行为". 也就是说,向不同的对象发送同一个消息, 不同的对象在接收时会产生不同的行为(即方法).也就是说,每个对象可以用自己的方式去响应共同的消息.所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数.这是一种泛型技术,即用相同的代码实现不同的动作.这体现了面向对象编程的优越性. 多态分为两种: (1)编译时多态:主要通过函数的重载和模板来实现. (2)运行时多态:主要通过虚函数来实现. 2.几个相关概念 (1)覆盖.

  • C++中继承与多态的基础虚函数类详解

    前言 本文主要给大家介绍了关于C++中继承与多态的基础虚函数类的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 虚函数类 继承中我们经常提到虚拟继承,现在我们来探究这种的虚函数,虚函数类的成员函数前面加virtual关键字,则这个成员函数称为虚函数,不要小看这个虚函数,他可以解决继承中许多棘手的问题,而对于多态那他更重要了,没有它就没有多态,所以这个知识点非常重要,以及后面介绍的虚函数表都极其重要,一定要认真的理解~ 现在开始概念虚函数就又引出一个概念,那就是重写(覆

  • 解析C++编程中virtual声明的虚函数以及单个继承

    虚函数 虚函数是应在派生类中重新定义的成员函数. 当使用指针或对基类的引用来引用派生的类对象时,可以为该对象调用虚函数并执行该函数的派生类版本. 虚函数确保为该对象调用正确的函数,这与用于进行函数调用的表达式无关. 假定基类包含声明为 virtual 的函数,并且派生类定义了相同的函数. 为派生类的对象调用派生类中的函数,即使它是使用指针或对基类的引用来调用的. 以下示例显示了一个基类,它提供了 PrintBalance 函数和两个派生类的实现 // deriv_VirtualFunctions

  • 探讨C++中不能声明为虚函数的有哪些函数

    常见的不不能声明为虚函数的有:普通函数(非成员函数):静态成员函数:内联成员函数:构造函数:友元函数. 1.为什么C++不支持普通函数为虚函数? 普通函数(非成员函数)只能被overload,不能被override,声明为虚函数也没有什么意思,因此编译器会在编译时邦定函数. 多态的运行期行为体现在虚函数上,虚函数通过继承方式来体现出多态作用,顶层 函数不属于成员函数,是不能被继承的 2.为什么C++不支持构造函数为虚函数? 这个原因很简单,主要是从语义上考虑,所以不支持.因为构造函数本来就是为了

  • 深入解析C++编程中的纯虚函数和抽象类

    C++纯虚函数详解 有时在基类中将某一成员函数定为虚函数,并不是基类本身的要求,而是考虑到派生类的需要,在基类中预留了一个函数名,具体功能留给派生类根据需要去定义. 纯虚函数是在声明虚函数时被"初始化"为0的函数.声明纯虚函数的一般形式是 virtual 函数类型 函数名 (参数表列) = 0; 关于纯虚函数需要注意的几点: 纯虚函数没有函数体: 最后面的"=0"并不表示函数返回值为0,它只起形式上的作用,告诉编译系统"这是纯虚函数"; 这是一个

  • C++中析构函数为何是虚函数

    目录 析构函数为什么是虚函数 构造函数为什么不能出虚函数 为什么构造函数和析构函数都不能调用虚函数 c++基类的析构函数为虚函数的原因 原因 例子 总结 析构函数为什么是虚函数 虚构函数是虚函数的情况只需要在特定场景下出现即可,正常情况下不必要弄成虚函数. 如果基类的析构函数不是虚函数,在特定情况下会导致派生来无法被析构. 情况1:用派生类类型指针绑定派生类实例,析构的时候,不管基类析构函数是不是虚函数,都会正常析构 情况2:用基类类型指针绑定派生类实例,析构的时候,如果基类析构函数不是虚函数,

  • 深入解析C++编程中基类与基类的继承的相关知识

    基类 继承过程将创建一个新的派生类,它由基类的成员加上派生类添加的任何新成员组成.在多重继承中,可以构建一个继承关系图,其中相同的基类是多个派生类的一部分.下图显示了此类关系图. 单个基类的多个实例 在该图中,显示了 CollectibleString 和 CollectibleSortable 的组件的图形化表示形式.但是,基类 Collectible 位于通过 CollectibleSortableString 路径和 CollectibleString 路径的 CollectibleSor

  • 深入解析C++编程中的静态成员函数

    C++静态成员函数 与数据成员类似,成员函数也可以定义为静态的,在类中声明函数的前面加static就成了静态成员函数.如 static int volume( ); 和静态数据成员一样,静态成员函数是类的一部分,而不是对象的一部分. 如果要在类外调用公用的静态成员函数,要用类名和域运算符"::".如 Box::volume( ); 实际上也允许通过对象名调用静态成员函数,如 a.volume( ); 但这并不意味着此函数是属于对象a的,而只是用a的类型而已. 与静态数据成员不同,静态成

  • 解析Java编程中对于包结构的命名和访问

    包的命名 包的名字应该避免与其他包冲突,所以选择一个既有意义又唯一的名字是包设计的一个重要方面.但是全球的程序员都在开发包,根本就没有办法获知谁采用了什么包名,因此选择唯一的包名是一个难题.如果我们确定某个包只在我们的组织内部使用,那么我们就可以让内部仲裁者(internal arbiter)来确保项目之间不会发生名字冲突. 但是对于整个世界而言,这种方法是不实际的.包的标识符都是简单的名字,一种比较好的能够确保包名唯一的方法是使用Internet域名.如果我们所就职的公司的名字为Magic.l

  • 解析C++编程中的继承方面的运用

    C++继承与组合详解 我们知道,在一个类中可以用类对象作为数据成员,即子对象(详情请查看:C++有子对象的派生类的构造函数).实际上,对象成员的类型可以是本派生类的基类,也可以是另外一个已定义的类.在一个类中以另一个类的对象作为数据成员的,称为类的组合(composition). 例如,声明Professor(教授)类是Teacher(教师)类的派生类,另有一个类BirthDate(生日),包含year,month,day等数据成员.可以将教授生日的信息加入到Professor类的声明中.如:

  • 构造函数不能声明为虚函数的原因及分析

    1. 从存储空间角度,虚函数对应一个指向vtable虚函数表的指针,这大家都知道,可是这个指向vtable的指针其实是存储在对象的内存空间的.问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数. 2. 从使用角度,虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用.构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀.所以构造函数没有必要是虚函数.虚函数的作用在于通过父类的指

随机推荐