15.15 练习
部分练习题的答案可以在本书的电子文档“Annotated Solution Guide forThinking in C++”中找到,只需支付很少的费用就可以从http://www.BruceEckel.com得到这个电子文档。
15-1 创建一个非常简单的“shape”层次:基类称为Shape,派生类称为Circle、Square和Triangle。在基类中定义一个虚函数draw(),再在这些派生类中重定义它。在堆中创建Shape对象,并且建立一个指向这些Shape对象的指针数组(这样就形成了指针向上类型转换)。并且通过基类指针调用draw(),检验虚函数的行为。如果调试器支持,就用单步执行这个例子。
15-2 修改练习1,使得draw()是纯虚函数。尝试创建一个类型为Shape的对象。并试着在构造函数内调用这个纯虚函数,看看结果如何。保留它的纯虚性,对draw()进行定义。
15-3 在练习2的基础上进一步,创建一个通过传值方式接收Shape对象参数的函数,并试着向上类型转换一个派生类对象作为参数。看看结果如何。通过把参数设为Shape对象的引用来修改这个函数。
15-4 修改C14:Combined.cpp,把基类中的f()设为虚函数。在main()中执行向上类型转换并且调用虚函数。
15-5 通过增加一个虚函数prepare()来修改Instrument3.cpp。在tune()中调用prepare()。
15-6 创建一个含有Rodent类的继承层次,它包括Mouse、Gerbil、Hamster等。在基类中提供对所有Rodent都适用的方法,并根据Rodent的特定类型,在派生类中执行不同的行为。创建一个Rodent指针数组,使它们指向Rodent不同的特定类型,并且调用基类中的方法,看看结果如何。
15-7 修改练习6,用vector<Rodent*>来代替指针数组。确保内存可以被正确地清除掉。
15-8 根据前面的Rodent类层次,从Hamster中继承出BlueHamster(是的,当我还是小孩时,我就有这么一只鼠),重新定义基类中的方法,并显示调用基类方法的代码不需要进行修改就可以在新类中使用。
15-9 在前面的Rodent类层次中,增加一个虚析构函数,并且使用new创建一个Hamster对象,向上类型转换成为一个Rodent*,然后delete该指针,显示它并不能调用层次中所有的析构函数。把析构函数改为虚函数,显示这样就可以正确地调用所有的析构函数。
15-10 在前面的Rodent类层次中,修改Rodent,使它成为一个纯抽象基类。
15-11 使用基类Aircraft和它的不同的派生类,创建一个空中交通系统。使用vector<Aircraft*>建立类Tower,给在它控制下的不同飞行器发送适当的信息。
15-12 通过从Plant中继承各种类型来建立一个温室的模型,并且在该温室中创建可以照看植物的机制。
15-13 在Early.cpp中,使Pet成为一个纯抽象基类。
15-14 在AddingVirtuals.cpp中,把Pet所有的成员函数改为纯虚函数,并对name()进行定义。使用name()的基类定义,对Dog进行必要的修改。
15-15 写出一个小程序以显示在普通成员函数中调用虚函数和在构造函数中调用虚函数的不同。这个程序应当表明两种调用会产生不同的结果。
15-16 通过从Derived中继承出一个类并且重新定义它的f()和析构函数来修改VirtualsIn-Destructors.CPP。在main()中,向上类型转换我们的新类,然后delete它。
15-17 在练习16的基础上,在每一个析构函数中增加对函数f()的调用。解释运行的结果。
15-18 创建含有一个数据成员的类和含用另一个数据成员的派生类。编写一个非成员函数,它通过传值方式接收一个基类的对象,并且使用sizeof打印出该对象的大小。在main()中创建一个派生类的对象,打印出它的大小,然后调用我们的函数。解释运行的结果。
15-19 创建一个虚函数调用的简单例子,并且输出其汇编代码。找出虚函数调用的汇编代码,跟踪运行并解释这些代码。
15-20 编写一个类,含有一个虚函数和一个非虚函数。继承出一个新类,并生成该类的对象,然后向上类型转换为基类的指针。使用<ctime>中的clock()函数(需要在本地C库指南中找到它)来测出虚函数调用和非虚函数调用的区别。为了看到区别,需要在时间循环中对每个函数进行多次调用。
15-21 通过在CLASS宏的基类中增加一个虚函数(使它打印些信息)并且把析构函数改为虚函数来修改C14:Order.cpp。生成不同子类的对象,然后把它们向上类型转换为基类对象。检验虚操作的运行以及发生的适当的构造操作和析构操作。
15-22 编写一个含有3个重载虚函数的类。在新建类中继承出一个新类,并且重新定义其中一个函数。生成派生类的一个对象。我们是否可以通过派生类对象调用所有的基类函数呢?把该对象的地址向上类型转换为基类对象。我们是否可以通过此基类对象调用所有的3个函数呢?删去在派生类中所做的重写定义。现在我们又是否可以通过派生类对象调用所有的基类函数呢?
15-23 修改VariantReturn.cpp,显示它的行为可以使用引用和指针来进行工作。
15-24 在Early.cpp中,如何才能分辨出编译器的调用是使用了早捆绑还是晚捆绑?判断我们自己的编译器的调用属于哪种情况?
15-25 创建一个基类,含有一个clone()函数,它返回指向当前对象拷贝的指针。派生出两个子类,同时重新定义clone(),它返回它们各自类型拷贝的指针。在main()中,生成并且向上类型转换两个派生类型的对象,然后分别调用它们的clone(),并检验所克隆的拷贝是正确的子类型。试验我们的clone()函数,使得返回的类型是基类,再试着返回准确的派生类型。我们能否考虑到后一种方法所必需的环境?
15-26 通过创建自己的类,然后对它和Object进行多重继承,生成的对象置于Stack中来修改OStackTest.cpp。在main()中测试我们的类。
15-27 在OperatorPolymorphism.cpp中增加一个类Tensor。
15-28 (中级)创建一个不带数据成员和构造函数而只有一个虚函数的基类X,从X继承出类Y,它没有显式的构造函数。产生汇编代码并检验它,以确定X的构造函数是否被创建和调用,如果是的,这些代码做什么?解释我们的发现。X没有默认的构造函数,但是为什么编译器不报告出错?
15-29 (中级)修改练习28,为这两个类创建构造函数,让每个构造函数调用一个虚函数。产生汇编代码。确定在每个构造函数内VPTR在何处被赋值。在构造函数内编译器使用虚函数机制吗?确定为什么这些函数的本地版本仍被调用。
15-30 (高级)如果对象的参数为传值方式传递的函数调用不用早捆绑,则虚调用可能会访问不存在的部分。这可能吗?编写一些代码强制进行虚调用,看看是否会引起冲突。解释这个行为,检验当对象以传值方式传递时会发生什么现象。
15-31 (高级)通过我们处理器的汇编语言信息或者其他技术,找出简单调用所需的时间数及虚函数调用所需的时间数,从而得出虚函数调用需要多出多少时间。
15-32 确定执行时VPTR的Sizeof。现在对两个含有虚函数的类进行多重继承。在派生类中可以得到一个还是两个VPTR?
15-33 创建一个含有数据成员和虚函数的类。编写一个监视我们类对象的内存的函数,它打印出变化的部分。要做到这一点,我们需要进行试验并且不断地找出对象中VPTR的所在位置。
15-34 假设不存在虚函数,修改Instrument4.cpp,使得它使用dynamic_cast来代替虚函数调用。解释为什么这不是一个好的方法。
15-35 修改StaticHierarchyNavigation.cpp,不使用C++RTTI,而是通过基类中的虚函数whatAmI()和enum type{Circles, Squares},来创建我们自己的RTTI。
15-36 在第12章的PointerToMemberOperator.cpp中,显示即使重载了operator->*,多态性依旧适用于成员指针。