在 cpp 中,引用是一个别名,它是一个对象的另一个名字。引用和指针的区别在于,引用不是一个对象,它只是一个对象的别名。引用一旦初始化之后,就不能再绑定到另一个对象,这一点和指针不同。
我们在使用的过程中,可以简单地将其理解为 “引用即别名”。对于某个变量,我们为它起一个另外的名字,使用它就是在使用原变量。
# 引用的定义
引用的定义格式如下:
type &name = object; |
其中, type
表示引用的类型, name
表示引用的名字, object
表示引用的对象, &
表示引用符号。这样定义之后, name
就是 object
的一个别名, name
和 object
共享 同一块 内存空间。
对于同一个对象 / 变量,可以定义多个引用,这些引用都是这个对象的别名,它们共享同一块内存空间,但是最初的那个对象 / 变量的名字仍然是它的名字。因此,我们可以对某个变量创建多个引用,分别用于不同的用途。
值得注意的是,引用的使用有一定的约束条件:
- 引用在定义时必须初始化。
- 引用一旦初始化后,就不能更改,引用必须初始化为合法的内存空间。
# 引用的使用
以下是一个引用的使用示例:
int main() | |
{ | |
int a = 10; // 定义一个整型变量 a,初始化为 10。cpp 会为其分配内存空间。 | |
int &b = a; // 定义一个整型引用 b,初始化为 a。b 和 a 共享同一块内存空间。 | |
int c = 20; | |
b = c; // 将 c 的值赋给 b,由于共享内存空间,此时 a 的值也会变为 20。 | |
cout << "a = " << a << endl; | |
cout << "b = " << b << endl; | |
cout << "c = " << c << endl; | |
return 0; | |
}; |
引用最常见的使用场景是作为函数的参数。我们都知道,在 cpp 中对于函数的传参,有两种主流方式:
- 值传递:将实参的值拷贝一份传递给形参,在函数体内部对形参的修改不会影响实参。通常只将计算结果返回给调用者,本质偏向于 纯函数。
- 指针传递:将实参的地址传递给形参,通过指针可以修改实参的值。通常用于需要修改实参的情况。
而 引用传递,则是将实参的引用传递给形参, 通过引用可以修改实参的值 。引用传递的优点是既能够避免数据拷贝,又能够修改实参的值,主要就是得益于引用的特性 —— 共享内存空间。
以下是一个引用传递的示例:
#include <iostream> | |
using namespace std; | |
// 交换函数,用于交换两个整型变量的值 | |
void swap3(int &a, int &b) | |
{ | |
int temp = a; | |
a = b; | |
b = temp; | |
} | |
int main() | |
{ | |
int a = 10; | |
int b = 20; | |
cout << "原值:a = " << a << endl; | |
cout << "原值:b = " << b << endl; | |
swap3(a, b); | |
cout << "现值:a = " << a << endl; | |
cout << "现值:b = " << b << endl; | |
return 0; | |
}; |
在上述代码中,我们定义了一个 swap3
函数,用于交换两个整型变量的值。在函数体内部,我们通过引用的方式传递了两个整型变量的引用,这样在函数体内部对这两个变量的修改会影响到实参。
看起来和指针传递很像,但是引用传递的 语法更加简洁,而且不需要在函数体内部进行解引用操作。因此,引用传递是 cpp 中常用的一种传参方式。
当然,引用本身也可以作为函数的返回值。在上述说明中,我们知道了引用本身是一个别名,它是一个对象 / 变量的另一个名字,我们对其调用,实际上就是拿到了这个别名而已。也正是基于这个特性,引用函数本身可以作为 左值,并对其进行赋值操作。
#include <iostream> | |
using namespace std; | |
int &test02() | |
{ | |
static int a = 10; // 静态变量保存在全局区,函数执行完后不会被释放 | |
return a; | |
} | |
int main() | |
{ | |
int &b = test02(); // 引用作为函数的返回值 | |
cout << "b = " << b << endl; //b 的值为 10 | |
b = 1000; // 通过引用修改静态变量的值 | |
cout << "b = " << b << endl; //b 的值会变为 1000 | |
test02() = 10000; // 通过引用修改静态变量的值 | |
cout << "b = " << b << endl; //b 的值也会变为 10000 | |
return 0; | |
}; |
如果函数的返回值为一个引用,返回的必须是定义在全局区的变量。因为函数执行完后,局部变量会被释放,返回的引用就会指向一个无效的内存空间,这样会导致程序崩溃。
# 引用的本质
引用的本质是 指针常量 ,它是一个指针,只是在定义时不需要使用 *
符号。也就是说,当 cpp 解析代码遇到 &
符号后,会进行如下的转换操作:
int a = 10; | |
int &b = a; | |
// ↓转换为↓ | |
int a = 10; | |
int *const b = &a; | |
b = 20; | |
// ↓转换为↓ | |
*b = 20; |
const
操作符表示常量,修饰指针时表示指针的指向不可变,但是指针指向的值是可以改变的。因此,引用在定义时必须初始化,且初始化后不能更改。
# 常量引用
常量引用的主要作用是 防止被调用函数修改实参的值,防止误操作。在函数的参数列表中,如果我们不希末修改实参的值,可以将形参定义为常量引用。
#include <iostream> | |
#include <windows.h> | |
using namespace std; | |
// 打印数据函数 | |
void showValue(const int &val) | |
{ | |
//val = 1000; // 对传进来的引用进行修改,会影响到实参!!! | |
// 可以使用 const 修饰形参,防止误操作 | |
cout << "val = " << val << endl; | |
} | |
int main() | |
{ | |
SetConsoleOutputCP(CP_UTF8); | |
// 常量引用:修饰形参,防止误操作 | |
int a = 10; | |
showValue(a); | |
//int &ref = 10; // 非常量引用的初始值必须为左值 | |
// 加上 const 之后,编译器将代码修改为:int temp = 10; const int &ref = temp; | |
const int &ref = 10; | |
//ref = 20; // 表达式必须是可修改的左值,加入 const 变为只读、不可修改 | |
return 0; | |
}; |