大家好!这篇我们说一下C++里的引用,内容很干,希望能够一起进步。
1. 引用
1.1 引用概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
写法如下:
类型& 引用变量名(对象名) = 引用实体;
void Test()
{
int a = 10;
int& b = a;//<====定义引用类型
}
注意:引用类型必须和引用实体是同种类型的。
1.2 引用特性
1.引用在定义时必须初始化
这样写就是不允许的。
2. 一个变量可以有多个引用
你会发现它们几个地址都是一样的。
3. 引用一旦引用一个实体,再不能引用其他实体
这个是什么意思?举个例子:
在这里,你会发现这里的a和b都被改成了20,但是a和b的地址和e不一样。
说明:e只是把值赋给了a,b,不是把b当作e的别名。
1.3 常引用
我们知道const可以修饰变量,但这里const也可以修饰引用。
取别名原则:对原引用变量,权限只能缩小,不能放大
什么意思呢?我来举个例子:
从这里,我们可以看见x给const修饰了,那么x只能读不能写。但这时,我们给x起别名时,没加const就是说它的权限变大了,可以读和写了。这是不允许的。
我们必须这样做:
那么缩小,肯定我们也能理解了。
此时,d的权限是缩小的,所以它只能读,不能写。
那么我们进行下一个问题:能不能给常量起别名。
从上面我们可以看到这样是不行的,我们必须加const修饰。
我们下面的情况:
我们知道:引用类型必须和引用实体是同种类型的,但我们加一个const就可以了
这是为什么呢?我们知道,如果一个浮点型的变量赋值给整型:
那么此时d会创建一个临时变量,d会把自己的整数部分放到这个临时变量里,然后赋给f。
那么上面的问题中,f其实真正引用的是d的临时变量,而临时变量具有常性。
如果没加const,那么权限就会放大,所以加上const就OK了。
1.4 引用的使用场景
1. 做参数
这样,我们就不需要使用指针,可以直接传过来,x和y就是c和d。
那么引用做参数有什么好处呢?
1.输出型参数(就是可以用来做输出的)
2.减少拷贝,提高效率。
2. 做返回值
我们先看这个代码,它是普通的传值返回。
其实传值返回时也会产生一个临时变量,如果n小就会存n的值,如果大就不存。然后,把临时变量拷贝到ret。
那么为什么会产生临时变量呢?
我们平时是不会加static的,因为static可能会造成一些进程问题。我们平时都是这样写:
这样,如果我们没有临时变量,那么当Count函数结束时,n也会销毁,还给操作系统,n就无法给到ret。
但是,我们引用又有什么用呢?
如果,我们改成引用做返回值的话,是什么一个情况呢?
在传值返回:会有一个拷贝,创建临时空间。而在传引用返回:就没有这个拷贝,函数返回的直接就是返回变量的别名。
然后,这个别名再赋值给ret。
我们可以来验证一下:
这里,ret和n的地址一样,也就是意味着ret其实就是n的别名。
但是这里的代码,存在一些问题。就是当n销毁时,还给操作系统了,我们还有一个n的别名ret可以非法访问。
所以我们应该这样写:
这样当n不会被销毁时,我们就可以使用引用返回,这样就不会造成非法访问了。
所以要注意:如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。
1.5 值、引用效率比较
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
1.6 引用和指针的区别
为什么说它们的底层实现是一样的呢?我们可以看一下它们的反汇编:
我们可以看到,它们的汇编代码是一样的。
引用和指针的不同点:
- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
- 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
- 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
2. 内联函数
2.1 概念
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,:没有函数压栈的开销,内联函数提升程序运行的效率。
为什么C++会增加内联函数呢?
因为在C语言里,如果我们频繁调用短小函数,就会频繁的开辟函数栈帧,效率就会低。所以C语言会使用宏来解决这个问题,但是宏的出错率高,所以C++就会出现内联函数。
那内联函数是这么用的呢?很简单:
如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。
2.2 特性
1. inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。
2. inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。
3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
第三个特性,我们来说一下:
当我们编译运行时,就会发生错误。
因为在编译时,.h会展开,inline会展开不会产生函数的地址,符号表里就没有函数的地址,当我们调用函数时,就会找不到函数。