核心概念:值传递(Pass by Value) vs. 引用/指针传递(Pass by Reference/Pointer)

在分析具体语言之前,必须理解这个根本区别:

  • 值传递 (Pass by Value): 函数接收到的是实参的一个副本。在函数内部修改这个副本,不会影响原始的实参。
  • 引用/指针传递 (Pass by Reference/Pointer): 函数接收到的是实参的内存地址(引用可以理解为语法糖形式的指针)。通过这个地址直接操作原始数据,在函数内部的修改会影响原始的实参。

1. Java 的情况

Java的语言规范规定,所有方法的参数传递都是“值传递”

java

class MyClass {
    public int value;
}

public class Test {
    public static void modifyObject(MyClass obj) { // obj是传入引用的一个副本
        obj.value = 10; // 通过副本找到原始对象,修改其属性
        obj = new MyClass(); //** 这里让副本指向一个新对象,但原始实参的指向不变
        obj.value = 20;
    }

    public static void main(String[] args) {
        MyClass myObj = new MyClass();
        myObj.value = 5;
        System.out.println("Before: " + myObj.value); // Output: Before: 5

        modifyObject(myObj); // **将myObj保存的引用(地址)复制一份传给obj

        System.out.println("After: " + myObj.value); // Output: After: 10
    }
}

分析:

  1. myObj 是一个引用变量,它的值是一个指向堆中MyClass对象的地址。
  2. 调用modifyObject(myObj)时,传递的是myObj这个引用变量里存储的地址值的副本。所以形参obj和实参myObj指向同一个对象
  3. obj.value = 10; 通过副本地址找到了原始对象,并修改了它的属性。所以外部的myObj.value也变成了10。
  4. obj = new MyClass(); 这一步是让形参obj这个副本指向了一个全新的对象。这与外部的myObj已经完全无关了。myObj仍然指向原来的对象。
  5. 结论: Java通过值传递引用的副本。你可以通过这个副本修改它所指向的对象的内容,但你不能通过修改副本本身(让它指向新对象)来影响实参的指向。

简单说:Java中,对于对象,你可以修改对象的“内容”,但不能修改外部变量的“指向”。

2. C++ 的情况

C++提供了更灵活的参数传递方式,这也是它比Java更复杂但也更强大的地方。

a) 值传递一个对象

cpp

#include <iostream>
using namespace std;

class MyClass {
public:
    int value;
};

void modifyByValue(MyClass obj) { // 值传递,调用拷贝构造函数生成obj副本
    obj.value = 10; // 修改的是副本的属性
} // 函数结束,副本obj被销毁

int main() {
    MyClass myObj;
    myObj.value = 5;
    cout << "Before: " << myObj.value << endl; // Output: Before: 5

    modifyByValue(myObj);

    cout << "After: " << myObj.value << endl; // Output: After: 5 (未改变)
    return 0;
}

分析: 和内置类型(如int)的值传递完全一样。myObj被完整地复制了一份给形参obj。函数里修改的是副本,原对象myObj丝毫未受影响。效率较低,尤其是对于大型对象,因为涉及拷贝构造。

b) 传递一个对象指针

cpp

void modifyByPointer(MyClass *objPtr) { // 传递的是对象地址的副本
    objPtr->value = 10; // 通过地址修改原始对象的属性
    // objPtr = new MyClass(); // 如果这样做,和Java一样,只修改了地址副本,不影响实参指针
    // delete ...; // 需要手动管理新对象的内存,容易出错
}

int main() {
    MyClass myObj;
    myObj.value = 5;
    cout << "Before: " << myObj.value << endl; // Output: Before: 5

    modifyByPointer(&myObj); // 传递myObj的地址

    cout << "After: " << myObj.value << endl; // Output: After: 10 (已改变)
    return 0;
}

分析: 传递的是指针(地址)的值。objPtr&myObj这个地址值的一个副本。通过这个地址副本,可以找到并修改原始对象myObj效果上是“引用传递”的效果,但机制上仍然是“值传递”(传递的是指针这个值本身)

