(缺省参数)&(函数重载)&(引用)&(内敛)&(atuo用法)&(C++中的NULL)
- 1.缺省参数
- 1.1缺省参数的概念
- 1.2缺省参数的分类
- 1.3缺省参数的注意事项
- 2.函数重载
- 2.1 重载的概念
- 2.2 C++支持函数重载的原理--名字修饰
- 3.引用
- 3.1 引用给概念
- 3.2 引用的使用规范
- 3.3常引用
- 3.4引用的使用场景
- 3.5引用和指针的区别
- 4. 内联
- 4.1内联的概念
- 4.2内联的特性
- 4.3 inline与宏的区别
- 5. C++中的nullptr
所属专栏:C“嘎嘎" 系统学习❤️
🚀 >博主首页:初阳785❤️
🚀 >代码托管:chuyang785❤️
🚀 >感谢大家的支持,您的点赞和关注是对我最大的支持!!!❤️
🚀 >博主也会更加的努力,创作出更优质的博文!!❤️
🚀 >关注我,关注我,关注我,重要的事情说三遍!!!!!!!!❤️
1.缺省参数
1.1缺省参数的概念
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实
参则采用该形参的缺省值,否则使用指定的实参。
void Func(int a = 0)
{
cout << a << endl;
}
int main()
{
Func(); // 没有传参时,使用参数的默认值
Func(10); // 传参时,使用指定的实参
return 0;
}
- 解释一下上述代码:
缺省参数你可以理解成是一个替补成员
,我们有正式成员的时候就不需要替补成员。反之如果我们没有正式成员的时候,这个时候就是我们的替补成员上场了。
1.2缺省参数的分类
- 全缺省参数
顾名思义就是形参列表中的参数都是缺省参数。
void Func(int a = 10, int b = 20, int c = 30)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
- 半缺省参数
半缺省参数的意思不是说形参列表中的参数只有一半是缺省参数,而是说形参列表中即有缺省参数也有正常的参数。
void Func(int a, int b = 10, int c = 20)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
1.3缺省参数的注意事项
- 半缺省参数
必须从右往左依次来给出
,不能间隔着给
。我们来看一下这个段代码:
void Func(int a = 100, int b , int c = 20)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
- 就像上面这段代码,我们的缺省参数在中间,二两边是正常的参数。
首先结论就是,这个样子是不行的。因为,如果我们传参的时候会出现二义性:
比如:
void Func(int a = 100, int b , int c = 20)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
int main()
{
Func(10,20);
return 0;
}
此时我们Func(10,20)我们可以说10和a匹配,20和b匹配。我们也可以说10和b匹配,20和c匹配。我们可以说10和a匹配,20和c匹配。所以会出现二义性。为了避免这种现象出现,C++规定了半缺省参数必须从右往左依次来给出,不能间隔着给
- 缺省参数不能在
函数声明和定义中同时出现
- 如果我们在声明和函数都定义了缺省参数:
//a.h
void Func(int a = 10);
// a.cpp
void Func(int a = 20)
{}
万一我们函数的声明与定义中的缺省参数的值不一样了,这个时候改取哪一个值呢?就会出现二义性。
- 如果我们只是在定义中定义缺省参数呢:
//a.h
void Func();
// a.cpp
void Func(int a = 20)
{}
//(main) a.cpp
#include "a.h";
int main()
{}
这个时候我们的主函数中包含了a.h,但是我们声明的时候没有定义缺省参数,这个时候我们在mian函数中使用这个Func函数的时候,我们没有传值的时候就不知道缺省参数的值了。
所以C++中定义了,函数的声明和定义中,只要声明定义缺省参数就行。
- 缺省值必须是常量或者全局变量
- C语言不支持(编译器不支持)
2.函数重载
在C语言中我们的函数名只能取一个,不能重名,也就是说如果我们要进行2个数相加,3个数相加……n+1个数相加,我们就要写上n个函数名,并且这些函数名都是不一样的。这下就有点为难我们了,如果这个n很大呢?那就的靠我们的想象力,和排列组合能力了,并且一点我们需要调用这些函数,我们还要记住这些函数名,显然如果函数不能重名的话,确实可能会给我们带来一些困难。
于是C++为了解决这种不方便的地方就有了函数重载的概念。
2.1 重载的概念
函数重载:
是函数的一种特殊情况,C++允许在同一作用域
中声明几个功能类似
的同名函数
,这
些同名函数
的形参列表(参数个数 或 类型 或 类型顺序)不同
,常用来处理实现功能类似数据类型
不同的问题。
-
参数个数不一样
-
参数类型不同
-
类型顺序不同
2.2 C++支持函数重载的原理–名字修饰
- 为什么C++支持函数重载,而C语言不支持函数重载呢?
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
- 实际项目通常是由多个头文件和多个源文件构成,而通过C语言阶段学习的编译链接,我们
可以知道,【当前a.cpp中调用了b.cpp中定义的Add函数时】,编译后链接前,a.o的目标
文件中没有Add的函数地址,因为Add是在b.cpp中定义的,所以Add的地址在b.o中。那么
怎么办呢? - 所以链接阶段就是专门处理这种问题,链接器看到a.o调用Add,但是没有Add的地址,就
会到b.o的符号表中找Add的地址,然后链接到一起。 - 那么链接时,面对Add函数,链接接器会使用哪个名字去找呢?这里每个编译器都有自己的
函数名修饰规则。 - 由于Windows下vs的修饰规则过于复杂,而Linux下g++的修饰规则简单易懂,下面我们使
用了g++演示了这个修饰后的名字。 - 通过下面我们可以看出gcc的函数修饰后名字不变。而g++的函数修饰后变成
【_Z+函数长度 +函数名+类型首字母】
。
-
采用C语言编译器编译的后果
* -
采用C++编译器编译后结果
- 通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修
饰规则来区分,只要参数不同,修饰出来的名字就不一样
,就支持了重载。 - 如果两个函数函数名和参数是一样的,
返回值不同是不构成重载的
,因为调用时编译器没办
法区分。
3.引用
3.1 引用给概念
- 引用不是新定义一个变量,而是给已存在变量
取了一个别名
,编译器不会为引用变量开辟内存空
间,它和它引用的变量共用同一块内存空间。
使用语法:类型& 引用变量名(对象名)= 引用实体;
例如:
3.2 引用的使用规范
- 引用在定义时
必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再
不能引用其他实体
- 引用一旦引用一个实体,再不能引用其他实体我们可以把 int& c = a 理解成
int * const c = &a
,这样我们就跟清楚的能够理解为什么引用一个实体化就不能引用其他实体了。
3.3常引用
我们能不能引用一个常量呢?
int main()
{
int a = 10;
int& c = a;
return 0;
}
- 上面这段代码是可行的,但是如果我们在int a的前面加上const呢?让他变成不可以修改的。
int main()
{
const int a = 10;
int& c = a;
return 0;
}
- 这个时候int& c = a还是正确的吗,显然不正确。a被const修饰后是不可修改的,现在我们给a取一个别名c但是c是可修改的,二c也是指向a这块空间的,现在矛盾来了,a不可修改,c可修改,二c也是指向a这块空间的,这显然会出错。所以想要得到解决方案就是让c也变成不可修改的,那么也加上const修饰。即:
int main()
{
const int a = 10;
const int& c = a;
return 0;
}
同样的道理我们也可以个一个常量取别名:
int main()
{
const int& c = 100;
return 0;
}
3.4引用的使用场景
- 做参数
我们对比一下我们之前写交换两个数:
C语言版本:
void Swap(int* a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
C++版本
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = c;
}
这样一对比就可看出C++是更直观的。
- 做返回值
我们来看接下来这段代码:
int& Add()
{
int n = 0;
n++;
return n;
}
int main()
{
int ret = Add();
cout << ret << endl;
return 0;
}
- 这个结果是什么呢?
- 这里虽然输入的结果是我们预想到的,但是这样的写法并不是正确的。
我们回顾一下我们的内存分为,栈区,堆区,静态区。我们的函数,局部变量都是存放在栈区的。栈区的特点是,进入作用域创建,出了后就会销毁。
-
虽然输出的是1,那是因为虽然Add栈区被回收了,但是之前使用的时候我们有值已经存放进去了,只是回收空间,而里面值
还没有被覆盖
,如果被覆盖了输出的就是一个随机值了。 -
注意:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用
引用返回,如果已经还给系统了,则必须使用传值返回。
3.5引用和指针的区别
- 在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
但是我们的底层上引用和指针其实区别不大,并且引用也是开辟了空间的。
这里我们会发现,引用和指针在汇编中其实没多大区别。可以说是基本相同的。
引用和指针的不同点:
- 引用概念上定义一个变量的别名,指针存储一个变量地址。
- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何
一个同类型实体 - 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
位平台下占4个字节) - 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
4. 内联
4.1内联的概念
- 以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调
用建立栈帧的开销,内联函数提升程序运行的效率。
如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的
调用。
查看方式:
- 此时我们就可以看到用来inline之后,函数是没有进行额外栈创建的,而是直接展开了。
4.2内联的特性
- inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会
用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运
行效率。
-
假如你有一个函数,它有10000行,如果我们强行的加上inline,那么在我们的汇编就会讲着10000行全部展开,而如果我们要多次进行使用这个函数,拿如果我们有100次调用这函数,拿我们全部展开是多少行呢?是100*10000行,这个时候我们的可执行程序的大小是不是就变大了啊。
而如果我们不进行只能开,而是直接call这个函数,那么我们每次调用这个函数时候,只需要接收这个函数的地址就行了,每次接收这个地址我们就跳转到这个函数二外创建好的空间,直接进行使用了,而这个我们之展开了100+10000行,这样我们的可执行程序的大小是不是就变小啊。 -
所以inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建
议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不
是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性
- inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址
了,链接就会找不到
4.3 inline与宏的区别
-
宏优点:
1.增强代码的复用性。
2.提高性能。 -
缺点:
1.不方便调试宏。(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查 。 -
C++有哪些技术替代宏?
1.常量定义 换用const enum
2.短小函数定义 换用内联函数
5. C++中的nullptr
- C++98中的指针空值:在良好的C/C++编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现
不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下
方式对其进行初始化。
int* ptr = NULL;
int* str = 0;
NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:
- 可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何
种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如
- 也就是说NULL被认定为是整形了,但是程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的
初衷相悖。
在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器
默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void*)0。
于是为了区分C++11中引入了一个关键子nullptr;
- 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入
的。 - 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
- 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。