本篇文章有以下内容
多态
多态分为静态多态和动态多态
(看完这篇文章,可能会不认识态这个字)
静态多态:体现在运算符重载,函数重载等方面,即程序再编译的时候编译器来确定使用哪个函数,所以也称为编译时多态。
动态多态:程序在编译阶段无法确定调用哪个函数,只能在程序运行的时候选择一个正确的参数,这种多态被称为动态多态。
由于动态多态的基础是虚函数(在后面),所以我们一点一点的看。
指针和引用的兼容性
指向基类的指针和引用,可以指向或引用派生类对象,而不需要进行强制类型转换。
举个例子:
B是A的派生类,那么可以这样写:
B b;
A* p = &b;//基类的指针
A& a = b;//基类的引用
这里用到了上行转换,等号右面的b转换成了A类的对象,然后使得式子成立。注意:上行转换不需要显式进行,但下行转换,一定要使用显式类型转换。
但是,上行转换后,指针只能访问派生类从基类继承的成员,不能访问派生类自己新增的成员。
虚函数(Virtual Function)
为了实现上文提到的运行时多态,C++引入了虚函数。
虚函数的定义:
虚函数只能是类的非静态成员函数。
class Student{
private:
int num;
string name;
public:
Student(int n,string name):num(n),name(name){}
virtual void Test();//这里就是虚函数的定义
};
注意:
- 派生类必须是公有继承
- 派生类中进行虚函数改写的时候一定要和基类中虚函数原型完全一致(函数返回值类型,参数,函数名),virtual关键字可加可不加。
下面的例子可以看出在调用虚函数的时候如何使用。
#include <iostream>
#include <string>
using namespace std;
class Student //基类
{
public:
Student(const string& = "", const string& = "");//声明构造函数
virtual void Test(); //虚函数
protected:
string strNum, strName; //描述学生的学号和姓名
};
Student::Student(const string& number, const string& name)
{
strNum = number;
strName = name;
}
void Student::Test() //实现基类的虚函数Test()
{
cout << "My God! 又要考试啦!" << endl;
}
class Doctor : public Student //公有派生Doctor(博士生类)
{
public:
Doctor(const string&, const string&, const string&);
void Test() //声明虚函数Test(),原型与基类的虚函数完全一样
{//直接在类内实现虚函数
cout << "Doctor..." << endl;
}
private:
string strMajor; //专业信息
};
Doctor::Doctor(const string& number, const string& name, const string& major) : Student(number, name)
{
strMajor = major;
}
class MidschoolStu : public Student//派生类中学生类
{
public:
void Test(){cout << "MidschoolStu..." << endl;}
};
class HighschoolStu : public MidschoolStu //高中生类由中学生类派生
{
public:
void Test(){cout << "Highschoolstu..." << endl;}
};
int main()
{
Student* pStu = nullptr; //创建一个Student类型的空指针
Doctor ds("2019416001", "Ailsa", "Computer Science and Technology");
MidschoolStu ms;
HighschoolStu hs;
pStu = &ds;
pStu->Test();//这里调用的就是虚函数
pStu = &ms;
pStu->Test();
pStu = &hs;
pStu->Test();
return 0;
}
这里总结一下:
重载(overload) 隐藏(hide) 覆盖(override)
-
重载
- 在同一作用域内的相同函数之间,并且函数的参数不同(个数或类型)。
-
隐藏
- 发生在派生类和基类的同名函数之间(继承中才存在隐藏),并且这些函数不是虚函数,那么就称派生类中的函数隐藏了基类中的函数。
- 派生类和基类是两个不同的作用域,所以即使两个函数参数不同,也不存在重载的概念。
- 如果函数在基类中是虚函数,但在派生类中同名函数的参数不同,那么派生类中的函数就会隐藏基类中的同名函数。
- 覆盖
- 当基类中的函数是虚函数,派生类中也存在同名函数,并且参数、返回值类型、函数名都相同,这里的派生类中的函数就会 覆盖(override) 基类中的虚函数。
C++中不能声明虚构造函数,但可以声明虚析构函数
默认情况下,编译器只调用基类的析构函数回收基类的数据成员占用的资源。那么当在派生类构造函数中使用new运算符为派生类新增成员申请的内存回收的时候,就要用到虚析构函数。
把基类的析构函数声明为虚析构函数,然后定义派生类的析构函数,在派生类中用delete运算符回收内存。
class Student {
public:
Student(const string& = "", const string& = "");
virtual ~Student(){cout << "Student destructor is called..." << endl;}
protected:
string strNum, strName;
};
class Doctor : public Student{
public:
Doctor(const string&, const string&, double, double);
~Doctor()
{
cout << "Doctor destructor is called..." << endl;
delete[] dScore;
}
private:
double* dScore; //保存成绩信息
};
一个例子:
解析:这里的func1由于不是虚函数,所以指针a会调用基类中的func1(),所以先输出了A1,但这里的func2()是虚函数,所以会调用派生类中的func2(),这样,就可以看出运行时多态(基类指针指向派生类对象的时候,调用的是派生类中的函数)。
纯虚函数
纯虚函数允许在基类中只给出虚函数的声明,不给出虚函数的实现。这样的纯虚函数只是为了给派生类提供一个接口,在派生类中实现,来实现运行时的多态。
纯虚函数的声明:
virtual void Test() = 0;
- =0只是代表这个函数是纯虚函数,并没有其他意义,在这里只有声明,没有函数体,没有任何功能,不能被调用。
- 通常纯虚函数一般会在派生类中实现,但如果派生类中也没有实现,那么他在派生类中仍然是一个纯虚函数。
注意:
包含纯虚函数的类被称为抽象类,抽象类基于纯虚函数,并且只用作基类,不能声明抽象类的对象,但可以通过创建抽象类的指针或引用来操作派生类对象。(如果在派生类中纯虚函数没有实现,那么也不能声明此派生类的对象,因为此派生类也是抽象类。)
本台插播
空指针(nullptr):
Student *p = nullptr;
这里p是定义的一个空指针。
多态的核心技术:
继承、虚函数、指针和引用类型的兼容赋值