1.函数缺省参数
1.1 缺省参数概念
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实
参则采用该形参的缺省值,否则使用指定的实参,有点备胎的意思。
void Func(int a = 0) { cout << a << endl; } int main() { Func(); // 没有传参时,使用参数的默认值 ,打印0 Func(10); // 传参时,使用指定的实参 , 打印10 return 0; }
1.2 缺省参数分类
1.全缺省参数
//全缺省 void Func(int a = 10, int b = 20,int c =30) { cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl << endl; } int main() { Func(); //显示传参,必须从左往右连续显示传参, Func(1); Func(1, 2); Func(1, 2, 3); return 0; }
2.半缺省参数
半缺省,缺省部分,必须从右往左,给缺省值 void Func(int a, int b = 20, int c = 30) { cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl << endl; } int main() { //没有缺省的必须传 Func(1); Func(1, 2); Func(1, 2, 3); }
注意:
- 半缺省参数必须从右往左依次来给出,不能间隔着给
- 缺省参数不能在函数声明和定义中同时出现,C++规定声明给,定义不给。错误示例:
//a.h void Func(int a = 10); // a.cpp void Func(int a = 20) {} // 注意:如果声明与定义位置同时出现,恰巧两个位置提供的值不同, //那编译器就无法确定到底该用那个缺省值
- 缺省值必须是常量或者全局变量
- C语言不支持(编译器不支持)
函数缺省参数的应用:
在使用某种数据结构例如栈在申请空间时,如果我们知道需要使用多少空间时,可以直接把空间大小传入,不用多次realloc调整空间,示例:
------------------------Stack.h------------ #include<iostream.h> #include<stdlib.h> namespace s { typedef struct Stack { int* a; int top; int capacity; }Stack; //不允许声明和定义同时给缺省参数 C++规定 声明给 定义不给 void StackInit(Stack* ps, int n = 4);//缺省值为4 void StackPush(Stack* ps, int x); } -----------------------Stack.cpp------------- #include"Stack.h" void s::StackInit(Stack* ps, int n) { ps->a = (int*)malloc(sizeof(int) * n); if (ps->a == NULL) { perror("malloc fail"); return; } ps->top = 0; ps->capacity = n; } void s::StackPush(Stack* ps, int x) { if (ps->top == ps->capacity) { int* ptr = (int*)realloc(ps->a, sizeof(int) * ps->capacity * 2); if (ptr == NULL) { perror("realloc fail"); return; } ps->capacity *= 2; } ps->a[ps->top] = x; ps->top++; } ----------------------test.cpp-------------- #include"Stack.h" using namespace s; int main() { //知道要插入多少个 Stack st1; StackInit(&st1, 10); for (int i = 0; i < 10; i++) { StackPush(&st1,i); } Stack st2; StackInit(&st2, 100); for (int i = 0; i < 100; i++) { StackPush(&st2,i); } //不知道要插入多少个时 Stack st3; StackInit(&st3); return 0; }
在头文件中定义函数时:
在头文件中定义函数,当这个头文件被多个.cpp文件包含时,会发生链接错误,重复定义,可以在函数前加static改变链接属性,由外部链接改为内部链接,只能在当前文件使用。
2.函数重载
自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。
比如:以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前者是"谁也赢不了! ",后者是"谁也赢不了!”
2.1 函数重载概念
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或类型或类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。
#include<iostream> using namespace std; //1.参数类型不同 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 func() { cout << "func()" << endl; } void func(int x) { cout << "func = " << x << 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); //cout自动匹配类型本质也是函数重载 f(); f(10); f(10, 'a'); f('a', 10); return 0; }
注意:
- 顺序不同不是指的形参的名字顺序不同,而是参数类型顺序不同
//func1构不成函数重载,会重定义 void func1(int x, double y) { //... } void func1(int y, double x) //... } //func2构成函数重载 void func2(int x, double y) { //... } void func2(double x, int y) { //... }
- 只有返回值不同构不成函数重载
不同作用域构不成函数重载,把命名空间展开也可以构成
//不同作用域构不成函数重载,但是展开作用域后构成 namespace s1 { void func(int x) { cout << "func = " << x << endl; } } namespace s2 { void func(double x) { cout << "func = " << x << endl; } } using namespace s1; using namespace s2;
命名空间名称相同合并,也构成函数重载
//命名空间相同合并,构成函数重载 namespace s1 { void func(int x) { cout << "func = " << x << endl; } } namespace s1 { void func(double x) { cout << "func = " << x << endl; } }
函数重载遇上函数缺省参数
//构成重载 参数个数不同 //调用时发生问题,调用存在歧义 void func(int x) { cout << "void func(int x)" << endl; } void func(int x,int b = 1) { cout << "void func(int x,int b = 1) " << endl; } int main() { func(1, 2);//可以调用 //调用时发生问题,调用存在歧义,与重载没有关系 //func(1); return 0; }
调用时发生问题,调用存在歧义,但会发生重载。
2.2 C++支持函数重载的原理
为什么C++支持函数重载,而C语言不支持函数重载呢?
在C/C++中,一个程序要运行起来,需要经历翻译:编译、链接。而编译又有三个阶段:预处理,编译,汇编。
程序翻译阶段:
程序编译阶段:
1. 实际项目通常是由多个头文件和多个源文件构成,编译是分隔开的。【当前a.cpp中调用了b.cpp中定义的Add函数时】,编译后链接前,a.o的目标文件中没有Add的函数地址,因为Add是在b.cpp中定义的,所以Add的地址在b.o中。那么怎么办呢?
2. 所以链接阶段就是专门处理这种问题,链接器看到a.o调用Add,但是没有Add的地址,就会到b.o的符号表中找Add的地址,然后链接到一起,即合并符号表,符号表上有每个函数名以及对应的地址。
3. 那么链接时,面对Add函数,链接接器会使用哪个名字去找呢?在C++中,编译器都有自己的函数名修饰规则,让重载的函数区别开来,是用修饰后的名字放到符号表。而C语言是直接使用函数名做符号表的名字,没有进行修饰,所以重载会冲突。
4. 由于Windows下vs的修饰规则过于复杂,而Linux下g++的修饰规则简单易懂,下面我们使用了g++演示这个修饰后的名字。
5. 通过下面我们可以看出gcc的函数修饰后名字不变。而g++的函数修饰后变成【_Z+函数长度+函数名+类型首字母
示例:
void func(int x, double y) { cout << "void func(int x, double y)" << endl; } void func(double x, int y) { cout << "void func(double x, int y)" << endl; } int main() { func(1, 1.1); func(1.1, 1); return 0; }
调用函数转到汇编:
可以发现是使用了 call 指令跳转到 func 函数第一行的地址。函数的地址本质可以认为是函数第一句指令的地址,只有声明没有定义会找不到,生不成地址。下面是只有声明没有定义的报错:
可以发现第一个函数名已经被修饰为:(?func@@YAXHN@Z),int被修饰成H,double被修饰成N。这里重载参数顺序不同,所以被修饰后的两个函数名也不同。
我们来看一Linux下g++的修饰规则:
一个是<_Z4funcid>一个是<_Z4funcdi> ,函数名修饰规则是:【_Z + 函数名字符个数 + 函数名 + 类型首字母】int 是 i ,double 是 d,指针类型前面加p,int * 是 pi,可以发现:
函数名修饰规则本质是把参数类型带进名字了,类型不同,个数不同,顺序不同,修饰的名称就不同。所以返回值不同,不能构成函数重载,因为函数名修饰规则里没有返回值。
结论:在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中。
这里就有一个问题了,如果把返回值带入函数名修饰规则,能不能构成重载?
答案是不能,你会发现在调用时,就不知道调用谁,示例:
void func(int x, double y) { //... } int func(int x, double y) { //... return 1; } //不会构成函数重载 int main() { func(1, 1.1);//不知道到底调用哪一个 func(1, 1.1); return 0; }
结论:
- 通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。
- 如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分
本篇结束。