1、c++中的虚函数
1.1、静态多态(早绑定)
程序在编译阶段就知道到底执行哪个函数了;
1.2、动态多态(晚绑定)
程序在编译阶段无法确定执行哪个函数,但在执行阶段,可以确定;
前提条件:有封装和继承的基础才会涉及动态多态,即必须至少得有2个类;
1.3、普通虚函数
virtual关键字的使用限制:
a.不能修饰非类成员的普通函数;
b.不能修饰构造函数;
c.不能修饰静态成员函数;
d.不能修饰内联函数;
1.4、虚析构函数
当父类类型的指针指向其子类,且父类的成员里含有指针型变量时,在堆中对子对象实例化时最后通过delete释放时,只会执行父类的析构函数,而不会执行子类的析构函数,这将造成内存的泄露,解决办法是将父类的析构函数声明成虚析构函数。
1.5、虚函数与虚析构函数原理
1、需要知识:函数指针的概念、虚函数表指针、虚析构函数指针、父类指针,虚函数表的原理。
2、函数指针:函数的本质是存放在计算机内存中的一段代码;函数名是其入口地址;
3、虚函数表指针:当类成员中有虚成员函数(虚析构函数或其他普通的类成员虚函数)时,在实例化对象时,将会在该对象中产生一个虚函数表指针,并且该指针所占的内存单元排在该类所有数据成员所占内存单元的前面,还有就是,同一个类的所有对象会共用同一张虚函数表。
2、纯虚函数与抽象类
2.1、什么是纯虚函数?
看如下Shape类的定义:
class Shape
{
public:virtual double calcArea() {return 0;} //虚函数
virtual double calcPerimeter() = 0; //纯虚函数
……
};
只有函数声明没有函数定义的虚函数是纯虚函数。含有纯虚函数的类称之为抽象类。抽象类无法实例化对象。抽象类的子类可能也是抽象类。
2.2、什么是接口类?
接口类应该是只提供方法声明,而自身不提供方法定义的抽象类。接口类自身不能实例化,接口类的方法定义/实现只能由接口类的子类来完成。
而对于C++,其接口类一般具有以下特征:
1.最好不要有成员变量,但可以有静态常量(static const或enum)
2.要有纯虚接口方法
3.要有虚析构函数,并提供默认实现
4.不要声明构造函数
如下就是一个最简单的例子:
class Shape
{
public:
static const double START = 0.1; //静态常量
enum { STOP = 2 }; //枚举量
virtual double calcArea()=0; //纯虚函数
virtual double calcPerimeter()=0; //纯虚函数
virtual ~Shape(){} //从C++11开始可以: virtual ~Shape() = default;
};
2.3、如何使用接口类?
3、运行时类型识别
首先要 include <typeinfo>
,然后才能使用typeid()及dynamic_cast。
c++的virtual关键字使得父类与子类的同名函数实现了多态,但对于子类独有的成员函数,并不能通过父类指针去调用该函数。这时运行时类型识别(Run-Time-Type-Identification)使得这一过程成为了可能。示例代码如下:
#include <iostream>
#include <string>
#include <typeinfo> //要使用typeid和dynamic_cast关键字必须包含此头文件
using namespace std;
/**
* 定义移动类:Movable
* 纯虚函数:move
*/
class Movable
{
public:
virtual void move() = 0;
};
/**
* 定义公交车类:Bus
* 公有继承移动类
* 特有方法carry
*/
class Bus : public Movable
{
public:
virtual void move()
{
cout << "Bus -- move" << endl;
}
void carry()
{
cout << "Bus -- carry" << endl;
}
};
/**
* 定义坦克类:Tank
* 公有继承移动类
* 特有方法fire
*/
class Tank :public Movable
{
public:
virtual void move()
{
cout << "Tank -- move" << endl;
}
void fire()
{
cout << "Tank -- fire" << endl;
}
};
/**
* 定义函数doSomething含参数
* 使用dynamic_cast转换类型
*/
void doSomething(Movable *obj)
{
obj->move();
cout << typeid(*obj).name() << endl;//typeid(a).name()直接显示变量a的类型名称。
if(typeid(*obj) == typeid(Bus))
{
Bus * bus = dynamic_cast<Bus *>(obj);
bus->carry();
}
if(typeid(*obj) == typeid(Tank))
{
Tank * tank = dynamic_cast<Tank *>(obj);
tank->fire();
}
}
int main(void)
{
Bus b;
Tank t;
doSomething(&b);
doSomething(&t);
cin.get();
return 0;
}
4、异常处理
a.概念:对可能发生异常的地方做出预见性的安排;
b.基本思想:主逻辑与异常处理分离;
c.异常处理的流程如下:
d.异常处理的相关函数:throw try…catch…
f.常见的异常有:1.内存不足 ;2.数组下标越界 ; 3.除数为0 ; 4.文件读写失败
异常处理的例子代码如下:
#include <iostream>
#include <string>
#include <stdlib.h>
using namespace std;
/**
* 定义函数division
* 参数整型dividend、整型divisor
*/
int division(int dividend, int divisor)
{
if(0 == divisor)
{
// 抛出异常,字符串“除数不能为0”
throw string("除数不能为0");
}
else
{
return dividend / divisor;
}
}
int main(void)
{
int d1 = 0;
int d2 = 0;
int r = 0;
cin >> d1;
cin >> d2;
// 使用try...catch...捕获异常
try
{
division(d1,d2);
}
catch(string & s)
{
cout << s << endl;
}
return 0;
}