继承和组合(Inheritance & Composition)

继承和组合(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";

 }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值