继承和组合(Inheritance & Composition)
一、相关日志
继承和派生(二)
http://blog.163.com/zhoumhan_0351/blog/static/39954227201002854625174
继承和派生(一)
http://blog.163.com/zhoumhan_0351/blog/static/3995422720100284731826
面向对象思想的相关概念
http://blog.163.com/zhoumhan_0351/blog/static/3995422720100188043690
http://blog.163.com/zhoumhan_0351/blog/static/399542272010230103414517
二、组合和继承
1、关键点
对于成员对象,构造函数调用的次序不受构造函数的初始化表达式中的次序影响,是由成员对象在类中声明的次序决定的。
构造函数和析构函数不能被继承,operator=也不能被继承。
在整个类层次中的所有构构函数中,从派生最底层的构函数函数开始调用,一直到根层。而在调用构造函数时,先调用最根层的构造函数,一直到父层,再初始化本类的。
2、隐藏和重写
在派生类中重定义基类的成员函数(操作和返回类型,参数列表不一定相同),称为重定义,此时基类中的成员函数被隐藏,不可再调用了。而如果成员函数为虚函数,则称为重写,以此实现多态。
函数隐藏和函数覆盖
http://blog.163.com/zhoumhan_0351/blog/static/39954227201035519333
//: C14:NameHiding.cpp
// Hiding overloaded names during inheritance
#include <iostream>
#include <string>
using namespace std;
class Base {
public:
int f() const {
cout << "Base::f()\n";
return 1;
}
int f(string) const { return 1; }
void g() {}
};
class Derived1 : public Base {
public:
void g() const {}
};
class Derived2 : public Base {
public:
// Redefinition:
int f() const {
cout << "Derived2::f()\n";
return 2;
}
};
class Derived3 : public Base {
public:
// Change return type:
void f() const { cout << "Derived3::f()\n"; }
};
class Derived4 : public Base {
public:
// Change argument list:
int f(int) const {
cout << "Derived4::f()\n";
return 4;
}
};
int main() {
string s("hello");
Derived1 d1;
int x = d1.f();
d1.f(s);
Derived2 d2;
x = d2.f();
//d2.f(s); // string version hidden
Derived3 d3;
x = d3.f(); // return int version hidden
Derived4 d4;
//x = d4.f(); // f() version hidden
x = d4.f(1);
} ///:~
任何时候重新定义了基类中的一个重载函数,在新类之中所有基类的其它版本(重载)都不可使用了。
如果要使用一个类的接口去做类似的别的事,可以使用继承(对于返回值进行类型强制转换,当然是正确的前途下),也可以定义成模板。
3、继承和静态成员函数
Static member functions act the same as non-static member functions:(共同点)
1)They inherit into the derived class(派生类).
2)If you redefine a static member, all the other overloaded(重载) functions
in the base class are hidden.
3)If you change the signature(如返回值,参数个数等) of a function in the
base class, all the base class versions(重载的所有同名函数) with that function
name are hidden (this is really a variation of the previous point).
However, static member functions cannot be virtual
4、组合和继承的选择
组合通常是在希望新类内部具有已存在类的功能时使用,而不是希望已存在的类作为他的接口。也就是说,派生类用户看到的是新定义的接口而不是基类的接口。而继承更多的是我们想要用基类(父类)的接口。如果派生类在使用过程中,不需要向上类型转换,则也应当用组合。
//: C14:Car.cpp
// Public composition
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; // 2-door
};
int main() {
Car car;
car.left.window.rollup();
car.wheel[0].inflate(72);
} ///:~
由上是组合的例子。现在假设我们想用ifstream的一些接口,而如果将之定义为组合的话,我们还需要重新写其中一些操作,如close()等。但是我们继承这个类的话,结果便很方便。
使用组合时:
//: C14:FName1.cpp
// An fstream with a file name
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
class FName1 {
ifstream file;
string fileName;
bool named;
public:
FName1() : named(false) {}
FName1(const string& fname)
: fileName(fname), file(fname.c_str()) {
named = true;
}
string name() const { return fileName; }
void name(const string& newName) {
if(named) return; // Don't overwrite
fileName = newName;
named = true;
}
operator ifstream&() { return file; }
};
int main() {
FName1 file("C:\\FName1.txt");
cout << file.name() << endl;
// Error: close() not a member:
//! file.close();
} ///:~
使用继承:
//: C14:FName2.cpp
// Subtyping solves the problem
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
class FName2 : public ifstream {
string fileName;
bool named;
public:
FName2() : named(false) {}
FName2(const string& fname)
: ifstream(fname.c_str()), fileName(fname) {
named = true;
}
string name() const { return fileName; }
void name(const string& newName) {
if(named) return; // Don't overwrite
fileName = newName;
named = true;
}
};
int main() {
FName2 file("C:\\FName2.cpp");
cout << "name: " << file.name() << endl;
string s;
getline(file, s); // These work too!
file.seekg(-200, ios::end);
file.close();
} ///:~
我们也称这种将基类中的所有成员全加进来的行为为子类型化(subtyping)。
总之,如果想重用已存在类型作为新类型的内部实现的话,我们最好用组合,如果想使新的类型的接口和基类的类型相同,则应当用继承。此外,我们的类层次应当具备这样的特性:它的每个类有专门的用途,它不能大太(不利于重用),也不能太小(功能不独立)。
5、私有继承的运用
私有继承下,基类的所有成员特性会发生变化,下面是一种使用方法。
#include "iostream"
#include "string"
using namespace std;
//: C14:PrivateInheritance.cpp
class Pet {
public:
char eat() const { return 'a'; }
int speak() const { return 2; }
float sleep() const { return 3.0; }
float sleep(int) const { return 4.0; }
};
class Goldfish : private Pet { // Private inheritance
public:
using Pet::eat; // Name publicizes member
using Pet::sleep; // Both overloaded members exposed
};
int main() {
Goldfish bob;
bob.eat();
bob.sleep();
bob.sleep(1);
//! bob.speak();// Error: private member function
} ///:~
6、向上类型转换(upcasting)
将派生类的引用和指针转变成基类的引用和指针的活动称为upcasting,反之。
如果允许编译器为派生类生成拷贝构造函数,它将首先自动地调用基类的拷贝构造函数,然后是各成员对象的拷贝构造函数。
C++编译器在派生类中给我们自动生成的四个函数是:拷贝构造函数,默认构造函数,析构函数,operator=。
无论何时,我们在定义了自己的拷贝构造函数后,都要正确的调用基类拷贝构造函数。
Child(const Child& c)
: Parent(c), i(c.i), m(c.m) {
cout << "Child(Child&)\n";
}