学而时习之:C++中的引用

C++ 中的引用

在 C++ 里,引用(reference)相当于给已有变量起了一个"别名",通过这个名字可以直接操作原变量的数据。

示例代码:

cpp 复制代码
#include <iostream>
using namespace std;

int main() {
    int x = 10;
    int& ref = x; // ref 是 x 的引用
    cout << ref << endl; // 通过引用打印值
    ref = 22;  // 修改引用指向的值,再打印
    cout << ref;
    return 0;
}
复制代码
输出:  
10  
22

解释:

在这段程序中,ref 是变量 x 的引用,也就是说 ref 只是 x 的另一个名字。当 ref 的值被修改时,x 的值也随之改变,因为 refx 指向同一块内存地址。

如果你之前用过指针,会发现引用和指针有些相似------它们都指向同一块内存。但引用提供了一种更简单、更易读的方式来给变量起别名,而且不需要使用 * 运算符。

1.语法

使用 & 可以创建一个变量的引用,形式如下:

cpp 复制代码
T &ref = var;

这里,`ref` 就是变量 `var` 的引用,类型为 `T`。

关于引用,必须牢记以下几点:

  1. 创建引用时必须立即初始化。
  2. 引用一旦声明,就不能再改指其他变量。

2.使用场景

在 C++ 中,引用有多种用途,下面列出几种常见场景:

(1)按引用传递参数

引用常用于函数参数,使得函数内部可以直接修改调用者传来的原始变量。对于占用内存较大的数据结构,还能避免复制,提高效率。

示例代码:

cpp 复制代码
#include <iostream>
using namespace std;

void modifyValue(int &x) {
    x = 20;// 直接修改原始变量
}

int main() {
    int a = 10;
    modifyValue(a);// 把 a 以引用方式传进去
    cout << a;
    return 0;
}
复制代码
输出:  
20

解释:
modifyValue() 通过引用直接修改了变量 a,过程中没有产生任何副本,因此原始变量被成功改写。

(2)从函数返回引用

在 C++ 中,函数可以返回变量的引用。这在需要返回占用内存较大的数据结构,或希望直接修改函数内部某个变量时特别有用。

示例代码:

cpp 复制代码
#include <iostream>
using namespace std;

int& getMax(int &a, int &b) {
    // 返回两个数中较大的那个的引用
    return (a > b) ? a : b;
}

int main() {
    int x = 10, y = 20;
    int &maxVal = getMax(x, y);
    // 直接修改较大的那个数
    maxVal = 30;
    cout << "x = " << x << ", y = " << y;
    return 0;
}
ini 复制代码
输出:  
x = 10, y = 30

解释:getMax() 返回的是两个整数中较大者的引用。由于 maxVal 是这个引用的别名,修改 maxVal 就等于修改了原来的变量 y

注意:不要返回局部变量的引用,因为函数执行完毕后,这些局部变量会被销毁,导致引用指向无效内存。

(3)在范围 for 循环中修改数据

范围 for 循环通常用于遍历容器。如果我们想在遍历的同时修改元素,就需要使用引用。

示例代码:

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> vect{ 10, 20, 30, 40 };
    // 使用引用即可修改元素
    for (int& x : vect) {
        x = x + 5;
    }

    // 打印修改后的结果
    for (int x : vect) {
        cout << x << " ";
    }
    return 0;
}
复制代码
输出:  
15 25 35 45

3.引用的限制

  • 一旦引用被绑定到某个对象,就不能再改绑到另一个对象;引用无法"重置"。这一点通常由指针来完成。
  • 引用不能为 NULL。指针可以赋值为 NULL 来表示"未指向任何有效对象"。
  • 引用在声明时必须立即初始化。指针则没有这条限制。

由于上述限制,C++ 的引用无法用来实现链表、树等动态数据结构;而这些结构在 Java 里完全可以靠引用实现。Java 的引用没有上述约束,功能更强大,这也是 Java 不需要指针的根本原因。

4.使用引用的优点

(1)更安全

引用必须初始化,因此像"野指针"那样的"野引用"几乎不可能出现(当然,仍然可能通过某些手段让引用绑定到无效内存,但概率远低于指针)。

(2)更易用

  • 访问引用指向的值时无需写解引用符号 *;用法跟普通变量完全一样。
  • 声明时只需写一次 &,之后全程当普通变量用。
  • 访问对象成员直接用点号 .;而指针得用箭头 ->,再多写一层符号。

(4) 语法强制需求,有些地方根本"非引用不可":

  • 拷贝构造函数的参数必须是引用,不能用指针。
  • 重载某些运算符(如前置 ++)时,也必须返回引用才能达到标准语义。

5.指针与引用在 C++ 中的区别

