所谓的多态,可以简单的理解为一个接口,多种方法,但是在程序运行时,特定的场合下,一个接口只能对应一个与其环境最匹配的方法。多态可以分为动态多态和静态多态。静态多态又可以称为是编译时多态;动态多态也可以称为运行时多态。C++的多态表现形式主要有函数重载、运算符重载和虚函数等,其中函数重载和运算符重载属于静态多态,即编译时多态;虚函数则是动态多态,即运行时多态。
编译时多态
· 运算符重载
o 运算符重载的本质:运算符的重载本质上和函数的重载是相同的,即调用时通过参数来区分调用对应匹配的函数,运算符重载和普通函数重载的唯一区别就是函数名,运算符重载的函数名是由关键字-operator-和其后要重载的运算符符号构成的,一般格式如下:
数据类型operator<运算符符号>(<参数表>)
{
<函数体>
}
有些运算符是无法被重载的,主要是以下几类:
1.对象访问成员运算符<.>
2.成员指针运算符 <.*>
3.作用域运算符<::>
4.<sizeof>运算符
5.三目运算符<?>
· 运算符重载的限制规则
1.重载之后的运算符不能改变运算符的优先性和结合性;
2.重载之后的运算符不能改变运算符操作数的个数和语法结构;
3.重载的运算符操作数至少有一个是自定义类型的;
4.重载运算符函数通常不能有默认的参数。
· 运算符重载的实现形式
1.重载为类的成员函数;
· 当运算符重载函数为类的成员函数时,函数的参数个数比原来的操作数是少一个的(后置单目运算符除外),这是因为成员函数用-this-指针隐式的访问了类的一个对象,它就充当了运算符函数最左边的操作数。
它的标准形式为:
<对象名>.operator<运算符>(<参数>)
等价于
<对象名><运算符><参数>
2.重载为类的非成员函数,非成员函数通常是友元。
· 当运算符重载为类的友元函数时,由于没有隐含的-this-指针,因此操作数的个数没有变化。它的标准形式为:
operator<运算符>(<参数1>, <参数2>)
等价于
<对象名><运算符><参数>
运算符重载函数从原则上讲可以是非成员、非友元的函数,但是定义成这样的函数是有很大的弊端的,因为 这样我们无法通过函数直接调用到类的私有及受保护的成员,这便会降低函数性能。所以,我们通常都是讲运算符重载函数定义成成员函数或者友元的形式!
多数情况下。这两种形式是可以互相转化的,但是有些场合下,是有限制的:
= () [] -> 这些运算符都是不能重载为类的友元函数的,只能重载为成员函数!
· 数据类型的隐式转换
· 在C++中数据类型的隐式转换是个运算符,我们可将他进行运算符重载从而扩展他的功能。隐式类型转换重载函数的标准形式为:
operator type()
其中-type-可以是内置类型名、类类型名或用户型别名所定义的名字。但是通常不允许隐式转换成数组或函数类型,建议转换成指针类型(或函数指针)或引用类型;并且隐式类型转换重载函数在使用时需要满足三个条件:
1.必须是成员函数;
2.不能指定返回类型;
3.函数形参必须为空。
运行时多态
· 在C++中,运行时的多态主要是通过虚函数来实现的!运行时多态性需要满足如下三个条件:
· 子类得包含虚函数;
· 子类得虚函数必须和父类的虚函数声明一致;
· 通过父类的指针或者引用访问虚函数实现多态。
· 在C++中,对象之间赋值的限制规则:
· 必须有继承关系
· 只能是子类赋值给父类(即向上转型!)
· 关于继承中父子类中数据的重载、覆盖和隐藏的区别
· 重载通常是指函数重载,即函数名相同,参数不同。我们可以通过调用函数传入参数的不同从而调用不同的函数。
· 覆盖是指子类覆盖父类的函数,覆盖的要求是函数名相同,参数相同,并且父类函数必须有-virtual-关键字,即被声明为虚函数,而关于覆盖函数的访问就是运行时多态的体现!
· 隐藏是指子类函数屏蔽了其同名的父类函数或者变量。即如果子类和父类中存在同名函数,那么不论参数是否相同,只要父类中的函数没有被定义为虚函数,都会被隐藏!即无法被直接访问到,但是可以通过声明父类访问!(子类对象.父类::父类公有函数或变量)
· 虚函数的特点
· 使用虚函数时,存在子类公有继承自父类才有意义;
· 如果在父类中显示声明函数为虚函数,在子类 中同名同参的函数,即使没有显示声明为虚函数,他也同样是虚函数。即虚函数可以被继承;即便存在多层继承,父类虚函数特征也会传递倒下一层子类!
· 虚函数必须是所在类的成员函数,而不能是友元函数,也不能是静态成员函数。因为虚函数调用要靠特定的对象来决定激活哪一个函数;
· 构造函数不能是虚函数,但是析构函数可以是虚函数;因为构造函数在创建是首先被调用,也就是说在编译时就要把构造函数的地址绑定好,而析构函数是在最后调用的,可以在运行是动态分配。
· 纯虚函数和抽象类
· 纯虚函数是虚函数的一种特例,他们之间的区别就是虚函数有函数体,而纯虚函数没有函数体。
virtual 返回值类型 函数名(参数表) = 0;
纯虚函数不具备函数功能,不能被调用。包含纯虚函数的类被称为抽象类,抽象类只能作为其他类的父类来使用,不能建立抽象类的对象。抽象类不能用作函数的参数类型。返回类型和显示转化类型!如果子类中没有定义纯虚函数的实现,而只是单纯的继承了父类的纯虚函数,那么该子类仍旧是抽象类!而只要给出了父类中纯虚函数的实现,那么子类就不再是抽象类!
· 虚函数的原理
我们首先要先了解类的内存存储,我们通过下面的这个示例代码来说明:
· 虚函数表:对于含有虚函数的无继承类,只含有一张虚表,不管其虚函数有多少个,系统为其分配的内存空间只有4个字节(位于类空间的最开始的位置),用来存放虚函数表的地址,比如下面的类A
虚函数表存放的虚函数顺序是先存放父类的虚函数,然后是子类的虚函数,如果子类的虚函数和父类的虚函数构成覆盖关系,那么存放父类虚函数的位置被同名的子类虚函数替换!
单继承中,子类虚函数表只有一个,而在多重继承中,虚函数表有多张,几重继承就有几张虚函数表,多张虚函数表地址的存放顺序按照继承的顺序依次排列,如果子类覆盖父类的虚函数,那么就会用子类自己的虚函数覆盖虚函数表相应的位置,如果子类有新的虚函数,那么就添加到对应虚函数表的末尾。有一点需要注意,如果子类中没有数据成员(包含显示或者隐式的数据成员),那么多个虚函数表的地址是连续存储的!如果子类中包含数据成员,那么子类的存储顺序遵循以下三个原则:
1.多重继承时,有几个父类,就有几张虚函数表;
2.子类成员与继承的父类成员存在覆盖关系,自动替换子类成员;
3.先存放虚函数地址,然后存储数据成员。
虚函数可以突破对象访问私有成员的限制,看下面的示例代码:
关于虚函数的默认参数是动态绑定还是静态绑定的,看下面的示例代码: