虚函数和动态绑定 C++学习

1.什么是虚成员函数

即其声明在返回类型的前面带有关键字virtual的类成员函数。定义为virtual的函数是基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数。

2.动态绑定

通过动态绑定,我们能够编写程序使用继承层次中任意类型的对象,无须关心对象的具体类型。

在C++中,通过基类的引用(或指针)调用虚函数时,发生动态绑定引用(或指针)既可以指向基类对象也可以指向派生类对象,这一事实是动态绑定的关键

用引用(或指针)调用的虚函数在运行时确定,被调用的函数是引用(或指针)所指对象的实际类型所定义的。

3.基类成员函数

成员默认为非虚函数,对非虚函数的调用在编译时确定。为了指明函数为虚函数,在其返回类型的前面加上virtual。除构造函数之外,任意非static成员函数都可以是虚函数保留字virtual只在类内部的成员函数中声明,不能用在类定义体外部出现的函数定义上。

4.派生类与虚函数

尽管不是必须这样做,派生类一般会重新定义所继承的虚函数。如果派生类没有重新定义虚函数,则使用基类中定义的版本。派生类型必须对想要重新定义的每个继承成员进行声明。派生类中虚函数的声明必须与基类中的定义方式一样,但有一个例外:返回基类型的引用(或指针)的虚函数。派生类的虚函数可以返回基类函数所返回类型的派生类的引用(或指针)。

注意:一旦函数在基类声明中为虚函数,它就一直为虚函数,派生类无法改变该函数为虚函数这一事实。派生类重定义虚函数时,可以使用virtual保留字,但不是必须这样做

5.virtual与其他成员函数

要触发动态绑定,必须满足两个条件:第一,只有指定为虚函数的成员函数才能进行动态绑定,成员函数默认为非虚函数,非虚函数不进行动态绑定;第二,必须通过基类类型的引用或指针进行函数调用

1)从派生类到基类的转换

因为每个派生类对象都包含基类部分,所以可将基类类型的引用绑定到派生类对象的基类部分,也可以将指向基类的指针指向派生类对象。

使用基类类型的指针和引用时,不知道指针和引用所绑定的对象的类型:基类类型的引用或指针既可以引用基类类型对象,又可以引用派生类型对象。无论实际对象是哪一种,编译器都将它当作基类类型对象。(将派生类对象当作基类对象是安全的,因为每个派生类对象都拥有基类子对象。而且,派生类继承基类的操作,即任何作用于基类对象上执行的操作也可以通过派生类对象使用)。

注:基类类型引用和指针的关键点在于静态类型和动态类型可能不同。静态类型,在编译时可知的引用类型或指针类型;动态类型,指针或引用类型绑定的对象的类型,这仅仅是运行时可知。

2)可以在运行时确定virtual函数的调用

c++动态绑定的关键:对象的实际类型(动态类型)可能不同于该对象的引用或指针的静态类型

3)在编译时确定非virtual函数的调用

非虚函数总是在编译时根据调用该函数的对象、引用、指针的类型而确定。

4)覆盖虚函数机制

在某些情况下,希望覆盖虚函数机制并强制函数调用使用虚函数的特定版本,这是可以使用作用域操作符。

如Item_base *baseP = &derived; baseP->Item_base::net_price(42);//该调用编译时确定(如果没有作用域则在运行时调用baseP所指向对象相对应的函数)

使用覆盖虚函数机制的理由是为了派生类虚函数调用基类中的版本。这种情况下,基类版本可以完成基础层次中所有类型的公共任务,而每个派生类型只添加自己的特殊工作。

派生类虚函数调用基类版本时,必须显式使用作用域操作符。如果派生类函数忽略了这样做,则函数调用会在运行时确定并且将会是一个自身调用,从而导致无穷递归

例子:

基类Item_base的定义(Item_base.h)

  1 #ifndef ITEM_BASE_H
  2 #define ITEM_BASE_H
  3 #include<string>
  4 class Item_base {
  5 public:
  6         Item_base(const std::string &s = "", double p = 0):isbn(s), price(p) { }
  7         std::string book() const { return isbn; }       /*返回书的编号*/
  8         virtual double net_price(std::size_t n) const   /*返回给定数目的某书的总价(实价)*/
  9         {
 10                 return n * price;       //基类中的net_price无折扣
 11         }
 12         virtual ~Item_base() { }
 13 private:
 14         std::string isbn;
 15 protected:
 16         double price;           /*price为protected,可以被派生类对象访问但不能被该类型的普通用户访问*/
 17 };
 18 #endif

派生类 Bulk_item(Bulk_item.h)

  1 #ifndef BULK_ITEM_H
  2 #define BULK_ITEM_H
  3 #include "Item_base.h"
  4
  5 class Bulk_item:public Item_base
  6 {
  7 public:
  8         Bulk_item(const std::string &s, double price, std::size_t mq, double dc):min_qty(mq), discount(dc), Item_base(s, price)
  9         {
 10         }
 11         double net_price(std::size_t n) const;
 12 private:
 13         std::size_t min_qty;    //折扣的最小购买数量
 14         double discount;        //折扣
 15 };
 16 #endif

派生类实现Bulk_item.cpp

  1 #include <iostream>
  2 #include "Bulk_item.h"
  3 using namespace std;
  4
  5 double Bulk_item::net_price(size_t n) const
  6 {
  7         if(n >= min_qty)
  8                 return n * (1 - discount) * price;
  9         else
 10                 return n * price;
 11 }
 12
 13 void print_total(ostream &os, const Item_base &item, size_t n)
 14 {
 15         os << "book isbn:" << item.book()
 16            << "net_price:" << item.net_price(n) << endl;
 17 }

测试test.cpp

  1 #include <iostream>
  2 #include "Item_base.h"
  3 #include "Bulk_item.h"
  4 using namespace std;
  5
  6 int main()
  7 {
  8         Bulk_item bulk("123", 23, 5, 0.8);
  9         Item_base &base = bulk;
 10
 11         cout << base.net_price(6) << endl;
 12         cout << base.Item_base::net_price(6) << endl;
 13 }

测试结果:27.6

128

使用基类Item_base的引用调用虚成员函数会发生动态绑定,被调用的函数是与动态类型即Bulk_item相对应的函数。

使用作用域操作符覆盖虚函数机制,即Item_base::可以调用基类的版本。

posted @ 2012-04-30 18:53  whu-小磊  阅读(296)  评论(0编辑  收藏  举报