目标:
了解构成函数重载的条件和重载函数的调用匹配,知道重载函数的优缺点
重载函数的发明:
我们设置一个计算器,里面设置一个加法,可以计算 int + double , int + int ,double + double 的算法,但是按照 c 语言的语法就需要设计三个名字不同但是逻辑类似的函数
//三个函数名都不同,不容易记,调用时还要自己分辨类型和选择调用 double add1(int x, int y) { return x + y; } double add2(int x, double y) { return x + y; } double add3(double x, double y) { return x + y; }
但这无疑会增加程序员的记忆成本,于是我们就引入了重载函数这个概念,函数重载通过设置相同的函数名和不同的形参列表来在一定程度上减少我们起名(我就十分讨厌起名字)和记名字的成本。缺点就是有些名字可以帮你展示函数的功能,重载了就丢掉了(所以我们要好好规划使用重载函数)
这时编译器就可以通过你传入的参数自动匹配对应的函数,实现不同的功能,比如add(1,2)就会调用 第一个函数,add( 1, 2.2 )就是第二个函数,真的很方便了。
//函数名都相同,调用时候不用在意类型,直接调用 add(x,y) double add(int x, int y) { return x + y; } double add(int x, double y) { return x + y; } double add(double x, double y) { return x + y; }
同一个作用域下函数名相同且形参列表不同的函数就构成重载,与返回值没有关联(可以随意设置)。接下来我们 一 一 介绍这三个语法。
基础语法:
1,函数名相同:就是函数名相同,就如上面的三个add函数,这是构成函数重载的最基本点,不同名的函数会被解释成不同的函数,就没有重载这个说法了。
2,形参列表不同:在函数名相同的基础上,编译器会对函数的名字和形参列表中的类型名进行处理。比如add(int x, int y) , add(double x, double y)这两个函数,编译器会生成一个特定的唯一的名字来标识,比如第一个可以是 add_int_int , 第二个是 add_double_double。编译器就可以通过名字找到对应的重载函数了,不同的编译器的取名方法不同,比如类型只取首字母( int -> i)等,不同在意如何取名,交给编译器吧。
从这里我们就能知道不同的形参列表可以指数量不同,顺序不同,类型不同。因为他们生成的名字不同不重复(重复就报错了),而编译器就可以通过这个唯一的名字找到对应的重载函数。
扩展:(还没学过对应语法的可以先跳过)
一:与默认参数的结合
add(int x = 1){} add(int x = 2){}
add( int x = 1) 和 add ( int x = 2),这里是一个挺迷惑的点,其实我们在上面已经讲解过了,编译器处理名字时只对函数名和形参的类型进行处理,所以默认参数不同不构成重载。同理,形参的名字不同也不构成。写了的话报错如下:
二:typedef 的取名
看似类型不同了,但是实际上typedef 只是给类型名取别名而已,实际上最后都是int,函数就不构成重载。(报错为已有实体,重定义的意思)
typedef int _int1; typedef int _int2; void add(_int1 x) {} void add(_int2 x) {} int main() { return 0; }
三:与 const 的结合
首先大家可以去了解一下顶层const 和 底层const。顶层const 形参不构成函数重载,而底层const形参构成,因为顶层的const在传参的时候会被忽略,但底层的 const 可以被分辨出来,底层const普通对象可以转换为常量对象 ,相反则不可以。
//顶层const,报错已有实体 void add(const int i) {} void add(int i) {} //底层const,可以通过编译 void del(const int* i) {} void del(int* i) {}
3,同一作用域:函数的声明都要在同一作用域
一:函数重载的作用域和声明有关:
void function(const char* str) { cout << "fuction(const char* str)" << endl; } //这个函数的定义放在另一个源文件中 void function(double x); //这里就是新建了一个作用域 namespace my { void function(int x) { cout << "function(int x)" << endl; } } //将上面的 my 的作用域的函数声明放在目前的作用域下 void my::function(int x); int main() { function(2.1); function("hello world"); function(1); return 0; }
从上面我们可以看出来我们将三个函数的声明都放在了同一个作用域,而他们的定义都放在了不同的作用域,构成了重载,说明主要看的是函数声明。
二:函数声明在内层作用域会隐藏外层作用域的函数声明:
void function(const char* str) { cout << "fuction(const char* str)" << endl; } void function(double x) { cout << "function(double x)" << endl; } int main() { void function(double x); function(2.1); //这里能运行,成功打印function(double x) //增加下面这条后过不了编译 function("hello world");//这里报const char*与double类型不符合,编译错误 return 0; }
虽然他们的定义在同一作用域,但是double 类型的function函数声明隐藏了外部的声明,所以编译器找不到 形参 const char* 类型的function函数,不构成重载。
由以上两点可以知道函数的声明要在同一作用域才能构成重载,但是函数可以有多个声明,大家要谨慎使用。
重载函数匹配:
重载函数写完了,接下来就是如何调用他们了,这里面也是有大学问的。
void add(double x, double y = 2.1) { cout << "add(double x, double y = 2.1)" << endl; } void add(int x) { cout << "add(int x)" << endl; } void add(double x) { cout << "add(double x)" << endl; } void add(double x,int y) { cout << "add(double x,int y)" << endl; } int main() { add(1);//打印 add(int x) add(3.3, 3);//打印 add(double x,int y) add(3.3, 3.3); //打印 add(double x, double y = 2.1) return 0; }
从上面我们可以看出调用函数的时候存在选择匹配的现象,传入一个 int 实参,这时 add(int x)是肯定可以匹配的,但是add(double x)通过类型转换也是可以匹配的。这里我们就要选择最优的匹配,匹配优先级按数字如下(这里就直接照搬资料,就像运算符优先级,太多了记不住(我就是),要用的时候来翻一下就好了):
1,
- 实参类型和形参类型相同。
- 实参从数组类型或者函数类型转换成对应的指针类型
- 向实参添加顶层const或者从实参中删除顶层const
2,通过const 转换实现的匹配
3,通过类型提升实现的匹配
4,通过算数类型转换的匹配
5,通过类类型转换实现的匹配
(类型提升和算数转换这里需要注意,比如 char 和 int 的重载, 传入 short 实参 会直接提升到int (整形提升)从而匹配 int 的那个函数,只有传入char 才匹配char函数),而且所有的算数转换的级别都一样,比如double 传 给 long 和 int 的重载,两个匹配优先级都一样的,程序会报错。
按照上面的规则来计算,如果一个函数所有参数都优于其他的函数或者相对高于(一个参数高于其他函数的参数)其他函数,当然也有势均力敌的情况,这时编译器会报有多个函数匹配的错误,如:
void add(double x,int y) { cout << "add(double x,int y)" << endl; } void add(int x, double y) { cout << "add(int x, double y)" << endl; } int main() { add(1, 2);//报错 return 0; }
add(1,1) 第一个参数更好匹配第二个函数,第二个参数又更好匹配第一个函数,这时候两个都是一胜一负,编译器分不出来就报错了(多个函数匹配)。
写了好久好久,给个赞吧大伙,下个博客写运算符重载,有兴趣可以来看。