多态概念

多态:相同对象收到不同消息或不同对象收到相同消息时产生不同的动作(不同对象对相同命令执行不同操作)。

多态可分为静态多态、动态多态。

静态多态又称为早绑定,编译时已确定,比如函数的重载。

1
2
3
4
class Rect {
int calcArea(int width);
int calcArea(int width, int height);
}

动态绑定又称为晚绑定,以封装、继承为基础。可借用虚函数实现。

虚函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Shape {
public:
virtual double calcArea() {//虚函数定义需前加关键字virtual
cout << "calcArea" << endl;
return 0;
}
};
class Circle: public Shape {
public:
Circle(double r) {
m_dR = r;
m_pCenter = new Coordinate(1, 2);
}
[virtual] double calcArea();//继承后virtual关键字可加可不加,但为了易读性,建议加上
private:
double m_dR;
Coordinate *m_pCenter;
}
class Rect: public Shape {
public:
Rect(double width, double height);
[virtual] double calcArea();
private:
double m_dWidth;
double m_dHeight;
}
int main(void) {
Shape *shape1 = new Circle(4.0);
Shape *shape2 = new Rect(3.0, 5.0);
shape1->calcArea();
shape2->calcArea();
//如果没有virtual,那么调用的都是父类的calcArea()成员函数
delete shape1;
delete shape2;
return 0;
}

但是使用虚函数需要注意:动态多态可能导致内存泄露,这种情况发生在父类指针操作子类虚函数后释放指针销毁子类对象时,只会执行父类的析构函数,从而导致子类中内存泄露。(例如上例中Circle构造函数实例化m_pCenter,m_pCenter为泄露内存)

为了解决上述问题,可以引进虚析构函数,用virtual修饰父类析构函数,这样在执行父类的虚析构函数时会先自动调用子类的析构函数。

对于virtual关键字有以下限制:

  • 1、不能修饰普通函数(全局函数)

  • 2、不能修饰静态成员函数

  • 3、修饰内联函数时,会使inline失去作用

  • 4、不能修饰构造函数

对于对象的大小,只计算数据成员所占大小,不考虑成员函数。

虚函数实现原理

首先我们需要知道一个概念,当指针指向函数时,实际指向的是函数的入口地址。

因此虚函数原理:实例化对象时,有一个虚函数表指针成员,指向虚函数表,从而找到虚函数。(虚函数表指针占用对象开始处的四个字节

纯虚函数与抽象类

1
2
3
4
class Shape {//抽象类
public:
virtual double calcArea() = 0;//纯虚函数
};

纯虚函数的定义即在虚函数的基础上后加= 0

抽象类:含有纯虚函数的类。

抽象类不能实例化对象,抽象类的子类也可以是抽象类。只有当抽象类子类实现了所有的纯虚函数,才可以实例化。

抽象类、纯虚函数的作用:确保基类不能实例化对象。

接口类

接口类:仅含有纯虚函数的类(成员函数都是纯虚函数)。

接口类更多表达一种能力或协议。

例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Flyable {
public:
virtual void takeoff() = 0;
virtual void land() = 0;
};
---
class Bird: public Flyable {
public:
....
virtual void takeoff() {...}
virtual void land() {...}
}

RTTI(运行时类型识别-Run Time Type Identification

使用typeid()、dynamic_cast来完成。见下例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Flyable {
public:
virtual void takeoff() = 0;
virtual void land() = 0;
};
class Bird: public Flyable {
public:
void foraging() {...}
virtual void takeoff() {...}
virtual void land() {...}
private:
... ...
};
class Plane : public Flyable {
public:
void carry() {...}
virtual void takeoff() {...}
virtual void land() {...}
};
void doSomething(Flyable *obj) {
obj->takeoff();
使用RTTI来完成下面的任务:
cout << typeid(*obj).name() << endl;//打印对象类型
if(typeid(*obj) == typeid(Bird)) {
Bird *bird = dynamic_cast<Bird *>(obj);//转为Bird指针
bird->foraging();
}
/*
if(Bird) obj->foraging();
if(Plane) obj->carry();
*/
obj->land();
}

dynamic_cast注意事项:

  • 只能用于指针和引用转换

  • 要转换的类型中必须包含虚函数

  • 转换成功返回子类地址,失败返回NULL

typeid注意事项:

  • type_id返回一个type_info对象的引用

  • 如果想要通过基类指针指向派生类的数据类型,基类必须带有虚函数

  • 只能获取对象的实际类型

异常处理

异常:程序运行期出现的错误。

异常处理:对可能发生异常的地方做出预见性的安排。

C++ OOP特性还可参考本站其他相关博文:

C++ 【oop】封装篇

C++ 【oop】继承篇

C++ 【oop】模板篇