返回顶部
分享到

C++中的引用传参和指针传参的区别介绍

C语言 来源:互联网 作者:佚名 发布时间:2026-02-23 08:09:48 人浏览
摘要

核心概念对比 概念 C++ 前端 (JS/TS) 值传递 默认方式,完整拷贝 基本类型 (number, string等) 引用传递 需显式使用 或指针 对象、数组、函数等引用类型 指针传递 传递内存地址 类似传递对象的引用

核心概念对比

概念 C++ 前端 (JS/TS)
值传递 默认方式,完整拷贝 基本类型 (number, string等)
引用传递 需显式使用 & 或指针 对象、数组、函数等引用类型
指针传递 传递内存地址 类似传递对象的引用

1. 值传递(默认)- 完整拷贝

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

#include <iostream>

using namespace std;

 

struct Point {

    int x, y;

    Point(int a, int b) : x(a), y(b) {

        cout << "构造: (" << x << "," << y << ")" << endl;

    }

    Point(const Point& p) : x(p.x), y(p.y) {

        cout << "拷贝构造: (" << x << "," << y << ")" << endl;

    }

    ~Point() {

        cout << "析构: (" << x << "," << y << ")" << endl;

    }

};

 

// ? 值传递:发生完整拷贝

void byValue(Point p) {

    p.x = 100;  // 修改的是副本,不影响原对象

    cout << "函数内: (" << p.x << "," << p.y << ")" << endl;

}

 

int main() {

    Point p1(1, 2);

    cout << "--- 调用函数 ---" << endl;

    byValue(p1);

    cout << "--- 函数返回 ---" << endl;

    cout << "原对象: (" << p1.x << "," << p1.y << ")" << endl;  // 仍是 (1,2)

    return 0;

}

输出:

构造: (1,2)
--- 调用函数 ---
拷贝构造: (1,2)
函数内: (100,2)
析构: (100,2)
--- 函数返回 ---
原对象: (1,2)
析构: (1,2)

?? 性能问题:大对象会发生昂贵的深拷贝

2. 引用传递(类似前端引用类型)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

// ? 引用传递:不拷贝,直接操作原对象(类似前端的对象引用)

void byReference(Point& p) {

    p.x = 100;  // 直接修改原对象!

    cout << "函数内: (" << p.x << "," << p.y << ")" << endl;

}

 

// ? const 引用:只读访问,不拷贝也不能修改

void byConstReference(const Point& p) {

    // p.x = 100;  // ? 编译错误:不能修改const引用

    cout << "只读访问: (" << p.x << "," << p.y << ")" << endl;

}

 

int main() {

    Point p1(1, 2);

     

    cout << "=== 引用传递 ===" << endl;

    byReference(p1);

    cout << "原对象已被修改: (" << p1.x << "," << p1.y << ")" << endl;  // (100,2)

     

    cout << "\n=== const引用 ===" << endl;

    byConstReference(p1);

     

    return 0;

}

与前端对比:

1

2

3

4

5

6

7

8

9

// JavaScript - 对象天然是引用传递

function modify(obj) {

    obj.x = 100;  // 直接修改原对象

}

let p = {x: 1, y: 2};

modify(p);

console.log(p.x);  // 100 ?

 

// C++ 必须显式加 & 才能实现同样效果

3. 指针传递(C风格,更灵活但危险)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

// 指针传递:传递内存地址

void byPointer(Point* p) {

    if (p != nullptr) {  // 必须检查空指针!

        p->x = 200;      // 使用 -> 访问成员

    }

}

 

// 指针的const版本

void byConstPointer(const Point* p) {

    // p->x = 200;  // ? 不能修改

    cout << p->x << endl;  // ? 可以读取

}

 

int main() {

    Point p1(1, 2);

     

    byPointer(&p1);  // 必须取地址

    cout << "修改后: (" << p1.x << "," << p1.y << ")" << endl;

     

    // 可以传 nullptr(这是与引用的重要区别)

    byPointer(nullptr);  // ? 合法

     

    return 0;

}

引用 vs 指针对比:

