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