C++第七章继承与派生练习题
习题来源: <<C++语言程序设计(第四版)>> 清华大学出版社 郑莉等编著
最后一次编辑时间: 2019-8-9
题目
7-1 比较类的3种继承方式public(公有继承)、protcted(保护继承)、Private(私有继承)之间的差别。
7-2 派生类构造函数执行的次序是怎样的?
7-3 如果派生类B已经重载了基类A的一个成员函数fn1(),没有重囊基类的成员函数fn2(),如何在派生类的函数中调用基类的成员函数n1(),fn2()?
7-4 什么叫做虚基类?它有何作用?
7-5 定义一个基类Shape,在此基础上派生出Rectangle和Cirele,二者都有getArea()函数计算对象的面积。使用Rectangle类创建一个派生类Square。
7-6 定义一个哺乳动物类Mammal,再由此派生出狗类Dog,定义一个Dog类的对象,观察基类与派生类的构造函数和析构函数的调用顾序。
7-8 定义一个Document类,有数据成员name,从Document派生出Book类,增加数据成员pagcCount.
7-12 组合与继承有什么共同点和差异?通过组合生成的类与被组合的类之间的逻辑关系是什么?继承呢?
7-13 思考例7-6和例7-8中Derived类的各个数据成员在Derived对象中存放的位置、编写程序输出它们各自的地址来验证自己的推断。
7-14 基类与派生类的对象、指针或引用之间,哪些情况下可以隐含转换,哪些情况下可以显示转换?在涉及多重继承或虚继承的情况下,在转换时会面临哪些新问题?
解答
7-1 比较类的3种继承方式public(公有继承)、protcted(保护继承)、Private(私有继承)之间的差别。
不同的继承方式导致不同访问属性的基类成员在派生类中有不同的访问方式
- 公有继承: 公有->公有, 保护->保护, 私有->私有
- 保护继承: 公有->保护, 保护->保护, 私有->私有
- 私有继承: 公有->私有, 保护->私有, 私有->私有
7-2 派生类构造函数执行的次序是怎样的?
- 调用基类的构造函数
- 调用成员对象的构造函数
- 调用派生类的构造函数
7-3 如果派生类B已经重载了基类A的一个成员函数fn1(),没有重载基类的成员函数fn2(),如何在派生类的函数中调用基类的成员函数n1(),fn2()?
A::fn1()
fn2()
7-4 什么叫做虚基类?它有何作用?
- 当某类的部分或者全部直接基类是从另一个基类派生而来时, 这些直接基类中, 从上一级基类继承来的成员就拥有了相同的名称, 派生类对象的这些同名成员在内存中拥有多个拷贝, 设为虚基类可以解决这个问题
- 当直接基类的共同基类设为虚基类时, 这时从不同路径继承过来的该成员在内存中只有一个拷贝
- 虚基类的成员在进一步派生过程中, 和派生类一起维护一个内存数据拷贝, 省空间的前提下, 也解决了同名冲突的问题
7-5 定义一个基类Shape,在此基础上派生出Rectangle和Cirele,二者都有getArea()函数计算对象的面积。使用Rectangle类创建一个派生类Square。
#include<iostream> #include<stdlib.h> #include"myheader.h" using namespace std; class Shape { public: Shape() {} ~Shape() {} virtual float getArea() { return -1;} }; class Circle : public Shape { private: float radius; public: Circle(float Radius) : radius(Radius) {} ~Circle() {} float getArea() { return 3.14*this->radius*this->radius; } }; class Rectangle :public Shape { private: float width; float length; public: Rectangle(float w, float l) { this->width = w; this->length = l; } ~Rectangle() {} virtual float getArea() { return this->length * this->width; } virtual float getLength() { return this->length; } virtual float getWidth() { return this->width; } }; class Square :public Rectangle { public: Square(float len) :Rectangle(len, len) {} ~Square() {} }; int main() { Shape* sp; sp = new Circle(5); cout << sp->getArea() << endl; delete sp; sp = new Rectangle(4, 6); cout << sp->getArea() << endl; delete sp; sp = new Square(6); cout << sp->getArea() << endl; delete sp; system("pause"); return 0; }
7-6 定义一个哺乳动物类Mammal,再由此派生出狗类Dog,定义一个Dog类的对象,观察基类与派生类的构造函数和析构函数的调用顾序。
#include<iostream> #include<stdlib.h> #include"myheader.h" using namespace std; class Mammal { private: int age; float weight; public: Mammal() { cout<<"Mammal constructor"<<endl;} ~Mammal() { cout << "Mammal destructor" << endl;} void speak() const { cout << "Mammal sound!" << endl;} }; class Dog:public Mammal { public: Dog() {cout<<"Dog constructor"<<endl;} ~Dog() { cout << "Dog destructor" << endl;} void wagTail() { cout << "Tail wagging" << endl; } }; int main() { Dog jack; jack.speak(); jack.wagTail(); system("pause"); return 0; }
7-8 定义一个Document类,有数据成员name,从Document派生出Book类,增加数据成员pagcCount.
#include<iostream> #include<stdlib.h> #include<string.h> #include"myheader.h" using namespace std; class Document { public: Document() {} Document(const char *nm); char* name; void printNameOf(); }; Document::Document(const char * nm) { name = new char[strlen(nm) + 1]; strcpy(name, nm); } void Document::printNameOf() { cout << this->name << endl; } class Book : public Document { public: Book(const char *nm, int pagecount); void printNameOf(); int pageCount; }; Book::Book(const char* nm, int pagecount) { this->name = new char[strlen(nm) + 1]; strcpy(name, nm); this->pageCount = pagecount; } void Book::printNameOf() { Document::printNameOf(); } int main() { Document a("d1"); Book b("b1", 100); b.printNameOf(); system("pause"); return 0; }
7-12 组合与继承有什么共同点和差异?通过组合生成的类与被组合的类之间的逻辑关系是什么?继承呢?
组合和继承都使得原有对象称为新对象的一部分, 从而实现代码复用
- 组合: has-a 的关系, 部分与整体的关系
- 继承: is-a 的关系, 特殊与一般的关系
7-13 思考例7-6和例7-8中Derived类的各个数据成员在Derived对象中存放的位置、编写程序输出它们各自的地址来验证自己的推断。
例7-6
#include<iostream> #include<stdlib.h> #include<string.h> using namespace std; class Base1{ public: int var; void fun() { cout << "Member of Base1" << endl;} }; class Base2 { public: int var; void fun() { cout << "Member of Base2" << endl; } }; class Derived :public Base1, public Base2 { public: int var; void fun(){cout << "Member of Derived" << endl;} }; int main() { Derived d; Base1 b1; Base2 b2; cout << "Base1 pointer:" << &b1 << endl; cout << "Base2 pointer:" << &b2<< endl; cout << "Basel's var pointer:" <<&b1.var << endl; cout << "Base2's var pointer:" << &b2.var << endl; cout << endl; cout << "Derived pointer:" << &d<< endl; cout << "Derived's Basel's var pointer:" << &(d.Base1::var) << endl; cout << "Derived's Base2's var pointer:" << &(d.Base2::var) << endl; cout << "Derived's var pointer:" << &(d.var) << endl; system("pause"); return 0; }
例7-8
Base1 pointer:00AFF828 Base2 pointer:00AFF81C Basel's var pointer:00AFF828 Base2's var pointer:00AFF81C Derived pointer:00AFF834 Derived's Basel's var pointer:00AFF834 Derived's Base2's var pointer:00AFF838 Derived's var pointer:00AFF83C
#include<iostream> #include<stdlib.h> #include<string.h> using namespace std; class Base0{ public: int var0; void fun0() { cout << "Member of Base0" << endl;} }; class Base1: virtual public Base0 { public: int var1; }; class Base2 : virtual public Base0 { public: int var2; }; class Derived :public Base1, public Base2 { public: int var; void fun(){cout << "Member of Derived" << endl;} }; int main() { Derived d; Base0 * b0_p = &d; Base0 b0; Base1 b1; Base2 b2; cout << "Base0 pointer: " << &b0 << endl; cout << "Base1 pointer:" << &b1 << endl; cout << "Base2 pointer:" << &b2 << endl; cout << "Derived pointer:" << &d << endl; cout << "implicet transfered Base0 pointer:" << &b0_p << endl; cout << endl; cout << "Base1's var pointer:" << &b1.var1 << endl; cout << "Base2's var pointer:" << &b2.var2<< endl; cout << "Base1's Base0's var pointer:" << &(b1.Base0::var0) << endl; cout << "Base2's Base0's var pointer:" << &(b2.Base0::var0) << endl; cout << endl; cout << "Derived's Base1's var pointer:" << &(d.Base1::var1) << endl; cout << "Derived's Base2's var pointer:" << &(d.Base2::var2) << endl; cout << "Derived's Base1's Base0's var pointer:" << &(d.Base2::Base0::var0) << endl; cout << "Derived's Base2's Base0's var pointer:" << &(d.Base2::Base0::var0) << endl; system("pause"); return 0; }
Base0 pointer: 00AFFD54 Base1 pointer:00AFFD40 Base2 pointer:00AFFD2C Derived pointer:00AFFD6C implicet transfered Base0 pointer:00AFFD60 Base1's var pointer:00AFFD44 Base2's var pointer:00AFFD30 Base1's Base0's var pointer:00AFFD48 Base2's Base0's var pointer:00AFFD34 Derived's Base1's var pointer:00AFFD70 Derived's Base2's var pointer:00AFFD78 Derived's Base1's Base0's var pointer:00AFFD80 Derived's Base2's Base0's var pointer:00AFFD80
7-14 基类与派生类的对象、指针或引用之间,哪些情况下可以隐含转换,哪些情况下可以显示转换?在涉及多重继承或虚继承的情况下,在转换时会面临哪些新问题?
- 子转父: 隐式, 逆过来: 显示
- 执行基类指针到派生类指针的显示转换时, 有时需要将指针所存储的地址值进行调整后才能得到新指针的值, 但是如果如果A类是B类的虚基类, 虽然B类指针可以隐式转换为A类指针, 但A类指针却无法通过 static_cast隐式转换为B类型的指针