1. 继承

1.1 术语

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

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

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

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

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

1.2 一个示例

MovablePoint.h

#ifndef MOVING_POINT_H
#define MOVING_POINT_H

#include "Point.h"

class MoviablePoint : public Point {
private:
	int xSpeed, ySpeed;

public:
	MovablePoint(int x, int y, int xSpeed = 0, int ySpeed = 0);
	int getXSpeed() const;
	int getYSpeed() const;
	void setXSpeed(int xSpeed);
	void setYSpeed(int ySpeed);
	void move();
	void print() const;
}
#endif

MovablePoint.cpp

#include <iostream>
#include "MovablePoint.h"

using namespace std;

MovablePoint::MovablePoint(int x, int y, int xSpeed, int ySpeed) : Point(x, y), xSpeed(xSpeed), ySpeed(ySpeed) {  }

// Getters
int MovablePoint::getXSpeed() const { return xSpeed; }
int MovablePoint::getYSpeed() const { return ySpeed; }

// Functions
void MovablePoint::print() const {
	cout << "Movable";
	Point::print();
	cout << " Speed=" << "(" << xSpeed << "," << ySpeed << ")";
}

void MovablePoint::move() {
	Point::setX(Point::getX() + xSpeed);
	Point::setY(Point::getY() + ySpeed);
}

1.3 示例:父类有 protected 成员

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

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

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

// Superclass Point

class Point {
protected:
	int x, y;
	......
};

// Subclass MovablePoint
class MovablePoint : public Point {
......
};

void MovablePoint::move() {
	x += xSpeed;
	y += ySpeed;
}

2. 多态

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

2.1 替换

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

#include <iostream>
#include "MovablePoint.h"

using namespace std;

int main() {
	Point *ptrP1 = new MovablePoint(11, 12, 13, 14); // upcast
	ptrP1->print();
	// ptrP1->move(); // error: 'class Point' has no member named 'move'
	delete ptrP1;

	MovablePoint map2(21, 22, 23, 24);
	Point &p2 = map2;
	p2.print();
	cout << endl;
	// p2.move(); // error: 'class Point' has no member named 'move'

	Point p3 = MovablePoint(31, 32, 33, 34);
	p3.print();
	cout << endl;
	// p3.move(); // error: 'class Point' has no member named 'move'
}

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

2.2 多态性

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

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

Class Point {
	......
		virtual void print() const;
}
/* Test Substituting a subclass instance to a superclass reference.
   (TestSubstitution.cpp) */
#include <iostream>
#include "MovablePoint.h"   // included "Point.h"
using namespace std;

int main() {
   // Substitute a subclass instance to a superclass reference

   // Using Object Pointer
   Point * ptrP1 = new MovablePoint(11, 12, 13, 14);   // upcast
   ptrP1->print(); // MovablePoint @ (11,12) Speed=(13,14)
                   //   - Run subclass version!!
   cout << endl;
   delete ptrP1;

   // Using Object Reference
   MovablePoint mp2(21, 22, 23, 24);
   Point & p2 = mp2;  // upcast
   p2.print();     // MovablePoint @ (21,22) Speed=(23,24)
                   //   - Run subclass version!!
   cout << endl;

   // Using object with explicit constructor
   Point p3 = MovablePoint(31, 32, 33, 34);  // upcast
   p3.print();     // Point @ (31,32) - Run superclass version!!
   cout << endl;
}

向上转型和向下转型

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

int i = 0;
double *ptr1 = &i; // error: cannot convert 'int*' to 'double*' in initialization

double &d = i; // error: invalid initialication of reference of type 'double&' from expression of type 'int'

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

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

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

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

#include <iostream>
#include "MovablePoint"

using namespace std;

int main() {
	// Object Pointer
	Pointer *ptrp1 = new MovablePoint(11, 12, 13, 14);
	// Upcast is always permissible and safe
	ptrP1->print();

	// MovablePoint *ptrMP1 = ptrP1; // error
	MovablePoint *ptrMp1 = (MovablePoint *) ptrP1;
	// Downcase requires explicit casting operator
	delete ptrP1;
}

dynamic_cast 操作符

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

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

typeid 操作符

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

/* Test typeid operator, which return an object of type_info (TestTypeID.cpp) */
#include <iostream>
#include <typeinfo>       // Need for typeid operator
#include "MovablePoint.h" // included "Point.h"
using namespace std;

int main() {
   // Object Pointer
   Point * ptrP1 = new MovablePoint(11, 12, 13, 14);  // upcast
   cout << typeid(*ptrP1).name() << endl;   // 12MovablePoint

   MovablePoint * ptrMP1 = dynamic_cast<MovablePoint *>(ptrP1);
   cout << typeid(*ptrMP1).name() << endl;  // 12MovablePoint
   delete ptrP1;

   Point p2;
   cout << typeid(p2).name() << endl;  // 5Point

   MovablePoint mp2(1, 2, 3, 4);
   cout << typeid(mp2).name() << endl; // 12MovablePoint
}

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

2.3 纯虚函数和抽象父类

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

virtual double getArea() = 0;

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

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

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

2.4 示例

Shape.h

#define SHAPE_H
#define SHAPE_H

#include <string>
using namespace std;

class Shape {
private:
	string color;
public:
	Shape(const string &color = "red");
	string getColor() const;
	void setColor(const string &color);
	virtual void print() const;
	// Purge virtual, to be implemented by subclass
	// You cannot create instance of Shape
	virtual double getArea() const = 0;
};

#endif

Shape.cpp

#include "Shape.h"
#include <iostream>

Shape::Shape(const string &color) {
	this->color = color;
}

string Shape::getColor() const {
	return color;
}

void Shape::setColor(const string &color) {
	this->color = color;
}

void Shape::print() const {
	std::cout << "Shape of color = " << color;
}

Circle.h

#ifndef CIRCLE_H
#define CIRCLE_H

#include "Shape.h"

class Circle : public Shape {
private:
	int radius;
public:
	Circle(int radius = 1, const String &color = "red");
	int getRadius() const;
	void setRadius(int radius);
	void print() const;
	double getArea() const;
};

#endif

Circle.cpp

/* Implementation for Circle (Circle.cpp) */
#include "Circle.h"
#include <iostream>
#define PI 3.14159265

// Constructor
Circle::Circle(int radius, const string & color)
   : Shape(color), radius(radius) { }

// Getters
int Circle::getRadius() const {
   return radius;
}

// Setters
void Circle::setRadius(int radius) {
   this->radius = radius;
}

void Circle::print() const {
   std::cout << "Circle radius=" << radius << ", subclass of ";
   Shape::print();
}

// Implement virtual function inherited for superclass Shape
double Circle::getArea() const {
   return radius * radius * PI;
}

Rectangle.h

/* Header for Rectangle class (Rectangle.h) */
#ifndef RECTANGLE_H
#define RECTANGLE_H

#include "Shape.h"

// The class Rectangle is a subclass of Shape
class Rectangle : public Shape {
private:
   int length;
   int width;

public:
   Rectangle(int length = 1, int width = 1, const string & color = "red");
   int getLength() const;
   void setLength(int length);
   int getWidth() const;
   void setWidth(int width);
   void print() const;      // Override the virtual function
   double getArea() const;  // to implement virtual function
};

#endif

Rectangle.cpp

/* Implementation for Rectangle (Rectangle.cpp) */
#include "Rectangle.h"
#include <iostream>

// Constructor
Rectangle::Rectangle(int length, int width, const string & color)
  : Shape(color), length(length), width(width) { }

// Getters
int Rectangle::getLength() const {
   return length;
}
int Rectangle::getWidth() const {
   return width;
}

// Setters
void Rectangle::setLength(int length) {
   this->length = length;
}
void Rectangle::setWidth(int width) {
   this->width = width;
}

void Rectangle::print() const {
   std::cout << "Rectangle length=" << length << " width=" << width << ", subclass of ";
   Shape::print();
}

// Implement virtual function inherited from superclass Shape
double Rectangle::getArea() const {
   return length * width;
}