函数重载(overload)和函数重写(override)
1. 前言:
在C++中有两个非常容易混淆的概念,分别是函数重载(overload)和函数重写(overwirte)。虽然只相差一个字,但是它们两者之间的差别还是非常巨大的。
而通过深入了解这两个概念的区别,会对C++的面向对象机制有一个更深入的理解。
2 函数重载(overload function)
2.1 函数重载的概念:
2.1.1 概念:
当函数具有相同的名称,但是参数列表不相同的情形(包括参数的个数不同或参数的类型不同),这样的同名而不同参数的函数之间,互相被称之为重载函数。
2.1.2 基本条件:
- 函数名必须相同;
- 函数参数必须不相同,可以是参数类型或者参数个数不同;
- 函数返回值可以相同,也可以不相同。(备注:但是如果函数的名称和参数完全相同,仅仅是返回值类型不同,是无法进行函数重载的。)
2.1.3 注意:
- 只能通过不同的参数样式进行重载,例如:不同的参数类型,不同的参数个数,或者不同的参数顺序;
- 不能通过访问权限、返回类型、抛出的异常不同而进行重载;
- 重载的函数应该在相同的作用域下。
2.1.4 函数重载实例判断:
以下的集中写法,分别表示了哪些是重载的,哪些不是重载的。
(1) void func1( int arg1);
(2) void func1( double arg1);
(3) void func1( int arg1, int arg2);
(4) bool func1(int arg1, double arg2)
(5) int func1(int arg1);
在上述的5个函数中,函数名称都是func1,完全相同;但是:
(2)与(1)的参数个数相同,参数类型不同,构成重载;
(3)与(1)和(2)的参数个数不同,构成重载;
(4)与(1)和(2)的参数个数不同,与(3)的参数个数相同,但是第二个参数类型不同,构成重载;
(5)与(1)的参数个数和参数类型均相同,仅返回值的类型不相同,不构成重载;但是(5)与(2),(3)和(4)除返回值不同外,均有参数类型或参数个数不同的情况,因此构成重载关系。
2.2 函数重载的应用
读者可能会问,既然函数重载这个概念这么拗口,而且有时候又容易和函数重写概念弄混而导致出错,那么为什么在C++里面要有这么一个概念出现呢?
原因其实也很简单,就是因为在一个程序中,会出现很多很多,完成的函数功能完全相同,而仅仅是函数的参数略有不同的情形。这时如果没有函数重载这个概念,那么开发人员恐怕就要为如何为功能完全相同的函数起不同的名而头疼了。
在各种开源的库中,我们也经常可以看到函数重载的身影。比如:
(1)类的构造函数,通常就是函数重载的典型应用。因为一个类通常是可以有很多种构造方式的。
如QT里面的QString类的构造函数,提供了9种不同的构造函数,这9种构造函数的函数名完全相同,但是它们的参数类型或参数个数却不完全相同,因此是合法的。如图所示:
(2)类的成员函数,如赋值函数等。
如VTK的vtkImageData类的两个成员函数就是重载的。如:
这两个成员函数的函数名称都是SetDimensions(),但是第一个函数的参数是3个int型的值;另一个函数的参数是一个const int型的数组,返回值都是void。这样也是可以构成函数重载的。
在安装有编程助手的情况下编写代码时,如果遇到一个类的成员函数有重载时,助手通常会提示开发者,要选择哪一个重载函数。如下图所示。vtkImageData的SetDimensions()函数有两个重载形式,因此在编写代码时,助手会提示2 of 2,表示这是2个重载函数中的第二个,点击可以切换到第一个重载函数。开发者需要根据上下文的要求,来选择相应的重载函数进行编写。
3 函数重写(override function)
虽然与函数重载仅仅只有一个字的差别,但是这两个概念却是相差了很远很远。它俩似乎一点关系都没有。也正因为如此,这个非常考验C++语言的基本功,也是历年C++笔试中经常会出现的考题。
3.1 函数重写(override function)
3.1.1 概念:
函数重写,也被称为覆盖,是指子类重新定义父类中有相同名称和参数的虚函数,主要在继承关系中出现。
3.1.2 基本条件:
- 重写的函数和被重写的函数必须都为virtual函数,并分别位于基类和派生类中;
- 重写的函数和被重写的函数,函数名和函数参数必须完全一致;
- 重写的函数和被重写的函数,返回值相同,或者返回指针或引用,并且派生类虚函数返回的指针或引用的类型是基类中被替换的虚函数返回的指针或引用的类型或者其子类型(派生类型)。
3.2 函数重写的应用
有个规则是赋值兼容性原则,而这个规则有bug,当子类和父类的函数重名的时候,不管是基类的对象去指向/引用子类对象,都是调用基类的函数,而和子类无关,这明显是一个bug,
所以,只能用函数重写(也就是引出了多态),这样就可以区分和调用子类和父类里重名的函数了。[这里不能用函数重载是因为这里发生在两个类里,而重载不仅仅是在一个类里进行区分]
今天在工作的时候,就是因为在重写基类的某一个虚函数时,由于在复制时把函数的参数类型和基类的参数类型搞得不一致了,导致重写失败。
因此,在调试代码时,本以为程序会进入派生类的重写后的函数中,但是实际却一直进入基类的函数中。最后在网上查询原因时,才恍然大悟,原来是由于自己的失误,而导致了重写失败。
具体是:
Dx3DActorRotationPanOplayer : public DxBaseOplayer.
在基类DxBaseOplayer中有一系列的关于响应鼠标事件的虚函数:
其中,在派生类中我想重新实现其中的一个虚函数 OnMouseLeave()。
但是,我在子类定义该函数时,却写成了:
表面上看起来似乎很像。但是仔细一看,函数的参数是不相同的。
基类的第一个参数类型是:QEvent, 而派生类的第一个参数类型是:QMouseEvent。
正是由于这个参数类型的不同,而导致了派生类实际并没有重写基类的这个成员函数。
因此,在基类的指针调用这个函数时,便无法调用到子类的这个重写函数了。
只要把派生类的第一个参数类型也修改为QEvent,那么便实现了函数的重写了。