C++ 函数进阶

目录

1. 函数的默认参数

2. 函数的占位参数

3. 函数重载

3.1 函数重载概述

3.2 函数重载注意事项

3.2.1 三种传递方式

使用值传递时 :

使用地址传递和引用传递时:

3.2.2 变量传递 与 直接数值传递

3.2.2.1 变量传递

3.2.2.2 直接数值传递

3.2.3 重载优先级

3.2.4 实参初始化

3.2.5 函数重载与默认参数


学习本篇前, 请确保你已经学习过 C++ 函数的基础内容.

C++ 函数_AusrEnder的博客-CSDN博客

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.

彻底搞懂函数重载匹配 - 知乎 (zhihu.com)

传入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 重载优先级

本节较为复杂, 需要一些资料辅助理解

调用函数时, 若有多个函数的参数列表符合, 按以下优先级进行执行.不在其中的为非法重载.

彻底搞懂函数重载匹配 - 知乎 (zhihu.com)

类型转换 和 类型提升:

【C语言】整型提升和算术转换_慕雪华年的博客-CSDN博客

算术转换,以及与整形提升的区别_类型提升与算术类型转换的区别_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) 这样调用时, 却会报错.

因为这两个函数都只需要一个参数就能调用, 虽然重载合法, 但在调用时仍有歧义.

在编程时请尽量避免这种情况.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值