1. 继承

1.1 术语

超类(基类)和子类(派生类):在面向对象程序设计中,我们通常使用继承来避免代码冗余。在 C++中,继承的语法规则如下:

1class SubclassName : inheritance-access-specifier SuperclassName {
2	......
3};

子类继承了父类所有的成员,子类也可以定义自己的构造器和成员。

访问标识符:C++支持三种访问标识符:privatepublicprotected。一个类的 public 成员变量、成员函数,可以通过类的成员函数、类的实例变量进行访问。 一个类的 protected 成员变量、成员函数,无法通过类的实例变量进行访问。但是可以通过类的友元函数、友元类进行访问。一个类的 private 成员变量、成员函数,无法通过类的实例变量进行访问。但是可以通过类的友元函数、友元类进行访问。

继承访问标识符:public 继承不改变基类成员的访问权限,private 继承使得基类所有成员在子类中的访问权限变为 private,protected 继承将基类中 public 成员变为子类的 protected 成员,其它成员的访问 权限不变。 基类中的 private 成员不受继承方式的影响,子类永远无权访问。

1.2 一个示例

MovablePoint.h

 1#ifndef MOVING_POINT_H
 2#define MOVING_POINT_H
 3
 4#include "Point.h"
 5
 6class MoviablePoint : public Point {
 7private:
 8	int xSpeed, ySpeed;
 9
10public:
11	MovablePoint(int x, int y, int xSpeed = 0, int ySpeed = 0);
12	int getXSpeed() const;
13	int getYSpeed() const;
14	void setXSpeed(int xSpeed);
15	void setYSpeed(int ySpeed);
16	void move();
17	void print() const;
18}
19#endif

MovablePoint.cpp

 1#include <iostream>
 2#include "MovablePoint.h"
 3
 4using namespace std;
 5
 6MovablePoint::MovablePoint(int x, int y, int xSpeed, int ySpeed) : Point(x, y), xSpeed(xSpeed), ySpeed(ySpeed) {  }
 7
 8// Getters
 9int MovablePoint::getXSpeed() const { return xSpeed; }
10int MovablePoint::getYSpeed() const { return ySpeed; }
11
12// Functions
13void MovablePoint::print() const {
14	cout << "Movable";
15	Point::print();
16	cout << " Speed=" << "(" << xSpeed << "," << ySpeed << ")";
17}
18
19void MovablePoint::move() {
20	Point::setX(Point::getX() + xSpeed);
21	Point::setY(Point::getY() + ySpeed);
22}

1.3 示例:父类有 protected 成员

再次强调一点,子类不能直接访问父类中被private修饰的成员。例如:

1void MovablePoint::move() {
2	x += xSpeed; // error: 'int Point::x' is private
3	...
4}

然而,如果我们把 x 改成protected的话,子类就可以直接访问了。

 1// Superclass Point
 2
 3class Point {
 4protected:
 5	int x, y;
 6	......
 7};
 8
 9// Subclass MovablePoint
10class MovablePoint : public Point {
11......
12};
13
14void MovablePoint::move() {
15	x += xSpeed;
16	y += ySpeed;
17}

2. 多态

多态作用于运行时使用动态绑定的对象指针和引用。多态对普通对象不起作用,因为普通对象是在编译时静态绑定的。

2.1 替换

子类实例在 public 继承方式下,会继承父类的所有属性。子类能够做父类能做的任何事情,这就是"is-a"关系。因此你可以用父类引用 替换子类实例。

 1#include <iostream>
 2#include "MovablePoint.h"
 3
 4using namespace std;
 5
 6int main() {
 7	Point *ptrP1 = new MovablePoint(11, 12, 13, 14); // upcast
 8	ptrP1->print();
 9	// ptrP1->move(); // error: 'class Point' has no member named 'move'
10	delete ptrP1;
11
12	MovablePoint map2(21, 22, 23, 24);
13	Point &p2 = map2;
14	p2.print();
15	cout << endl;
16	// p2.move(); // error: 'class Point' has no member named 'move'
17
18	Point p3 = MovablePoint(31, 32, 33, 34);
19	p3.print();
20	cout << endl;
21	// p3.move(); // error: 'class Point' has no member named 'move'
22}

被替换的实例能够调用父类的所有方法,但是不能盗用子类中定义的函数,因为该引用是父类引用,不能识别子类成员。

