JMing's Blog

C++中的引用(入门)

字数统计: 1.2k阅读时长: 5 min
2024/05/14

C++中的引用是什么

主要是用来解决指针太麻烦的问题而设计的一种新的语法格式,本质上是指针常量。

相较于指针的优缺点

  • 优点:避免指针的麻烦写法
  • 缺点:无法改变引用的指向、必须声明时初始化

使用引用

引用的语法格式

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

1
2
int a = 3;
int &ra = a; // 相当于 const int *ra = a;

引用的特点

  • 必须在声明的同时初始化
1
2
3
int a = 3;
int &ra; // ❌ declaration of reference variable 'ra' requires an initializer
int &ra = a; // ✅
  • 必须保证引用类型和被引用变量的类型一致
1
2
int a = 3;
long &lra = a; // ❌ non-const lvalue reference to type 'long' cannot bind to a value of unrelated type 'int'
  • 不能够改变指向(不能够换绑)
1
2
3
4
5
6
int a = 3, b = 4;
int &ra = a;
ra = b; // 实际上等同于 a = 4, 并不是改变指向
std::cout << "ra: " << ra << std::endl; // 4
std::cout << "a: " << a << std::endl; // 4
std::cout << "b: " << b << std::endl; // 4

引用作为函数参数

例子引入:交换两变量的值

  • 传统指针交换两个变量的值
1
2
3
4
5
6
7
8
9
10
11
void SwapByPointer(int *pa, int *pb) {
// 需要频繁解引用,差评❌
int t = *pa;
*pa = *pb;
*pb = t;
}

int main() {
SwapByPointer(&a, &b); // 传入需要加取地址符,差评❌
return 0;
}
  • 引用作为函数参数交换两个变量的值
1
2
3
4
5
6
7
8
9
10
11
void SwapByReference(int &ra, int &rb) {
// 可以直接当成普通变量使用,好评✅
int t = ra;
ra = rb;
rb = t;
}

int main() {
SwapByReference(a, b); // 直接当成普通的值传入,好评✅
return 0;
}

例子引入:对传入字面量或常量的处理

  • 形参为普通引用时传入字面量
1
2
3
4
5
6
7
8
void PrintValue1(int &ra) {
cout << "ra: " << ra << endl;
}

int main() {
PrintValue1(10); // ❌ no matching function for call to 'PrintValue2'
return 0;
}
  • 形参为常引用时传入字面量
1
2
3
4
5
6
7
8
9
10
void PrintValue2(const int &cra) {
cout << "cra: " << cra << endl;
}

int main() {
int a = 20;
PrintValue2(10); // ✅
PrintValue2(a); // ✅
return 0;
}

原理:虽然说参数为常引用时,它本质还是指针,依然需要接收地址,但是 C++ 对策略是:当传入参数时, 如果传入的是一个右值, 那么将会有一个临时变量存放这个值, 并且引用将会绑定这个临时变量。

1
2
3
// 当字面量 20 传入时, 相当于
int temp = 20;
const int &cra = temp;

对指针的引用

语法格式

数据类型 *&引用名 = 原指针;

例子引入:在函数内改变指针的指向

  • 传统二级指针
1
2
3
4
5
6
7
8
9
void Allocate1(int **pp) {
*pp = new int(10);
}

int main() {
int *p1 = nullptr;
Allocate1(&p1);
return 0;
}
  • 对一级指针的引用
1
2
3
4
5
6
7
8
9
void Allocate2(int *&rp) {
rp = new int(20);
}

int main() {
int *p2 = nullptr;
Allocate1(p2);
return 0;
}

引用作为函数的返回值

返回一个有效引用的前提

如果要保证返回一个有效的引用,那么返回变量的生命周期必须要长于用于接收的引用,否则就是野指针。

返回引用的一些例子

  • 局部变量作为引用返回
1
2
3
4
5
6
7
8
9
10
// a 的生命周期在函数运行结束之前, 无法被真正接收
int& ReturnLocalVar() {
int a = 10;
return a;
}

int main() {
int &ra = ReturnLocalVar();
std::cout << "ra: " << ra << std::endl; // 野指针, 乱值
}
  • 静态局部变量作为引用返回
1
2
3
4
5
6
7
8
9
10
// 无论是全局还是局部的静态变量,生命周期都在程序结束之前, 可以被接收
int& ReturnStaticVar() {
static int a = 10;
return a;
}

int main() {
int &ra = ReturnLocalVar();
std::cout << "ra: " << ra << std::endl; // 10
}
  • 引用参数作为返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 把传进来的引用 ra 处理完再返回去
int& ReturnRefParam(int &ra, const int value) {
ra = value;
return ra;
}

int main() {
int c = 10;
int &rc = ReturnRefParam(c, 20);
cout << "rc: " << rc << endl; // 20

// 返回的引用可作为左值, 在这里相当于 c = 40
// c 原本的值是 20, 调用函数时, 在函数内部被改为了 30, 作为引用被返回后又被赋值为 40
ReturnRefParam(c, 30) = 40;
cout << "rc: " << rc << endl; // 40
}

总结

  • 引用的本质就是指针常量,但是对写法进行了包装,既有指针的功能,又简便了写法
  • 引用必须在声明时初始化,且不能换绑或进行类型强转
  • 可以对一级指针进行引用,以避免二级指针的写法,但注意声明语句的 * 一定是在 & 前面
  • 注意作为引用返回的变量的生命周期,避免野指针
CATALOG
  1. 1. C++中的引用是什么
    1. 1.1. 相较于指针的优缺点
  2. 2. 使用引用
    1. 2.1. 引用的语法格式
    2. 2.2. 引用的特点
  3. 3. 引用作为函数参数
    1. 3.1. 例子引入:交换两变量的值
    2. 3.2. 例子引入:对传入字面量或常量的处理
  4. 4. 对指针的引用
    1. 4.1. 语法格式
    2. 4.2. 例子引入:在函数内改变指针的指向
  5. 5. 引用作为函数的返回值
    1. 5.1. 返回一个有效引用的前提
    2. 5.2. 返回引用的一些例子
  6. 6. 总结