一.相关知识
使用其他人已经创建并调试过的类:
关键是使用类而不是更改已存在的代码。这一章将介绍两种完成这件事的方法。第一种方法是很直接的:简单地创建一个包含已存在的类对象的新类,这称为组合,因为这个新类是由已存在类的对象组合的。
第二种方法更巧妙,创建一个新类作为一个已存在类的类型,采取这个已存在类的形式,对它增加代码,但不修改它。这个有趣的活动被称为继承,其中大量的工作由编译器完成。继承是面向对象程序设计的基石,并且还有另外的含义,将在下一章中探讨。
对于组合和继承(感觉上,它们都是由已存在的类型产生新类型的方法),它们在语法上和行为上是类似的。这一章中,读者将学习这些代码重用机制。
二.相关代码
1.
<span style="font-size:18px;"><strong>/*USEFUL.cpp*/ #ifndef USEFUL_H_ #define USEFUL_H_ class X { int i; enum{ factor = 11 }; public: X() { i = 0; } void set(int I) { i = I; } int read() const { return i; } int permute() { return i = i * factor; } }; #endif</strong></span>
<span style="font-size:18px;"><strong>/*COMPOSE.h*/ //在这个类中,数值成员是私有的,所以对于将类型 X 的一个对象作为公共对象嵌入到一个新 //类内部,是绝对安全的。 #include "useful.h" class Y { int i; public: X x; Y() { i = 0; } void f(int I) { i = I; } int g() const { return i; } }; int main() { Y y; y.f(47); y.x.set(37); return 0; }</strong></span>
<span style="font-size:18px;"><strong>/*对于新类的public接口函数,包含对嵌入对象的使用,但不必模仿这个嵌入对象的接口。*/ /*COMPOSE2.h*/ //这里,permute()函数的执行调用了X的接口,而X的其他成员函数也在Y的成员函数中被调用。 #include "useful.h" class Y { int i; X x; public: Y() { i = 0; } void f(int I) { i = I; x.set(I); } int g() const { return i * x.read(); } void permute() { x.permute(); } }; int main() { Y y; y.f(47); y.permute(); return 0; }</strong></span>
<span style="font-size:18px;"><strong>/*INHERIT.cpp*/ #include "useful.h" #include <iostream.h> class Y : public X { int i; public: Y() { i = 0; } int change() //在change()中,基类permute()函数被调用,派生类对所有的public基类函数都有直接的访问权 { i = permute(); return i; } void set(int I) //在派生类中的set()函数重定义了基类中的set()函数 //这就是,如果对于类型 Y 的对象调用函数read()和permute(),得到的是这些函数的基类版本 //(可以在main()中看到表现)。但如果对于对象 Y 调用set(),得到的是重定义的版本。这意 //味着,如果我们不喜欢在继承中得到某个函数的基类版本,可以改变它(还能够增加全新的函 //数,例如 change())。 { i = I; X::set(I); } }; int main() { cout << "sizeof(X) = " << sizeof(X) << endl; cout << "sizeof(Y) = " << sizeof(Y) << endl; Y D; D.change(); D.read(); D.permute(); D.set(12); return 0; }</strong></span>
<span style="font-size:18px;"><strong>/*COMBINED.cpp*/ #include <iostream.h> class A { int i; public: A(int I) { i = I; } ~A(){} void f() const{} }; class B { int i; public: B(int I) { i = I; } ~B(){} void f() const {} }; class C : public B { A a; //C 继承 B 并且有一个成员对象(这是类 A的对象) public: C(int I):B(I), a(I){} //构造函数的初始化表达式表中调用了基类构造函数和成员对象构造函数。 ~C(){} void f() const //函数C::f()重定义了它所继承的B::f(),并且还调用基类版本,它还调用了a.f() { a.f(); B::f(); } }; int main() { C c(47); }</strong></span>
<span style="font-size:18px;"><strong>/*ORDER.cpp*/ /*可以看出,构造在类层次的最根处开始,而在每一层,首先调用基类构造函数,然后调用 成员对象构造函数。调用析构函数则严格按照构造函数相反的次序—这是很重要的,因为要 考虑潜在的相关性。*/ #include <fstream.h> ofstream out("order.out"); #define CLASS(ID) class ID{public: ID(int){ out << #ID " constructor\n";} ~ID(){ out << #ID " destructor\n";}}; CLASS(base1); CLASS(member1); CLASS(member2); CLASS(member3); CLASS(member4); class derived1 : public base1 { member1 m1; member2 m2; public: derived1(int) : m2(1), m1(2), base1(3) { out << "derived1 constructor\n"; } ~derived1() { out << "derived1 destructor\n"; } }; class derived2 : public derived1 { member3 m3; member4 m4; public: derived2() : m3(1), derived1(2), m4(3) { out << "derived2 constructor\n"; } ~derived2() { out << "derived2 destructor\n"; } }; int main() { derived2 d2; return 0; }</strong></span>
<span style="font-size:18px;"><strong>/*名字隐藏 如果在基类中有一个函数名被重载几次,在派生类中重定义这个函数名会掩盖所有基类版 本,这也就是说,它们在派生类中变得不再可用。*/ /*HIDE.cpp*/ /*因为 bart 重定义了 d o h ( ),这些基类版本中没有一个是对于 bart 对象可调用的。这时,编译 器试图变换参数成为一个 milhouse 对象,并报告出错,因为它不能找到这样的变换。*/ #include <iostream.h> class homer { public: int doh(int) const { return 1; } char doh(char) const { return 'd'; } float doh(float) const { return 1.0; } }; class bart : public homer { public: class milhouse{}; void doh(milhouse) const {} }; int main() { bart b; //!b.doh(1); //!b.doh('x'); //!b.doh(1.0); }</strong></span>
<span style="font-size:18px;"><strong>/*由编译器创建而不是继承的函数*/ /*NINHERIT.cpp*/ #include <fstream.h> ofstream out("ninherit.out"); class root { public: root() { out << "root()\n"; } root(root&) { out << "root(root&)\n"; } root(int) { out << "root(int)\n"; } root& operator=(const root&) { out << "root::operator=()\n"; return *this; } class other{}; operator other() const //operator other() 完成自动类型变换,从 root 对象到被嵌入的类 other 的对象 { out << "root::operator other()\n"; return other(); } ~root() { out << "~root()\n"; } }; class derived : public root {}; //类 derived 直接从root 继承,并没有创建函数(观察编译器如何反应) //在 derived 中, operator=( ) 也被综合为新函数,使用成员函数赋值,因为这个函数在新类中不显式地写出 void f(root::other){} //函数 f() 取一个 other 对象以测试这个自动类型变换函数 int main() { derived d1; derived d2 = d1; //!derived d3(1); d1 = d2; f(d1); return 0; }</strong></span>
<span style="font-size:18px;"><strong>/*组合通常在希望新类内部有已存在类性能时使用,而不希望已存在类作为它的接口。这就 是说,嵌入一个计划用于实现新类性能的对象,而新类的用户看到的是新定义的接口而不是来 自老类的接口。为此,在新类的内部嵌入已存在类的 private 对象。 有时,允许类用户直接访问新类的组合是有意义的,这就让成员对象是 public。成员函数 隐藏它们自己的实现,所以,当用户知道我们正在装配一组零件并且使得接口对他们来说更容 易理解时,这样会安全的*/ /*CAR.cpp*/ //稍加思考就会看到,用车辆对象组合小汽车是无意义的—小汽车不能包含车辆,它本身 //就是一种车辆。这种is-a关系用继承表达,而 has-a 关系用组合表达。 #include <iostream.h> class engine { public: void start() const {} void rev() const {} void stop() const {} }; class wheel { public: void inflate(int psi) const {} }; class window { public: void rollup() const {} void rolldown() const {} }; class door { public: window Window; void open() const {} void close() const {} }; class car { public: engine Engine; wheel Wheel[4]; door left, right; }; int main() { car Car; Car.left.Window.rollup(); Car.Wheel[0].inflate(72); return 0; }</strong></span>
<span style="font-size:18px;"><strong>/*子类型设置*/ /*FNAME1.cpp*/ #include <fstream.h> #include <strstrea.h> #include "E:\VC++\7_31_2\allege.h" class fname1 { ifstream File; enum{ bsize = 100 }; char buf[bsize]; ostrstream Name; int nameset; public: fname1() : Name(buf, bsize), nameset(0) {} fname1(const char* filename) : File(filename), Name(buf, bsize) { allegefile(File); Name << filename << ends; nameset = 1; } const char* name() const { return buf; } void name(const char* newname) { if(nameset) { return; } Name << newname << ends; nameset = 1; } operator ifstream&() { return File; } }; int main() { fname1 file("fname1.cpp"); cout << file.name() << endl; //Error:rdbuf() not a member //!cout << file.rdbuf() << endl;//因为自动类型转换只发生在函数调用中,而不在成员选择期间 return 0; }</strong></span>
<span style="font-size:18px;"><strong>/*子类型设置*/ /*FNAME2.cpp*/ #include <fstream.h> #include <strstrea.h> #include "E:\VC++\7_31_2\allege.h" class fname2 : public ifstream { enum{ bsize = 100 }; char buf[bsize]; ostrstream Name; int nameset; public: fname2() : Name(buf, bsize), nameset(0) {} fname2(const char* filename) : ifstream(filename), Name(buf, bsize) { Name << filename << ends; nameset = 1; } const char* name() const { return buf; } void name(const char* newname) { if(nameset) { return; } Name << newname << ends; nameset = 1; } }; int main() { fname2 file("fname2.cpp"); allegefile(file); cout << "name: " << file.name() << endl; const bsize = 100; char buf[bsize]; file.getline(buf, bsize); file.seekg(-200, ios::end); cout << file.rdbuf() << endl; return 0; }</strong></span>
<span style="font-size:18px;"><strong>/*继承也就是取一个已存在的类,并制作它的一个专门的版本*/ /*INHSTAK.cpp*/ #include "E:\VC++\8_4_3\stackl1.h" #include "E:\VC++\8_4_1\strings.h" #include <fstream.h> #include "E:\VC++\7_31_2\allege.h" class Stringlist : public stack { public: void push(String* str) { stack::push(str); } String* peek() const { return (String*)stack::peek(); } String* pop() { return (String*)stack::pop(); } }; int main() { ifstream file("text.cpp"); allegefile(file); const bufsize = 100; char buf[bufsize]; Stringlist textlines; while(file.getline(buf, bufsize)) { textlines.push(String::make(buf)); } String* s; while((s = textlines.pop()) != 0) { cout << *s << endl; } return 0; }</strong></span>
<span style="font-size:18px;"><strong>/*对私有继承成员公有化 当私有继承时,基类的所有 public成员都变成了 private。如果希望它们中的任何一个是可 视的,只要用派生类的public选项声明它们的名字即可。*/ /*PRIVING.cpp*/ #include <iostream.h> class base1 { public: char f() const { return 'a'; } int g() const { return 2; } float h() const { return 3.0; } }; class derived : base1 { public: base1::f; base1::h; }; int main() { derived d; d.f(); d.h(); //!d.g(); return 0; }</strong></span>
<span style="font-size:18px;"><strong>/*数据成员最好是private,因为我们应该保留改变内部实现的权利。然后我们才能通过保护 成员函数控制对该类的继承者的访问。*/ /*PROTECT.cpp*/ /*被保护的继承 继承时,基类缺省为 private,这意味着所有 public成员函数对于新类的用户是 private的。 通常我们都会让继承 public,从而使得基类的接口也是派生类的接口。然而在继承期间,也可 以使用protected关键字。 被保护的派生意味着对其他类来“照此实现”,但对派生类和友元是“is-a”。它是不常用 的,它的存在只是为了语言的完整性。*/ #include <fstream.h> class base { int i; protected: int read() const { return i; } void set(int I) { i = I; } public: base(int I = 0) : i(I) {} int value(int m) const { return m * i; } }; class derived : public base { int j; public: derived(int J = 0) : j(J) {} void change(int x) { set(x); } }; int main() { return 0; }</strong></span>
<span style="font-size:18px;"><strong>/*向上映射*/ /*WIND.cpp*/ /*在tune()中,这些代码对instrument和从instrument派生来的任何类型都有效,这种将 wind的对象、 引用或指针转变成instrument对象、引用或指针的活动称为向上映射。*/ #include <iostream.h> enum note { middleC, Csharp, Cflat }; class instrument { public: void play(note) const {} }; class wind : public instrument {}; void tune(instrument& i) { i.play(middleC); } int main() { wind flute; tune(flute); //wind w; //instrument* ip = &w; //instrument& ir = w; //编译器只能把ip作为一个instrument指针处理。这就是说,它不知道 ip实际上指向wind的对象。 //所以,当调用play()成员函数时,如果使用 //ip->play(middleC) ; //编译器只知道它正在对于一个instrument指针调用play(),并调用instrument∷play()的基本版本, //而不是它应该做的调用wind∷play()。这样将会得到不正确的结果。 return 0; }</strong></span>
<span style="font-size:18px;"><strong>/*stringlist对象仅作为string包容器,不需向上映射,所以更合适的方法可能是组合*/ /*INHSTAK2.cpp*/ #include "E:\VC++\8_4_3\stackl1.h" #include "E:\VC++\8_4_1\strings.h" #include <fstream.h> #include "E:\VC++\7_31_2\allege.h" class Stringlist { stack Stack; public: void push(String* str) { Stack.push(str); } String* peek() const { return (String*)Stack.peek(); } String* pop() { return (String*)Stack.pop(); } }; int main() { ifstream file("text.cpp"); allegefile(file); const bufsize = 100; char buf[bufsize]; Stringlist textlines; while(file.getline(buf, bufsize)) { textlines.push(String::make(buf)); } String* s; while((s = textlines.pop()) != 0) { cout << *s << endl; } return 0; }</strong></span>
三.习题+解答
1. 修改CAR.CPP,使得它也从被称为 vehicle的类继承,在 vehicle中放置合适的成员函数(也就是说,补充一些成员函数)。对vehicle增加一个非缺省的构造函数,在car的构造函数内部必须调用它。
#include <iostream.h> class engine { public: void start() const {} void rev() const {} void stop() const {} }; class wheel { public: void inflate(int psi) const {} }; class window { public: void rollup() const {} void rolldown() const {} }; class door { public: window Window; void open() const {} void close() const {} }; class vehicle { public: vehicle() {} void g(){} void f(){} }; class car : vehicle { public: engine Engine; wheel Wheel[4]; door left, right; car() { vehicle(); } }; int main() { car Car; Car.left.Window.rollup(); Car.Wheel[0].inflate(72); return 0; }
#include <iostream.h> class A { int i; public: A(int I = 0) { i = I; cout << "A::A(int I)" << endl; } }; class B { int j; public: B(int J = 0) { j = J; cout << "B::B(int J)" << endl; } }; class C : public A { public: B b; }; int main() { C c; return 0;
3. 使用继承,专门化在第 1 2章( PSTASH.H & PSTASH. CPP)中的pstash类,使得它接受和返回String针。修改PSTEST. CPP并测试它。改变这个类使得pstash是一个成员对象。
/*PSTASH.h*/ #include "E:\VC++\8_4_1\strings.h" #ifndef PSTASH_H_ #define PSTASH_H_ class pstash { int quantity; int next; void** storage;//storage是一个 void指针数组 void inflate(int increase); public: pstash() { quantity = 0; storage = 0; next = 0; } ~pstash() { delete storage; } int add(void* element); void* operator[](int index) const; int count() const { return next; } }; class Stringlist : public pstash { public: int add(String* element) { return pstash::add(element); } String* operator[](int index) const { return (String*)pstash::operator[](index); } int count() const { return pstash::count(); } }; #endif
/*PSTASH.cpp*/ #include "pstash.h" #include <iostream.h> #include <string.h> int pstash::add(void* element) { const InflateSize = 10; if(next >= quantity) { inflate(InflateSize); } storage[next+1] = element; return (next-1);//index number } void* pstash::operator[](int index) const { if(index >= next || index < 0) { return 0; } return storage[index]; } void pstash::inflate(int increase) { const psz = sizeof(void*); void** st = new void*[quantity + increase]; memset(st, 0, (quantity + increase) * psz); memcpy(st, storage, quantity * psz); quantity += increase; delete storage; storage = st; }
/*PSTEST.cpp*/ #include "pstash.h" #include <fstream.h> #include "E:\VC++\7_31_2\allege.h" int main() { ifstream file("pstest.cpp"); allegefile(file); const bufsize = 80; char buf[bufsize]; Stringlist textlines; while(file.getline(buf, bufsize)) { textlines.add(String::make(buf)); } return 0; }
#include <iostream.h> enum note { middleC, Csharp, Cflat }; class A { public: void play(note) const {} }; class B : private A {}; class C : protected A {}; void tune(A& i) { i.play(middleC); } int main() { B flute1; //!tune(flute1); //error C2243: 'type cast' : conversion from 'class B *' to 'class A &' exists, but is inaccessible C flute2; //!tune(flute2); //error C2243: 'type cast' : conversion from 'class C *' to 'class A &' exists, but is inaccessible return 0; }
以上代码仅供参考,如有错误请大家指出,谢谢大家~
版权声明:本文为博主原创文章,未经博主允许不得转载。
《C++编程思想》 第十三章 继承和组合 (原书代码+习题+解答)
原文地址:http://blog.csdn.net/qaz3171210/article/details/47400279