标签:
char boy[] = "Danny"; char *p_son; ... p_son = new char[strlen(boy) + 1]; strcpy(p_son, boy); ... if (!strcmp(p_son, boy)) take_to_disneyland(boy);
String girl = "Anna"; String daughter; ... // String::operator=(); daughter = girl; ... // String::operator==(); if (girl == daughter) take_to_disneyland(girl);
void check_in(Library_materials *pmat) { if (pmat->late()) pmat->fine(); pmat->check_in(); if (Lender *plend = pmat->reserved()) pmat->notify(plend); }纯粹以一种paradigm写程序,有助于整体行为的良好巩固,然而如果混合了不同的paradigms,就可能会带来让人惊吓的后果,特别是在没有谨慎处理的情况下。最常见的疏忽发生在以一个base class的具体实体如:
Library_materials thing1;来完成某种多态(polymorphism)局面时:
class Book : Public Library_materials {...}; Book book; // thing1不是一个Book,book被切割(sliced),不过thing1仍保有一个Library_materials. thing1 = book; // 调用Library_materials::check_in() thing1.check_in();而不是通过base class的pointer或reference来完成多态局面:
Library_materials &thing2 = book; thing2.check_in();虽然可以直接或间接处理继承体系中的一个base class object,但只有通过pointer或者reference的间接处理,才支持OO程序设计所需的多态性质。上个例子中的thing2的定义和运用,是OO paradigm中一个良好的例证。thing1的定义和运用则逸出了OO的习惯,它反映的是一个ADT paradigm的良好行为。thing1的行为是好是坏,视程序员的意图而定。
// 没有多态,因为操作对象不是class object int *pi; // class x视为一个base class(可以有多态的效果) x *px;在C++,多态只存在与一个个的public class体系中,例如px可能指向自我类型的一个object,或者指向以public派生而来的一个类型。Nonpublic的派生行为以及类型为void*的指针可以说是多态,但它们并没有被语言明白地支持,也就是说它们必须由程序员通过显式的转换操作来管理。
shape *ps = new circle();
ps->rotate();
if (circle *pc = dynamic_cast<circle *>(ps))多态的主要用途是经由一个共同的接口来影响类型的封装,这个接口通常被定义在一个抽象的base class 中。例如Library_materials class 就为Book、Vedio等subtype定义了一个接口。这个共享接口是以virtual function机制引发的,它可以在执行期根据object的真正类型解析出到底是哪一个函数实体被调用,经由这样的操作:
Library_material->check_out();代码可以避免由于"借助某一特定library的materials"而导致变动无偿,这不只使得"当类型有所增加、修改、删减时,程序代码不需改变",而且也使一个新的Library_materials subtype的供应者不需要重新编写出"对继承体系中所有类型都共通"的行为和操作.
void rotate(X datum, const X *pointer, const X &reference) { // 在执行期之前,无法决定到底调用哪一个rotate()实体 (*pointer).rotate(); reference.rotate(); // 下面这个操作总是调用X::rotate() datum.rotate(); } main() { Z z; // Z是X的一个子类型 rotate(z, &z, z); return 0; }经由pointer和reference完成的两个"函数调用指针"会被动态完成。此例中它们都调用Z::rotate(),经由datum完成的"函数调用操作"则可能(可能不)经由virtual机制。不过,它总是调用X::rotate()。(这就是所谓的"编译素养"问题:不管经由datum所调用的virtual function采不采用virtual机制,从语意来说,结果都是一样的)
class ZooAnimal { public: ZooAnimal(); virtual ~ZooAnimal(); // ... virtual void rotate(); protected: int loc; string name; }; ZooAnimal za("Zoey"); ZooAnimal *pza = &za;其中的class object za和指针pza的可能布局为loc(1000~1003),name(1004~1011),virtual(1012~1015)。
ZooAimal *px; int *pi; Array<string> *pta;以内存需求的观点来说,没有不同。它们三个都需要有足够的内存来存放一个机器地址。"指向不同类型的指针"间的差异,既不在于指针表示法不同,也不在于其内容(代表一个地址)不同,而是在于其所寻址出来的object类型不同。也就是说,"指针类型"会指示编译器如何解释某个特定地址中的内存内容以及其大小。
class Bear : public ZooAnimal { public: Bear(); ~Bear(); // ... void rotate(); virtual void dance(); // ... protected: enum Dances {...} Dances dances_known; int cell_block; }; Bear b("Yogi"); Bear *pb = &b; Bear &rb = *pb;b,pb,rb会有怎样的内存需求呢?不管是pointer或reference都只需要一个word的空间(在32位机器上是4-bytes)。Bear object需要24 bytes,也就是ZooAnimal的16 bytes加上Bear所带来的8 bytes。
Bear b; ZooAnimal *pz = &b; Bear *pb = &b;它们每个都指向Bear object的第一个byte,其间的差别是pb所涵盖的地址包含整个Bear object,而pz所涵盖的地址只包含Bear object中的ZooAnimal subobject。
// 不合法:cell_block不是ZooAnimal的一个member,虽然pz当前指向一个Bear object pz->cell_block; // OK:经过一个显示的downcast操作就没问题 ((Bear *)pz)->cell_block; // 下面这样更好,但它是一个run-time operation(成本较高) if (Bear *pb2 = dynamic_cast<Bear *>(pz)) pb2->cell_block; // OK:因为cell_block是Bear的一个member pb->cell_block;如果写如下语句
pz->rotate();时,pz的类型将在编译时决定以下两点:
Bear b; ZooAnimal za = b; // 这会引起切割(sliced) // 调用ZooAnimal::rotate() za.rotate();为什么rotate()所调用的是ZooAnimal实体而不是Bear实体?此外,如果初始化函数(应用于上述assignment操作发生时)将一个object内容完全拷贝到另一个object中去,为什么za的vptr不指向Bear的virtual table?
{ ZooAnimal za; ZooAnimal *pza; Bear b; Panda *pp = new Panda; pza = &b; }将za或b的地址,或pp的内容(也是个地址)赋给pza,显然不是问题。一个pointer或者一个reference之所以支持多态,是因为它们并不引发内存中任何"与类型有关的内存委托操作(type-dependent commitment)",会受到改变的知识它们所指向的内存的"大小和内容解释方式"而已。
版权声明:本文为博主原创文章,未经博主允许不得转载。
标签:
原文地址:http://blog.csdn.net/yiranant/article/details/47121893