代码仓库shanchuann/CPP-Learninng

引用

定义:类型& 引用变量名称 = 表变量名称;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main() {
int a = 10; // a| 10 |
int& r = a; // r是a的引用 // r,a| 10 |
cout << "a = " << a << ", r = " << r << endl;
r = 20; // 修改r的值,实际上是修改a的值 // r,a| 20 |
cout << "a = " << a << ", r = " << r << endl;
int b = 30;
r = b; // 将b的值赋给r,实际上是将b的值赋给a // r,a| 30 |
cout << "a = " << a << ", r = " << r << endl;
// int& r2; // 错误,引用必须初始化
int& r3 = a; //r,a,r3| 30 |
cout << "a = " << a << ", r3 = " << r3 << endl;
return 0;
}

输出为

1
2
3
4
a = 10, r = 10
a = 20, r = 20
a = 30, r = 30
a = 30, r3 = 30

没有二级引用:

1
2
3
int a = 10;
int &b = a;
int &&C = b;//c++11中叫右值引用

引用的特点

  • 定义引用必须初始化int a = 10;int &x //error

  • 没有空引用int &y = nullptr; //error

  • 没有二级引用

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
#include<assert.h>
using namespace std;
//引用
void valSwap(int a, int b) {
int temp = a;
a = b;
b = temp;
}
void refSwap(int& a, int& b) { //无空引用
int temp = a;
a = b;
b = temp;
}
void ptrSwap(int* a, int* b) {
assert(a != nullptr && b != nullptr); //使用指针时,最好检查指针是否为空
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int a = 10;
int b = 20;
cout << "Before swap: a = " << a << ", b = " << b << endl;
valSwap(a, b);
cout << "After valSwap: a = " << a << ", b = " << b << endl;
refSwap(a, b);
cout << "After refSwap: a = " << a << ", b = " << b << endl;
ptrSwap(&a, &b);
cout << "After ptrSwap: a = " << a << ", b = " << b << endl;
return 0;
}

const引用

1
2
3
4
5
6
7
int main() {
int a = 10;
int& ra = a; // r是a的引用
ra += 10; // 修改r的值,实际上是修改a的值
const int& rca = a; // rca是a的常量引用
// rca += 10; // 错误,不能通过常量引用修改值
}

同样的,当我们定义一个常变量const int a = 10;,是否可以使用int& b = a;来创建他的引用?

1
error C2440: “初始化”: 无法从“const int”转换为“int &”

很可惜,如果为常变量,那么他的引用需要也是常性引用

1
2
3
const int a = 10;
const int& ra = a;
cout << "a = " << a << ", ra = " << ra << endl; //a = 10, ra = 10

但是,当变量a为普通的时,我们可以使用普通的引用&ra引用他,也可以通过常性引用const int& cra引用他

1
2
3
4
5
int a = 10;
int& ra = a; // ra是a的引用
const int& cra = a;
ra += 10; // 修改ra的值,实际上是修改a的值
// ra2 += 10; // 错误,不能通过常量引用修改值

关于字面量:const引用也可以引用字面量

1
2
3
const int& a = 10;
//int tmp = 10;
//const int& a = tmp;

右值引用

用于绑定右值(临时对象、字面量等),常用于实现移动语义和完美转发。类型名 &x为左值(能取地址的变量)引用,不能取地址的则称为右值

1
2
3
4
int&& rightrefval = 12; //类型&& 引用变量名 = 右值表达式;
//int tmp = 12;
//int&& rightrefval = tmp;
rightrefval = 120; //对tmp进行修改而并非12

数组的引用

1
2
3
4
5
6
7
8
9
const int N = 10;
int a[N] = { 0,1,2,3,4,5,6,7,8,9 };
// 0 1 2 3 4 5 6 7 8 9
int& ia = a[0]; // ia是a[0]的引用
ia += 10; // 修改ia的值,实际上是修改a[0]的值
cout << "a[0] = " << a[0] << ", ia = " << ia << endl;
a[0] += 10; // 修改a[0]的值,实际上是修改ia的值
cout << "a[0] = " << a[0] << ", ia = " << ia << endl;
int(&ra)[N] = a; // ra是a的引用

形似与数组指针,当我们想用指针指向数组时,不应该单纯的是int* p = &a,应该改为int(*p)[10] = &a。同样的,引用一个数组也应该为int(&ra)[N] = a。注意:我们可以引用一个数组,但不能创建一个元素是引用的数组

指针的引用

1
2
3
4
int a = 20;
int* p = &a; // p是a的指针
int* s = p; // s是p的指针
int* &rp = p; // rp是p的引用
1
a = 20, *p = 20, *rp = 20

