一、引用
1.1 引用的概念和定义
引用不是新定义的一个变量,而是给已存在变量取一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。比如水浒传中李逵,宋江叫铁牛,江湖人称黑旋风;林冲,外号豹子头。
类型&引用别名=引用对象
C++ 中为了避免引入太多运算符,会复用 C 语言的一些符号,比如前面的<<和>>,这里引用也和去地址使用同一个符号&,要注意区分。
创建 i 这个变量的时候会开辟一块空间叫 i,int& j = i,就是给这块空间又去了一个名字叫 j,还可以再取一个名字叫 k。
引用可以给一个变量取多个别名,也可以给别名取别名。
1.2 引用的特性
- 引用在定义时必须初始化(在定义时必须清楚是谁的别名)
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
k 已经是 i,j 的别名了,就不能是实体 m 的别名了,图中 k=m 就是赋值了。
1.3 引用的使用
C++ 中引用就是来解决指针不足的问题,引用的作用就是在大部分场景去替代指针,但是部分场景还是离不开指针。
1.3.1 引用传参的使用
之前完成 x 和 y 的交换,是使用指针来完成的,这也可以使用引用平替。
rx 和 ry 是 x 和 y 的别名,rx 和 ry 的交换,就是 x 和 y 的交换。这里引用看似没有初始化,其实是有的,引用在函数调用的时候才定义,定义的时候 x 和 y 传过来了。
并且指针交换和引用交换是能同时存在的,在 C++ 之中二者构成了函数重载。
在数据结构这里也可以直接使用引用,图中形参就是实参的别名。
再比如说现在要交换 p1 和 p2 两个指针,使用二级指针的话比较绕(二级指针解引用就是一级指针),可以使用引用来简化一下,引用既然可以给普通变量定义别名,也可以给指针变量定义别名。
这里报错是因为调用不明确,他们两个函数参数按道理是不同的两个类型,但是二者都可以接收指针,所以不支持重载。将 1 注释掉就好了。
再举一个 C 语言单链表的例子。
不使用引用的时候,链表的尾插是这样的,二级指针确实很绕人。
这样 phead 就是 plist 的别名了,插入第一个节点的时候 newnode 给 phead,phead 改变就是 plist 改变。
学校的一些教材上是这样写的,让人很难理解。
首先要理解,如果结构体前面没有 typedef,这里的 x 就是定义的结构体变量,p 就是结构体的指针变量,且二者都是全局变量。
struct A {} x, *p;
前面有 typedef,就是对类型的别名。
//等价于 typedef struct SListNode SLTNode; typedef struct SListNode* PSLTNode;
所以* PSLTNode,其实就是 SListNode *,就是结构体的指针。就可以这样就和前面没什么区别了。我感觉他的设计本意是为了避免二级指针让人绕进去,但是加了 C++ 的一些语法,反而写的更复杂了。
引用也是不能完全替代指针的,像在链表,树,这些节点定义的位置,只能使用指针,节点与节点之间的物理空间并不连续,一定是一块物理空间存下一块物理空间的地址,所以至少要有一个指针。
树和链表的操作都有一个要求,需要改变指向,如图使用引用的话,第一个节点存了第二个节点的别名,但是如果第二个节点删了之后,是不能变成第三个节点的别名的。指针核心不能被引用的点就是,C++ 的引用无法改变指向。
但是 Java 没有指针,只有引用。Java 的引用是可以改变指向的。
再说一下返回的问题,传值返回和传值传参类似,都会产生临时变量,这里并不是用 ret 作为一个返回值(func 函数调用结束之后才会有返回值,此时 ret 都销毁了),返回的是 ret 的拷贝的临时变量。一般返回的对象比较小,这种情况下临时变量会存到寄存器里面,如果比较大,就会在两个栈帧中开一片空间来存(在函数调用之前就会开好),func 销毁,中间的空间再拷贝给 x,再把中间的栈帧(寄存器存的值)销毁。
要看懂这个图首先要理解函数调用会在栈这个区域建立一个栈帧。
() += ;


