C++函数新特性

C++函数新特性

C++的函数相比于C语言又增加了一些新的功能,比如函数的缺省参数和函数的重载,本篇将着重介绍这两个特性。

缺省参数特性:

#include<iostream>
using namespace std;
void func(int a = 10) {//全缺省
	cout << a << endl;
}
int main() {
	func();
}

这种写法是C++相比于C语言特有的写法,在C语言里函数的形参和实参必须要一一对应,才可以正确编译通过。但是我们的C++祖师爷认为这是不合理的,所以在C++里对函数做了缺省参数的修改。

说明:

C++里,在函数的声明阶段对函数的形参设置参数定义,即可在调用时忽略相应的参数,如上边的代码一样。

需要注意点的点:

a、 C++虽然支持缺省参数的写法,但是并不是可以随心所欲的去写的,我们要保证自己的代码没有二义性,比如有的写法,缺省参数的参数和要传入的参数跳跃着写,这样是不可以的。

eg:

#include<iostream>
using namespace std;
//这样写编译器会直接报错的,因为我们在调用时候不能这么写func(,1,)或者func(1)这样都不会通过编译
void func(int b=5,int c,int a = 10) {
	cout << a << endl;
}

b: C++函数声明的半缺省参数必须从右往左依次来给出,不能间隔着给,无他,都是为了保证没有二义性

c: 缺省参数不能在函数声明和定义中同时出现

在声明和定义分离的情况下可能存在的一些问题

这里主要是针对上边提到的第三条需要注意点事情的一些特别说明,在大型项目中,声明和变量分离是常用的技巧,但是缺省参数在这里是有一些坑还是演示出来印象会比较深刻,下边给出几段示例代码:

//错误示例!!!
//test.cpp
#include"head.h"
using namespace std;
int main() {
	func();
	return 0;
}
//=====================================

//head.h
#include"iostream"
using namespace std;
void func(int x=10);
//=====================================

//head.cpp
#include"head.h"
void func(int x=10) {
  cout<<"func(int x)"<<endl;
}

这样的代码运行会报编译错误:

在这里插入图片描述

这里的原因就是我们在head.h里声明了缺省参数函数的参数,然后又在head.cpp的定义里写了参数定义,这是不合规的写法,所以不会通过编译。可能会有人有疑惑,那我写在定义里不写在声明里行吗?回答是不行的,至于为什么不行,这是我们的祖师爷决定的哈哈


函数重载特性:

和缺省参数一样,这部分是祖师爷对C语言的一些功能的补充,在同名函数不同参数的使用上祖师爷认为这样是合理的,C++可以通过参数的不同来区分我们要调用的是哪个函数

说明:

函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。

eg:

#include<iostream>
using namespace std;
int Add(int left, int right)
{
	cout << "int Add(int left, int right)" << endl;
	return left + right;
}
double Add(double left, double right)
{
	cout << "double Add(double left, double right)" << endl;
	return left + right;
}
// 2、参数个数不同
void f()
{
	cout << "f()" << endl;
}
void f(int a)
{
	cout << "f(int a)" << endl;
}
// 3、参数类型顺序不同
void f(int a, char b)
{
	cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
	cout << "f(char b, int a)" << endl;
}
int main()
{
	Add(10, 20);
	Add(10.1, 20.2);
	f();
	f(10);
	f(10, 'a');
	f('a', 10);
	return 0;
}

运行结果:

在这里插入图片描述

重载的原理是什么呢?

这个部分理解会有一些难度,但是还是需要尝试着去理解一下,因为还是比较重要的这部分知识

我们可以通过一个简单程序的反汇编来分析

void func() {
	cout << "func()" << endl;
}
void func(int x ) {
	cout << "void func(int x) " << endl;
}
int main(){
	func();
	func(1);
	return 0;
}

在这里插入图片描述

以上是函数调用的汇编过程,其实func(int x)的调用也是如此的,但是在vs的汇编里他省略了一些细节,就是如何区分同名函数的重载,那就是“函数名修饰规则”,这个规则各个编译器的实现并不相同,vs的实现规则比较复杂,不利于理解。我们用Linux下的g++编译器来生成目标文件,并查看:

//head.cpp file
#include"head.h"
void func(int x=10) {
  cout<<"func(int x)"<<endl;
}
void func(char c ,int x) {
  cout<<"func (char c,int x)"<<endl;
}