特性 引用 & 指针 *
语法 更简洁,像原对象 需解引用 -> 或 *
空值 不能为 null 可以为 nullptr
重新绑定 不能改指向其他对象 可以指向不同对象
安全性 更高(必须初始化) 需检查空指针

4. 返回值优化(RVO)与移动语义

传统返回值(有拷贝)

1

2

3

4

5

6

7

8

// ? C++11前:返回时会发生拷贝(或编译器RVO优化)

Point createPoint() {

    Point p(3, 4);

    return p;  // 理论上会拷贝,但编译器通常优化掉

}

 

// 接收时再次拷贝

Point p2 = createPoint();

现代C++:移动语义(C++11起)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

struct BigData {

    int* data;

    size_t size;

     

    // 移动构造函数(转移资源所有权,不拷贝数据)

    BigData(BigData&& other) noexcept

        : data(other.data), size(other.size) {

        cout << "移动构造(偷资源)" << endl;

        other.data = nullptr;  // 置空源对象

        other.size = 0;

    }

     

    // 移动赋值

    BigData& operator=(BigData&& other) noexcept {

        cout << "移动赋值" << endl;

        if (this != &other) {

            delete[] data;

            data = other.data;

            size = other.size;

            other.data = nullptr;

        }

        return *this;

    }

};

 

BigData createBigData() {

    BigData bd(1000000);  // 分配大量内存

    return bd;  // 触发移动语义,而非深拷贝!

}

 

int main() {

    BigData bd1 = createBigData();  // 移动构造,几乎零开销

    BigData bd2;

    bd2 = createBigData();          // 移动赋值

    return 0;

}

5. 最佳实践总结

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

// ? 最佳实践速查表

 

// 1. 小对象(int, double, 小结构体):直接值传递

void f(int x, Point p); 

 

// 2. 大对象只读访问:const 引用(性能+安全)

void process(const BigData& data);

 

// 3. 需要修改原对象:非const引用

void modify(Point& p);

 

// 4. 参数可选(可能为空):指针

void maybeProcess(BigData* data);  // data可以是nullptr

 

// 5. 转移所有权(C++11):右值引用

void takeOwnership(BigData&& data);

 

// 6. 返回值:优先返回值(依赖RVO/移动),而非输出参数

BigData create();  // ? 现代C++推荐

void create(BigData& out);  // 旧式风格,避免

类比总结

1

2

3

4

5

6

// C++ 显式控制传递方式

void f(Point p);      // 值传递(拷贝)    ← 像 JS 的 structuredClone

void f(Point& p);     // 引用传递(别名)   ← 像 JS 的对象引用

void f(const Point& p); // const引用       ← 像 JS 的只读引用

void f(Point* p);     // 指针传递         ← 像 JS 的弱引用/可空引用

void f(Point&& p);    // 右值引用(移动)   ← 像 JS 的 对象转移/解构赋值

关键区别:C++ 默认是值语义(拷贝),必须显式使用 & 才能获得引用语义;而前端(JS)对象默认就是引用语义。

以下深入剖析 C++ 中引用传递和指针传递的本质差异。

核心对比表

维度 引用传递 T& 指针传递 T*
本质 别名(alias),必须绑定有效对象 内存地址,可独立存在
空值 ? 不能为 null ? 可以为 nullptr
重新绑定 ? 终身绑定,不能改指向 ? 可随时指向其他对象
语法 使用原对象语法 . 需解引用 -> 或 *
初始化 必须初始化 可以延迟初始化
内存占用 通常优化为无开销 占用指针大小(4/8字节)

本质差异图解

1

2

3

4

5

6

引用 (&)                    指针 (*)

┌─────────┐                ┌─────────┐

│  引用变量  │ ──→ 对象      │ 指针变量  │ ──→ 对象

│  (别名)   │   (必须存在)   │ (地址)   │   或 ──→ nullptr

└─────────┘                └─────────┘

   语法糖,编译器处理           真正的内存实体

代码层面深度对比

1. 基础语法差异

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

#include <iostream>

using namespace std;

 

struct Config {

    int port;

    string host;

};

 

// ========== 引用版本 ==========

