1. 概念
1.1 纯虚函数
仅仅给出声明,不对虚函数实现定义,而是在派生类中实现。
- 纯虚函数需要在声明之后加个=0;
- 纯虚函数没有函数体。
- 纯虚函数的作用是作为派生类中的成员函数的基础,并实现动态多态性。
1 | class <基类名> |
##1.2 抽象类
含有纯虚函数的类被称为抽象类。
- 抽象类只能作为派生类的基类,不能定义对象,但可以定义指针。
- 抽象类不能用作参数类型、函数返回类型或显式转换的类型。
- 在派生类实现该纯虚函数后,定义抽象类对象的指针或引用,并指向或引用子类对象,从而实现多态。(根据指针指向的类型的不同来决定调用哪个子类的方法)
- 在定义纯虚函数时,不能定义虚函数的实现部分。
- 在没有重新定义这种纯虚函数之前,是不能调用这种函数的。
- 抽象类的唯一用途是为派生类提供基类。
- 继承于抽象类的派生类如果不能实现基类中所有的纯虚函数,那么这个派生类也就成了抽象类。
1.3 接口和抽象类的区别
- C++中的接口,表示对外提供的方法,提供给外部调用。是沟通外部跟内部的桥梁。也是以类的形式提供的,但一般该类只具有成员函数,不具有数据成员;
- 抽象类可以既包含数据成员又包含方法。
2. 实例
抽象类IShape作为基类:只有头文件,没有实现文件
1
2
3
4
5
6
7
8
9
10
11
12
13//Shape.h
using std::string;
//interface
class IShape
{
public:
virtual float getArea()=0; //纯虚函数,获得面积
virtual string getName()=0; //纯虚函数,返回图形的名称
};派生类Circle类继承自抽象类IShape
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//Circle.h
class CCircle : public IShape //公有继承自IShape类
{
public:
CCircle(float radius); //构造函数
public:
virtual float getArea(); //实现声明实现两个基类的函数,声明的时候需要加virtual关键字,实现的时候就不需要加virtual关键字了。
virtual string getName();
private:
float m_fRadius; //派生类可以拥有自己的成员
};派生类Circle类实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//Circle.cpp
CCircle::CCircle(float radius)
:m_fRadius(radius) //使用构造函数的初始化列表初始化
{
}
float CCircle::getArea() //实现实现两个基类的函数
{
return 3.14 * m_fRadius * m_fRadius;
}
string CCircle::getName()
{
return "CCircle";
}测试文件main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13//main.cpp
using namespace std;
int main()
{
IShape* pShape = NULL; //定义了一个抽象类的指针,注意抽象类不能定义对象但是可以定义指针
pShape = new CCircle(20.2); //基类指针指向派生类的对象
cout<getName()<<" "<getArea()<<endl; //根据指针指向的类型的不同来决定调用的方法
delete pShape; //释放了CCirle对象所占的内存,但是指针是没有消失的,它现在就是一个野指针,我们在使用之前必须对它赋值
return 0;
}
3. 用“初始化列表”来初始化构造函数
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
如:
1 | CCircle::CCircle(float r): m_r(r) //将r赋值给m_r |
等同于:
1 | CCircle::CCircle(float r) |
使用初始化列表和没使用初始化列表的区别
- 使用初始化列表的构造函数:是显式的初始化类的成员;
- 没使用初始化列表的构造函数:是对类的成员赋值,并没有进行显式的初始化。
初始化和赋值的区别
对于内置类型、复合类型(指针和引用)成员变量:
- 在成员初始化列表和构造函数体内进行,在性能和结果上都是一样的
对于用户自定义类型(类类型)成员变量:
- 结果上相同,但是性能上存在很大的差别。
- 构造函数直接赋值:类类型的数据成员对象在进入函数体前先调用构造函数,完成该成员的构造;在进入函数体之后,调用相应的拷贝赋值操作,对已经构造好的类对象进行赋值。
- 使用初始列表:只需要调用一次拷贝构造函数。
- 为了避免两次构造,推荐使用类构造函数初始化列表
但有的时候必须用带有初始化列表的构造函数:
- 成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
- const成员或引用类型的成员。因为const对象或引用类型只能初始化,不能对他们赋值。
初始化列表的成员初始化顺序:
- 按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序。