产生的目标文件:

head.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <_Z4funci>:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	48 83 ec 10          	sub    $0x10,%rsp
   8:	89 7d fc             	mov    %edi,-0x4(%rbp)
   b:	be 00 00 00 00       	mov    $0x0,%esi
  10:	bf 00 00 00 00       	mov    $0x0,%edi
  15:	e8 00 00 00 00       	callq  1a <_Z4funci+0x1a>
  1a:	be 00 00 00 00       	mov    $0x0,%esi
  1f:	48 89 c7             	mov    %rax,%rdi
  22:	e8 00 00 00 00       	callq  27 <_Z4funci+0x27>
  27:	c9                   	leaveq 
  28:	c3                   	retq   

0000000000000029 <_Z4funcci>:
  29:	55                   	push   %rbp
  2a:	48 89 e5             	mov    %rsp,%rbp
  2d:	48 83 ec 10          	sub    $0x10,%rsp
  31:	89 f8                	mov    %edi,%eax
  33:	89 75 f8             	mov    %esi,-0x8(%rbp)
  36:	88 45 fc             	mov    %al,-0x4(%rbp)
  39:	be 00 00 00 00       	mov    $0x0,%esi
  3e:	bf 00 00 00 00       	mov    $0x0,%edi
  43:	e8 00 00 00 00       	callq  48 <_Z4funcci+0x1f>
  48:	be 00 00 00 00       	mov    $0x0,%esi
  4d:	48 89 c7             	mov    %rax,%rdi
  50:	e8 00 00 00 00       	callq  55 <_Z4funcci+0x2c>
  55:	c9                   	leaveq 
  56:	c3                   	retq   

0000000000000057 <_Z41__static_initialization_and_destruction_0ii>:
  57:	55                   	push   %rbp
  58:	48 89 e5             	mov    %rsp,%rbp
  5b:	48 83 ec 10          	sub    $0x10,%rsp
  5f:	89 7d fc             	mov    %edi,-0x4(%rbp)
  62:	89 75 f8             	mov    %esi,-0x8(%rbp)
  65:	83 7d fc 01          	cmpl   $0x1,-0x4(%rbp)
  69:	75 27                	jne    92 <_Z41__static_initialization_and_destruction_0ii+0x3b>
  6b:	81 7d f8 ff ff 00 00 	cmpl   $0xffff,-0x8(%rbp)
  72:	75 1e                	jne    92 <_Z41__static_initialization_and_destruction_0ii+0x3b>
  74:	bf 00 00 00 00       	mov    $0x0,%edi
  79:	e8 00 00 00 00       	callq  7e <_Z41__static_initialization_and_destruction_0ii+0x27>
  7e:	ba 00 00 00 00       	mov    $0x0,%edx
  83:	be 00 00 00 00       	mov    $0x0,%esi
  88:	bf 00 00 00 00       	mov    $0x0,%edi
  8d:	e8 00 00 00 00       	callq  92 <_Z41__static_initialization_and_destruction_0ii+0x3b>
  92:	c9                   	leaveq 
  93:	c3                   	retq   

0000000000000094 <_GLOBAL__sub_I__Z4funci>:
  94:	55                   	push   %rbp
  95:	48 89 e5             	mov    %rsp,%rbp
  98:	be ff ff 00 00       	mov    $0xffff,%esi
  9d:	bf 01 00 00 00       	mov    $0x1,%edi
  a2:	e8 b0 ff ff ff       	callq  57 <_Z41__static_initialization_and_destruction_0ii>
  a7:	5d                   	pop    %rbp
  a8:	c3                   	retq   

如上图的:_Z4funci这个便是他的命名规则

g++的函数修饰规则:

开头_Z接数字表示函数名的长度 故 _Z4 则表示函数名长度4

随后就是函数名func

最后就是参数类型char 就是c ,int就是i

我们的计算机就是这样识别匹配同名函数重载的,vs也有相应的规则,有兴趣的可以自己去查一查文档。

总结:

函数在定义时候编译器会有名称修饰规则来区分同名的重载函数,在调用时候会通过调用的名称来进行相应函数的匹配。并且调用的过程是比较复杂的,先找到代码段的函数位置,再跳转到为这个函数开辟的栈帧开始执行。

END

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

栗悟饭&龟波气功

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值