在学习的时候,容易搞混这三种返回方式,所以博主用这一篇来帮助大家区分
目录
在编程中,传值返回、传指针返回和传引用返回是函数返回数据的三种不同方式,主要区别在于返回的是数据本身、数据的地址还是数据的引用。
一 什么是传值返回?什么是传指针返回?什么是传引用返回?
1. 传值返回
定义:函数执行结束后,返回的是目标数据的独立副本—— 即先将函数内的返回值(如局部变量、计算结果)复制一份,再把这份副本传递给调用者;函数内原有的数据(如局部变量)会在函数生命周期结束后被销毁,调用者操作的副本与原数据完全独立。
2. 传指针返回
定义:函数返回的不是数据本身,而是目标数据在内存中的地址(即指针)。调用者获取该指针后,需通过 “解引用” 操作(如 C/C++ 中的 *
运算符),才能间接访问或修改指针指向的内存中的原数据;返回的指针需指向 “生命周期不随函数结束而销毁” 的内存(如静态变量、全局变量、动态分配的内存),否则会成为无效的 “野指针”。
3. 传引用返回(C++ 特有)
定义:函数返回的是目标数据的 “引用”(即数据的别名)—— 引用本质上是对原数据的 “绑定”,不额外占用内存(可理解为 “带类型的指针简化形式”)。调用者获取引用后,可直接通过引用操作原数据(无需解引用);与指针类似,不能返回函数局部变量的引用(局部变量销毁后,引用会失效),通常返回全局变量、静态变量或函数参数的引用
二 区别
关键差异总结:
-
数据关联性:
传值返回是 “副本独立”,修改返回值不影响原数据;
传指针 / 引用返回是 “直接关联原数据”,修改会影响源头。 -
效率与风险平衡:
传值安全但效率低(复制开销);
传指针 / 引用高效但有风险(需确保指向的内存长期有效,否则会导致程序崩溃)。 -
语法便捷性:
传引用返回比指针更简洁(无需解引用操作*
),但仅在 C++ 中支持;
指针是 C/C++ 通用机制,兼容性更广但语法稍繁琐。
三 Java只有值传递
在 Java 中,“只有值传递” 是一个核心特性,指的是函数传参时,传递的始终是实际参数的 “值副本”,而非参数本身(或其内存地址的直接引用)。这与 C++ 中的 “引用传递” 有本质区别,具体可从以下两方面理解:
1. 对基本类型的传递:传递的是 “值的副本”
当参数是基本类型(如 int
、char
、boolean
等)时,函数接收的是该值的独立副本。在函数内部修改这个副本,不会影响原变量的值。
示例:
public static void main(String[] args) {
int num = 10;
changeValue(num);
System.out.println(num); // 输出 10(原变量未被修改)
}
public static void changeValue(int x) {
x = 20; // 修改的是副本x,与原变量num无关
}
2. 对引用类型的传递:传递的是 “引用地址的副本”
当参数是引用类型(如对象、数组、字符串等)时,变量存储的是对象在堆内存中的地址(引用)。传参时,传递的是这个 “地址的副本”—— 函数内部的形参和外部的实参,会指向同一个对象。
此时,若在函数内部修改对象的属性,会影响原对象(因为操作的是同一个内存地址指向的内容);但如果修改形参的引用指向(让它指向新对象),则不会影响原变量的引用。
示例:
class Person {
String name;
Person(String name) { this.name = name; }
}
public static void main(String[] args) {
Person p = new Person("Alice");
changeName(p);
System.out.println(p.name); // 输出 "Bob"(对象属性被修改)
changeReference(p);
System.out.println(p.name); // 仍输出 "Bob"(原引用未变)
}
// 修改对象属性(影响原对象)
public static void changeName(Person obj) {
obj.name = "Bob"; // obj和p指向同一个对象,修改属性会同步
}
// 修改形参的引用指向(不影响原变量)
public static void changeReference(Person obj) {
obj = new Person("Charlie"); // obj指向新对象,与原p无关
}
为什么说 Java 只有值传递?
-
核心判断标准:传递的是 “副本” 还是 “原数据本身”。
无论是基本类型(传递值的副本)还是引用类型(传递地址的副本),Java 始终传递的是 “副本”,而非原变量或原地址本身。
即便引用类型能修改对象内容,本质也是因为 “副本地址” 与原地址指向了同一个对象,而非传递了 “原引用”。 -
与 C++ 引用传递的区别:
C++ 的引用传递(void func(int &x)
)中,形参是原变量的 “别名”,操作形参等同于直接操作原变量;而 Java 中不存在这种 “直接绑定原变量” 的机制,因此没有真正的引用传递。
总结:Java 的传参机制可概括为 ——“基本类型传值的副本,引用类型传地址的副本”,本质上都是 “值传递”。
四 区分
1 传值返回:
传值返回适合用于不修改原始数据的传递,因为只是把值传递了给形参,而实参的地址没有传,所以如果需要修改数据的话,不能使用传值返回
注意:传值返回的是临时对象,当函数销毁的时候,对应的临时对象也会销毁
以下是传值返回的错误使用:
#include <iostream>
using namespace std;
// 传值返回:返回局部变量的副本
int getNum() {
int num = 10; // 局部变量
return num; // 返回num的副本
}
int main() {
// 错误:试图通过修改返回的副本来改变原数据(原数据已销毁)
getNum() = 20; // 编译错误(临时副本不可修改)
// 即使先接收副本再修改,也与原数据无关
int x = getNum(); // x是副本(值为10)
x = 20; // 仅修改副本x,原函数中的num早已销毁
cout << getNum() << endl; // 输出10(原逻辑未变)
return 0;
}
所以,传值返回只适用于小型的场景,容易出现错误
所以,如果需要修改数据,通过参数传递指针或引用更合适
2 传指针返回
使用场景
(1):返回动态分布的内存:通过new(c++)或malloc(c)创建的内存,生命周期由开发者控制,适合返回大型数据
示例:
// 返回动态分配的数组(避免复制整个数组)
int* createArray(int size) {
int* arr = new int[size]; // 堆上分配内存,函数结束后不销毁
for (int i = 0; i < size; i++) {
arr[i] = i * 10;
}
return arr; // 返回数组地址(指针)
}
// 调用:必须手动释放内存,否则泄漏
int* myArr = createArray(5); // 接收指针,指向堆上的数组
// 使用数组...
delete[] myArr; // 用完必须释放,否则内存泄漏
(2)返回全局/静态变量的地址
全局变量或静态变量的地址与生命周期一致,返回其指针安全且无需管理内存
// 全局变量
int globalCount = 0;
// 返回全局变量的指针
int* getGlobalCount() {
return &globalCount; // 全局变量地址有效
}
// 调用:通过指针修改全局变量
int* countPtr = getGlobalCount();
*countPtr = 10; // 直接修改globalCount的值
注意:严禁返回局部变量的指针:局部变量在函数结束后被销毁,返回的指针变成野指针,访问可能导致指针崩溃
什么是野指针?
野指针是指向非法内存地址的指针变量,它既不指向有效,可访问的数据,也不指向空地址,使用时会触发不可预期的错误
核心特质:
1 指向非法地址:可能指向已释放的内存,未分配的内存,系统保护的内存(如内核内存)
2 状态未定义:编译器无法检测野指针(语法合法),运行时才会暴露问题,错误具有随机性(看似正常,实则已破坏内存)
3 不可安全使用:对野指针进行解引用(*ptr),赋值(*ptr = 10),或访问成员(ptr->age),都会触发未定义行为
注意:
1 定义指针变量的时候,若未显示赋值,其值会是随机的垃圾值,直接成为野指针
正确做法:定义指针时,要么指向合法内存,要么显示赋值为NULL/nullptr(空指针,安全)
2 指针指向的内存被释放后,未置空:使用malloc/new分配的动态内存管理,若用free/delete释放后,原指针的指向不会自动失效,指针依然保存已释放的内存,但此地址已归还给系统,不允许访问,成为野指针
正确做法:释放内存后,立即将指针置为
NULL
/nullptr
,避免后续误用free(ptr); ptr = NULL; // 关键:释放后将指针置空,后续可通过判断避免误用 if (ptr != NULL) { // 此时ptr为NULL,判断为假,不会执行危险操作 *ptr = 30; }
3 指针指向的局部变量销毁后,仍被使用
正确做法:若需返回指针,应指向全局变量、静态局部变量(生命周期与程序一致)或动态分配的内存(需手动释放)
int* getValidPtr() { static int num = 10; // 静态局部变量:生命周期贯穿程序,地址长期有效 return # // 正确:返回的地址始终合法 }
3 传引用返回
传引用返回时c++特有的,其他语言不能使用
注意:传引用返回的操作符为&,和取地址的操作符一样,注意区分
区分:
在 C++ 中,&
符号在不同场景下含义不同,尤其在传引用返回和指针取地址时容易混淆。两者的核心区别在于:传引用返回中的 &
是 “引用声明符”,表示返回的是变量的引用(别名);指针中的 &
是 “取地址运算符”,表示获取变量的内存地址。具体区分如下:
一、语法形式与含义
1. 传引用返回中的 &
(引用声明符)
- 位置:出现在函数返回类型后,用于声明 “函数返回的是引用”。
- 含义:表示函数返回的是某变量的引用(别名),而非副本或地址。
- 语法示例:
int global = 100;
// 函数返回类型后加&,表示返回引用
int& getGlobalRef() {
return global; // 返回global的引用(别名)
}
- 这里的
int&
是 “引用类型”,&
是类型的一部分,表明函数返回的是int
类型的引用。
2. 指针中的 &
(取地址运算符)
- 位置:出现在变量前,用于获取该变量的内存地址。
- 含义:返回变量在内存中的地址值(可赋值给指针变量)。
- 语法示例:
int num = 20;
int* ptr = # // &是取地址运算符,获取num的地址,赋值给指针ptr
- 这里的
&num
表示 “变量num
的地址”,ptr
是指向int
类型的指针,存储了这个地址。
二、本质与操作方式
1. 传引用返回的 &
:绑定原变量
- 本质:返回的引用是原变量的 “别名”,与原变量共享同一块内存,不占用额外空间。
- 操作方式:使用引用时无需解引用,直接操作即可修改原变量。
int& ref = getGlobalRef(); // ref是global的别名
ref = 200; // 直接修改ref,等价于修改global
cout << global; // 输出200
2. 指针的 &
:获取地址
- 本质:
&
运算符获取的地址是一个数值(内存位置编号),需要用指针变量存储。 - 操作方式:通过指针访问原变量时,必须使用解引用运算符
*
int num = 20;
int* ptr = # // ptr存储num的地址
*ptr = 30; // 必须解引用指针,才能修改num的值
cout << num; // 输出30
三、使用场景与限制
1. 传引用返回的 &
- 场景:需要直接修改原变量、实现链式操作(如
cout << a << b
)、避免返回值复制开销。 - 限制:
- 不能返回局部变量的引用(局部变量销毁后,引用会悬空)。
- 引用必须绑定到有效变量,不能为
nullptr
(区别于指针)。
2. 指针的 &
- 场景:需要传递变量地址、动态内存管理(如
new
/delete
)、实现多态等。 - 限制:
- 指针可以指向
nullptr
(空指针),使用前需判断有效性。 - 若指针指向的内存被释放,需及时置空,否则会成为野指针。
- 指针可以指向
使用
1. 返回全局 / 静态变量的引用
与指针类似,但语法更简洁,无需解引用即可操作原数据。
// 静态变量(生命周期与程序一致)
static string globalStr = "Hello";
// 返回静态变量的引用
string& getGlobalStr() {
return globalStr; // 返回引用(别名)
}
// 调用:直接通过引用修改原变量
string& ref = getGlobalStr();
ref = "World"; // 直接修改globalStr的值
2. 返回对象成员的引用(实现链式修改)
在类中常用,允许连续修改对象的多个成员,简化代码。
class Student {
private:
string name;
int age;
public:
// 返回name的引用,允许外部直接修改
string& getName() { return name; }
// 返回age的引用,允许外部直接修改
int& getAge() { return age; }
};
// 调用:链式修改对象成员
Student s;
s.getName() = "Alice"; // 直接修改name
s.getAge() = 20; // 直接修改age
3. 实现流操作符重载(标准库常用)
cout << "a" << "b"
的链式调用,依赖引用返回ostream
对象。
// 重载<<,返回ostream引用以支持链式调用
ostream& operator<<(ostream& os, const string& s) {
os << s;
return os; // 返回os的引用,允许继续调用<<
}
// 调用:链式输出
cout << "Hello" << " World"; // 等价于 (cout << "Hello") << " World"
注意事项:
- 严禁返回局部变量的引用:局部变量销毁后,引用会成为 “悬空引用”,访问时行为未定义
string& badRefExample() {
string local = "test";
return local; // 错误!local销毁后,引用无效
}
- 引用返回的是原数据的 “别名”,修改引用会直接影响原数据,需谨慎使用(避免意外修改)。
- 引用必须绑定到有效对象,不能为
nullptr
(区别于指针),因此返回前需确保目标数据存在。
五 总结
使用选择原则
- 优先传值返回:基本类型、小型结构体,或需返回独立数据(避免外部修改)时。
- 传指针返回:返回动态分配的大型数据、全局变量,或需兼容 C 语言时(C 无引用)。
- 传引用返回:C++ 中需简化语法、实现链式操作,或明确允许外部修改原数据时。
核心原则:确保返回的数据(或其地址 / 引用)在函数结束后仍有效,并根据 “是否允许修改原数据”“性能开销”“语法简洁性” 综合选择。