C++支持如下三种programming paradigms(程序设计典范):
1. The procedural model as programmed in C, and, of course, supported within C++.
2. The abstract data type (ADT) model in which users of the abstraction are provided with a set of operations (the public interface), while the implementation remains hidden.
3. The object-oriented (OO) model in which a collection of related types are encapsulated through an abstract base class providing a common interface.
可以看到对于procedural模式, 就等同于C编程, 这个很好理解
ADT模式, 就是象前面提过一样, 把数据和一组operation建立联系, 即封装成class. 很多人把这种模式理解为OO, 这个是不准确的, 这种模式被称为object-based (OB) —nonpolymorphic data types, such as a String class.
OB相对于OO, 不支持多态, 设计速度更快, 空间更紧凑, 可以说, ADT相对于C编程, 从空间和效率上, 没有很大的差别, 所以也很高效.
但是在设计上没有多态, 缺乏弹性.
OB和OO各自都有着拥护者和批评者,
The trade-off usually boils down to one of flexibility (OO) versus efficiency (OB).
所以其实没有那个更好, 两者各有特点, 有着不同的应用领域.
OO- Polymorphism
下面就重点看看, C++语言OO设计的精髓, 多态特性
有一组彼此相关的类型, 通过一个抽象的base class被封装起来, 这个base class规定了这组类型的共通的接口.
程序员无需关心, 运行时得到的对象具体是哪个类型, 只需要用base class指针或引用来使用它的共通的接口(虚函数). 运行时, 此段代码可以适用于这组类型中的任一类型, 所以对于程序员来说, 此时的base class指针所可能指向的对象, 是未知, 可能是这个, 也可能是哪个, 多态的.
指针的类型
多态能够起作用, 必须借助于指针, 为什么用指针能够实现那么灵活的特性了
首先指针是个地址, 无论什么类型的指针的大小都是一样的, 因为都是存放了一个机器地址, 大小都是一个机器字.
所以对于不同类型的指针, 以内存需求的观点来说, 没有什么不同的, 那这个类型意味着什么?
指针记录的是一个地址, 而且是一个数据的首地址, 对于一个数据而言, 光知道首地址是不够的, 对不?
我们还需要知道数据的length, 这样才能取出数据, 那么指针的类型就是来规定length的. 指针的类型就是告诉编译器如何来解释这块地址上所存放的内容, 及内容的大小.
所以Void* 指针可以包含一个地址, 但不能用他来操作所指的对象, 因为你没有告诉编译器, 你指向的是什么, 编译器也不知道该怎么取.
所以转型(Cast)其实是一个编译器指令, 他不会改变地址值, 只是改变对内存内容及大小的解释方式.
Polymorphism
那么我们来看看下面这个多态的例子
有一个ZooAnimal基类, 和Bear派生类, 下面是Bear类对象的内存布局,
注意看这个内存布局, 基类的data member放完后, 紧接着放的是虚表指针, 然后再放派生类data members.
这点很重要, 这种内存布局决定了多态的可行性.
因为你可以看到, 对于任意ZooAnimal的派生类对象, 前sizeof(ZooAnimal)大小的内存布局是一样的, 不会有任何不同.
所以你可以用ZooAnimal指针指向任意派生类对象, 并使用共通接口(通过vptr).
但是你如果想用基类指针, 直接访问派生类成员是不行的
ZooAnimal *pz = &b;
pz->cell_block; //fail
因为这时对于编译器而言, 你给的对象length中, 没有cell_block, 必须要Cast来改变指针的解释
// okay: an explicit downcast
(( Bear* )pz)->cell_block;
// better: but a run-time operation
if ( Bear* pb2 = dynamic_cast< Bear* >( pz )) pb2->cell_block;
上面两种Cast, 动态Cast的好处, 是它会从虚表中第一个slot取出对象类型, 进行验证, 如果要求Cast的类型不兼容, 会报错.
这样效率会低一些, 而且dynamic_cast的实现是编译器相关的, 但他的好处就是安全, 不然cast的类型不对, 会导致程序crash, 因为你可能把不属于对象的内存也包含进来了, 而编译器是严格按照你的解释去取数据的.
本文章摘自博客园,原文发布日期:2011-07-05