2.2 多态性

  1. 子类实例能够被父类引用替换
  2. 一旦被替换,该实例只能调用父类的方法,不能调用子类的
  3. 如果子类重写了父类的方法,我们期望调用的是重写后的方法,而不是父类原有的方法

虚函数:为了实现多态机制,我们需要使用virtual关键字来修饰函数。此时,如果父类作用于子类实例,调用被virtual修饰的 函数时,会调用子类中重写的函数,而不是父类中的原始函数。例如:

1Class Point {
2	......
3		virtual void print() const;
4}
 1/* Test Substituting a subclass instance to a superclass reference.
 2   (TestSubstitution.cpp) */
 3#include <iostream>
 4#include "MovablePoint.h"   // included "Point.h"
 5using namespace std;
 6
 7int main() {
 8   // Substitute a subclass instance to a superclass reference
 9
10   // Using Object Pointer
11   Point * ptrP1 = new MovablePoint(11, 12, 13, 14);   // upcast
12   ptrP1->print(); // MovablePoint @ (11,12) Speed=(13,14)
13                   //   - Run subclass version!!
14   cout << endl;
15   delete ptrP1;
16
17   // Using Object Reference
18   MovablePoint mp2(21, 22, 23, 24);
19   Point & p2 = mp2;  // upcast
20   p2.print();     // MovablePoint @ (21,22) Speed=(23,24)
21                   //   - Run subclass version!!
22   cout << endl;
23
24   // Using object with explicit constructor
25   Point p3 = MovablePoint(31, 32, 33, 34);  // upcast
26   p3.print();     // Point @ (31,32) - Run superclass version!!
27   cout << endl;
28}

向上转型和向下转型

通常情况下,C++不允许我们将一种类型的地址赋值给另一种类型的指针(或引用)。例如:

1int i = 0;
2double *ptr1 = &i; // error: cannot convert 'int*' to 'double*' in initialization
3
4double &d = i; // error: invalid initialication of reference of type 'double&' from expression of type 'int'

然而,父类指针或引用能够存放子类对象,而不需要显式的转型:

1MovablePoint mp(......);
2Point *ptrP1 = &mp; // Okay - Implicit upcast
3Point & p2 = mp;    // Okay - Implicit upcast

将子类对象转成父类引用或父类指针被称为向上转型。在 public 继承中,向上转型是一定被允许的,而且不需要显式的转型操作。因为 public 继承是"is-a"关系。子类实例也是父类的一个实例。

相反,将一个父类对象转成子类引用或指针被称为向下转型。向下转型需要显式操作:

 1#include <iostream>
 2#include "MovablePoint"
 3
 4using namespace std;
 5
 6int main() {
 7	// Object Pointer
 8	Pointer *ptrp1 = new MovablePoint(11, 12, 13, 14);
 9	// Upcast is always permissible and safe
10	ptrP1->print();
11
12	// MovablePoint *ptrMP1 = ptrP1; // error
13	MovablePoint *ptrMp1 = (MovablePoint *) ptrP1;
14	// Downcase requires explicit casting operator
15	delete ptrP1;
16}

dynamic_cast 操作符

C++提供了一种新的操作符,叫做dynamic_cast<type>(value),如果操作失败的话,会返回空指针。

1MovablePoint *ptrMP1 = dynamic_cast<MovablePoint *>(ptrP1);

typeid 操作符

typeid操作符返回一个type_info的对象(在头文件中定义)的引用,其中包含了它操作的对象的信息。你可以使用 type_info的成员方法name()来获取所操作的类型名称:

 1/* Test typeid operator, which return an object of type_info (TestTypeID.cpp) */
 2#include <iostream>
 3#include <typeinfo>       // Need for typeid operator
 4#include "MovablePoint.h" // included "Point.h"
 5using namespace std;
 6
 7int main() {
 8   // Object Pointer
 9   Point * ptrP1 = new MovablePoint(11, 12, 13, 14);  // upcast
10   cout << typeid(*ptrP1).name() << endl;   // 12MovablePoint
11
12   MovablePoint * ptrMP1 = dynamic_cast<MovablePoint *>(ptrP1);
13   cout << typeid(*ptrMP1).name() << endl;  // 12MovablePoint
14   delete ptrP1;
15
16   Point p2;
17   cout << typeid(p2).name() << endl;  // 5Point
18
19   MovablePoint mp2(1, 2, 3, 4);
20   cout << typeid(mp2).name() << endl; // 12MovablePoint
21}

