Skip to content

指针与引用

约 10857 字大约 36 分钟

2025-06-25

指针

指针

在C++中,指针(Pointer) 是一种特殊的变量,它存储的不是数据本身,而是另一个变量的内存地址。通过指针,我们可以间接访问和修改它所指向的变量的值。指针是C++的核心特性之一,在信奥赛中广泛应用于动态内存管理、数组操作、函数参数传递等。

一、指针的基本概念

指针的基本概念

1. 内存地址

计算机内存被划分为一个个字节,每个字节都有一个唯一的编号(即地址),用于标识其在内存中的位置。例如,一个int类型变量通常占用4个字节,其地址是第一个字节的编号。

2. 指针的作用

指针通过存储变量的地址,实现对变量的“间接访问”。想象指针是一个“门牌号”,通过这个门牌号可以找到对应的“房间”(变量)并修改其中的内容。

二、指针的定义与初始化

指针的定义与初始化

1. 定义指针变量

指针变量的定义需要指定它所指向的变量类型(即“基类型”),语法:

c++
数据类型 *指针名;
  • * 表示这是一个指针变量。
  • 数据类型是指针所指向的变量的类型(如intdoublestruct等)。

示例

c++
int *p;       // 定义一个指向int类型变量的指针p
double *dp;   // 定义一个指向double类型变量的指针dp
char *cp;     // 定义一个指向char类型变量的指针cp

2. 初始化指针(赋值地址)

指针必须指向一个已存在的变量才能使用,通过取地址运算符(&) 获取变量的地址并赋值给指针:

c++
int a = 10;
int *p = &a;  // p存储a的地址(p指向a)
  • &a 表示取变量a的内存地址。
  • 此时,p&a的值相同(都是a的地址)。

三、指针的解引用(访问指向的变量)

指针的解引用(访问指向的变量)

通过解引用运算符(*) 可以访问指针所指向的变量的值,语法:

c++
*指针名;  // 表示指针指向的变量

示例

c++

关键*p 等价于 p 所指向的变量(上例中即a),对*p的操作就是对a的操作。

四、指针的核心特性

指针的核心特性

1. 指针的类型必须与指向的变量类型一致

c++
int a = 10;
double *p = &a;  // 错误:double*指针不能指向int类型变量

2. 指针可以改变指向

指针可以重新赋值,指向另一个同类型变量:

c++
int a = 10, b = 20;
int *p = &a;  // p指向a
p = &b;       // p改为指向b(此时*p是20)