void setupByRef(Config& cfg) {

    // 语法:直接使用对象

    cfg.port = 8080;

    cfg.host = "localhost";

     

    // ? 不能检查是否为空(假设一定存在)

    // if (&cfg == nullptr) {}  // 无意义,引用不能为null

}

 

// ========== 指针版本 ==========

void setupByPtr(Config* cfg) {

    // 语法:必须先检查空指针

    if (cfg == nullptr) {

        cerr << "错误:配置为空" << endl;

        return;

    }

     

    // 解引用方式1:->

    cfg->port = 8080;

     

    // 解引用方式2:*(获取引用后再用.)

    (*cfg).host = "localhost";

}

 

int main() {

    Config c{0, ""};

     

    // 引用调用:简单直接

    setupByRef(c);

     

    // 指针调用:必须取地址

    setupByPtr(&c);

    setupByPtr(nullptr);  // ? 可以传空,函数内部处理

     

    return 0;

}

2. 重新绑定能力(关键差异)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

void demonstrateRebinding() {

    int a = 10, b = 20;

     

    // ========== 引用:终身绑定 ==========

    int& ref = a;

    ref = b;      // ? 这不是让ref指向b!而是把b的值赋给a

                  // 结果:a = 20, ref仍是a的别名

     

    // 想改指向?不可能!

    // &ref = b;   // 编译错误!

     

    // ========== 指针:灵活重定向 ==========

    int* ptr = &a;

    cout << *ptr;  // 10

     

    ptr = &b;      // ? 随时改指向

    cout << *ptr;  // 20

     

    ptr = nullptr; // ? 可以指向空

}

3. 多级间接访问

1

2

3

4

5

6

7

8

9

10

11

12

13

// 指针可以指向指针,引用不行

void pointerChain() {

    int x = 42;

    int* p = &x;

    int** pp = &p;      // ? 指针的指针

    int*** ppp = &pp;   // ? 可以无限套娃

     

    cout << ***ppp;     // 42

     

    // 引用只有一级(引用的引用被折叠)

    int& r = x;

    // int&& rr = r;    // 这是右值引用,不是引用的引用!

}

本质差异:编译器视角

1

2

3

4

5

6

7

8

9

// 源代码

void byRef(int& r) { r = 10; }

void byPtr(int* p) { *p = 10; }

 

int main() {

    int x = 5;

    byRef(x);

    byPtr(&x);

}

编译后的伪代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

; 引用版本(编译器优化后,与指针相同)

byRef:

    mov eax, [esp+4]    ; 取地址(与指针一样!)

    mov dword [eax], 10 ; 解引用赋值

     

; 指针版本

byPtr:

    mov eax, [esp+4]    ; 取地址

    test eax, eax       ; ? 检查空指针(编译器可能优化掉)

    je .null_handler

    mov dword [eax], 10

     

; 结论:引用是指针的语法糖,但编译器保证非空

本质真相:引用在底层就是指针,但编译器添加了非空约束和自动解引用的语法糖。

适用场景详解

? 引用传递适用场景

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

// 场景1:参数必须存在(业务逻辑要求)

void connectDatabase(const ConnectionConfig& config) {

    // 配置必须提供,不存在"无配置"的情况

    // 无需检查空,简化代码

}

 

// 场景2:操作符重载(只能用引用)

class Vector {

public:

    // 必须返回引用才能链式赋值:v1 = v2 = v3

    Vector& operator=(const Vector& other) {

        // ...

        return *this;  // 解引用返回引用

    }

     

    // 下标运算符

    int& operator[](size_t index) {

        return data[index];  // 必须返回引用才能:v[0] = 10

    }

};

 

// 场景3:函数式编程风格(明确所有权)

void sort(vector<int>& data);           // 修改原数据

void print(const vector<int>& data);    // 只读访问

? 指针传递适用场景

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

// 场景1:参数可选(可为空)

void logMessage(const string* prefix = nullptr) {

    if (prefix) {

        cout << "[" << *prefix << "] ";

    }

    cout << "日志内容" << endl;

}

// 调用:logMessage(); 或 logMessage(&debugTag);

 

// 场景2:动态内存管理(所有权转移)