说明:返回的类型名前的数字是该字符串的长度。

2.3 纯虚函数和抽象父类

纯虚函数的声明语法如下:

1virtual double getArea() = 0;

纯虚函数通常没有函数体,因为该类不确定如何实现这个函数。一个包含一个或多个纯虚函数的类被成为抽象类。我们不能直接对抽象类进行实例化,因为它的定义是不完整的。

抽象类只能作为父类,然后派生出子类,重写并实现所有的纯虚函数。

C++允许纯虚函数有函数体,那么这时候=0就仅仅是使该类为抽象类而已。但是,对于这样的抽象类,你依然不能直接实例化。

2.4 示例

Shape.h

 1#define SHAPE_H
 2#define SHAPE_H
 3
 4#include <string>
 5using namespace std;
 6
 7class Shape {
 8private:
 9	string color;
10public:
11	Shape(const string &color = "red");
12	string getColor() const;
13	void setColor(const string &color);
14	virtual void print() const;
15	// Purge virtual, to be implemented by subclass
16	// You cannot create instance of Shape
17	virtual double getArea() const = 0;
18};
19
20#endif

Shape.cpp

 1#include "Shape.h"
 2#include <iostream>
 3
 4Shape::Shape(const string &color) {
 5	this->color = color;
 6}
 7
 8string Shape::getColor() const {
 9	return color;
10}
11
12void Shape::setColor(const string &color) {
13	this->color = color;
14}
15
16void Shape::print() const {
17	std::cout << "Shape of color = " << color;
18}

Circle.h

 1#ifndef CIRCLE_H
 2#define CIRCLE_H
 3
 4#include "Shape.h"
 5
 6class Circle : public Shape {
 7private:
 8	int radius;
 9public:
10	Circle(int radius = 1, const String &color = "red");
11	int getRadius() const;
12	void setRadius(int radius);
13	void print() const;
14	double getArea() const;
15};
16
17#endif

Circle.cpp

 1/* Implementation for Circle (Circle.cpp) */
 2#include "Circle.h"
 3#include <iostream>
 4#define PI 3.14159265
 5
 6// Constructor
 7Circle::Circle(int radius, const string & color)
 8   : Shape(color), radius(radius) { }
 9
10// Getters
11int Circle::getRadius() const {
12   return radius;
13}
14
15// Setters
16void Circle::setRadius(int radius) {
17   this->radius = radius;
18}
19
20void Circle::print() const {
21   std::cout << "Circle radius=" << radius << ", subclass of ";
22   Shape::print();
23}
24
25// Implement virtual function inherited for superclass Shape
26double Circle::getArea() const {
27   return radius * radius * PI;
28}

Rectangle.h

 1/* Header for Rectangle class (Rectangle.h) */
 2#ifndef RECTANGLE_H
 3#define RECTANGLE_H
 4
 5#include "Shape.h"
 6
 7// The class Rectangle is a subclass of Shape
 8class Rectangle : public Shape {
 9private:
10   int length;
11   int width;
12
13public:
14   Rectangle(int length = 1, int width = 1, const string & color = "red");
15   int getLength() const;
16   void setLength(int length);
17   int getWidth() const;
18   void setWidth(int width);
19   void print() const;      // Override the virtual function
20   double getArea() const;  // to implement virtual function
21};
22
23#endif

Rectangle.cpp

 1/* Implementation for Rectangle (Rectangle.cpp) */
 2#include "Rectangle.h"
 3#include <iostream>
 4
 5// Constructor
 6Rectangle::Rectangle(int length, int width, const string & color)
 7  : Shape(color), length(length), width(width) { }
 8
 9// Getters
10int Rectangle::getLength() const {
11   return length;
12}
13int Rectangle::getWidth() const {
14   return width;
15}
16
17// Setters
18void Rectangle::setLength(int length) {
19   this->length = length;
20}
21void Rectangle::setWidth(int width) {
22   this->width = width;
23}
24
25void Rectangle::print() const {
26   std::cout << "Rectangle length=" << length << " width=" << width << ", subclass of ";
27   Shape::print();
28}
29
30// Implement virtual function inherited from superclass Shape
31double Rectangle::getArea() const {
32   return length * width;
33}