本篇文章,我们继续接着上一篇,对C++的基础进行讲解,本篇将会讲到C++中引用,incline内联以及nullptr的相关知识。
目录
1.引用
1.1引用的概念和定义
引用不是新定义⼀个变量,而是给已存在变量取了⼀个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同⼀块内存空间。
引用的使用即 类型& 引用别名 = 引用对象
注意:C++中为了避免引入太多的运算符,会复用C语言的⼀些符号,比如前前的<< 和 >>,这里引用也和取地址使用了同⼀个符号&。大家一定要分清楚用法,不要混淆了!
我们可以通过一个代码更为清晰的了解,引用和变量的关系
通过上面的代码,我们可以发现,b,d是对a的引用即是a的别名,而c是b的引用,即c是b的别名,我们可以看到,abcd的地址都指向同一块空间,即可以得出一个结论,引用并不开辟新的空间。
由于abcd都位于同一块空间,因此当修改其中一个值的时候,abcd的值都会跟着改变,如下图代码运行图所示
1.2 引用的特性
- 引用定义时必须进行初始化
我们可以想一下,如果引用定义时不初始化,那么在后面的代码中进行赋值时,到底是赋值还是初始化呢,这就导致了代码的意义混淆,因此规定引用定义时必须进行初始化。
- ⼀个变量可以有多个引用
在上面的代码中,我们已经演示了变量a可以有多个引用,但是他们所在的空间都是同一个,修改变量都会让他们同时改变。
- 引用一旦引用以恶搞实体,再不能引用其他实体
这个点和第一个点说的一致,C++中引用不能改变指向,当用=赋值时,是对变量进行赋值,而非改变引用对象。
1.3 引用的使用
在具体了解了引用后,大家一定会有这种想法,引用的使用场景到底是什么啊,引用和指针感觉没什么区别啊,,我只能说别急,我们一步一步来。
- 引用在实践中主要是于引用传参和引用做返回值中减少拷贝提高效率和改变引用对象时同时改变被引用对象
我举一个例子,就以交换两个值为例,平常我们交换两个变量的值,由于直接传入的值是形参,需要传入变量的地址,并且用指针来接受,这样才能改变两个变量的值,有点麻烦。
但我们知道了引用就是变量的别名,他们都在一个地址,改变引用对象即改变变量的值,因此我们可以在利用这点特性,设计带有两个引用变量的函数,进行直接的交换
如图所示
可以说引用传参跟指针传参功能是类似的,引用传参相对更方便⼀些。
- 引用还有对返回值的用处,但是这涉及C++之后类与对象的知识,我们放到后面再去讲
1.4 关于 const 引用
可以引用⼀个const对象,但是必须⽤const引用。const引用也可以引用普通对象,因为对象的访
问权限在引用过程中可以缩小,但是不能放大。
我们分析一下,变量a,是const int 类型即只可进行读操作,不能进行写操作,而当用ra引用时,他的类型是int,所拥有的权限为可读可写,因此很显然,发生了权限的放大,会出现编译错误
要利用const int& ra = a就可以了
接下来,我们看一下权限缩小
很显然这个操作是可以的,因为变量是int类型,可读可写,而引用为const int 类型,权限为可读不可写,自己缩小了权限,这是被允许的。
接下来我们先引入一个临时对象的概念
定义:所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象,C++中把这个未命名对象叫做临时对象。
int& rb = a*3;
double d = 12.34;
int& rd = d;
以上的两个例子,
表达式:在C++中,表达式赋值,是先将表达式计算出来,将结果放置到一个空间中,再赋值给变量,而这个结果定义为一个常量,因此需要在前面加上const,即常引用
而在隐式转换中也有着相同的意思,double转换为int型时,类型转换也是将double截断为int存在常变量中,再将常变量赋值给int型变量,因此也需要用const引用,即常引用。
以上便是常引用的相关知识点。
1.5 指针和引用的关系
- 语法概念上引用是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。
- 引用在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
- 引用在初始化时引用⼀个对象后,就不能再引用其他对象;而指针可以在不断地改变指向对象。
- 引用可以直接访问指向对象,指针需要解引用才是访问指向对象。
- sizeof中含义不同,引⽤结果为引⽤类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8个字节)
- 指针很容易出现空指针和野指针的问题,引用很少出现,引用使用起来相对更安全⼀些。
2. inline关键字
inline即修饰内联函数的关键字,放到函数的最前面
- 用inline修饰的函数叫做内联函数,编译时C++编译器会在调用的地方展开内联函数,这样调用内联函数就需要建立栈帧了,就可以提高效率。
- inline对于编译器而言只是⼀个建议,也就是说,你加了inline编译器也可以选择在调用的地⽅不展开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline适用于频繁调用的短小函数,对于递归函数,代码相对多⼀些的函数,加上inline也会被编译器忽略。
即加上inline,编译器也不一定会执行这个编译操作,编译器会根据情况使用,如果代码量很多的情况下,展开函数会导致内存变大,不利于代码的执行。
我们可以举一个例子,假设一个内联函数有10000条指令,如果你要调用100次,若函数展开则在编译器中会有100*10000条的指令,若不展开则只有100+10000条的指令,两者一比较,就很容易发现,展开的话,可执行程序就会变大,对用户会产生较差的体验,这也是为什么编译器并不会根据inline的有无而是否执行展开函数,而是根据实际的代码量和调用次数进行判断是否展开。
即inline关键字只是一个建议,编译器不一定会执行
以上的代码可以看到,当函数比较代码量比较小时,在加inline的情况下,编译器会将其展开,若代码量稍微多一点时,即使加了inline关键字,也没有展开,而是建立函数栈帧,调用函数。
- C语言实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不方便调试,C++设计了inline目的就是替代C的宏函数。
可以说,inline的出现可以很大程度上,防止宏函数的错误,当我们想用简单的宏定义时,就可以直接用inline来替代。
- inline不建议声明和定义分离到两个文件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错。
由于内联函数,编译器是默认不需要地址的,即会在使用的地方展开,所以在链接的时候,找不到函数的地址,这个大家记住用法就好。
一般来说,内联函数直接放到头文件中,声明和实现都放在头文件中。
3.nullptr
NULL实际上就是一个宏
在上面的宏定义中,我们可以看到,在C++中NULL被定义为0,而在C中NULL被定义为强制类型转换的(void*)0
但是这个设计会有一个巨大的缺陷
那我们怎么调用f(int* x)呢?
能否将传入参数变为(void *)NULL,这样是不是就调用第二个函数了呢?
很显然答案是否定的,在C语言中允许将(void*)0给任意类型的指针,但在C++中这是不允许的
这里显示没有对应的函数参数,因为C++中void*是不能给int*类型的,因此函数参数类型不匹配
这里是由于C和C++里面的差异,C不严格,而C++这里很严格
C++兼容C的大部分语法,不过这里就是一个小的差异
而C++为了解决这个问题,所以在C++中就引入了一个新的关键字nullptr介入
nullptr的定义
C++11中引入nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字面量,它可以转换
成任意其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被
隐式地转换为指针类型,而不能被转换为整数类型。
实例
在刚才的程序中,c语言中(void*)0即能转换为int型,也能转换为int*型,在C++中就有歧义,到底转换成哪一种呢
而nullptr规定可以转换为其他指针类型,不能转换为整型,就解决了上面的歧义问题
所以在C++中,以后的空指针就专用nullptr。
总结
以上就是本章节的全部内容,包含了引用,inline,以及nullptr,希望对大家有帮助,本章也是完结了C++的入门部分,之后我将继续更新类和对象的部分。