C 与 C++ 支持指针,这与大多数其他编程语言(如 Java、Python、Ruby、Perl 和 PHP)不同,这些语言只支持引用。但有趣的是,C++ 除了指针之外,同时还支持引用。

表面上看,引用和指针非常相似:二者都能让"一个变量"去访问"另一个变量"。由于它们提供的功能有很多重叠,很多人搞不清楚这两种机制到底差在哪里。

(1)指针(Pointers):

指针是一个变量,其值为另一个变量的内存地址。要使用指针所指向的内存内容,必须显式地用 * 运算符进行解引用。

(2)引用(References):

引用变量是"别名",即给某个已存在的变量起另一个名字。虽然底层实现里引用也保存了对象的地址,但它可以被视为"带自动解引用的常量指针"(注意:不是"指向常量的指针")。编译器会在需要时自动帮你加上 * 运算符。

理解指针与引用的差异对高效编写 C++ 代码至关重要。《C++ 课程》对这两个概念做了深入讲解,帮助你在实际开发中选择合适的方案。

ini 复制代码
int i = 3; 

// 指针变量ptr 存放 i 的地址
int *ptr = &i; 

// i 的引用(或别名)
int &ref = i; 

差异 1:初始化方式

指针可以这样初始化:

cpp 复制代码
int a = 10;
int *p = &a;   // 方式 1:声明时一起初始化

// 也可以分两步:
int *p;        // 方式 2:先声明
p = &a;        // 后赋值

指针允许"先声明、后赋值"。而引用必须在声明的同时完成绑定

cpp 复制代码
int a = 10;
int &p = a;    // 正确:声明即绑定

下面这种写法是错误的:

cpp 复制代码
int &p;        // 错误:引用未初始化
p = a;         // 非法,编译器会报错

注意:不同编译器对"未初始化引用"的诊断信息可能略有差别,上面现象基于 Turbo IDE 测试。

差异2: 重新赋值

指针可以随时重新指向别的对象,这一特性对实现链表、树等数据结构至关重要:

cpp 复制代码
int a = 5;
int b = 6;
int *p;
p = &a;   // p 指向 a
p = &b;   // 现在改指向 b,完全合法

差异3:引用则不能被重新绑定

必须在定义时一次性完成绑定,后续再写同名声明会报错:

cpp 复制代码
int a = 5;
int b = 6;
int &p = a;     // 正确,p 是 a 的别名
int &p = b;     // 错误:重复定义,编译器报"multiple declaration"

// 但可以用另一个引用来引用现有引用
int &q = p;     // 合法,q 同样绑定到 a

差异4:内存地址

指针本身占用独立的栈空间,有自己的地址;而引用与原变量共享同一地址,不会额外占内存。

cpp 复制代码
int &p = a;
cout << &p << '\n' << &a;   // 两行输出结果相同

差异5:NULL 值

指针可以直接赋 NULL(或 nullptr)表示"不指向任何对象";引用不允许绑定为空,必须始终绑定到有效对象。这种约束避免了空引用导致的运行时异常。

C++ 中引用(Reference)与指针(Pointer)的差异对照表

特性 引用(Reference) 指针(Pointer)
定义 现有变量的别名(alias) 存放另一个变量地址的变量
初始化 必须在声明时初始化,且不可重新绑定 可延迟初始化,并可随时重新指向别的对象
空值性 不能为 null,必须始终绑定有效对象 可为 null(如 nullptr),表示不指向任何对象
语法 声明与使用均用 &;使用时不需额外符号 声明用 *,解引用用 *,取地址用 &
解引用 无需显式解引用,像普通变量一样直接使用 必须用 * 解引用才能访问所指对象的值
void 类型 不能有 void 引用 可以声明 void*(通用指针)
多级间接 只有一级间接(无"引用的引用"语法) 支持 n 级间接(单重、双重、三重指针...)
指针算术 不支持自增、自减等算术运算 支持算术运算(++、--、+、- 等)
典型用途 函数参数、返回值,语义简单、可读性好 动态内存管理、数据结构、数组操作等复杂场景
示例 int& ref = x; int* ptr = &x;

6.什么时候用引用,什么时候用指针?

二者性能完全一致,因为引用在底层就是用指针实现的。不过,下面几条经验可以帮你快速决定:

用引用(Reference)的场景

  • 函数参数与返回值------语义清晰、语法简洁,无需判空。

用指针(Pointer)的场景

  • 需要指针算术或允许传入"空值"时。例如:处理数组(数组下标本质上就是指针算术)。
  • 实现链表、树等数据结构及其算法------需要频繁地"改指"不同节点,必须能重新指向(reseating)。

一句话总结(引自 C++ FAQ Lite)

能引用就引用,非得"改指"再用指针。

