C++多态性

1、C++中不能重载的运算符:①类属关系运算符“.”;②成员指针运算符“.*”;③三目运算符“?:”,④作用域分辨符“::”

2、多态的类型

①面向对象的多态可以分为重载多态、强制多态、包含多态和参数多态。重载多态和强制多态是专用多态,包含多态和参数多态是通用多态。

  • 强制多态:将一个变元的类型加以变化,以符合一个函数或者操作的要求
  • 包含多态:类族中不同于类中的同名成员函数的多态行为,主要是通过虚函数来实现
  • 参数多态:与类模板相关联,在使用时必须赋予实际的类型才可以实例化

3、多态的实现

①多态从实现的角度来讲可以划分为两类:编译时的多态和运行时的多态。前者是在编译的过程中确定了同名操作的具体操作对象,而后者则是在程序运行过程中才动态地确定操作所针对的具体对象。这种确定操作的具体对象的过程就是绑定。绑定是指计算机程序自身彼此关联的过程,也就是把一个标识符名和一个存储地址联系在一起的过程。用面向对象的术语讲,就是把一条消息和一个对象的方法相结合的过程。按照绑定进行的阶段的不同,可以分为两种不同的绑定方法:静态绑定和动态绑定,这两种绑定过程中分别对应着多态的两种实现方式。

绑定工作在编译连接阶段完成的情况称为静态绑定。因为绑定过程是在程序开始执行之前进行的,因此有时也称为早期绑定或前绑定。

③与静态绑定相对应,绑定工作在程序运行阶段完成的情况称为动态绑定。

4、运算符重载

①运算符重载就是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据时导致不同的行为。

  • 只能重载C++中已经存在的运算符
  • 重载之后运算符的优先级和结合性都不会改变

②运算符的重载形式有两种,重载为类的非静态成员函数和重载为非成员函数。
image

  • 运算符:要重载的运算符名称
  • 当以非成员函数形式重载运算符时,有时需要访问运算符参数所涉及类的私有成员,这时可以把该函数声明为类的友元函数
  • 当运算符重载为类的成员函数时,函数的参数个数比原来的操作数个数要少一个(后置“++”“减减”)除外;当重载为非成员函数时,参数个数与原操作数个数相同。

两种情况参数个数有所差异的原因是,重载为类的成员函数时,第一个操作数会被作为函数调用的目的对象,因此无须出现在参数表中,函数体中可以直接访问第一个操作数的成员;而重载为非成员函数时,运算符的所有操作数都必须显式通过参数传递。

5、运算符重载为成员函数
image
【!!!重载++i和i++记得看】

①前置单目运算符和后置单目运算符的重载最主要的区别就在于重载函数的形参。语法规定,前置单目运算符重载为成员函数时没有形参,而后置单目运算符重载为成员函数时需要有一个int型形参。

6、运算符重载为非成员函数

①运算符也可以重载为非成员函数。这时,运算所需要的操作数都需要通过函数的形参表来传递,在形参表中,形参从左到右的顺序就是运算符操作数的顺序。如果需要访问运算符参数对象的私有成员,可将该函数声明为类的友元函数。

7、虚函数

①虚函数是动态绑定的基础。虚函数必须是非静态的成员函数。虚函数经过派生后,在类族中就可以实现运行过程中的多态。

如果需要通过基类的指针指向派生类的对象,并访问某个与基类同名的成员,那么首先在基类中将这个同名函数说明为虚函数。这样,通过基类类型的指针,就可以使属于不同派生类的不同对象产生不同的行为,从而实现了运行过程的多态。

8、一般虚函数成员
image
虚函数声明只能出现在类定义中的函数原型声明中,而不能在成员函数实现的时候。

②运行过程中的多态需要满足3个条件,首先类之间满足赋值兼容规则,其二是要声明虚函数,第三是由成员函数来调用或者通过指针、引用来访问虚函数。如果是使用对象名来访问虚函数,则绑定在编译过程中就可以进行(静态绑定),而无须在运行过程中进行。

虚函数一般不声明为内联函数,因为对虚函数的调用需要动态绑定,而对内联函数的处理是静态的,所以虚函数一般不能以内联函数处理。

