网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
引用就相当于给变量取别名,使得变量在同一空间内有多个名字。
你对引用操作,和对引用的实体操作的是一样的。
并且这和指针不一样,指针是指向a这个变量的地址。而引用就是引用的实体,它们两个是一样的。
对引用和引用的实体取地址你就明白了。
引用的特性:
- 引用必须进行初始化
- 一个变量可以有多个引用
- 一个引用一旦引用了一个实体,就不能引用其他实体了
Java和Python的引用是可以改的,但C++不可以。
从这方面看,C++的引用不可能会代替指针。就比如在写一个链表的时候,如果要遍历一个链表,我们会用到一个链表节点的指针,并且不断改变这个指针的指向,如果用引用的话,就不能实现这个功能了。
引用的使用场景:
- 作参数
这里也可以用指针,但是用引用效率会更高一些。 - 作返回值
引用是可以作为返回值的,并且效率比较高,但只适用于特定的情况下
要明白这个道理,首先我我们要明白传值返回和传引用返回的区别
这样是可以的,为什么可以?int类型的变量用static修饰之后会存储在静态区,而不会存储在栈区,这样在函数的栈帧销毁之后n这个变量就不会随之销毁了,这个时候n还是存在的,我们把它的值拷贝给ret当然是可以的。
这样也是可以,虽然在函数栈帧销毁之后存储在函数栈帧的n也随之销毁,但是在销毁之前会把n的值保存起来,具体保存在哪里需要看变量的大小,如果变量较小就会保存在寄存器中,如果变量较大就会保存在上一层函数的栈帧中。所以,这是可以的,虽然n不存在了,但是n的值已经被保存下来了,我们可以将保存的这个值拷贝给ret。
这样呢?我们将引用作为返回值,返回的就不是n了,而是n的引用。此时和第一种情况一样,n仍然是存在的,所以n的引用的值和n是相同的,我们用ret是可以得到n的值了。
这样就会出现问题了,编译时虽然可以通过,并且ret的值也可能是我们想要的,但是存在一些风险。
在返回引用时可就不会像第二种情况临时拷贝一份n的值并保存起来了,而是直接返回,不关心n是否被销毁。这样的话,n已经被销毁,再返回n的引用的,我们可以根据引用来找到n,但是得到的值是不确定的。
首先我们应该好好想想,n被销毁了,也就是说保存n的空间被销毁了。
空间被销毁了,空间还存在吗?存在!只是没有了这个空间的使用权了而已。
空间被销毁了,我们还能访问那?可以!只是访问得到的值不确定了,这块空间上的值,可能还是原来的值,也可能值是随机的,也可能空间被别人使用了,从而值被修改了。
如果你不明白,我给你打一个比方?
空间的申请和释放就像住酒店,你申请一块空间就像开一个房间,你释放这块空间就像退房。假如你退房之后,悄悄地赔了一把这个房间的钥匙,然后你仍然可以进入这个房间,虽然这是非法的。但是,如果你原来在房间内放了一个苹果,你再次进入这个房间之后,这个苹果的情况你确定吗?它可能还是原来的苹果,也可能被别人咬了一口,还有可能这个苹果被换成了梨。
那你空间释放之后,你再访问这个空间的变量,就像你再次非法进入了这个房间,虽然你能访问(虽然你能进去),但是得到的值可能是原来的值(苹果还是原来的苹果),也可能值不确定了(苹果被咬了一口),还有可能这个值被其他的值替换了(苹果被换成了梨)
你也可以看下这种情况,如果你返回引用,并且用ret这个引用来接收,那么ret就是n的引用,我们打印ret的值,ret可能是原来的值,但是之后调用test2这个函数之后,原来n的空间存的1就被100覆盖了,这个时候ret的值也就变为100了。
说了这么多,我就是要说明传引用返回在一些情况下是不适用的。
只有变量在被static修饰的时候传引用返回才是可以的,这个时候我们就相当于是把苹果放在了酒店的前台,不管我们上面时候去拿这个苹果,它也都是原来的苹果。
并且,当大量调用这个函数时,用传引用返回是最好的,因为它的效率比较高。
那么,关于传引用返回的一些细节就介绍到这里。
关于引用,也有一些奇妙的东西,下面我们一起来看一下。
引用和指针一样,在赋值时,权限可以缩小,但不能放大。
当变量是只读时,引用的权限也只是只读。
很多时候,用引用作参数都将引用设为了只读,可以避免权限的放大。
但是,有些要对变量进行修改的函数就不能作为const了,比如swap。
并且用const修饰的实体可以为常量。
再来看一些好玩的东西。
我们知道,类型转换的时候会产生临时变量
不管是加括号的强制类型转换,还是隐形转换,都是产生了一个转换后的临时变量,然后将这个临时变量赋值给其他变量。
并且临时变量具有常性,是不可以修改的。如果用要引用的实体和引用不同,那么引用的实体也会产生一个转换后的临时变量,然后将它赋值给引用,同时因为临时变量不可修改,引用必须要加const来修饰。
同时,如果要用引用接收函数的返回值也要加const修饰。刚才我们提到,返回值是临时拷贝了一份,然后再赋值给接收的变量,刚才我们又说临时变量是不可修改的,所以用来接收返回值的引用也要加const修饰。
引用占空间吗?引用在语法上面,它只是一个名字,是不占空间的。但是在底层实现上,引用是用指针实现的,它是占有一定空间的。
引用和指针的对比:
- 引用在概念上是一个变量的别名,而指针是存储变量的地址。
- 引用必须初识化,而指针不用
- 引用在引用一个实体之后,就不能引用其他实体,但指针可以改变指向。
- 没有NULL引用,但有NULL指针
- 使用sizeof,括号内是引用得到引用的实体的大小,括号内是指针得到存储地址的大小。
- 引用自加则实体也会自加,而指针自加表示则向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 引用的解引用编译器会处理,而指针的解引用需要程序员来处理
- 引用比指针更加安全
内联函数
定义:以inline关键字修饰的函数是内联函数,在如果调用该内联函数,编译时会直接将函数体展开在程序中,而不会建立栈帧,从而提升了程序运行的效率,是一种以空间换时间的做法
若要大量使用某个函数,同时又不想建立栈帧,C语言可以使用宏来进行处理。
但是宏是有很多确定的:
- 宏不可以进行调试
- 宏没有类型安全的检查
- 宏会因为优先级的问题而产生许多不可预料的结果
基于这个方面,C++的方案是内联函数。
这样的话,就既有了函数的优点,又具有了宏的优点。
不是所有的函数都适合内联,比如递归函数和比较长的函数就不适合,一般内联函数都是在十行以内。
关于内联函数,我们需要知道以下几点:
- inline是一种以时间换空间的做法,若使用内联函数,在编译阶段就会将该调用内联函数的地方替换为函数体,就不需要进行函数栈帧的创建和销毁了。这样做的缺点是会使目标文件变大,优点是不需要进行函数栈帧的创建和销毁,大大提高了程序的运行效率
- 内联函数的使用,是向编译器发出了一个使用的建议,而具体使用不使用还是取决于编译器,如果内联函数规模较大(就是比较长,具体标准是多少取决于编译器),或者用到了递归,又或者是频繁地调用,编译器就会忽略这个内联的特性。为什么会这样?防止代码膨胀!
- 如果使用内联函数,定义和声明不能分离在不同文件中,分离会导致链接错误。因为在编译阶段内联函数就被展开了,地址也就不存在了,链接也就找不到了。
auto关键字
使用auto声明指针类型时,auto和auto*没有区别,但是使用auto声明引用类型必须使用auto&。
auto不能作为函数参数,并且auto也不能声明数组。
基于范围for循环
它就是用来更方便地遍历一个数组
这样写没有效果,不能真的将数组元素乘2,为什么?
因为x只是数组元素的临时拷贝!
正确的写法是这样的
并且这里我们不能用指针,因为我们每次取到的是每个元素的值,而不是每个元素的地址
就比如你在这个函数内对arr进行sizeof,得到的只是arr这个数组首元素地址的大小而已。
指针空值 – nullptr
在C++中,空指针有两种表示方式。
并且NULL的值就是0
这是因为
可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如:
这样就存在二义性了,程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。
在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。
语言的更新有一个原则,就是不变动原来的语法规则(随意变动原来的语法规则会导致原来的许多程序崩溃),而是加入新的特性。
所以,在C++11中,我们引入nullptr这个关键字。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!