void processLargeFile(unique_ptr<FileData>* filePtr) {

    // 可能转移所有权,或释放内存

    if (filePtr && *filePtr) {

        auto data = std::move(*filePtr);  // 接管所有权

        // filePtr 现在指向空unique_ptr

    }

}

 

// 场景3:C接口兼容/多态数组

void drawShapes(Shape** shapes, size_t count) {

    // 指针数组,支持运行时多态

    for (size_t i = 0; i < count; ++i) {

        shapes[i]->draw();  // 虚函数调用

    }

}

 

// 场景4:需要重新指向(迭代、遍历)

Node* findNode(Node* head, int value) {

    Node* current = head;      // 从head开始

    while (current) {

        if (current->val == value) return current;

        current = current->next;  // ? 指针可以遍历

    }

    return nullptr;

}

// 如果用引用:Node& current = head; current = ... 会修改原head!

现代C++的最佳实践

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

// ???? 参数传递决策树

 

// 1. 小对象(<= 2个指针大小):值传递

void f(int x, Point p);

 

// 2. 只读大对象:const 引用(首选)

void process(const BigData& data);

 

// 3. 必须修改原对象:非const 引用

void modify(Config& cfg);

 

// 4. 参数可选/可为空:指针

void render(const Camera* cam = nullptr);

 

// 5. 转移所有权(C++11):右值引用

void consume(vector<int>&& data);

 

// 6. 动态数组/多态:智能指针

void loadModel(shared_ptr<Mesh> mesh);      // 共享所有权

void takeData(unique_ptr<Buffer> buffer);   // 独占所有权

一句话总结

引用是"承诺存在的别名",指针是"可能为空的地址"。

用引用表达契约(必须存在),用指针表达可选性(可能为空)。


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 :
相关文章
  • C++中的引用传参和指针传参的区别介绍
    核心概念对比 概念 C++ 前端 (JS/TS) 值传递 默认方式,完整拷贝 基本类型 (number, string等) 引用传递 需显式使用 或指针 对象、数组、函数等引
  • C++中std::functional使用场景
    std::functional 是 C++ 标准库中一个非常强大的工具,它提供了一种**类型擦除(type erasure)**机制,让你能够存储、传递和调用任何可调用对象
  • C++入门指南:零基础入门教学
    学习C++就像学习一门新的语言,需要从字母开始,逐步构建句子和段落。本文就是你的C++字母表。本文介绍了C++编程语言的基本概念和学习
  • C++ io_uring的使用
    io_uring是 Linux 内核在 5.1 版本引入的一套全新的、高性能的异步 I/O (Asynchronous I/O) 接口。它的出现是为了解决旧有的epoll和linux-aio在面对现代
  • C++特有的bool变量使用
    C++中的bool类型 在C++中,bool是一种基本数据类型,专门用于表示布尔值(真或假)。它是C++语言特有的布尔类型,与C语言中使用整数模拟布
  • C++右值引用(rvalue references)与移动语义(move semant
    一、右值引用(rvalue references)与移动语义(move semantics)设计动机 1.1 为什么需要移动语义 传统 C++ 的对象拷贝(copy)在管理资源(堆内存
  • c++日志库log4cplus快速入门

    c++日志库log4cplus快速入门
    log4j 用于Java,log4cplus从它衍生而来,用于c++。 用于c++的日志库还有很多,如 log4cxx等,可以根据实际需求选择使用。 log4cplus 的地址:http
  • 从入门到精通C++11 <chrono> 库特性

    从入门到精通C++11 <chrono> 库特性
    在 C++11 标准中,引入了许多新的库特性,其中chrono库为时间处理提供了强大而灵活的支持。这个库使得在 C++ 中处理时间变得更加方便和精
  • C++ Sort函数使用场景分析

    C++ Sort函数使用场景分析
    C++ Sort函数详解 前言 :sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某
  • 使用DeepSeek API 结合VSCode提升开发效率

    使用DeepSeek API 结合VSCode提升开发效率
    在当今的软件开发领域,API 的使用已经成为不可或缺的一部分。DeepSeek 是一个强大的 API 平台,提供了丰富的功能和数据,可以帮助开发者
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计