前言:
C++入门我们主要是补充C语言的不足,为后续类和对象学习打基础。在前面我们学了命名空间、输入输出,今天我们继续学习。
上期链接:
C++入门——关键字|命名空间|输入&输出_wangjiushun的博客-CSDN博客
一、缺省参数(也叫默认参数)
1、缺省参数概念
缺省参数是声明或定义函数时为函数参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
代码演示:
#include<iostream> using namespace std; //在缺省参数是在声明或定义函数时为函数的参数指定一个缺省值。 //如Func函数,在定义时为形参a提供了默认实参,默认实参作为形参的初始值出现在形参列表中。 void Func(int a = 0) { cout << a << endl; } int main() { Func();//①没有传参时,使用参数的默认值 Func(10);//②传参时,使用指定的实参 return 0; }
说明:
1、函数参数指定缺省值时,可以不传参(使用参数缺省值),也可以传参(使用指定的实参)。
2、函数参数没有指定缺省值时,必须传参。
2、缺省参数分类
(1)全缺省参数 —— 所有形参都提供缺省值
代码实例:
#include<iostream> using namespace std; void Func(int a = 10, int b = 20, int c = 30) { cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; } int main() { Func(); Func(1); Func(1, 2); Func(1, 2, 3); return 0; }
我们参数的传递是怎么传递的呢?能跳着传吗?
答案是:语法规定实参传实参的顺序是从左向右依次传递的。注意:不能跳着传参,因为祖师爷不喜欢(即规定是这样,祖师爷最大)。
(2)半缺省参数 —— 是形参缺省一部分,不是就说缺省一半。并且规定缺省值是从右向左依次缺省的。
代码演示:
#include<iostream> using namespace std; void Func(int a, int b = 20, int c = 30) { cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; } int main() { //参数的传递规定是从左向右依次传递的,不能跳着传。 //所以标准规定缺省值是从右向左依次缺省的。 //因为Func的第一个形参a没有缺省,所以Func的第一个参数必须传 Func(1); Func(1, 2); Func(1, 2, 3); return 0; }
(3)使用场景示例
代码演示:栈的初始化
分析:
①静态的栈,给小了不够用,给大了浪费,但是效率比较高(因为不存在扩容问题)。
②当我们明确知道栈要插入几个数据时,传参;当我们不知道栈要插入几个数据时,那就不传参了。
①C语言实现:
#define DEFAULT_CAPACITY 4 void StackInit(struct Stack* pst) { pst->a = (int*)malloc(sizeof(int) * DEFAULT_CAPACITY); if (pst->a == NULL) { perror("malloc fail:"); return; } pst->top = 0; pst->capacity = DEFAULT_CAPACITY; }
说明:
1、C语言有几个形参就必须传几个实参,一一对应。(即C语言不支持缺省参数)
2、C语言使用宏替代不灵活 ——定义不同栈的时,他们的初始化都相同。
②C++实现:
#include<iostream> using namespace std; struct Stack { int* a; int top; int capacity; }; //初始化栈 void StackInit(struct Stack* pst,int defaultCapacity = 4) { pst->a = (int*)malloc(sizeof(int) * defaultCapacity); if (pst->a == NULL) { perror("malloc fail:"); return; } pst->top = 0; pst->capacity = defaultCapacity; } int main() { struct Stack st1; //插入100个数据 StackInit(&st1, 100); struct Stack st2; //不知道要插入多少数据 StackInit(&st2); return 0; }
说明:
1、缺省参数——①传参时,使用指定的实参;②没有传参时,使用参数的缺省值。
2、对比C语言,有了缺省参数就灵活了一些。
3、缺省参数的注意事项(易错点)
1、半缺省参数必须从右往左依次给出,不能间隔着给
2、缺省参数不能在函数声明和定义中同时出现
3、缺省值必须是常量或者全局变量
4、C语言不支持缺省参数
讲解:
(1)缺省参数不能在函数声明和定义中同时出现
答案是:语法规定只能一个给,不能两个同时给(就像你小时候,学校让你交资料费时,你只有向你妈或你爸要,不能两边都要,否则就要被打了)。
代码演示:
#include<iostream> using namespace std; void Func(int a, int b = 20, int c = 30); int main() { Func(1); Func(1, 2, 3); return 0; } void Func(int a, int b = 20, int c = 30) { cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; }
报错信息:
(2) 缺省参数不能在定义和声明同时出现,那给谁呢?
答案是:给函数声明,应该在函数声明中指定缺省值,并将该声明放在合适的头文件中。
代码演示:
Func.h文件:
#include<iostream> using namespace std; void Func(int a, int b = 20, int c = 30);
Func.cpp文件:
#include"Func.h" void Func(int a, int b , int c ) { cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; }
test.cpp文件:
1. #include"Func.h" 2. 3. int main() 4. { 5. Func(1); 6. Func(1, 2, 3); 7. return 0; 8. }
说明:
1、详解编译+链接:
程序的从源文件到执行程序,是经过翻译环境才到执行环境的,翻译环境又分为编译和链接。编译时源文件各走各的通过编译过程分别转化成目标代码,每个目标文件由链接器捆绑在一起,形成一个单一而完整的可执行程序。
(1)编译又分为预编译、编译、汇编三个部分:①预编译时,它会完成头文件的包(即:它会打开指定的头文件,并将其中的代码插入到包含该指令的源文件中,然后再进行编译);②编译时进行语法分析、词法分析、语义分析、符号汇总,将C++代码转换成汇编代码;③汇编时生成符号表,把汇编代码转换成机器指令(二进制指令)。
(2)链接(链接器):把多个目标文件和链接库进行链接:①合成段位;②符号表的合并和重定位。
2、缺省参数一般在函数声明中指定缺省值,并将该声明放在合适的头文件中。因为编译时每个源文件都是独立转换为目标文件的,如果不在函数声明中指定缺省值在函数定义中指定缺省值,那test.cpp文件在编译过程的第二个步骤,编译时语法不通过,如图:
在声明的时候,有缺省值,函数调用时没有传参,就编译时就默认使用指定的缺省值,没有缺省值,函数调用时就必须传参。
二、函数重载
自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真正的含义,即该词被重载了。
1、函数重载概念
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类型的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。
(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; } int main() { Add(10, 20); Add(10.1, 20.2); return 0; }
运行结果:
(2)参数个数不同
#include<iostream> using namespace std; void f() { cout << "f()" << endl; } void f(int a) { cout << "f(int a)" << endl; } int main() { f(); f(10); return 0; }
运行结果:
(3)参数类型顺序不同
#include<iostream> using namespace std; 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() { f(10, 'a'); f('a', 10); return 0; }
运行结果:
说明:
1、调用重载函数时有三种可能的结果:
(1)编译器找到一个与实参最佳匹配(匹配重载函数的顺序:首先寻找一个精确匹配,如果能找到,调用该函数;其次进行提升匹配,通过内部类型转换(窄类型到宽类型的转换)寻求一个匹配,如char到int、short到int等,如果能找到,调用该函数;最后通过强制类型转换寻求一个匹配,如int到double等,如果能找到,调用该函数。)的函数,并生成调用该函数的代码。
(2)找不到任何一个函数与调用的实参匹配,此时编译器发出无匹配的错误信息。
(3)有多于一个函数可以匹配,但是每一个都不是明显的最佳选择。此时也将发生错误,称为二义性调用。
2、尽管函数重载能在一定程度上减轻我们为函数起名字、记名字的负担,但是最好只重载那些确实非常相似的操作。
注意(混淆点):
1、函数重载对返回值没有要求:返回值不同,其他所有要素(函数名、形参列表)都相同,不构成重载。
2、二义性调用:可构成重载函数,但是对于重载函数的调用不明确。
讲解:
(1)二义性调用:可构成重载函数,但是对于重载函数的调用不明确。
代码演示:当函数重载使用缺省参数时,注意二义性调用
#include<iostream> using namespace std; void f() { cout << "f()" << endl; } void f(int a = 10) { cout << "f(int a)" << endl; } int main() { f();//err,对函数重载的调用不明确 f(10);//ok return 0; }
int Add(int x, int y) { return x + y; } double Add(double x, double y) { return x + y; }
Add.h文件:
int Add(int x, int y); double Add(double x, double y);
test.cpp文件:
#include"Add.h" int main() { Add(1, 2); Add(1.1, 2.2); return 0; }
说明:
1、实际项目通常是由多个头文件和多个源文件构成的,去上面两张图,我们可以知道,在预处理、编译、汇编三个阶段源文件独立完成,各走各的分别转化成目标代码,在链接阶段每个目标文件由链接器捆绑在一起,形成一个单一而完整的可执行程序。
2、我们发现在编译后链接前,test.o的目标文件中没有Add的函数地址,因为Add定义在Add.cpp中的,所以Add的地址在Add.o中。所以链接阶段就是专门处理这种问题,链接器看到test.o调用Add,但是没有Add的地址,就会到Add.o的符号表中找Add的地址,然后链接到一起。
3、为什么test.i阶段,test.i没有Add函数没有报错了,因为有Add函数的声明——声明就像承诺。链接——找到Add的定义,兑现承诺。
4、我们发现Add函数的名字在编译过程发生了改变——这里每个编译器都有自己的函数名修饰规则。
5、由于Windows下vs的修饰规则过于复杂,而Linux下g++的修饰规则简单易懂,下面我们使用g++演示了这个修饰的名字。
6、通过下面我们可以看出gcc的函数修饰后名字不变。而g++的函数修饰变成【_Z+函数长度+函数名+类型首字母】。
(1)采用C语言编译器编译后的结果:
结论:在Linux下,采用gcc编译完成后,函数名字的修饰没有发生改变。
(2)采用C++编译器后结果:
结论:在Linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中。
7、通过这里我们就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持重载。
8、如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没有办法区分。
今天我们讲解了C++的缺省函数和重载函数,后续再有一节讲解引用、内联函数、auto关键字等我们就结束C++入门了。