C++面向对象

概述

面向对象编程基于三个基本概念:数据抽象继承动态绑定。在C++中,用类进行数据抽象,用类派生从一个类继承基类的成员。动态绑定使编译器能够在运行时决定是使用基类中定义的函数还是派生类中定义的函数。
继承和动态绑定在两个方面简化了程序:

  1. 能够容易地定义与其他类相似但又不同的新类;
  2. 能都更容易地忽略这些相似类型之间区别的程序。

继承

通过继承可以实现类型之间的建模,共享公共的东西,仅仅特化本质上不同的东西。派生类可以继承基类定义的成员,派生类可以无需改变而使用那些与派生类型具有特性不相关的操作,派生类可以重定义那些与派生类相关的成员函数,将函数特化,考虑派生类型的特性。派生类还可以定义更多的成员。

定义基类

// C++ primer中的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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; }
// reutrn total sales price for a specified number of items
// derived classes will override and apply different discount algorithm
virtual double net_price(std::size_t n) const
{ return n * price; }

virtual ~Item_base() { }
private:
std::string isbn; // identifier for the iterm
protected:
double price; // nomal, undiscounted price
}

Item_base定义了两个成员函数,一个带有保留字virtual。目的是启用动态绑定。成员默认为非虚函数,引入了protected访问标号,具体在以下介绍。

访问控制

public与private具有普通含义,public: 可以被类的用户和类的成员访问也可以被类的派生类访问(除私有派生),private:可以被类的成员访问但不能被类的用户和派生类访问。引入protectd,是public与private的混合:

  • 像private一样,不能被类的用户访问;
  • 像public一样,可被类的派生类访问。
    另外,派生类只能通过派生类对象访问其基类的protected成员,但不能访问其基类对象的protected成员。

定义派生类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// discount kicks in when a specified number of copies of same book are sold
// the discount is expresses as a fraction used reduce the normal price
class Bulk_item : public Item_base {
public:
// redefines base version so as to implement bulk purchaes discount policy
double net_price(std::size_t) const;
private:
std::size_t min_qty; // minimum purchaes for discount to apply
double discount; // fractional discount to apply
}
double Bulk_item::net_price(std::size_t cnt) const
{
if(cnt >= min_qty)
return cnt * (1 - discount) * price;
else
return cnt * price;
}

每个Bulk_item对象包含四个数据成员:从Item_base继承的两个isbn和price,自己定义的两个min_qty和discount。并重写了net_price()函数。

动态绑定

C++函数调用默认不使用动态绑定,要触发动态绑定,必须满足两个条件:

  1. 只有指定为虚函数的成员函数才能进行动态绑定;
  2. 必须通过基类类型的引用或指针进行函数调用;

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

通过基类引用或指针调用基类中定义的函数时,我们并不知道执行函数的对象的确切类型,执行函数的对象可能是基类类型的,也可能是派生类型的。如果调用非虚函数,则无论实际对象是什么类型,都执行基类类型定义的函数。因此在编译是就确定了非虚函数的调用。