1. 引用传递和地址传递:别再傻傻分不清
先聊个扎心的事实:我在面试候选人的时候,只要问到函数传参,十个里面起码有六七个会把“引用传递”和“地址传递”当成一回事儿。哪怕工作了三五年的老手,也经常在口头表达上混淆这两个概念。但真要较真起来,这俩在底层机制上有着本质差异,理解不到位,写多线程、写底层封装、写性能敏感代码的时候容易踩坑。
先说这两个概念解决的是什么问题。不管是 Java、C++、Python 还是 Go,写代码绕不开一件事:把变量传给函数时,函数内部改了值,外部变量到底变不变?变了叫“引用传递”,不变叫“值传递”。但“地址传递”是另一个维度的事儿——它说的是“把变量的内存地址作为值传进去”,本质上是值传递的一种特殊形态。这个区分搞清楚了,很多“我以为我懂了,其实我没懂”的 bug 就能解释通了。
这篇内容适合刚入门想夯实基础的新手,也适合被面试官问懵了想彻底搞懂原理的进阶开发者。我会用 C++ 和 Java 两种典型语言做对照,把概念、底层实现、实战场景全部拆开讲清楚。文章里的代码我都实际跑过,结论可以直接用在你的项目里。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心概念:先搞清楚变量的本质是什么
2.1 变量就是一块内存的“门牌号”
在聊传递方式之前,必须先建立一张清晰的内存模型图。很多初学者之所以搞混引用和地址,本质上是没搞懂“变量名”“变量值”“内存地址”这三者的关系。
举个生活化的例子。你住在一个小区里,小区有楼栋号、单元号、门牌号,比如“3栋2单元501室”。这个门牌号就是内存地址。房间里住的人,就是你变量存的值。而你自己的名字,比如“张三”,就是变量名。
当你写 int a = 10 的时候,编译器做了一件什么事?它帮你在内存里找了一个空闲的“房间”(一块 4 字节的空间,假设地址是 0x7ffd1234),然后把门牌号和“张三”这个名字挂上钩,再往房间里放数字 10。以后你说“张三”,编译器就自动翻译成“去 0x7ffd1234 这个房间取 10”。
这个模型是理解一切传递机制的基石。记住一句话:变量名是给人看的,地址是给机器用的,值才是真正操作的数据。
2.2 值传递、地址传递、引用传递的严格定义
三个概念先用一句话分别定义,后面再展开细讲。
- 值传递(Pass by Value):把变量里存的“值”复制一份,传给函数。函数拿到的是副本,改副本不影响原件。
- 地址传递(Pass by Address / Pointer):把变量所在的“内存地址”这个值复制一份,传给函数。函数拿着地址去访问原件,改地址指向的内容就是在改原件。
- 引用传递(Pass by Reference):直接把“变量本身”传给函数,函数内的参数是外部变量的别名,二者是同一个东西。
从定义上就能看出,值传递和地址传递其实都属于“传值”——只不过地址传递传的“值”恰好是一个地址。而引用传递在语义上完全不复制任何东西,它是给原变量起了一个“别名”。
3. 地址传递的底层逻辑:本质还是在传值
3.1 指针变量是如何存储地址的
在 C/C++ 里,地址传递的实现方式是指针。比如:
c复制void change(int *p) {
*p = 100;
}
int main() {
int a = 10;
change(&a);
printf("%d\n", a); // 输出 100
}
在这段代码里,&a 取的是变量 a 的内存地址,这个地址本身是一个整数(在 64 位系统上通常是 8 字节)。调用 change(&a) 的时候,实际上是把 a 的地址这个数值复制了一份,传给了形参 p。
关键点来了:p 是一个独立的变量,它自己有内存空间,里面存的是 a 的地址。p 和 a 是两个完全不同的变量,只是 p 的值“碰巧”指向了 a 的家门口。
所以,如果你在函数里写 p = NULL,外部完全无感知,因为改的是 p 自己的值。但如果你写 *p = 100,那是在通过地址找到 a 的房间,往里面放新值,外部当然能看到变化。
3.2 为什么说地址传递不是引用传递
这是最容易混淆的地方。很多人看到“传地址能改外部变量”,就以为它是引用传递。但严格来说,C 语言里根本没有引用传递,只有值传递。只不过这个“值”特殊了一点——它是一个地址。
用代码证明一下地址传递的本质是值传递:
c复制void test(int *p) {
printf("p 的地址: %p\n", &p); // p 自己的地址
printf("p 存的值: %p\n", p); // 指向的地址,也就是 a 的地址
}
int main() {
int a = 10;
printf("a 的地址: %p\n", &a);
test(&a);
return 0;
}
运行结果会显示,&p 和 &a 是不同的。这就说明 p 是独立的变量,只不过它的值是 a 的地址。p 是“副本”,不是“别名”。
这个特性带来一个实际后果:如果想在函数里改变指针本身指向的对象(而不是改变指向的内容),单靠地址传递是做不到的,得用“指向指针的指针”(二级指针),或者用引用。
提示:理解“
p是副本”这一点,是区别地址传递和引用传递的分水岭。
4. 引用传递的底层机制:编译器的魔法
4.1 C++ 引用的本质是受限的指针
C++ 里引用的写法很简洁:
cpp复制void change(int &x) {
x = 100;
}
int main() {
int a = 10;
change(a);
cout << a << endl; // 输出 100
return 0;
}
这段代码看起来和地址传递效果一样,外部变量 a 变成了 100。但底层呢?
