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也有相应的规则,有兴趣的可以自己去查一查文档。
总结:
函数在定义时候编译器会有名称修饰规则来区分同名的重载函数,在调用时候会通过调用的名称来进行相应函数的匹配。并且调用的过程是比较复杂的,先找到代码段的函数位置,再跳转到为这个函数开辟的栈帧开始执行。