c++ primer 学习笔记25 面向对象编程

来源:互联网 发布:gta5捏脸数据希斯莱杰 编辑:程序博客网 时间:2024/05/29 07:51

面向对象编程基于三个基本概念:数据抽象、继承和动态绑定。

继承

派生类(derived class)能够继承基类(base class)定义的成员,除了从基类继承的成员之外,派生类还可以定义更多的成员。

在 C++ 中,基类必须指出希望派生类重写哪些函数,定义为 virtual 的函数是基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数。 

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

定义基类和派生类

 // Item sold at an undiscounted price      // derived classes will define various discount strategies      class Item_base {      public:          Item_base(const std::string &book = "", double sales_price = 0.0):                           isbn(book), price(sales_price) { }          std::string book() const { return isbn; }          // returns total sales price for a specified number of items          // derived classes will override and apply different discount algorithms          virtual double net_price(std::size_t n) const                     { return n * price; }          virtual ~Item_base() { }      private:          std::string isbn;     // identifier for the item      protected:          double price;         // normal, undiscounted price      };  
基类成员函数 
任意非 static 成员函数都可以是虚函数。保留字只在类内部的成员函数声明中出现,不能用在类定义体外部出现的函数定义上。

基类通常应将派生类需要重定义的任意函数定义为虚函数。 

访问控制和继承

在基类中,public 和 private 标号具有普通含义:用户代码可以访问类的public 成员而不能访问 private 成员,private 成员只能由基类的成员和友元访问。protected 成员可以被派生类对象访问但不能被该类型的普通用户访问。

派生类

 class classname: access-label base-class 

这里 access-label 是 public、protected 或 private

访问标号决定了对继承成员的访问权限。如果想要继承基类的接口,则应该进行public 派生。 
派生类继承基类的成员并且可以定义自己的附加成员。每个派生类对象包含两个部分:从基类继承的成员和自己定义的成员。一般而言,派生类只(重)定义那些与基类不同或扩展基类行为的方面。 

尽管不是必须这样做,派生类一般会重定义所继承的虚函数。派生类没有重定义某个虚函数,则使用基类中定义的版本。 

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

派生类函数可以在类的内部或外部定义。

因为每个派生类对象都有基类部分,类可以访问共基类的public 和 protected 成员,就好像那些成员是派生类自己的成员一样。 

已定义的类才可以用作基类。如果已经声明了 Item_base 类,但没有定义它,则不能用 Item_base 作基类:

  class Item_base; // declared but not defined      // error: Item_base must be defined      class Bulk_item : public Item_base { ... }; 
 virtual  与其他成员函数

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

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

 // function with an Item_base reference parameter      double print_total(const Item_base&, size_t);      Item_base item;           // object of base type  // ok: use pointer or reference to Item_base to refer to an Item_base object      print_total(item, 10);    // passes reference to an Item_base object      Item_base *p = &item;     // p points to an Item_base object       Bulk_item bulk;           // object of derived type      // ok: can bind a pointer or reference to Item_base to a Bulk_item object      print_total(bulk, 10);    // passes reference to the Item_base part of bulk      p = &bulk;                // p points to the Item_base part of bulk 
将派生类对象当作基类对象是安全的,因为每个派生类对象都拥有基类子对象。而且,派生类继承基类的操作,即,任何可以在基类对象上执行的操作也可以通过派生类对象使用。

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

 void print_total(ostream &os,                       const Item_base &item, size_t n)      {          os << "ISBN: " << item.book() // calls Item_base::book             << "\tnumber sold: " << n << "\ttotal price: "             // virtual call: which version of net_price to call is resolved at run time             << item.net_price(n) << endl;      } 
  Item_base base;      Bulk_item derived;      // print_total makes a virtual call to net_price      print_total(cout, base, 10);     // calls Item_base::net_price      print_total(cout, derived, 10);  // calls Bulk_item::net_price 
通过基类引用或指针调用基类中定义的函数时,我们并不知道执行函数的对象的确切类型,执行函数的对象可能是基类类型的,也可能是派生类型的。 
如果调用非虚函数,则无论实际对象是什么类型,都执行基类类型所定义的函数。如果调用虚函数,则直到运行时才能确定调用哪个函数,运行的虚函数是引用所绑定的或指针所指向的对象所属类型定义的版本。 

覆盖虚函数机制 

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

  Item_base *baseP = &derived;      // calls version from the base class regardless of the dynamic type of baseP      double d = baseP->Item_base::net_price(42); 
这段代码强制将 net_price 调用确定为 Item_base 中定义的版本,该调用将在编译时确定。

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


 



 


 

0 0
原创粉丝点击