本文目录
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/cbe8aa4475b60c66b8da908e676cede1.png)
缺省参数的使用,好比是备胎,在没有正宫出现的时候,备胎就开始起作用了。
1、缺省参数
1.1、缺省参数的概念
概念:缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。
让我们用代码来感受一下。
代码一:
void TestFunc1(int a = 0)
{
cout << a << endl;
}
int main()
{
TestFunc1(10); // 传参时,使用指定的实参
TestFunc1(); // 没有传参时,使用参数的默认值
}
从代码的输出结果,分析两种情况:
情况一:调用TestFunc1()传实参:此时缺省参数不起作用,实实在在传10过去。
情况二:调用TestFunc1()不传实参:此时缺省参数起作用,采用默认值0。
1.2、缺省参数的分类
首先缺省参数分为:全缺省参数和半缺省参数。
废话不多说,直接上代码。
//全缺省参数!!!
void TestFunc2(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
//半缺省参数!!!
void TestFunc3(int a, int b = 10, int c = 20)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
TestFunc2();
TestFunc2(1,2,3);
TestFunc2(1);
TestFunc2(1,2);
TestFunc3(1);//半缺省必须至少有一个
TestFunc3(1, 2);
TestFunc3(1, 2, 3);
}
输出结果:
注意:
a. 半缺省参数必须从右往左依次来给出,不能间隔着给,即必须是连续的
b. 缺省参数不能在函数声明和定义中同时出现
//a.h
void TestFunc(int a = 10);
// a.c
void TestFunc(int a = 20)
{}
// 注意:如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。
c. 缺省值必须是常量或者全局变量
d. C语言不支持(编译器不支持)
1.3、缺省参数的作用
作用:调用更灵活
比如:我们有一个栈,里面有一个数组用于存放数据,还有两个int类型的size和capacity分别表示大小和容量,我们要用这个栈存放一个班、一个年级甚至一个学校的信息。这个数组的大小是不确定的,比如一个班级有50个学生,刚开始定义数组大小为0,然后不断的malloc,最后malloc到50,这是可以接收的,但如果存放一个学校20000个学生呢?要从0不断malloc到20000吗?我们直到malloc是消耗内存的,而且操作系统要分精力来进行操作,会造成不必要的麻烦,更严重会导致死机。
那我们应该怎么解决这个问题呢?
我们在初始化函数的参数方面做文章,不仅将结构体传过去,还要再传一个常数值,用来初始化赋值。简单粗暴的代码!!
struct Stack
{
int* a;
int size;
int capacity;
};
void StackInit(struct Stack* ps, int InitCapacity = 4)
{
ps->a = (int*)malloc(sizeof(int)* InitCapacity);
ps->size = 0;
ps->capacity = InitCapacity;
}
int main()
{
struct Stack s1;
//假设我们事先知道栈里面至少要存100个数据
StackInit(&s1,100);
struct Stack s2;
//假设我们事先知道栈里面至少要存10个数据
StackInit(&s2,10);
struct Stack s3;
//假设我们事先不知道栈里面要存多少数据
StackInit(&s3);
return 0;
}
这样就能有效避免这个问题。你们说是吗?
2、函数重载
2.1、什么是函数重载?
定义:C语言不允许定义同名函数,但是C++可以,是用函数重载支持的 -> 函数名相同,参数不同:参数类型不同;参数个数不同。返回值不算,只看参数。
我们先给大家看一下什么样的才算是函数重载。
int Add(int left, int right)
{
return left + right;
}
char Add(char left, char right)
{
return left + right;
}
double Add(double left, double right)
{
return left + right;
}
long Add(long left, long right)
{
return left + right;
}
以上4个Add函数全部构成重载,函数重载与函数返回值无关,所以抛开返回值不看,看函数的形参列表,每一个形参都是不一样类型的接收参数,int、char、double、long,所以构成重载。以上函数跟下面的Add同样构成重载,因为参数的个数不同,所以构成函数重载的两个条件满足其一即可:
a:参数类型不同;b:参数个数不同。
int Add(int left, int right,int mid)
{
return left + right + mid;
}
那下面两个Add函数呢?
int Add(int left, int right)
{
return left + right;
}
char Add(int left, int right)
{
return left + right;
}
聪明的你肯定想到了,这是不构成重载的,因为只是返回值不同,参数完全相同。
你已经掌握了函数重载的精髓了哦!
高能时刻到了:大家擦亮眼睛哦
这两个f函数构成重载吗?第一个有三个参数,但是最后一个是缺省参数,第二个有实实在在的两个参数,满足重载的条件之一 - 形参个数不同。答案是什么呢???
void f(int a,int b,int c = 1)
{}
void f(int a, int b)
{}
答案是:构成重载!
不过请注意,虽然构成重载,但需要注意:我们在调用函数的传实参的时候有一个小坑,需要大家注意下。
编译报错!!!!!!
我们的想法是:利用缺省参数的特性,只传两个参数,第二个参数调用默认值,但是编译器做不到,不知道我们是调用第二个f还是第一个f,这里是比较容易错的地方,大家一定要注意哦。
2.2、为什么CPP支持函数重载而C不支持呢?
这就要说起C++底层的知识了,首先我们需要明确的是,C++之所以支持函数重载是因为函数名修饰规则。
一个C/C++程序要在系统上运行,要经过4个阶段:预编译、编译、汇编、链接
编译分为:预编译、编译、汇编。
每一个步骤都有不同的功能,对应会生成不同的文件,如图所示:
编译链接的过程:f.h f.cpp main.cpp
a:预处理 - 头文件展开+宏替换+去掉注释+条件编译
然后生成两个文件:f.i main.i
b:编译 - 检查语法,如果语法没问题就会生成汇编代码,汇编代码就是一条一条指令集了,但是CPU仍不认识它,CPU只认二进制码
然后生成两个文件:f.s mian.s
c:汇编 - 把汇编代码转成二进制的机器码,生成目标文件
然后生成两个目标文件:f.o mian.o
d:链接 - 链接到一起,生成可执行程序
如果没给指定类型,就会生成默认的a.out。windows下就会默认生成一个xx.exe
每个.o文件会生成一个相应的符号表,符号表中包含每个函数、每个变量、每个对象的地址,用来链接使用。
test文件回去f文件的符号表找对应函数变量的地址,而链接过程,就会将两个文件中的函数、变量的地址进行整合,如果找不到就会报我们常见的链接错误。
别眨眼,重点来了!!!!!!
在链接过程中,因为C语言没有函数名修饰规则,编译器直接拿函数名去找对应的地址,所以函数名不能相同,如果相同,编译器就无法区分具体找的是哪一个函数。
而我们的C++呢,其有函数名修饰规则,它不是直接拿函数名去符号表里寻找,而是有前缀有后缀,比如下方图片中,拿_Z3Addii去寻找,其中**_Z是前缀,3是Add函数的字符长度,ii表明函数需要两个参数,两个都是int类型**。
经过这个步骤,就算函数名相同,也会根据参数的不同区分不同的函数,所以这也就是为什么函数重载要求参数不同,这就是关键所在,相信到这里大家应该对函数重载有了更深刻的了解。
谢谢你能看到这里,你能看小编的文章就是对小编最大的鼓励,小编一定会写出更好的文章供大家阅读,我们一起学习进步。冲冲冲!
3、引用
3.1、引用的概念
引用 - 对应C的指针,作用与指针类似 - 类型& 引用变量名(对象名) = 引用实体;注意:引用类型必须和引用实体是同种类型的
引用概念:引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
int main()
{
int a = 10;
//b是a的别名,b是a的引用
int& b = a;
b = 20;//a也就改了
printf("%d\n",a);//20
}
3.2、引用的作用
相信学过C语言指针的你们看过上面的代码就已经对引用了解了,那么引用到底有什么用呢?
拿我们经常使用的Swap函数举例,交换两个函数的值,用C语言的话,我们要用指针进行交换,我们在传参数的时候就要采用传址操作,而传址就要建立新的空间,就会产生内存的消耗。那么用引用是怎么操作的呢?
//C
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
//C++
void Swap(int& r1, int& r2)
{
int tmp = r1;
r1 = r2;
r2 = tmp;
}
我们直接传值就可以了,但是在内部还是进行的传址操作,这个我们在后续的文章再进行讲解,但是我们看到的确实是进行了传值。
当然引用还可以做参数,做返回值。
//a:做参数
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
//b:做返回值(特殊场景下)
int& Count()
{
static int n = 0;
n++;
// ...
return n;
}
3.3、引用和指针的区别
说了这么多,也将引用和指针进行了多次比较,那么指针和引用有什么区别呢?
小编进行了相应的总结,大家记住结论即可:
a:引用必须初始化,必须在定义引用时明确引用是哪个变量或者对象,否则语法错误。指针不是必须初始化,指针不初始化,会指向随机值。
b:引用一旦定义时初始化指定,就不能被修改,指针可以改变指向。
c:引用不能出现空引用,即不存在指向空值的引用,而指针可以指向空
d:一个引用可以看作变量或对象的别名。
e:引用表面是在传值,其本质也是在传地址,只是这个工作由编译器来做,指针也是传地址。
f:函数参数的声明可以为引用或指针类型。
g:可以删除空指针,但不能删除引用,因为引用是别名,删除引用就是删除真实对象。