C++基础知识整理五(多态)

       所谓的多态,可以简单的理解为一个接口,多种方法,但是在程序运行时,特定的场合下,一个接口只能对应一个与其环境最匹配的方法。多态可以分为动态多态和静态多态。静态多态又可以称为是编译时多态;动态多态也可以称为运行时多态。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.先存放虚函数地址,然后存储数据成员。


虚函数可以突破对象访问私有成员的限制,看下面的示例代码:

关于虚函数的默认参数是动态绑定还是静态绑定的,看下面的示例代码:




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值