1. 继承
1.1 术语
超类(基类)和子类(派生类):在面向对象程序设计中,我们通常使用继承来避免代码冗余。在 C++中,继承的语法规则如下:
子类继承了父类所有的成员,子类也可以定义自己的构造器和成员。
访问标识符:C++支持三种访问标识符:private
,public
和protected
。一个类的 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
修饰的成员。例如:
然而,如果我们把 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 多态性
- 子类实例能够被父类引用替换
- 一旦被替换,该实例只能调用父类的方法,不能调用子类的
- 如果子类重写了父类的方法,我们期望调用的是重写后的方法,而不是父类原有的方法
虚函数:为了实现多态机制,我们需要使用virtual
关键字来修饰函数。此时,如果父类作用于子类实例,调用被virtual
修饰的
函数时,会调用子类中重写的函数,而不是父类中的原始函数。例如:
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 = ∓ // 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}
评论