区分:传值返回,传指针返回,穿引用返回

在学习的时候,容易搞混这三种返回方式,所以博主用这一篇来帮助大家区分

目录

一 什么是传值返回?什么是传指针返回?什么是传引用返回?

1. 传值返回

2. 传指针返回

3. 传引用返回(C++ 特有)

二 区别

三 Java只有值传递

1. 对基本类型的传递:传递的是 “值的副本”

2. 对引用类型的传递:传递的是 “引用地址的副本”

为什么说 Java 只有值传递?

四 区分

1 传值返回:

2 传指针返回

使用场景

3 传引用返回

一、语法形式与含义

1. 传引用返回中的 &(引用声明符)

2. 指针中的 &(取地址运算符)

二、本质与操作方式

1. 传引用返回的 &:绑定原变量

2. 指针的 &:获取地址

三、使用场景与限制

1. 传引用返回的 &

2. 指针的 &

使用

1. 返回全局 / 静态变量的引用

2. 返回对象成员的引用(实现链式修改)

3. 实现流操作符重载(标准库常用)

注意事项:

五 总结


在编程中,传值返回、传指针返回和传引用返回是函数返回数据的三种不同方式,主要区别在于返回的是数据本身、数据的地址还是数据的引用。

一 什么是传值返回?什么是传指针返回?什么是传引用返回?

1. 传值返回

定义:函数执行结束后,返回的是目标数据的独立副本—— 即先将函数内的返回值(如局部变量、计算结果)复制一份,再把这份副本传递给调用者;函数内原有的数据(如局部变量)会在函数生命周期结束后被销毁,调用者操作的副本与原数据完全独立。

2. 传指针返回

定义:函数返回的不是数据本身,而是目标数据在内存中的地址(即指针)。调用者获取该指针后,需通过 “解引用” 操作(如 C/C++ 中的 * 运算符),才能间接访问或修改指针指向的内存中的原数据;返回的指针需指向 “生命周期不随函数结束而销毁” 的内存(如静态变量、全局变量、动态分配的内存),否则会成为无效的 “野指针”。

3. 传引用返回(C++ 特有)

定义:函数返回的是目标数据的 “引用”(即数据的别名)—— 引用本质上是对原数据的 “绑定”,不额外占用内存(可理解为 “带类型的指针简化形式”)。调用者获取引用后,可直接通过引用操作原数据(无需解引用);与指针类似,不能返回函数局部变量的引用(局部变量销毁后,引用会失效),通常返回全局变量、静态变量或函数参数的引用

二 区别

关键差异总结:

  1. 数据关联性
    传值返回是 “副本独立”,修改返回值不影响原数据;
    传指针 / 引用返回是 “直接关联原数据”,修改会影响源头。

  2. 效率与风险平衡
    传值安全但效率低(复制开销);
    传指针 / 引用高效但有风险(需确保指向的内存长期有效,否则会导致程序崩溃)。

  3. 语法便捷性
    传引用返回比指针更简洁(无需解引用操作*),但仅在 C++ 中支持;
    指针是 C/C++ 通用机制,兼容性更广但语法稍繁琐。

三 Java只有值传递

在 Java 中,“只有值传递” 是一个核心特性,指的是函数传参时,传递的始终是实际参数的 “值副本”,而非参数本身(或其内存地址的直接引用)。这与 C++ 中的 “引用传递” 有本质区别,具体可从以下两方面理解:

1. 对基本类型的传递:传递的是 “值的副本”

当参数是基本类型(如 intcharboolean 等)时,函数接收的是该值的独立副本。在函数内部修改这个副本,不会影响原变量的值。

示例:

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 &num; // 正确:返回的地址始终合法
}

3 传引用返回

传引用返回时c++特有的,其他语言不能使用

注意:传引用返回的操作符为&,和取地址的操作符一样,注意区分

区分:

在 C++ 中,& 符号在不同场景下含义不同,尤其在传引用返回指针取地址时容易混淆。两者的核心区别在于:传引用返回中的 & 是 “引用声明符”,表示返回的是变量的引用(别名);指针中的 & 是 “取地址运算符”,表示获取变量的内存地址。具体区分如下:

一、语法形式与含义
1. 传引用返回中的 &(引用声明符)
  • 位置:出现在函数返回类型后,用于声明 “函数返回的是引用”。
  • 含义:表示函数返回的是某变量的引用(别名),而非副本或地址。
  • 语法示例
int global = 100;

// 函数返回类型后加&,表示返回引用
int& getGlobalRef() { 
    return global; // 返回global的引用(别名)
}
  • 这里的 int& 是 “引用类型”,& 是类型的一部分,表明函数返回的是 int 类型的引用。
2. 指针中的 &(取地址运算符)
  • 位置:出现在变量前,用于获取该变量的内存地址。
  • 含义:返回变量在内存中的地址值(可赋值给指针变量)。
  • 语法示例
int num = 20;
int* ptr = &num; // &是取地址运算符,获取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 = &num; // 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(区别于指针),因此返回前需确保目标数据存在。

五 总结

使用选择原则

  1. 优先传值返回:基本类型、小型结构体,或需返回独立数据(避免外部修改)时。
  2. 传指针返回:返回动态分配的大型数据、全局变量,或需兼容 C 语言时(C 无引用)。
  3. 传引用返回:C++ 中需简化语法、实现链式操作,或明确允许外部修改原数据时。

核心原则:确保返回的数据(或其地址 / 引用)在函数结束后仍有效,并根据 “是否允许修改原数据”“性能开销”“语法简洁性” 综合选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值