4. 缺省参数
4.1 缺省参数概念
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该 形参的缺省值,否则使用指定的实参。
void Func(int a = 0) { cout<<a<<endl; } int main() { Func(); // 没有传参时,使用参数的默认值 Func(10); // 传参时,使用指定的实参 return 0; }
注: 其中void Func(int a = 0)中的a=0就是缺省函数
4.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; }
(2) 半缺省参数
#include<iostream> using namespace std; void Func(int a, int b = 10, int c = 20) { cout<<"a = "<<a<<endl; cout<<"b = "<<b<<endl; cout<<"c = "<<c<<endl; } int main(){ Func(1); return 0; }
注意:
1.半缺省参数必须从右往左依次来给出,不能间隔着给
2. 缺省参数不能在函数声明和定义中同时出现
3. 缺省值必须是常量或者全局变量
4. C语言不支持(编译器不支持)
//a.h void Func(int a = 10); // a.cpp void Func(int a = 20) {}
// 注意:如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那 个缺省值。
5. 函数重载
自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。 比如:以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前 者是“谁也赢不了!”,后者是“谁也赢不了!”
5.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 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; }
5.2 C++支持函数重载的原理--名字修饰(name Mangling)
为什么C++支持函数重载,而C语言不支持函数重载呢?
因为在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
1. 实际项目通常是由多个头文件和多个源文件构成,而通过C语言阶段学习的编译链接,我们可以知道, 【当前a.cpp中调用了b.cpp中定义的Add函数时】,编译后链接前,a.o的目标文件中没有Add的函数地 址,因为Add是在b.cpp中定义的,所以Add的地址在b.o中。那么怎么办呢?
2. 所以链接阶段就是专门处理这种问题,链接器看到a.o调用Add,但是没有Add的地址,就会到b.o的符 号表中找Add的地址,然后链接到一起。
3. 那么链接时,面对Add函数,链接接器会使用哪个名字去找呢?这里每个编译器都有自己的函数名修饰 规则。
4. 通过下面我们可以看出gcc的函数修饰后名字不变。而g++的函数修饰后变成【_Z+函数长度+函数名+类 型首字母】。
5..通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区 分,只要参数不同,修饰出来的名字就不一样,就支持了重载。
6. 如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分。
(1) 采用C语言编译器编译后结果
结论:在linux下,采用gcc编译完成后,函数名字的修饰没有发生改变。
(2) 采用C++编译器编译后结果
结论:在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息 添加到修改后的名字中。
5.3 extern “C”
由于C和C++编译器对函数名字修饰规则的不同,在有些场景下可能就会出问题,比如:
1. C++中调用C语言实现的静态库或者动态库,反之亦然
2. 多人协同开发时,有些人擅长用C语言,有些人擅长用C++
在这种混合模式下开发,由于C和C++编译器对函数名字修饰规则不同,可能就会导致链接失败,在该种场景 下,就需要使用extern "C"。在函数前加extern "C",意思是告诉编译器,将该函数按照C语言规则来编译。 下面演示一个在C++工程中使用C语言静态库的例子:
// 创建一个C语言的静态库 //calc.h/// #pragma once /* * 注意: * 在实现该库时,并不知道将来使用该静态库的工程是C语言工程还是C++工程 * 为了能在C/C++工程中都能使用,函数声明时需加上extern "C" * * __cplusplus:是C++编译器中定义的宏,即用该宏来检测是C工程还是C++工程 * #ifdef __cplusplus extern "C" { #endif // 要导出函数的声明 #ifdef __cplusplus } #endif
作用:如果是C++工程,编译器已经定义_cplusplus宏,编译时该宏是可以被识别的,被声明的函数就被
extern "C"修饰了,
此时C++编译就知道,静态库中的函数是按照C的方式编译的,这样在链接时就会按照C的方式找函 数名字
如果是C工程,编译器未定义_cplusplus宏,编译时该宏无法被是被,则条件编译就无效,函数就 不会被extern "C"修饰
*/
#ifdef __cplusplus extern "C" { #endif int Add(int left, int right); int Sub(int left, int right); #ifdef __cplusplus } #endif //calc.c/// #include "calc.h" int Add(int left, int right) { return left + right; } int Sub(int left, int right) { return left - right; }
创建一个c++工程,使用上面程序编程工程的静态库
using namespace std; #include "./../Debug/calc.h" #pragma comment(lib, "./../Debug/CalcLib.lib") int main() { int ret = Add(10, 20); cout << ret << endl; ret = Sub(30, 20); cout << ret << endl; return 0; }
如果在实现静态库时,函数没有使用extern "C"修饰:就会报无法找到xxx函数的链接错误。
1>TestCalc.cpp 1>TestCalc.obj : error LNK2019: 无法解析的外部符号 "int __cdecl Add(int,int)" (? Add@@YAHHH@Z),函数 _main 中引用了该符号 1>TestCalc.obj : error LNK2019: 无法解析的外部符号 "int __cdecl Sub(int,int)" (? Sub@@YAHHH@Z),函数 _main 中引用了该符号 1>D:\WorkStations\Gitee\cppLesson\TestCalc\Debug\TestCalc.exe : fatal error LNK1120: 2
个无法解析的外部命令
函数重载面试题浅析
函数重载的规则:
1.函数名称必须相同。
2.参数列表必须不同(个数不同或类型不同或参数排列顺序不同)。
3.函数的返回类型可以相同也可以不相同。
4.仅仅返回类型不同不足以成为函数的重载。
多态:用同一个东西表示不同的形态;
多态分为:
静态多态(编译时的多态);
动态多态(运行时的多态);
函数重载是一种静态多态;
1、C语言中为什么不能支持函数重载?
C语言只是在函数名前添加了下划线,因此当工程中存在相同函数名的函数,就会产生冲突。
2、C++中函数重载底层是怎么处理的?
编译器在底层实际使用的不是函数的名字,而是被重新修饰过的一个较复杂的名字,被重新修饰后的名字包含了:函数的名字以及参数类型。这就是为什么函数重载中几个同名函数要求其参数列表不同的原因。只要参数列表不同,编译器在编译时通过对函数名字进行重新修饰,将参数类型包含在最终的名字中,就可保证名字在底层的全局唯一性。
3、C++中能否将一个函数按照C的风格来进行编译?
有时候C++工程中可能需要将某些函数按照C的风格来进行编译,在函数前加 extern “C” ,意思是告诉编译器,将该函数按照C语言的规则编译。
注意:
重载函数可以出现默认参数,但要注意二义性问题:
void foo(); void foo(int a=0);
重载函数中编译时根据参数表进行选择
构造函数重载将会给初始化带来多种方式