目录
学习本篇前, 请确保你已经学习过 C++ 函数的基础内容.
1. 函数的默认参数
定义函数的时候, 要写形参列表.
形参 在 形参列表 中就可以初始化, 这一过程被成为被 函数的默认参数.
默认参数在函数的 声明和定义中, 都可以写.但是如果已经在声明中写过, 定义中就不能再写.
同时, 当某个参数初始化, 它参数列表中右侧的其他参数也必须初始化.
如果一个函数具有默认参数, 在调用时可以相应地少给参数.
在不超过最大参数的前提下, 也可以多给, 传递参数会从第一个参数开始从左向右赋值.
当传递参数与默认参数冲突时, 优先使用传递参数.
比如第一参数无初始化, 第二/第三参数有初始化, 给函数传递两个参数, 则第一个参数为传递值,
第二参数会被传递值覆盖, 第三参数仍为初始化值.
#include <iostream>
using namespace std;
//若参数列表中某个参数有默认值, 则右侧的参数都必须有默认值
int function(int a, int b = 10, int c = 20)
{
return a + b + c;
}
//函数的声明中也可以有默认参数, 但这样的话, 定义时就不能再写默认参数了
//int function1(int a = 10, int b = 20);
//int function1(int a = 10, int b = 20) //报错了, 声明已经写过默认参数, 定义不能再写, 避免冲突
//{
// return 0;
//}
int main()
{
int a = function(1); //函数中具有两个默认参数, 因此可以只给一个参数
//这也是默认参数右侧也必须是默认参数的原因, 否则传递起来有歧义
cout << a << endl; //输出31
a = function(1, 2); //若传递的参数多余未初始化的参数, 则在列表中从左向右覆盖默认值 (即优先用传递值)
cout << a << endl; //输出23
//a = function1();
//cout << a << endl;
return 0;
}
2. 函数的占位参数
在写参数列表的时候, 可以放一些占位参数, 暂时不进行命名和初始化.
但也可以只初始化, 不命名. 前提是它右侧的参数都经过初始化.
但是在调用该函数时, 必须传递所有未被初始化的参数.
//占位参数, 可以暂时不进行命名和初始化, 但是调用时仍需传递足够的参数
//注意最后一个参数没有命名却可以初始化, 前提是它在参数列表末尾, 或者它右侧的参数都有初始化
void function2(int a, int , int = 10)
{
}
int main()
{
function2(1, 1); //调用时必须传递足够的参数,包括占位参数, 但不包括初始化过的参数
function2(1, 1, 1); //传递的参数会覆盖初始化值
return 0 ;
}
3. 函数重载
3.1 函数重载概述
C++中, 函数名可以相同, 提高复用性. 当然, 这是有条件的.
要求:
1. 同一个作用域下 (比如都在全局区)
2. 函数名相同
3. 函数的参数 类型不同 或 个数不同 或 顺序不同
注意: 函数的返回值类型不能作为函数重载的条件. 意思是, 如果两个函数名称相同, 参数列表完全相同, 那么即便返回值类型不同, 也不会被编译器认为是函数重载, 而被认为是重名然后报错.
(也就是说程序是根据 函数的参数列表来区分函数的).
3.2 函数重载注意事项
3.2.1 三种传递方式
使用值传递时 :
常量和变量也属于相同类型, 如 const int 和 int 就是同一类型, 根据这种方式来区分两个函数是非法的, 编译器不允许你这样重载. 以下为错误示范.
#include <iostream>
using namespace std;
void function(int a)
{
cout << "function(int)调用" << endl;
}
void function(const int a)
{
cout << "function(const int)调用" << endl;
}
int main()
{
int a = 10 ;
function(a);
return 0;
}
还没执行到传递变量 a 就报错了, 编译器认为这是两个重复的函数.
由于函数的定义失效, function(a); 也是非法的, 因为编译器不知道 function() 是什么.
另外, 使用值传递时, 实参必须经过初始化, 否则报错.
使用地址传递和引用传递时:
但在使用地址传递和引用传递时, 编译器能识别出 const int 和 int 的区别, 重载合法.
这里以引用传递为例.
#include <iostream>
using namespace std;
void function(int &a)
{
cout << "function(int)调用" << endl;
}
void function(const int &a)
{
cout << "function(const int)调用" << endl;
}
int main()
{
int a = 10 ;
function(a);
return 0;
}
输出结果为 int 调用.
但是要特别注意的是, 编译器能识别出区别, 不代表 const int 和 int 是两种不同的类型.
如果你将 int 调用的函数注释掉, 输出结果就会变为 const int 调用.
这意味着, 将已经初始化过的 int 实参 引用为 int 或者 const int 形参都是合法的, 只是程序调用优先级不同.
int 类型的实参, 程序优先调用 int ;
const int 类型的实参则不允许传递给 int.
还要注意的是, 传递类型也不能作为重载依据.
无论实参 a 初始化与否, 如果你将上面任意一个 & 去掉, 重载仍然是非法的.
因为编译器并不会因为你使用了不同的传递方式, 就认为你的定义有区别, 它只看参数列表.
3.2.2 变量传递 与 直接数值传递
在传递参数的时候, 可以将实参传入, 也可以直接将数值传入.
int a =0;
function(a);
function(0);
这两者看似是等价的, 但在函数重载的时候就会体现出区别.
编译器认为, 任何直接的数值都是一个常量.
在3.2.1 的例子中, 如果 function(10);
就会输出 const int 调用.
(当然, int & a = 10 这种语法本身也是无法通过编译的.)
3.2.2.1 变量传递
下面将加入 单精度浮点型 来更深入地研究.
#include <iostream>
using namespace std;
void function(int &a)
{
cout << "function(int)调用" << endl;
cout << a << endl;
}
void function(const int &a)
{
cout << "function(const int)调用" << endl;
cout << a << endl;
}
void function(float & a)
{
cout << "function(float)调用" << endl;
cout << a << endl;
}
void function(const float &a)
{
cout << "function(cosnt float)调用" << endl;
cout << a << endl;
}
int main()
{
const int a = 10.1f;
function(10.1f);
const int& b = a;
function(b);
return 0;
}
测试结果:
在 a 没有初始化的情况下 :
优先调用最近似类型, 然后是该类型的 const 修饰类型, 其余重载非法.
且函数输出数据为乱码.
特别地, 如果 a 的类型 有 const 修饰, 则未初始化的 a 本身就非法, 四种重载均无法生效.
在 a 有初始化的情况下 , 无论右值是什么, 始终遵循 :
优先调用最近似类型, 然后是该类型的 const 修饰类型, 然后是 int-float 转换后的 const 类型
其余非法. 若 单精度 转向 整型, 则小数部分丢失.
如 int a = 10; 优先调用 int , 然后 const int , 然后 cosnt float , 其余非法.
如 float a = 10; 优先调用 float , 然后 const float , 然后 const int , 其余非法.
特别地, 如果 a 本身为 const 修饰类型, 则重载参数必须是 const 修饰类型.
即允许 变量传递给常量, 不允许 常量传递给变量.
如 const int a = 10; 优先调用 const int , 然后 cosnt float , 其余非法.
如 const float a = 10; 优先调用 const float , 然后 const int , 其余非法.
总结:
规则1 : 有初始化时允许 算术类型转换 ; 未初始化时不允许, 且取得参数值为乱码;
规则2 : 优先调用最近似类型, 然后 未转换算术类型 大于 转换算术类型;
规则3 : 允许 变量传递给常量, 不允许 常量传递给变量;
规则4 : 不初始化的常量非法;
规则5 : 高精度转向低精度时, 精度丢失.
这是本人结合实践的理解, 可能有误, 具体机制请看 3.2.3
3.2.2.2 直接数值传递
还是刚才那个案例, 但是不再传递实参 a ,而是直接传递 数值常量.
然后再额外加上 调用 double 和 调用 const double 的函数.
如 function(10);
测试结果:
传入 10 , 优先调用 const int , 然后 const float;
传入 10f, 10f本身非法;
传入 10.1, 优先调用 const double, 注释掉后 const int 和 const float 同时符合,引起歧义, 然后非法.
传入 10.1f, 优先 const float ,然后 const double ,然后 const int.
传入 (double)10.1,优先调用 const double, 注释掉后 const int 和 const float 同时符合,
引起歧义, 然后非法.
总结: 函数重载优先级排序
1. 精确匹配 (实参类型与形参类型完全相同)
2. 顶层 const修饰 (指形参多加最前方 const修饰, 但若实参本身就有顶层const, 则此优先级非法)
3. 类型提升转换 (有符号或无符号的 char / short 提升为 int 或 unsigned int ; float -> double)
注意类型提升转换是单向的, 只能提升, 不能降低.
而且, 形参必须具有顶层 const 修饰.
4. 算术类型转换 (char / short / int / float / double 之间转换, 优先级相同, 会引起歧义)
若 double 转为 float , 这属于算术类型转换, 因为 类型提升转换 必须是单向提升.
而且, 形参必须具有顶层 const 修饰.
5. 类类型提升 (暂时没学到这部分)
注意, 任何时候, 若实参本身有顶层 const 修饰, 或者实参为直接数值, 则形参必须有顶层 const.
传入10 相当于传入 const int
传入10.1 相当于传入 (double)10.1, 也就相当于 const double
优先调用最近似类型 const double ,然后const double 可以算术类型转换成
const int 和 const float. 注释掉 const double 调用后, 两个相同的优先级引起了歧义.
传入10.1f 相当于传入 const float , 优先调用最近似类型 cosnt float , 然后 const float
可以通过类型提升变为 const double, 也可以通过算术类型转换变成 const int, 但是类型提升优先于算术类型转换, 故此处不存在歧义.
但若加入 const short 调用, 则注释掉 cosnt float 和 const double 后 ,
const short 和 const int又会引起歧义. 因为 const float 转换成它们都是经过算术类型转换.
使用直接数值传递时, 还是先取最近似类型, 然后选内存大的, 若内存一样则优先级相同.
如果 最优先级 存在两个或以上的调用函数, 则引起歧义.
3.2.3 重载优先级
本节较为复杂, 需要一些资料辅助理解
调用函数时, 若有多个函数的参数列表符合, 按以下优先级进行执行.不在其中的为非法重载.
类型转换 和 类型提升:
算术转换,以及与整形提升的区别_类型提升与算术类型转换的区别_Sandm *的博客-CSDN博客
C++ 中的类型转换 · You Know Nothing (xutree.github.io)
[C]类型提升详解_c类型提升_~Ran~的博客-CSDN博客 (这篇有些字打错, 需要自己推敲一下.)
二进制运算:
DJ2-2 运算方法(二进制)_原码加减法运算规则_狂放不羁霸的博客-CSDN博客
int型溢出问题_int溢出_燕南路GISer的博客-CSDN博客
3.2.4 实参初始化
以下代码是错误示例.
#include <iostream>
using namespace std;
void function(int a)
{
cout << "function(int)调用" << endl;
}
int main()
{
int a ;
function(a);
return 0;
}
请忽略注释部分.
这样的代码是无法通过编译的, 因为定义变量 a 时, 不对 a 进行初始化, 是合法的.
但调用未初始化的变量 a 作为实参, 这样的操作就是非法的.
那如果是常量呢?
那就更错了, cosnt int 在定义的时候本来就必须初始化, 不初始化的常量本身就是非法的.
3.2.5 函数重载与默认参数
函数重载 允许根据 参数个数不同 来进行区分.
默认参数 允许 少传递参数.
则二者同时使用时, 容易引起歧义.
//函数重载与默认参数
//以下两个函数能够一起通过编译, 但若 function3(1) 这样调用, 就是非法的.
//因为这样引起了歧义, 无论是第一个函数还是第二个函数, 均可以只给一个参数, 若只传一个参数, 编译器无法区分.
void function3(int a, int b = 10)
{
}
void function3(int a)
{
}
这两个函数是可以一起通过编译的.
但在 function3(1) ; 或者 function3(a) 这样调用时, 却会报错.
因为这两个函数都只需要一个参数就能调用, 虽然重载合法, 但在调用时仍有歧义.
在编程时请尽量避免这种情况.