④程序中使用对象指针来访问函数成员,这样绑定过程就是在运行中完成,实现了运行中的多态。

⑤当派生类没有显式给出虚函数声明,这时系统就会遵循以下规则来判断派生类的一个函数成员是不是虚函数。

  • 该函数是否与基类的虚函数有相同的名称
  • 该函数是否与基类的虚函数有相同的参数个数及相同的对应参数类型
  • 该函数是否与基类的虚函数有相同的返回值或满足赋值兼容规则的指针、引用型的返回值

如果从名称、参数及返回值3个方面检查之后,派生类的函数满足了上述条件,就会自动确定为虚函数。这时,派生类的虚函数便覆盖了基类的虚函数,不仅如此,派生类中的虚函数还会隐藏基类中同名函数的所有其他重载形式。

用指向派生类对象的指针仍然可以调用基类中被派生类覆盖的成员函数,方法是使用"::"进行限定。
image
【注1】派生类覆盖基类的成员函数时,既可以使用virtual关键字,也可以不使用,二者没有差别。

【注2】当基类构造函数调用虚基类时,不会调用派生类的虚函数。这是因为当基类被构造时,对象还不是一个派生类的对象。

【注3】当基类被析构时,对象已经不再是一个派生类对象了。

【注4】只有虚函数是动态绑定的,如果派生类需要修改基类的行为(即重写与基类函数同名的函数),就应该在基类中将相应的函数声明为虚函数

【注5】在重写继承来的虚函数时,如果函数有缺省形参值,不要重新定义不同的值,原因是:虽然虚函数是动态绑定的,但缺省形参值是静态绑定的。也就是说,通过一个指向派生类对象的基类指针,可以访问到派生类的虚函数,但缺省形参值却只能来自基类的定义。

【注6】只有通过基类的指针或引用调用虚函数时,才会发生动态绑定。
image
⑦override:C++中可以使用override关键字来说明派生类中的虚函数,这么做的好处是通过标记使得编译器能够发现一些错误,如果使用override标记了某个函数,但该函数并没有覆盖已存在的虚函数,此时编译器将报错
image
⑧可以把某个函数指定为final,意味着该函数不能被覆盖,任何试图覆盖该函数的操作都将引发错误:
image
【注】final和override说明符需要出现在参数列表以及尾置的返回类型以后

9、虚析构函数

在C++中,不能声明虚构造函数,但是可以声明虚析构函数
image
如果一个类的析构函数是虚函数,那么,由它派生来的所有子类的析构函数也是虚函数。析构函数设置为虚函数后,在使用指针引用时可以动态绑定,实现运行时的多态,保证使用基类类型的指针就能调用适当的析构函数针对不同的对象进行清理工作。

③如果有可能通过基类指针调用对象的析构函数(通过delete),就需要让基类的析构函数成为虚函数,否则会产生不确定的后果。

10、纯虚数

①纯虚函数是一个在基类中声明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据实际需要定义自己的版本,纯虚函数的声明格式为:
image
【注1】实际上,纯虚函数与一般虚函数成员的原型在书写上的不同就在于后面加了“=0”。声明为纯虚函数后,基类中就可以不再给出函数的实现部分。纯虚函数的函数体由派生类给出。

【注2】基类中仍然允许对纯虚函数给出实现,但即使给出实现,也必须由派生类覆盖,否则无法实现实例化。对基类中为纯虚函数定义的函数体的调用,必须通过“类名::函数名(参数表)”的形式。如果将析构函数声明为纯虚函数,必须给出它的实现,因为派生类的析构函数体执行完后需要调用基类的纯虚函数。
image
11、抽象类

①抽象类是带有纯虚数的类

②抽象类不能实例化:即不能定义一个抽象类的对象,但是,我们可以定义一个抽象类的指针和引用。通过指针或引用,就可以指向并访问派生类对象,进而访问派生类的成员,这种访问是具有多态特征的。

③抽象类只能作为基类使用。因为抽象类包含纯虚数,这些函数没有具体实现,需要由派生类来实现这些纯虚函数。抽象类的主要目的,就是为派生类提供接口。

④抽象函数可以有构造函数和析构函数。

posted @ 2025-04-14 16:13  风归去  阅读(23)  评论(0)    收藏  举报