3. 空指针(nullptr

未指向任何有效变量的指针应赋值为nullptr(C++11引入),表示“空指针”(不指向任何内存):

c++
int *p = nullptr;  // 空指针,不指向任何变量
// *p = 10;  // 错误:解引用空指针会导致程序崩溃

注意:避免使用未初始化的“野指针”(未赋值的指针),其指向随机地址,操作会导致不可预知的错误:

c++
int *p;  // 野指针,未初始化,指向随机地址
// *p = 10;  // 危险:可能修改任意内存,导致程序崩溃

五、指针与数组(信奥赛核心)

指针与数组(信奥赛核心)

数组名本质是指向数组第一个元素的指针,这是指针与数组关联的核心。

1. 数组名作为指针

c++
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;  // 等价于 p = &arr[0](p指向数组第一个元素)

2. 通过指针访问数组元素

指针的算术运算(+-)表示移动到下一个/上一个同类型元素:

  • p + i 指向arr[i](即数组第i个元素)。
  • *(p + i) 等价于arr[i](访问第i个元素)。

示例

c++
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;

cout << *(p) << endl;    // 1(arr[0])
cout << *(p + 1) << endl;  // 2(arr[1])
cout << *(p + 3) << endl;  // 4(arr[3])

// 遍历数组
for (int i = 0; i < 5; i++) {
    cout << *(p + i) << " ";  // 输出1 2 3 4 5
}

3. 指针遍历数组(更高效)

通过指针自增(p++)移动到下一个元素,避免每次计算p + i

c++
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
for (int i = 0; i < 5; i++) {
    cout << *p << " ";  // 输出当前元素
    p++;  // 指针移动到下一个元素
}

六、指针作为函数参数(传地址)

指针作为函数参数(传地址)

指针作为函数参数时,函数接收的是变量的地址,通过解引用可以直接修改实参的值(类似传引用,但语法不同)。

1. 实现两数交换(对比传值)

c++

2. 函数返回多个结果(通过指针输出)

函数只能有一个返回值,通过指针参数可“输出”多个结果:

c++
// 计算商和余数,通过指针输出余数
int divide(int a, int b, int *remainder) {
    *remainder = a % b;  // 余数通过指针输出
    return a / b;       // 商作为返回值
}

int main() {
    int a = 10, b = 3, rem;
    int quotient = divide(a, b, &rem);  // 传入rem的地址
    cout << "商:" << quotient << ",余数:" << rem;  // 3,1
    return 0;
}

七、const与指针(常指针与指针常量)

const与指针(常指针与指针常量)

const与指针结合有两种常见形式,需注意区别:

1. 指向常量的指针(const 类型 *指针

指针指向的内容不能修改,但指针可以改变指向:

c++
int a = 10, b = 20;
const int *p = &a;  // p指向a,*p不可修改
// *p = 30;  // 错误:不能修改指向的内容
p = &b;  // 正确:可以改变指向

2. 指针常量(类型 *const 指针

指针本身不能改变指向,但指向的内容可以修改:

c++
int a = 10, b = 20;
int *const p = &a;  // p指向a,不能改指向
*p = 30;  // 正确:可以修改指向的内容
// p = &b;  // 错误:不能改变指向

八、信奥赛常见应用

信奥赛常见应用

1. 动态数组(未知大小的数组)

通过newdelete动态分配内存(信奥赛中处理输入大小不确定的场景):

c++
int n;
cin >> n;  // 输入数组大小(运行时确定)
int *arr = new int[n];  // 动态分配n个int的数组

// 使用数组
for (int i = 0; i < n; i++) {
    cin >> arr[i];
}

// 释放内存(避免内存泄漏)
delete[] arr;

2. 处理二维数组(传递数组到函数)

二维数组作为函数参数时,需指定第二维大小,通过指针更灵活:

c++
// 打印二维数组(3行4列)
void print2DArray(int (*arr)[4], int rows) {  // arr是指向4个int的数组的指针
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 4; j++) {
            cout << arr[i][j] << " ";
        }
        cout << endl;
    }
}

int main() {
    int matrix[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
    print2DArray(matrix, 3);  // 传递二维数组名
    return 0;
}

3. 链表与树(数据结构基础)

指针是实现链表、树等动态数据结构的核心(信奥赛进阶内容):

c++
// 链表节点结构体
struct Node {
    int data;       // 数据
    Node *next;     // 指向 next 节点的指针
};

// 创建新节点
Node* createNode(int val) {
    Node *newNode = new Node;  // 动态分配节点
    newNode->data = val;
    newNode->next = nullptr;
    return newNode;
}

九、避坑指南

避坑指南

  1. 解引用空指针/野指针
    这是最常见的错误,会导致程序崩溃。始终确保指针指向有效内存后再解引用:

    c++
    int *p = nullptr;
    if (p != nullptr) {  // 先判断指针是否有效
        *p = 10;
    }
  2. 指针越界访问
    访问数组时,指针不能超出数组范围(如*(p + n)n大于数组长度时):

    c++
    int arr[5] = {1,2,3,4,5};
    int *p = arr;
    // *(p + 10) = 100;  // 错误:越界访问,可能修改其他内存
  3. 内存泄漏
    动态分配的内存(new)必须用delete释放,否则会耗尽内存:

    c++
    int *p = new int;
    // ... 使用p ...
    delete p;  // 释放内存(单个变量用delete)
    p = nullptr;  // 避免野指针
    
    int *arr = new int[5];
    delete[] arr;  // 数组用delete[]
  4. 指针类型不匹配
    避免将一种类型的指针强制转换为另一种类型(除非明确知道后果):

    c++
    double d = 3.14;
    int *p = (int*)&d;  // 危险:类型不匹配,*p的值不确定

十、实战:用指针反转数组

用指针反转数组

题目:通过指针操作,将数组元素反转(如[1,2,3,4,5][5,4,3,2,1])。

c++

重要

  • 指针是C++中强大而灵活的工具,掌握指针能让你更深入地理解内存操作,尤其在信奥赛中处理动态数据、数组和复杂数据结构时不可或缺。- 核心是理解“指针存储地址,解引用访问值”的逻辑,同时警惕空指针、野指针和内存泄漏等常见错误。通过大量练习指针与数组、函数的结合使用,能显著提升代码的效率和灵活性。

基于指针的数组访问

基于指针的数组访问

在C++中,数组与指针存在紧密联系——数组名本质上是指向数组第一个元素的常量指针。利用这一特性,我们可以通过指针灵活访问数组元素,这在信奥赛中对优化数组操作、处理动态内存等场景非常重要。

一、数组名与指针的关系

数组名与指针的关系

数组名在大多数情况下会被隐式转换为指向数组首元素的指针(即&arr[0]),这是指针访问数组的基础。

1. 数组名作为指针

c++
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;  // 等价于 int *p = &arr[0];(p指向数组第一个元素)

此时:

  • parr 的值相同(都是数组首元素的地址)。
  • *p 等价于 arr[0](访问第一个元素)。

2. 指针的算术运算与数组访问

指针的算术运算(+-)是以所指向元素的大小为单位的(而非字节)。对于int指针,p + 1表示移动到下一个int元素(通常4字节)。

核心公式
*(p + i) 等价于 arr[i](访问数组第i个元素)。

示例

c++

二、通过指针遍历数组(高效方式)

通过指针遍历数组(高效方式)

相比指针自增(p++)移动到下一个元素,比*(p + i)更高效,是信奥赛中遍历数组的常用技巧。

1. 基本遍历

c++
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;  // 指向首元素

for (int i = 0; i < 5; i++) {
    cout << *p << " ";  // 输出当前元素
    p++;  // 指针移动到下一个元素
}

2. 首尾指针遍历(双向遍历)

c++

三、指针与多维数组

指针与多维数组

多维数组的数组名是指向第一行(或第一个维数组)的指针,通过指针访问需要注意维度的层级关系。

1. 二维数组与指针

对于二维维数组int arr[m][n]中:

  • arr 是指向arr[0](第一行)的指针,类型为 int (*)[n](指向含nint的数组的指针)。
  • arr[i] 是指向arr[i][0](第i行首元素)的指针,类型为 int*

访问方式

c++

2. 遍历二维数组

c++

四、指针与动态数组(信奥赛核心)

指针与动态数组(信奥赛核心)

当数组大小未知(需运行时确定)时,需用new动态分配内存,此时必须通过指针访问。

1. 动态一维数组

c++

2. 动态二维数组

动态二维数组本质是“指针的数组”(每个元素是指向一行的指针):

c++

五、指针访问数组的优势(信奥赛视角)

指针访问数组的优势(信奥赛视角)

  1. 灵活性:指针可随时改变指向,适合动态遍历(如双向遍历、跳跃访问)。
  2. 效率:指针算术运算通常比数组下标访问更快(尤其对大型数组)。
  3. 动态内存支持:动态数组只能通过指针访问,这是处理未知大小数据的必备能力。
  4. 函数传参:数组作为函数参数时,本质是传递指针,可避免整个数组的复制(节省内存)。

六、避坑指南

避坑指南

  1. 指针越界
    指针访问不能超出数组范围(如*(p + n)n等于数组长度时),否则会访问非法内存:

    c++
    int arr[5] = {1,2,3,4,5};
    int *p = arr;
    // *(p + 5) = 10;  // 错误:越界访问(数组只有5个元素,下标0~4)
  2. 数组名不可修改
    数组名是“常量指针”,不能赋值或自增(区别于普通指针):

    c++
    int arr[5];
    // arr++;  // 错误:数组名是常量,不能修改指向
    int *p = arr;
    p++;  // 正确:普通指针可以修改
  3. 动态数组忘记释放
    动态分配的数组(new)必须用delete[]释放,否则会导致内存泄漏(信奥赛中程序可能因内存耗尽崩溃)。

  4. 指针类型匹配
    指向二维数组行的指针必须指定列数,否则无法正确访问:

    c++
    int matrix[3][4];
    int (*p)[4] = matrix;  // 正确:指定列数为4
    // int **p = matrix;  // 错误:类型不匹配

七、实战:用指针实现数组去重

用指针实现数组去重

题目:删除有序数组中的重复元素,返回新长度(如[1,1,2,2,3][1,2,3],长度3)。

c++

重要

基于指针的数组访问是C++的核心技巧,尤其在信奥赛中处理动态内存、优化数组操作时不可或缺。核心是理解“数组名即指针”和“指针算术运算”的逻辑,通过指针的灵活性实现高效的遍历、修改和动态内存管理。同时需严格避免越界访问和内存泄漏,确保程序的正确性和稳定性。

字符指针

字符指针

在C++中,字符指针(char* 是指向字符类型的指针,因其与字符串的紧密关联而成为处理字符串的重要工具。字符指针既能指向单个字符,也能指向以'\0'结尾的字符数组(C风格字符串),在信奥赛中常用于字符串操作、动态字符串处理等场景。

一、字符指针与字符串的关系

字符指针与字符串的关系

C风格字符串本质是'\0'结尾的字符数组,而字符指针可以指向这个数组的首地址,从而间接访问整个字符串。

1. 字符指针指向字符串常量

字符串常量(如"Hello")在内存中以字符数组形式存储(自动添加'\0'),字符指针可直接指向其首地址:

c++
#include <iostream>
using namespace std;

int main() {
    const char *str = "Hello";  // str指向字符串首字符'H'
    // 等价于:const char arr[] = "Hello"; const char *str = arr;

    cout << str << endl;  // 输出"Hello"(cout会自动打印到'\0')
    cout << *str << endl; // 输出'H'(解引用获取首字符)
    cout << *(str + 1) << endl; // 输出'e'(访问第二个字符)

    return 0;
}

注意:字符串常量是只读的,因此指针通常声明为const char*,避免误修改导致程序崩溃。

2. 字符指针指向字符数组

字符指针可指向手动定义的字符数组(字符串),此时可通过指针修改数组内容:

c++
char arr[] = "World";  // 字符数组(可修改)
char *p = arr;         // p指向数组首字符'W'

*p = 'w';  // 修改首字符为小写
cout << p << endl;  // 输出"world"

*(p + 2) = 'R';  // 修改第三个字符
cout << p << endl;  // 输出"woRld"

二、字符指针的运算与遍历

字符指针的运算与遍历

与其他指针类似,字符指针的算术运算以char大小(1字节)为单位,非常适合遍历字符串。

1. 遍历字符串(方式1:下标法)

c++
const char *str = "Hello";
for (int i = 0; str[i] != '\0'; i++) {  // 以'\0'为结束标志
    cout << str[i];
}
// 输出:Hello

2. 遍历字符串(方式2:指针自增)

c++
const char *str = "Hello";
const char *p = str;
while (*p != '\0') {  // 指针未指向结束符时循环
    cout << *p;
    p++;  // 指针移动到下一个字符
}
// 输出:Hello

3. 计算字符串长度(模拟strlen

c++
int myStrlen(const char *str) {
    int len = 0;
    while (*str != '\0') {  // 遍历到'\0'
        len++;
        str++;  // 指针后移
    }
    return len;
}

// 调用:
const char *s = "Hello";
cout << myStrlen(s);  // 输出5

三、字符指针与字符串函数(<cstring>

C标准库提供了丰富的字符串函数,这些函数大多以字符指针作为参数,例如:

函数功能示例(str1="abc", str2="def"
strlen(p)计算字符串长度(不含'\0'strlen("abc") → 3
strcpy(dst, src)复制字符串(srcdststrcpy(dst, "abc")dst为"abc"
strcat(dst, src)拼接字符串(src追加到dststrcat(dst, "def")dst为"abcdef"
strcmp(p1, p2)比较字符串(字典序)strcmp("abc", "abd") → -1

字符指针与字符串函数(<cstring>

示例:使用字符指针调用字符串函数

c++

四、动态字符串(字符指针与new

动态字符串(字符指针与new

字符指针结合new可创建动态字符串(长度在运行时确定),这是信奥赛中处理输入长度不确定的字符串的常用方式。

1. 动态分配字符串

c++

2. 动态字符串的复制

c++

五、字符指针与string类的转换

字符指针与string类的转换

在C++中,string类与字符指针可相互转换,兼顾易用性和兼容性。

1. string → 字符指针(c_str()

string类的c_str()方法返回指向其内部字符数组的const char*指针:

c++
#include <string>

string s = "Hello";
const char *p = s.c_str();  // 转换为字符指针
cout << p << endl;  // 输出"Hello"

2. 字符指针 → string(直接赋值)

字符指针可直接赋值给string对象:

c++
const char *p = "World";
string s = p;  // 转换为string
cout << s << endl;  // 输出"World"

六、信奥赛常见应用

信奥赛常见应用

1. 字符串反转

c++

2. 字符串比较(忽略大小写)

c++

3. 统计子串出现次数

c++

七、避坑指南

避坑指南

  1. 修改字符串常量
    字符串常量(如"Hello")是只读的,用非const指针指向并修改会导致程序崩溃:

    c++
    char *p = "Hello";  // 危险:应声明为const char*
    // *p = 'h';  // 错误:修改只读内存,程序崩溃
  2. 字符数组未初始化'\0'
    手动创建字符数组时,必须确保以'\0'结尾,否则字符串函数会读取垃圾值:

    c++
    char arr[6];
    for (int i = 0; i < 5; i++) {
        arr[i] = 'A' + i;
    }
    arr[5] = '\0';  // 必须添加结束符,否则strlen结果不确定
  3. 动态字符串忘记释放
    new[]分配的字符数组必须用delete[]释放,否则导致内存泄漏:

    c++
    char *str = new char[100];
    // ... 使用 ...
    delete[] str;  // 不可遗漏
    str = nullptr;  // 避免野指针
  4. strcpy的安全问题
    strcpy不会检查目标数组的容量,若源字符串过长会导致溢出,信奥赛中需确保目标容量足够:

    c++
    char dst[5];
    const char *src = "HelloWorld";  // 长度10,超过dst容量
    // strcpy(dst, src);  // 错误:溢出,可能导致程序崩溃

八、实战:字符串替换

字符串替换

题目:将字符串中所有指定字符替换为新字符(如将"Hello"中的'l'替换为'x',结果为"Hexxo")。

c++

重要

字符指针是处理C风格字符串的核心工具,在信奥赛中常用于需要直接操作内存的场景(如动态字符串、字符串算法优化)。掌握字符指针与字符串的关系、指针运算及常用字符串函数,能高效解决字符串反转、比较、替换等问题。同时需注意字符串结束符'\0'的作用和动态内存的释放,避免常见错误。

指向结构体的指针

指向结构体的指针

在C++中,指向结构体的指针是存储结构体变量内存地址的指针变量。通过这种指针,我们可以间接访问和修改结构体的成员,在信奥赛中常用于动态创建结构体对象、处理结构体数组或实现链表等数据结构,能显著提升代码的灵活性和效率。

一、指向结构体的指针的定义与初始化

指向结构体的指针的定义与初始化

1. 定义语法

指向结构体的指针需要指定结构体类型,语法为:

c++
struct 结构体名 *指针名;

2. 初始化(指向结构体变量)

通过取地址运算符(&) 获取结构体变量的地址,赋值给指针:

c++

二、通过指针访问结构体成员

通过指向结构体的指针访问结构体成员

通过指向结构体的指针访问成员有两种方式:

1. 解引用 + 点运算符((*p).成员

先解引用指针得到结构体变量,再用点运算符访问成员:

c++
Student s = {"Alice", 15, 92.5};
Student *p = &s;

cout << (*p).name << endl;   // 输出Alice
cout << (*p).age << endl;    // 输出15
(*p).score = 95.0;           // 修改score为95.0

2. 箭头运算符(p->成员,推荐)

C++为结构体指针提供了更简洁的箭头运算符(->),直接通过指针访问成员:

c++
Student s = {"Alice", 15, 92.5};
Student *p = &s;

cout << p->name << endl;   // 输出Alice(等价于(*p).name)
cout << p->age << endl;    // 输出15
p->score = 95.0;           // 修改score为95.0(等价于(*p).score)

推荐使用箭头运算符,代码更简洁直观,是信奥赛中的常用写法。

三、指向结构体数组的指针

指向结构体数组的指针

结构体数组的数组名是指向第一个结构体元素的指针,通过指针遍历数组非常高效。

1. 基本用法

c++

2. 遍历结构体数组

c++

四、动态结构体(指针 + new)

动态结构体(指针 + new)

通过new动态分配结构体对象,返回指向该对象的指针,这是信奥赛中处理动态数据的常用方式。

1. 动态创建单个结构体

c++

2. 动态创建结构体数组

c++

五、信奥赛核心应用:链表

信奥赛核心应用:链表

指向结构体的指针是实现链表的基础,链表中每个节点是一个结构体,通过指针指向向下一个节点。

c++

六、避坑指南

避坑指南

  1. 空指针访问成员
    指针未指向有效结构体对象时(如nullptr),访问成员会导致程序崩溃:

    c++
    Student *p = nullptr;
    // cout << p->name;  // 错误:解引用空指针
  2. 忘记释放动态结构体
    new创建的结构体/数组必须用delete/delete[]释放,否则内存泄漏:

    c++
    Student *p = new Student;
    // ... 使用 ...
    delete p;  // 单个对象用delete
    
    Student *arr = new Student[5];
    delete[] arr;  // 数组用delete[]
  3. 指针算术运算越界
    指向结构体数组的指针,p + i不能超出数组范围:

    c++
    Student arr[3];
    Student *p = arr;
    // (p + 5)->age = 10;  // 错误:越界访问
  4. 结构体指针的类型匹配
    指针类型必须与结构体类型严格匹配:

    c++
    struct A { int x; };
    struct B { int y; };
    A a;
    B *p = &a;  // 错误:类型不匹配

七、实战:结构体指针排序

结构体指针排序

题目:用指针数组存储结构体指针,按成绩排序后输出学生信息。

c++

重要

指向结构体的指针是处理复杂数据的强大工具,尤其在信奥赛中实现动态数据结构(如链表)、优化结构体数组操作时不可或缺。核心是掌握箭头运算符(->)的使用,以及动态内存管理(new/delete)。通过指针操作结构体,既能节省内存(避免复制大对象),又能提高代码灵活性,是进阶编程的必备技能。

引用

引用的基本定义与特性

在C++中,引用(Reference) 是变量的“别名”,它与指针类似但语法更简洁,用于间接访问变量。引用一旦绑定到某个变量,就会一直指向该变量,不能再改变指向。在信奥赛中,引用常用于函数参数传递和返回值,既能避免值传递的内存开销,又比指针更安全易用。

一、引用的基本定义与特性

引用的定义与特性

1. 定义语法

引用的定义需要在变量类型后加&,并必须在声明时初始化(绑定到一个已存在的变量):

c++
数据类型 &引用名 = 变量名;

示例

c++

2. 核心特性

  • 必须初始化:引用声明时必须绑定到一个变量,不能像指针一样先声明后赋值。
c++
int &b;  // 错误:引用必须初始化
  • 不可更改指向:一旦绑定到某个变量,引用就不能再指向其他变量。
c++
int a = 10, c = 20;
int &b = a;
b = c;  // 这是将c的值赋给a(而非改变引用指向)
  • 无空引用:引用必须指向有效变量,不存在“空引用”(区别于空指针nullptr)。
  • sizeof引用sizeof(引用)等于所指向变量的大小(而非地址大小)。

二、引用作为函数参数(传引用)

引用作为函数参数

引用最常用的场景是作为函数参数,实现“传引用调用”,此时函数内部对引用的修改会直接影响实参。

1. 实现变量交换(对比传值)

c++

2. 高效传递大对象

对于结构体、数组等大对象,传值会复制整个对象(开销大),传引用无需复制,效率更高:

c++
struct Student {
    string name;
    int age;
    // ... 其他成员
};

// 传引用访问学生信息(不复制,效率高)
void printStudent(const Student &s) {  // const确保不修改实参
    cout << s.name << " " << s.age << endl;
}

const引用:用于只读场景,防止函数内部意外修改实参,同时允许传递常量或表达式:

c++
void func(const int &x) {
    // x = 10;  // 错误:const引用不可修改
}

// 调用
int a = 5;
func(a);       // 正确
func(10);      // 正确(const引用可绑定常量)
func(a + 3);   // 正确(可绑定表达式)

3. 函数返回多个结果

函数只能有一个返回值,通过引用参数可“输出”多个结果(替代指针的常用方式):

c++
// 计算商和余数,通过引用参数输出余数
int divide(int a, int b, int &remainder) {
    remainder = a % b;  // 余数通过引用输出
    return a / b;       // 商作为返回值
}

int main() {
    int a = 10, b = 3, rem;
    int quotient = divide(a, b, rem);
    cout << "商:" << quotient << ",余数:" << rem;  // 3 1
    return 0;
}

三、引用作为函数返回值

引用作为函数返回值

函数可以返回引用,此时返回的是变量的别名,可用于赋值或连续操作。

1. 基本用法

c++

2. 注意事项

  • 不能返回局部变量的引用:局部变量在函数结束后销毁,引用会变成“悬垂引用”(指向无效内存)。
c++
int &badFunc() {
    int x = 10;
    return x;  // 错误:返回局部变量的引用
}
  • 通常返回类成员或全局变量的引用(生命周期长于函数调用)。

四、引用与指针的区别(核心对比)

特性引用(int &指针(int *
初始化必须在声明时初始化(绑定变量)可先声明后赋值(可指向nullptr
指向修改一旦绑定,不能改变指向可随时改变指向其他变量
空值无空引用(必须指向有效变量)有空指针(nullptr
语法直接使用(b = 20需要解引用(*p = 20
内存开销无额外开销(只是别名)存储地址,有内存开销(通常4/8字节)
安全性更高(无空引用、悬垂引用风险较低)较低(可能解引用空指针、野指针)

五、信奥赛常见应用

信奥赛常见应用

1. 优化递归/循环中的参数传递

对于大型数组或结构体,传引用可避免重复复制,显著提升效率:

c++
// 递归计算数组和(传引用避免数组复制)
int sumArray(const int arr[], int n) {  // 数组参数本质是指针,但const仍有效
    if (n == 0) return 0;
    return arr[n-1] + sumArray(arr, n-1);
}

2. 简化结构体/类的操作

通过引用直接修改结构体成员,代码更简洁:

c++
struct Point {
    int x, y;
};

// 移动点的坐标(传引用直接修改)
void movePoint(Point &p, int dx, int dy) {
    p.x += dx;
    p.y += dy;
}

3. 实现链式操作

返回引用允许连续赋值,如a = b = c的逻辑:

c++

六、避坑指南

避坑指南

  1. 引用必须初始化
    忘记初始化引用会导致编译错误:

    c++
    int &ref;  // 错误:引用声明时必须初始化
  2. 避免返回局部变量的引用
    局部变量在函数返回后销毁,引用会指向无效内存,导致程序崩溃:

    c++
    string &badReturn() {
        string s = "hello";
        return s;  // 危险:s会被销毁
    }
  3. const引用的只读性
    试图修改const引用会导致编译错误,这是一种保护机制:

    c++
    const int &ref = 10;
    // ref = 20;  // 错误:const引用不可修改
  4. 引用不是变量
    引用没有自己的内存空间,只是原变量的别名,不能对引用取地址再赋值给指针:

    c++
    int a = 10;
    int &b = a;
    int *p = &b;  // 正确:p指向a(&b等价于&a)

七、实战:用引用排序结构体数组

用引用排序结构体数组

题目:定义学生结构体,包含姓名和成绩,用引用传递比较函数,按成绩降序排序。

c++

重要

引用是C++中简化指针操作的重要特性,核心优势是语法简洁安全性高。在信奥赛中,传引用参数是替代指针的首选方式,尤其适合需要修改实参、传递大对象或实现多返回值的场景。掌握引用与指针的区别,合理使用const引用,能写出更高效、清晰且安全的代码。