本期我们要讲的是 C++ 中的引用。
上期我们讨论了指针,如果你没有看过那期内容,你一定要回去看看,因为引用实际上只是指针的扩展,你至少需要在基本层面上理解指针是如何工作的,然后才能继续学习本期的内容,本期内容是指针那一期的链接。
先了解这些
指针 和 引用 是被大量翻来覆去提及的两个关键字,在 C++ 语言和其他语言中,它们本质上是一样的。
没错,指针和引用在计算机中做的事情几乎是一样的,从语义上说,我们如何使用和书写它们,有一些细微的区别,但从根本上来讲,引用只是指针的伪装,它们只是在指针的语法糖,它可以让程序更容易阅读,更容易被理解。
顾名思义,引用是一种我们引用现有变量的方式,不像指针。你可以创建一个新的指针变量或类似的东西,设它等于零,但是你不能对引用这样做,因为引用必须 “引用” 已经存在的变量。引用本身并不是新的变量,它们并不占用内存,它们没有真正的存储空间,它们不像典型的变量,因为它们是作为一个变量的引用而存在的。
下面我会举几个不同的例子来说明,例子会尽量简短一些,因为引用并不复杂,学习如何使用它们的最好方法就是随着系列的进行开始使用它们,后面我们会一直使用它们,你会看到一些很好的使用方式。
这就是引用
[
我有一个变量,记作 a,我让它等于 5,它是一个整数,我给这个变量创建一个引用,我可以输入变量的类型,后面跟着 & 符号,注意 & 符号实际上是变量声明的一部分。
如果你看过指针那一期,你会知道我们可以使用 & 符号获取现有变量的内存地址,这里不一样,因为 & 符号实际 不在类型旁边,它只是类型的一部分,要注意区别,因为很多人只要看到 &,就认为都是引用,或者都是取地址,其实具体是啥取决于上下文,在当前例子中,因为它在类型的旁边,所以它是一个引用。
我们继续解释代码,我将基命名为 ref, 并将其值设为 a,然后这里就不需要其他奇怪的运算符了,我们只是让它等于一个现有的变量就可以了。
我们现在创造了一个叫做 别名 的东西,因为这个 ref “变量” ,——我说的 “变量” 是带引号的,因为它不是一个真正的变量,这只是一个引用,ref 变量实际上不存在,它只存在于我们的源代码中。如果你现在编译这段代码,你不会得到两个变量 a 和 ref,你只会得到 a。
我们现在能做的是,我们可以使用 ref 就像它就是 a,如果我们设 ref 等于 2,然后打印 a,你会发现 a 的值变为了2。
[
因为 ref 就是 a,我们只是给 a 起了个别名,在这种情况下,我们的引用不是一个指针或其他类似的东西,编译器不需要实际创建一个新变量,如果你编译代码,这代码就相当于你设置了 a = 2,我们就是这么做的,因为如果给变量起一个别名的话,某些情况下我们的代码逻辑会更简单一些。
让我们尝试一些更复杂的东西。
我们定义一个函数 Increment
,让整型变量递增。你觉的下面的写法会实现这个功能吗?
[
并不能。
我们调用 Increment
函数,将 a 作为参数传递进去,你可以看到我们井没有把它作为一个指针或者引用等类似的东西传进去,它会拷贝这个值 5 复制到函数中,复制会创建一个全新的变量 value,这就是程序中发生的事情,如果运行代码我们可以看到打印出的值还是 5。
我们需要做的是通过引用来传递变量,这样它才会递增,因为我真正想做的是影响这个变量,那么我们该怎么做呢,如何通过将这个变量传递到函数中来修改它呢?
上期我们讨论了指针,还记得指针就是我们的内存地址吧,从理论上讲,我们可以做的是,可以没有将实际值 5 传递给函数,但是可以把 a 变量的内存地址传递进去,因为我们可以在这个函数中做的是可以查找那个内存地址,然后修改那个内存地址中的值。因为我们已经将该内存地址传递给了函数, 所以我们理论上是可以这样做的,所以需要怎么修改呢?采用指针。
[
在调用函数 Increment
时我传入的是 a 的内存地址,而不仅仅是值 5;我做的另外一件事就是 逆向引用 value,这样我们就可以直接将值写入这个内存,而不是修改指针本身,如果我们在 value++ 之前不加 *,没有 dereference 操作符,那么它就会递增这个内存地址,而不是实际值。还有就是你会看到我在逆向引用后加了括号,因为优先级的关系,如果不加括号,它会先做 value++ 增量运算,然后再做逆向引用,但我们想做的刚好相反,是先做逆向引用,然后是增量运算。
如果我们编译代码,你会看到打印出了 6。我们成功地将变量传递到一个函数中。
本期是关于引用的,所以我们要用更加简单、更少的代码、更少的修饰语法来实现上面的过程。
简洁的引用
我会用一个引用来解决问题。
[
我重写了这个函数,使用引用代替指针,这样我们就不需要逆向引用了,然后我们就不要传递 a 的内存地址,只需要传递 a 即可,它是通过引用传递的。运行之后,效果和之前是完全一样的,不过这次的代码明显比之前的更漂亮,其实这也是唯一的区别了。
好了,这就是引用的全部了,它们只是语法糖,对于引用没有什么是指针不能做的,指针就像引用,不过它更有用,也更强大。然而,你可以使用引用的时候,就一定要用引用,因为它会让你代码更加简洁和简单,引用使得你的代码干净得多。
关于引用,我想提到的另一件重要的事情是, 一旦你声明了一个引用,你不能改变它引用的东西。
举个例子。
[
编译当然可以通过,不过可不是你想的那个意思。它仅仅是把 a 的值变为了 b 的值,也就是 8。
这意味着如果你引用一个变量时,你需要把他赋值,你不能不赋值,编译器不会让你这么做的。你可以试一下。
当你声明一个引用时,必须马上给它赋值,因为他必须引用一些东西,时刻牢记它不是一个真正的变量,只是一个引用。
在这个例子中。如果我真的需要实现这种功能,——想改变引用的值,我该怎么做呢?
可以这样做
好吧,也有办法可以实现。
[
首先它这不是一个真正的变量,我们需要先创建变量,然后设置它来指向 a,然后,我们更改为指向 b,当然,我老是说 “指向” 这个词,我们说的其实是指针,所以改一下,让我们把它作为一个指针,我们一开始可以把它设为 a 的内存地址,当我把指向变成了 b 时,我们需要 dereference (逆向引用)这个指针,然后给它赋值,在这个例子中我让 a 等于 2,b 等于 1,打印出来应该是 2 和 1,运行之后,你可以看到结果是对的。
后话
这就是我想说的关于引用的内容了,非常简单的东西,我们在未来会使用更多的引用操作,你以后会一直用它,像指针一样。随着这个系列的深入你会看到更多的例子。
本期就是这样,下期再见。