单继承

  • 继承关系中存在两个类:基类(或称父类)和派生类(或称子类)

    派生类拥有基类所有成员,并可以

    • 定义新的成员
    • 对基类的一些成员函数重定义

    class <派生类名>: [<继承方式>] <基类名>

  • 派生类不会继承基类的构造函数、赋值操作符重载函数、析构函数

  • 定义派生类时一定要见到基类的定义

  • 友元性不继承:如果在派生类中没有显式说明,基类的友元不是派生类的友元;如果基类是另一个类的友元,而该类没有显式说明,则派生类也不是该类的友元

  • 派生类不能直接访问基类的私有成员

  • protected: 解决继承与封装的矛盾

  • 作用域

    • 对基类而言,派生类成员标识符的作用域是嵌套在基类作用域中的
    • 派生类中定义了与基类同名的成员,则基类的成员名在派生类的作用域内不直接可见
    • 使用全局函数,加全局作用域 ::
      class A {
          int x,y;
          public:
              void f();
              void g();
      };
      class B: public A
      {
          int z;
          public:
              void f(int); //不是重载
              void h()
              {
                  f(1);
                  A::f();
              }
              //or using A::f
              //f()
      };
    
      B b;
      b.f(1);
      b.A::f();
    
  • 继承方式:默认为 private

继承方式\派生类\基类成员 public private protected
public public 不可访问 protected
private private 不可访问 private
protected protected 不可访问 protected
  • 在C++中,把类看作类型,把以public方式继承的派生类看作是基类的子类型
    • 在需要基类对象的地方可以用派生类对象去替代
    • 发给基类对象的消息也能发给派生类对象
//合法操作
A a;
B b;//B 继承 A
a = b;
A *p = &b;
void func1(A *p);
void func2(A &x);
void func3(A x);
func1(&a); func2(a); func3(a);
func1(&b); func2(b); func3(b);
  • 代码复用
    • 继承:is-a-kind-of
    • 聚集:is-a-part-of,基于对象的程序设计中,一般采用聚集来实现代码复用的

派生类

  • 派生类的初始化:派生类对象的初始化由基类和派生类共同完成
    • 从基类继承的数据成员由基类的构造函数初始化
    • 派生类的数据成员由派生类的构造函数初始化
  • 创建派生类的对象
    • 默认情况下,调用基类的默认构造函数,如果要调用基类的非默认构造函数,则必须在派生类构造函数的成员初始化表中指出
  • 派生类对象的析构由基类和派生类共同完成
    • 从基类继承的数据成员由基类的析构函数析构
    • 派生类的数据成员由派生类的析构函数析构
  • 如类D既有基类B、又有成员对象类M,则
    • 在创建D类对象时,构造函数的执行次序为:B->M->D
    • 当D类的对象消亡时,析构函数的执行次序为:D->M->B
  • 派生类拷贝构造函数:
    • 派生类的隐式拷贝构造函数(由编译程序提供)将会调用基类的拷贝构造函数。
    • 派生类自定义的拷贝构造函数在默认情况下则调用基类的默认构造函数。需要时,可在派生类自定义拷贝构造函数的“成员初始化表”中显式地指出调用基类的拷贝构造函数
  • 派生类隐式的赋值操作除了对派生类成员进行赋值外,还将调用基类的赋值操作对基类成员进行赋值。派生类自定义的赋值操作符重载函数不会自动调用基类的赋值操作,需要在自定义的赋值操作符重载函数中显式地指出

多态

  • 对于具有public继承关系的两个类,在C++中存在下面的多态

    • 对象类型多态:对于具有public继承关系的两个类,在C++中存在下面的多态
    • 对象标识多态:基类的指针或引用可以指向或引用基类对象,也可以指向或引用派生类对象
    • 消息多态:一个可以发送到基类对象的消息,也可以发送到派生类对象,从而可能会得到不同的解释
  • 消息的绑定问题:一个可以发送到基类对象的消息,也可以发送到派生类对象,从而可能会得到不同的解释

  • 静态绑定

      void func1(A& x)
      {
          x.f(); //调用A::f
      }
      void func2(A *p)
      {
          p->f(); //调用A::f
      }
    
  • 虚函数

    • 实现消息的动态绑定
    • 指出基类中可以被派生类重定义的成员函数:对于基类中的一个虚函数,在派生类中定义的、与之具有相同型构的成员函数是对基类该成员函数的重定义(或称覆盖,override)
      • 成员函数的名字,参数个数和类型与基类相应成员函数相同
      • 返回值类型与基类成员函数返回值类型或者相同或为子类型
    • 静态成员函数不能是虚函数
    • 构造函数不能是虚函数,析构函数可以(往往)是虚函数
  • 动态绑定

    • 只有通过基类的指针或引用访问基类的虚函数时才进行动态绑定
    • 基类的构造函数、析构函数中对虚函数的调用不进行动态绑定
  • 通过基类指针访问派生类中新定义的成员:运行时刻类型信息(RTTI)支持

B *q=dynamic_cast<B *>(p);
if (q!=NULL) q->g();
  • 纯虚函数:virtual int f()=0;
  • 抽象类:包含纯虚函数的类
    • 不能用于创建对象
    • 提供基本框架和公共对外接口
    • 可以在 C 中用联合类型实现
  • 使用抽象类进行访问控制
    • A.h: 公开,包含抽象类 I_A,对外接口
    • A.cpp: 不公开,包含类 A 的定义和成员函数的实现
  • 虚函数的实现
typedef void (*FuncPtr)(void *);//指向函数的指针
typedef FuncPtr *VtblPtr;
//每个类维护虚函数表
p->f(); //编译为 (*(*(VtblPtr*)p))(p);
p->g(); //编译为 (*(*(VtblPtr*)p+1))(p);

多继承

class C: public A, public B
{
  int r;
  public:
    void fc();
};
  • 派生类拥有所有基类的所有成员
  • 基类的声明次序决定
    • 对基类构造函数/析构函数的调用次序
    • 对基类数据成员的存储安排
  • 名冲突:基类名受限

虚基类

  • 重复继承问题:类D从类A继承两次
class B: virtual public A {...};
class C: virtual public A {...};
class D: public B, public C {...};
D d;//D只包含一个A中元素
  • 间接包含虚基类的虚基类构造函数
    • 虚基类的构造函数由最新派生出的类的构造函数调用
    • 虚基类的构造函数优先非虚基类的构造函数执行
  • 虚基类的实现
    • 虚基类数据成员移到最后
    • 原来存放虚基类数据成员的地方储存一个偏移量指针
    • 维护虚基类表

转移构造函数与转移赋值操作符重载函数

  • 拷贝构造问题:当用一个临时或即将消亡的对象去初始化另一个同类的对象时,目前的拷贝构造函数的实现效率有时是不高的
  • C++新国际标准(C++11)为C++提供了一种新的构造函数――转移构造函数(move constructor)
    • 当用一个临时对象或即将消亡的对象去初始化另一个对象时如果对象类中有转移构造函数,则会去调用转移构造函数来对对象初始化,否则去调用拷贝构造函数进行对象初始化
    • 系统不提供隐式转移构造函数
A(A&& x){//参数类型右值引用类型
  p = x.p;
  x.p = NULL;
}
A& operator=(A&& x){//转移赋值操作符重载函数
  if (&x == this) return *this;
  delete []p;
  p = x.p;
  x.p = NULL;
  return *this;
}