1. 值传递(Call by Value)

值传递是编程中最常见的参数传递方式。它的核心在于:当一个变量以值传递的方式传入函数时,调用方和被调用方各自拥有独立的变量副本。因此,被调用函数对参数的修改不会影响调用方原始变量的值。

举个例子:

function callee(x passed by value):
    x <- x + 1

function caller():
    a <- 5
    print a
    callee(value of a)
    print a

// 输出结果:
// 5
// 5

在这个例子中,caller 函数定义了一个变量 a,并赋值为 5。然后调用 callee 并将 a 的值传入。此时,callee 中的 xa 的一个副本。即使 x 被修改为 6,a 的值仍然保持为 5。

总结:值传递是“复制一份给你”,你改你的,我不受影响。


2. 引用传递(Call by Reference)

引用传递的核心在于:被调用函数的参数和调用方的变量指向的是同一个对象。因此,被调用函数对参数的修改会直接影响调用方的原始变量。

看一个例子:

function callee(x passed by reference):
    x <- x + 1

function caller():
    a <- 5
    print a
    callee(reference to a)
    print a

// 输出结果:
// 5
// 6

在这个例子中,callee 接收到的是 a 的引用。当 x 被修改为 6 时,a 的值也随之改变。

总结:引用传递是“给你一个指针”,你改了我也改。


3. 现代语言中的值传递与引用传递

在现代编程语言中(如 Java、Python、Go、C# 等),大多数对象都存储在堆内存中,变量本身只保存指向对象的“指针”或“引用”。

这就引出了一个常见误区:Java 是不是引用传递?

答案是 ❌ 不是。Java 始终是 值传递,只不过这个“值”是一个引用地址。

我们来看一个 Java 示例:

public class Main {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("hello");
        modify(sb);
        System.out.println(sb); // 输出 hello world
    }

    static void modify(StringBuilder s) {
        s.append(" world");
    }
}

在这个例子中,modify 方法修改了 sb 的内容,这是因为传入的是 StringBuilder 对象的引用地址,函数内部操作的是同一个对象。

但如果你在函数中重新赋值引用:

static void modify(StringBuilder s) {
    s = new StringBuilder("new");
}

这时候,main 函数中的 sb 并不会被改变,因为这是对引用变量的重新赋值,而不是修改引用指向的对象。

总结

  • Java 中对象是通过引用地址传递的。
  • 但参数本身是值传递(传的是引用地址的副本)。
  • 如果你修改对象内容,会影响外部变量。
  • 如果你重新赋值引用变量,不会影响外部变量。

4. 选择值传递还是引用传递?

选择方式 优点 缺点
值传递 安全性高,副作用小 复制大对象效率低
引用传递 高效,可修改原始数据 容易引入副作用,调试困难

⚠️ 踩坑提醒

  • 不要误以为 Java 是引用传递,容易写出意料之外的副作用。
  • 修改对象时要明确是否希望影响外部状态。
  • 对不可变对象(如 String)使用引用传递时,其行为仍类似值传递。

5. 总结

本文讲解了函数参数传递的两种基本机制:

  • 值传递:传递变量的副本,函数内部修改不影响外部。
  • 引用传递:传递变量的引用,函数内部修改会影响外部。

虽然 Java 始终使用值传递,但由于对象引用的存在,其行为在某些场景下看起来像引用传递。理解这一点,有助于我们更好地控制函数副作用,避免 bug。

关键点总结

  • Java 是值传递。
  • 对象引用作为值传递。
  • 修改对象内容会影响外部。
  • 重新赋值引用不影响外部。

如果你对函数参数传递机制还有疑惑,建议多动手写几个测试用例,观察变量变化,加深理解。


原始标题:Function Parameters: Call by Reference vs Call by Value