引用
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空
间,它和它引用的变量共用同一块内存空间。
我们在使用b这个变量的时候,即是访问a这一块空间,对a的改变便是对a的空间的改变,自然会改变b,而对b的操作是对b这块空间的操作,也会改变a。
引用的底层即为指针,而引用和指针的区别是,引用不使用新的空间,而指针需要开辟一块新的空间来存储指针变量
引用的使用
1.引用的定义
我们在一个数据类型后加上&,此时就代表该类型为引用类型
我们修改a,b也会随之修改
而我们输出a和b的地址,我们发现a和b的地址是相同的
2.常引用
我们可以通过指针来类比,指针无法指向一个常数的地址,引用同样无法引用一个常数
所以我们在初定义引用时,我们无法引用一个常变量
但是,我们却可以使用常引用
此时a仍是b的别名,而b是一个常变量,a是一个变量,我们可以改变a的值,b的值会随着a的值的改变而改变,却无法单独改变b的值
此时,我们便引入了一个权限问题
权限
我们在C语言中初步了解的权限为变量和常变量的权限不同。变量的权限非常广,可以访问,修改和删除,而常变量的权限则会相对低一点,常变量无法进行修改。我们将变量变成常变量,即进行了权限的缩小,而将常变量变成变量,则进行了权限的放大,让一个变量等于另一个变量,则进行了权限的平移。
在引用中有这样一条规定:引用无法进行权限放大,引用只能缩小权限或平移权限,否则会导致空间访问的冲突
我们给a取别名b,即进行了权限的平移,把a的权限平移给了b;而我们让const int& b=a,则是进行了权限的缩小,把a的权限缩小给了b。但是,如果我们给一个常变量取别名,则会产生权限的放大,因此编译器会报错。
了解了以上这些,我们来看一段代码:
我们发现,编译器报错了,我们可能会认为是类型不同无法进行引用,而我们将代码进行修改
我们发现,此时编译器并没有报错,也就证明了我们的猜想是错误的,那么真正的原因是什么?
我们在C语言知道,如果赋值的数类型不同,那么编译器会对其进行类型转换
所以,在不同类型的赋值中,我们看似是将a赋值给了b,实际上是将1赋值给了b
此时,问题的答案便很清晰了:不同类型的引用,我们实际上是让b引用了一个常数,而非引用了一个变量,所以我们对b进行了权限的扩大,自然编译器会报错。
而在类型前加上const,我们进行了权限的平移,故而该操作是合法的。
3.引用作为参数
在C语言里,我们想实现Swap函数,常常会使用指针传参来实现
而在C++中,有了引用这一语法,我们可以对其进行优化
根据引用的特征,引用作为参数,实际上参数是传入值的别名,也就是传入的变量和参数共用一块空间,我们对参数的修改即是对空间的修改,故而可以影响到传入的变量
4.引用作为返回值
我们可以将引用作为参数,同样可以将引用作为返回值。
比如该函数,我们在函数内部定义了一个新变量i,我们的返回值类型是int&,返回值是i的别名,故我们在访问这个返回值时,访问的是i所在的空间。但是我们都知道,在函数中的局部变量出作用域后即销毁,我们访问的是一个野空间,我们输出i的结果
我们惊讶地发现,输出结果居然还是6,难道这里的i没有被销毁吗?
我们再输出以下结果:
我们这时候发现,j突然变成了一个随机值,为什么?
栈帧
我们在调用函数时,会对函数进行一个压栈的行为
在函数被调用时,函数会在main函数上进栈,而函数调用完成,函数便会出栈,此时该栈空间即为野空间。所以我们访问这个野空间的时候,仍会保留出栈前的值 ,而等下一个函数被调用再一次进栈,野空间的值就会被修改,此时我们再去访问这个空间的值,我们实际访问的是被修改后的值。
我们通过以下程序来验证
所以,我们想进行引用作为返回值,我们必须传回一个全局变量,或在局部变量前加上static
引用的注意点
1.引用必须初始化
不同于指针可以先定义后赋值,引用必须进行初始化
2.引用无法修改
通过必须初始化我们便可以知道,引用初始化后便无法修改,只能删除。
3.引用类型必须相同
我们在权限中已经说明了这个问题,如果想进行非相同类型的引用,我们必须要在前加上const