指针和引用的区别

指针和引用是 C++ 中用于间接访问变量的两种机制,两者在语法和功能上有相似之处,但本质和使用场景差异显著。

核心定义与本质区别

指针(Pointer):指针是一个变量,其存储的内容是另一个变量的内存地址。通过指针可以间接访问该地址对应的变量。例:int a = 10; int* p = &a;p 存储 a 的地址,*p 表示访问 a)。

引用(Reference):引用是一个变量的别名,它与被引用的变量共享同一块内存空间,本身不占用额外内存。例:int a = 10; int& ref = a;refa 的别名,操作 ref 等价于操作 a)。

关键语法与特性对比

特性 指针(Pointer) 引用(Reference)
初始化要求 可以不初始化(未初始化的指针为 “野指针”,危险);可初始化为 nullptr(空指针)。 必须在定义时初始化,且必须引用一个已存在的变量(不能引用空值)。
可修改指向 可以改变指向的对象(指针的值可被重新赋值)。 一旦初始化,不能改变引用的对象(始终绑定最初的变量)。
内存占用 占用独立内存空间(通常为 4 字节或 8 字节,取决于系统位数)。 不占用额外内存空间(仅作为原变量的别名)。
空值支持 可以指向 nullptr(空指针),表示不指向任何有效对象。 不支持空值,必须引用一个有效的变量(不存在 “空引用”)。
解引用操作 需要通过 * 运算符解引用才能访问指向的变量。 无需解引用运算符,直接使用引用名即可访问原变量。
取地址操作 对指针取地址(&p)得到指针自身的地址。 对引用取地址(&ref)得到被引用变量的地址(与 &a 等价)。
算术运算 支持指针算术(如 p++p--),用于数组遍历等场景。 不支持算术运算(引用不是变量,无法进行地址偏移)。
多级嵌套 支持多级指针(如 int**p,指针的指针)。 不支持多级引用(不存在 “引用的引用”,如 int&& ref 是右值引用,非多级引用)。

使用场景差异

函数参数传递
  • 指针作为参数:传递的是指针的副本(地址的拷贝),可通过解引用(*p)修改原变量;也可传递指针的引用(int*& p)修改指针本身的指向。适合需要传递 “可选参数”(可传入 nullptr 表示无值)的场景。例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    void modify(int* p) {
    if (p != nullptr) *p = 20; // 需判断空指针,否则可能崩溃
    }
    int main() {
    int a = 10;
    modify(&a); // 传入a的地址
    modify(nullptr); // 允许传入空值
    return 0;
    }
  • 引用作为参数:传递的是原变量的别名,修改引用直接影响原变量,语法更简洁;无需判断空值(引用一定有效)。适合需要 “必选参数” 且避免拷贝大对象(如自定义类)的场景。例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    void modify(int& ref) {
    ref = 20; // 直接修改,无需解引用
    }
    int main() {
    int a = 10;
    modify(a); // 直接传变量,无需取地址
    // modify(nullptr); // 编译错误,引用不能绑定空值
    return 0;
    }
函数返回值
  • 指针作为返回值:可返回动态分配的内存(如 new int)或全局 / 静态变量的地址,但禁止返回局部变量的地址(局部变量销毁后地址无效,导致 “悬空指针”)。例:

    1
    2
    3
    4
    5
    6
    7
    int* create() {
    return new int(10); // 正确:返回动态分配的内存
    }
    int* bad() {
    int a = 10;
    return &a; // 错误:返回局部变量地址,悬空指针
    }
  • 引用作为返回值:可返回全局 / 静态变量或类的成员变量的引用,用于链式操作(如 cout << 重载),但禁止返回局部变量的引用(局部变量销毁后引用无效,导致 “悬空引用”)。例:

    1
    2
    3
    4
    5
    6
    7
    8
    int& getGlobal() {
    static int g = 10; // 静态变量,生命周期与程序一致
    return g; // 正确:返回静态变量的引用
    }
    int& badRef() {
    int a = 10;
    return a; // 错误:返回局部变量引用,悬空引用
    }
const 修饰场景
  • const 指针:有两种形式:

    • const int* p:指针指向的内容不可修改(常量指针);
    • int* const p:指针本身的指向不可修改(指针常量)。
    1
    2
    3
    const int a = 10;
    const int* p = &a; // 正确:常量指针指向常量
    *p = 20; // 错误:不能修改指向的常量
  • const 引用const int& ref 表示引用指向的内容不可修改,常用于函数参数(避免拷贝且防止修改原变量)。

    1
    2
    3
    4
    void print(const int& ref) {
    // ref = 20; // 错误:const引用不可修改
    cout << ref << endl;
    }