引用通常出现在对象的"外壳"(public 接口),指针则藏在"内部"实现。

唯一例外: 如果函数需要"哨兵值"表示"无对象可引用",此时应使用指针并把 nullptr 赋予特殊含义;引用绝不能绑定到空对象。

7.拓展 什么时候应该"按指针传参"?

在 C 语言里,"按指针传递"会把实参的地址而不是值交给函数。这样函数内部就能直接改到调用者的数据,同时也能提升性能(避免大块数据复制)。

以下场景建议使用指针传参:

  1. 需要修改调用者手里的局部变量,函数内部通过解引用指针,就能把改动写回原来的变量。

示例

c 复制代码
#include <stdio.h>

// 通过指针把调用者的 x 改成 20
void fun(int *x)
{
    *x = 20;
}

int main(void)
{
    int x = 10;
    fun(&x);                    // 把 x 的地址传过去
    printf("New value of x is %d\n", x);
    return 0;
}
vbnet 复制代码
New value of x is 20
  1. 传递"大块头"参数时避免高成本拷贝,当实参是体积很大的对象(如结构体、类)时,按指针传递只需要传一个地址,可显著减少复制开销。

示例

下面先用"值传递"实现打印员工信息:

c 复制代码
#include <stdio.h>
#include <string.h>

struct Employee {
    char name[50];
    char desig[50];
};

/* 值传递:每次调用都会复制整个结构体 */
void printEmpDetails(const struct Employee emp)
{
    printf("Name: %s\n", emp.name);
    printf("Designation: %s\n", emp.desig);
}

int main(void)
{
    struct Employee emp1;
    strcpy(emp1.name, "geek");
    strcpy(emp1.desig, "Software Engineer");

    printEmpDetails(emp1);   // 实参被完整拷贝一次
    return 0;
}
makefile 复制代码
Name: geek
Designation: Software Engineer

问题 :每次调用 printEmpDetails() 都会生成一份 Employee 的副本,字符串数组全部复制,效率低。改用指针传递:

c 复制代码
#include <stdio.h>
#include <string.h>

struct Employee {
    char name[50];
    char desig[50];
};

/* 指针传递:只传 8 字节地址,无结构体拷贝 */
void printEmpDetails(const struct Employee *emp)
{
    printf("Name: %s\n", emp->name);
    printf("Designation: %s\n", emp->desig);
}

int main(void)
{
    struct Employee emp1;
    strcpy(emp1.name, "geek");
    strcpy(emp1.desig, "Software Engineer");

    printEmpDetails(&emp1);  // 只传地址
    return 0;
}

运行结果完全相同,但省去了复制成本。

注意:该优势仅对结构体/类等"大对象"有意义;基本类型(intchar 等)按值传递反而更简洁高效。

  1. 一次性返回多个值,当函数需要"吐出"不止一个结果时,可以把额外的结果通过指针参数写回调用者。

示例

c 复制代码
#include <stdio.h>

/* 同时计算两数之和与积,结果通过指针带回 */
void calcSumAndProduct(int x, int y, int *sum, int *prod)
{
    *sum = x + y;   /* 写回和 */
    *prod = x * y;  /* 写回积 */
}

int main(void)
{
    int x = 5, y = 7;
    int sum, prod;

    calcSumAndProduct(x, y, &sum, &prod);  /* 传地址让函数回填 */

    printf("Sum is: %d\nProduct is: %d\n", sum, prod);
    return 0;
}
csharp 复制代码
Sum is: 12
Product is: 35

要点

  • 函数返回值只能有一个,但通过指针参数可以"带"出任意多个结果

  • 调用者只需把待写入变量的地址传进去即可,无需额外全局变量或结构体

相关推荐
L_09072 小时前
【Algorithm】Day-11
c++·算法·leetcode
近津薪荼3 小时前
每日一练 1(双指针)(单调性)
c++·算法
qq_479875434 小时前
C++ ODR
java·开发语言·c++
攒钱植发5 小时前
嵌入式Linux——解密 ARM 性能优化:LDR 未命中时,为何 STR 还能“插队”?
linux·arm开发·c++·性能优化
茉莉玫瑰花茶5 小时前
从零搭建 C++ 在线五子棋对战项目:从环境到上线,全流程保姆级教程
开发语言·c++
一匹电信狗5 小时前
【C++】哈希表详解(开放定址法+哈希桶)
服务器·c++·leetcode·小程序·stl·哈希算法·散列表
Larry_Yanan5 小时前
QML学习笔记(五十一)QML与C++交互:数据转换——基本数据类型
c++·笔记·学习
梵尔纳多6 小时前
ffmpeg 使用滤镜实现播放倍速
c++·qt·ffmpeg
白曦6 小时前
switch语句的使用
c++