代码改变世界

C++面向对象编程的若干概念

2012-01-03 14:50  马哈鱼  阅读(699)  评论(0编辑  收藏  举报

面向对象基于三个基本概念:抽象,继承和动态绑定。在C++中,用类实现数据抽象;派生类继承基类成员;动态绑定在运行时决定是使用基类的成员还是派生类的成员。继承能够定义相似但不同的类,动态绑定能够写出忽略这些不同的程序。

动态绑定
动态绑定使我们可以在程序中使用类继承层次中的任一对象,无需关心对象的类型,无需区分函数是在基类中定义还是在派生类中定义。C++中,通过引用或指针调用虚函数时发生动态绑定,引用或虚函数既可以指向基类对象,也可以指向派生类对象,这是动态绑定的关键,被调用的函数是由引用或指针指向的实际对象类型决定的。

虚函数
基类通常应该将派生类需要重定义的函数定义为虚函数。除了构造函数之外,任意非 static 成员函数都可以是虚函数。保留字只在类内部的成员函数声明中出现,不能用在类定义体外部出现的函数定义上。派生类一般会重定义所继承的虚函数。派生类没有重定义某个虚函数,则使用基类中定义的版本。一旦函数在基类中声明为虚函数,它就一直为虚函数,派生类无法改变该函数为虚函数这一事实。派生类重定义虚函数时,可以使用 virtual 保留字,但不是必须这样做。作用域操作符可以覆盖虚函数机制。

接口继承和实现继承
public 派生类继承基类的接口,它具有与基类相同的接口。使用 private 或 protected 派生的类不继承基类的接口,相反,这些派生通常被称为实现继承。派生类在实现中使用被继承但继承基类的部分并未成为其接口的一部分。
派生类到基类的转换
可以使用派生类对象的地址或引用对基类对象的地址或引用进行初始化或赋值,但是编译器不会将派生类的对象自动转换为基类对象。可以使用派生类对象对基类对象进行赋值或初始化,对对象进行初始化或赋值,与自动转换引用和指针是有区别的:
用派生类对象对基类对象进行初始化或赋值时,有两可能性。第一种(虽然不太可能的)可能性是,基类可能显式定义了将派生类型对象复制或赋值给基类对象的含义,这可以通过定义适当的构造函数或赋值操作符实现。第二种可能性,基类一般(显式或隐式地)定义自己的复制构造函数和赋值操作符,这些成员接受一个形参,该形参是基类类型的(const)引用。因为存在从派生类引用到基类引用的转换,这些复制控制成员可用于从派生类对象对基类对象进行初始化或赋值。
派生类到基类转换的可访问性
如果是 public 继承,则用户代码和后代类都可以使用派生类到基类的转换。如果类是使用 private 或 protected 继承派生的,则用户代码不能将派生类型对象转换为基类对象。如果是 private 继承,则从 private 继承类派生的类不能转换为基类。如果是 protected 继承,则后续派生类的成员可以转换为基类类型。

构造函数和复制控制
构造函数和复制控制成员不能继承,每个类定义自己的构造函数和复制控制成员。像任何类一样,如果类不定义自己的默认构造函数和复制控制成员,就将使用合成版本。一个类只能初始化自己的直接基类。只包含类类型或内置类型数据成员、不含指针的类一般可以使用合成操作,复制、赋值或撤销这样的成员不需要特殊控制。具有指针成员的类一般需要定义自己的复制控制来管理这些成员。

虚析构函数
处理继承层次中的对象时,指针的静态类型可能与被删除对象的动态类型不同,可能会删除实际指向派生类对象的基类类型指针。如果删除基类指针,则需要运行基类析构函数并清除基类的成员,如果对象实际是派生类型的,则没有定义该行为。要保证运行适当的析构函数,基类中的析构函数必须为虚函数。如果析构函数为虚函数,那么通过指针调用时,运行哪个析构函数将因指针所指对象类型的不同而不同。像其他虚函数一样,析构函数的虚函数性质都将继承。因此,如果层次中根类的析构函数为虚函数,则派生类析构函数也将是虚函数,无论派生类显式定义析构函数还是使用合成析构函数,派生类析构函数都是虚函数。

构造函数和析构函数中的虚函数
运行派生类的构造函数或析构函数的时候,对象都是不完整的。为了适应这种不完整,编译器将对象的类型视为在构造或析构期间发生了变化。在基类构造函数或析构函数中,将派生类对象当作基类类型对象对待。如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本。要理解这种行为,考虑如果从基类构造函数(或析构函数)调用虚函数的派生类版本会怎么样。虚函数的派生类版本很可能会访问派生类对象的成员。

继承情况下的类作用域
在继承情况下,派生类的作用域嵌套在基类作用域中。如果不能在派生类作用域中确定名字,就在外围基类作用域中查找该名字的定义。与基类成员同名的派生类成员将屏蔽对基类成员的直接访问。使用作用域操作符访问被屏蔽成员。设计派生类时,只要可能,最好避免与基类成员的名字冲突。

理解 C++ 中继承层次的关键在于理解如何确定函数调用。确定函数调用遵循以下四个步骤:
(1)首先确定进行函数调用的对象、引用或指针的静态类型。
(2)在该类中查找函数,如果找不到,就在直接基类中查找,如此循着类的继承链往上找,直到找到该函数或者查找完最后一个类。如果不能在类或其相关基类中找到该名字,则调用是错误的。
(3)一旦找到了该名字,就进行常规类型检查,查看如果给定找到的定义,该函数调用是否合法。
(4)假定函数调用合法,编译器就生成代码。如果函数是虚函数且通过引用或指针调用,则编译器生成代码以确定根据对象的动态类型运行哪个函数版本,否则,编译器生成代码直接调用函数。

纯虚函数
将函数定义为纯虚能够说明,该函数为后代类型提供了可以覆盖的接口,但是这个类中的版本决不会调用。重要的是,用户将不能创建该类型的对象。含有(或继承)一个或多个纯虚函数的类是抽象基类。除了作为抽象基类的派生类的对象的组成部分,不能创建抽象类型的对象。

句柄类和继承
句柄类存储和管理基类指针。指针所指对象的类型可以变化,它既可以指向基类类型对象又可以指向派生类型对象。用户通过句柄类访问继承层次的操作。因为句柄类使用指针执行操作,虚成员的行为将在运行时根据句柄实际绑定的对象的类型而变化。因此,句柄的用户可以获得动态行为但无须操心指针的管理。