核心概念:值传递(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
}
}
分析:
myObj是一个引用变量,它的值是一个指向堆中MyClass对象的地址。- 调用
modifyObject(myObj)时,传递的是myObj这个引用变量里存储的地址值的副本。所以形参obj和实参myObj指向同一个对象。 obj.value = 10;通过副本地址找到了原始对象,并修改了它的属性。所以外部的myObj.value也变成了10。obj = new MyClass();这一步是让形参obj这个副本指向了一个全新的对象。这与外部的myObj已经完全无关了。myObj仍然指向原来的对象。- 结论: 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) | 可以 | 高 | 标准做法。传递指针副本,通过指针修改原结构体。 |
核心记忆点:
- Java: 只有一种方式(值传递引用),效果是“半修改”(能改内容,不能改指向)。
- C++: 有三种方式。想要在函数内修改外部对象,优先使用引用传递(
&)。指针传递也可以,但语法更繁琐。值传递通常不用于需要修改的场景。 - C: 只有两种方式。必须使用指针传递(
*)来修改外部结构体。值传递只用于不需要修改且结构体很小的场景。
针对java:传参可以理解成指针的副本,myClass clazz = new myClass(), 将clazz参数传入方法。可以类比成,clazz是一个指针指向一个对象(new myClass()),传入时类比成新建了一个指针也只向new myClass()。所以就能修改属性。
针对java:基本数据类型,int,long,char之类的8种基本数据类型。采用的是真正的值传递