多态
多态性意味着有多种形式。通常,多态发生在类之间存在层级关系且这些类有继承关系的时候。
C 多态性是指不同的对象发送同一个消息,不同对象对应同一消息产生不同行为。
考虑下面的例子,一个基类派生了其他的两类:
代码语言:javascript复制#include <iostream>
using namespace std;
// 形状父类
class Shape {
protected:
int width, height;
public:
Shape(int a=0, int b=0) {
width = a;
height = b;
}
int area() {
cout << "Parent class area :" <<endl;
return 0;
}
};
// 矩形类
class Rectangle : public Shape {
public:
Rectangle(int a=0, int b=0):Shape(a, b) { }
int area() {
cout << "Rectangle class area :" <<endl;
return (width * height);
}
};
// 三角形
class Triangle : public Shape {
public:
Triangle(int a=0, int b=0):Shape(a, b) { }
int area() {
cout << "Triangle class area :" <<endl;
return (width * height / 2);
}
};
int main() {
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
// 指向矩形地址
shape = &rec;
// 调用对象求面积的方法
shape->area();
// 执行三角形的地址
shape = &tri;
// 调用三角形求面积的方法
shape->area();
return 0;
}
上面的代码编译和执行时,它产生以下结果:
代码语言:javascript复制Parent class area
Parent class area
输出结果不正确的原因是对函数 area() 的调用被编译器设置了一次,即在基类中定义的版本,这被称为对函数调用的静态分辨或者静态链接,静态链接就是在程序被执行之前函数调用是确定的。这有时也被称为早期绑定,因为函数 area() 在编译程序期间是固定的。
但是现在,让我们对程序做略微修改,并在 Shape 类中 area() 的声明之前加关键字 virtual
,它看起来像这样:
class Shape {
protected:
int width, height;
public:
Shape(int a=0, int b=0) {
width = a;
height = b;
}
virtual int area() {
cout << "Parent class area :" <<endl;
return 0;
}
};
这轻微的修改后,前面的示例代码编译和执行时,它会产生以下结果:
代码语言:javascript复制Rectangle class area
Triangle class area
这一次,编译器关注的是指针的内容而不是它的类型。因此,由于三角形和矩形类对象的地址被存储在形状类中,各自的 area() 函数可以被调用。
正如你所看到的,每个子类都有一个对 area() 函数的实现。通常多态就是这样使用的。你有不同的类,它们都有一个的相同名字的函数,甚至有相同的参数,但是对这个函数有不同的实现。
综合案例:
代码语言:javascript复制#include <iostream>
using namespace std;
// 形状父类
class Shape {
protected:
int width, height;
public:
Shape(int a = 0, int b = 0) {
width = a;
height = b;
}
// 使用virtual虚函数的逻辑是可以被派生的子类重写
/*virtual int area() {
cout << "Parent class area :" << endl;
return 0;
}*/
// 纯虚函数,没有函数体, 定义该函数的类,必须是抽象的类,抽象类就是不能实例化类
virtual int area() = 0;
};
// 矩形类
class Rectangle : public Shape {
public:
Rectangle(int a = 0, int b = 0) :Shape(a, b) { }
int area() {
cout << "Rectangle class area :" << (width * height) << endl;
return (width * height);
}
};
// 三角形
class Triangle : public Shape {
public:
Triangle(int a = 0, int b = 0) :Shape(a, b) { }
int area() {
cout << "Triangle class area :" << (width * height / 2) << endl;
return (width * height / 2);
}
};
int main() {
Shape* shape;
Rectangle rec(10, 7);
Triangle tri(10, 5);
// A 父类
// B C 子类
// A 引用 B实例 / A 指向 C实例 (表现为多态)
// B 引用 B实例 调用的是本身的函数,而不是父类中同名函数。 (就近原则)
rec.area();
// 指向矩形地址
shape = &rec;
// 调用对象求面积的方法
shape->area();
// 执行三角形的地址
shape = &tri;
// 调用三角形求面积的方法
shape->area();
return 0;
}
虚函数
基类中的虚函数是一个使用关键字 virtual
声明的函数。派生类中已经对函数进行定义的情况下,定义一个基类的虚函数,就是要告诉编译器我们不想对这个函数进行静态链接。
我们所希望的是根据调用函数的对象的类型对程序中在任何给定指针中被调用的函数的选择。这种操作被称为动态链接,或者后期绑定。
纯虚函数
可能你想把虚函数包括在基类中,以便它可以在派生类中根据该类的对象对函数进行重新定义,但在许多情况下,在基类中不能对虚函数给出有意义的实现。
我们可以改变基类中的虚函数 area() 如下:
代码语言:javascript复制class Shape {
protected:
int width, height;
public:
Shape(int a=0, int b=0) {
width = a;
height = b;
}
// 纯虚函数定义
virtual int area() = 0;
};
area() = 0 就是告诉编译器上面的函数没有函数体。上面的虚函数就被称为纯虚函数。
【小结】
对比一下C#语言,概念上还是有所不同。