c) 引用传递一个对象

cpp

void modifyByReference(MyClass &objRef) { // objRef是myObj的别名(引用)
    objRef.value = 10; // 直接操作原始对象
    // objRef = MyClass(...); // 这里赋值会直接改变原始对象!
}

int main() {
    MyClass myObj;
    myObj.value = 5;
    cout << "Before: " << myObj.value << endl; // Output: Before: 5

    modifyByReference(myObj); // 直接传递对象本身,语法更简洁

    cout << "After: " << myObj.value << endl; // Output: After: 10 (已改变)
    return 0;
}

分析这是真正的“引用传递”。形参objRef不是副本,而是实参myObj的一个别名。它们根本就是同一个东西。函数内对objRef的任何操作都等价于直接对myObj操作。语法简洁,效率高(无拷贝开销),是C++中修改入参的推荐方式


3. C 语言的情况

C语言没有引用和类的概念,只有结构体(struct)和指针。

a) 将结构体作为参数值传递

c

#include <stdio.h>

typedef struct {
    int value;
} MyStruct;

void modifyStruct(MyStruct s) { // 值传递,发生结构体拷贝
    s.value = 10; // 修改的是副本
}

int main() {
    MyStruct myStruct;
    myStruct.value = 5;
    printf("Before: %d\n", myStruct.value); // Output: Before: 5

    modifyStruct(myStruct);

    printf("After: %d\n", myStruct.value); // Output: After: 5 (未改变)
    return 0;
}

分析: 和C++的值传递对象完全一样。整个结构体的内容被复制到形参s中,修改副本不影响原始结构体。对于大型结构体,性能开销很大

b) 将结构体指针传递

c

void modifyStructByPointer(MyStruct *sPtr) { // 传递结构体指针(地址)
    sPtr->value = 10; // 通过指针(地址)修改原始结构体
}

int main() {
    MyStruct myStruct;
    myStruct.value = 5;
    printf("Before: %d\n", myStruct.value); // Output: Before: 5

    modifyStructByPointer(&myStruct); // 传递myStruct的地址

    printf("After: %d\n", myStruct.value); // Output: After: 10 (已改变)
    return 0;
}

传参为结构体数组

int delete_contact(struct person contacts[], int count){
	//传入的是数组的首地址。也就是指针指向这个地址
}

分析: 这是C语言中实现“函数内部修改外部结构体”的标准做法。机制和效果与C++的指针传递完全相同。传递的是地址值的副本,通过这个地址副本可以找到并修改原始数据。


总结与对比

语言传递方式语法示例是否修改原对象效率说明
Java对象引用的值传递modify(Object obj)可以修改对象内容传递引用的副本。可修改指向的对象,但不能让实参指向新对象。
C++对象值传递modify(Class obj)不能创建完整副本,修改不影响原对象。
C++对象指针的值传递modify(Class *objPtr)可以传递指针副本。通过指针修改原对象。机制同C。
C++对象引用传递modify(Class &objRef)可以推荐方式。形参是实参的别名,操作的是原对象。
C结构体值传递modify(Struct s)不能创建完整副本,修改不影响原结构体。
C结构体指针传递modify(Struct *sPtr)可以标准做法。传递指针副本,通过指针修改原结构体。

核心记忆点:

  1. Java: 只有一种方式(值传递引用),效果是“半修改”(能改内容,不能改指向)。
  2. C++: 有三种方式。想要在函数内修改外部对象,优先使用引用传递(&)。指针传递也可以,但语法更繁琐。值传递通常不用于需要修改的场景。
  3. C: 只有两种方式。必须使用指针传递(*)来修改外部结构体。值传递只用于不需要修改且结构体很小的场景。

针对java:传参可以理解成指针的副本,myClass clazz = new myClass(), 将clazz参数传入方法。可以类比成,clazz是一个指针指向一个对象(new myClass()),传入时类比成新建了一个指针也只向new myClass()。所以就能修改属性。

针对java:基本数据类型,int,long,char之类的8种基本数据类型。采用的是真正的值传递