1. 前言
-
以往学习过程中将 指针和引用 作为平行的概念,很容易混淆。本文第一件事需要搞懂他们属于什么维度的东西。 -
提出 指针和引用有什么区别? 本身就是一个很糟糕的提问方式。应该先将指针这个概念剥离出命题,得到正确的问法:方法签名中,变量声明为普通类型和引用类型有什么区别? 。解决了第一个问题后,如果已经对结构体、指针、引用不陌生了,那么研究结构体指针的引用的作用 就好理解了。 -
& 作为操作符的用法,不是本文重点,将不做介绍。本文仅梳理& 在方法签名中的意义。 -
文末联系Java中方法除了基本类型都为引用传递的设计,加深对引用的理解。
2. 概念解释
2.1 结构体定义
typedef struct Goods {
int code;
Goods *next;
} Goods;
2.2 指针和引用属于不同维度
void f() {
Goods g1;
Goods* g2;
Goods& g3;
}
void f(Goods g1, Goods* g2, Goods& g3) {
}
综上,Goods& 是引用 能定义在方法签名上而不能在方法内部定义。 基本数据类型、结构体类型、结构体指针类型属于一个范畴A; 引用类型属于另外一个范畴B; 通过方法签名,两个范畴得以联系,A能被声明成B; 换言之,指针也有引用类型,并且有实际作用,结构体指针也是指针,方法签名中能声明为引用。
2.3 结构体指针类型的引用
void f(Goods g1, Goods* g2, Goods& g3, Goods*& g4) {
}
3. 使用的场景
下题摘自广东工业大学考研真题:
3.1 题目
某仓库用一个带头节点的循环链表L存储各种货物的代码,链表的定义如下:看不懂以下的链表定义点这里
typedef struct Goods {
int code;
Goods *next;
} Goods, *GoodsList;
试写一个算法 void f(GoodsList L, GoodsList &Lc, int c) ,将其中代码code大于c的货物从链表L删除,并将删除的货物组成的一个新的带头节点的循环链表。
3.2 题解
3.2.1 审题
题目要求传入L链表,修改L链表,组成一个新的链表
3.2.1 观察函数签名
void f(GoodsList L, GoodsList &Lc, int c) {
}
void f(Goods* L, Goods*& Lc, int c) {
}
Goods*& Lc 的作用:作为算法最终要得到的链表的指针,不用显式return。 如果用return,等价于:
Goods* f(Goods* L, int c) {
Goods* p;
return p;
}
-
使用返回值,调用层写法: Goods* request;
Goods* response = f(request, 3);
-
使用引用,调用层写法: Goods* request;
Goods* response;
f(request, response, 3);
这题使用引用,需要我们在业务逻辑中注意到方法签名中的引用是算法需要返回的指针 ,就足够解题了。为了更加深刻的理解引用,下文将讨论一个话题:使用引用的必要性是什么?。
4. 方法签名上使用引用的必要性
4.1 引例
牧童用绳子牵着牛去吃草。分类讨论牛在方法签名上的应用场景 牧童 == main方法 牛 == main方法定义的变量 绳子 == main方法定义的变量的指针
typedef struct Cow {
bool wantEat;
int times;
} Cow ;
typedef struct Visitor {
bool saw;
Cow *ownCow;
} Visitor ;
-
结构体类型 牧童放牛,目的是取悦游客。如果牛吃草了,游客就会看。本质是读牛的数据但是不修改。 void f(Cow cow, Visitor& vistor) {
if (cow.wantEat) {
vistor.saw = true;
}
}
main方法:
Cow cow;
Visitor vistor;
cow.wantEat = randonBool();
f(cow, vistor);
-
结构体引用 牧童放牛,取悦游客的同时,还不能让牛吃太多,牛每次吃草都要记录。本质上是读牛的数据但是且进行修改。 void f(Cow& cow, Visitor& vistor) {
if (cow.eated && cow.times < 5) {
cow.times++;
vistor.saw = 1;
}
}
main方法:
Cow cow;
Visitor vistor;
cow.wantEat = randonBool();
cow.times = randonInt();
f(cow, vistor);
-
结构体指针 牧童放牛,游客发现后,牧童和游客同时握着绑住牛的绳子。绳子就是牛的指针。 void f(Cow* cow, Visitor& vistor) {
vistor.ownCow = cow;
}
main方法:
Cow cow;
Visitor vistor;
f(&cow, vistor);
-
结构体指针的引用 牧童放牛,游客也带了一头牛,他们两个交换牛的绳子。本质上,调用方的变量的指针也被改变了 void f(Cow&* cow, Visitor& vistor) {
Cow* temp = vistor.cow;
vistor.ownCow = cow;
cow = temp;
}
main方法:
Cow cow;
Visitor vistor;
f(&cow, vistor);
4.2 结论
如果要对结构体入参用写操作,那么就需要使用引用类型的入参。如果main方法中定义的变量A,传入方法中后将得到新的变量覆盖A,那么方法签名就需要使用结构体指针的引用。
5. Java Web 中对象为引用传递
基于Spring框架的Web 处理层级结构见图 所有request 和response 都是引用传递,能层层封装包装request 和response 都是同一个对象,但是有不会传指针的引用,程序无法交换掉或者篡改request 和response 。
|