引用(Reference)
引用一种是临时、轻量级的资源访问机制,本质是变量的别名,引用允许我们通过别名访问变量,而不需要复制其值。
1. 格式:type &ref = variable
。
2. 特点:
- 引用在声明时就必须初始化且之后不能改变引用的对象。
- 引用本质上是一个别名,它和原始对象共享同一内存地址。
- 引用用于函数参数传递时,避免了大对象的复制,并且可以在函数内部修改外部对象的值。
3. 用法:
void increment(int &x) {
x++; // 通过引用修改外部变量的值
}
int& getMax(int &a, int &b) {
return (a > b) ? a : b; // 返回引用
}
int x=10,y=20;
getMax(x,y)=30; //现在y=30
void printValue(const int &x) { //传递大型对象/结构体时尤其重要,可以避免不必要的复制,同时保护原始数据。
std::cout << x << std::endl; // 不能修改 x
}
int x;
printValue(x);
4. 与指针的区别:
特性 | 引用(Reference) | 指针(Pointer) |
---|---|---|
语法 | type &ref = variable; |
type* ptr = &variable; |
是否可为空 | 不可为空(必须引用有效的对象) | 可以为空(指向 nullptr ) |
是否可以修改指向对象 | 不可以修改引用指向的对象 | 可以通过修改指针来改变它指向的对象 |
内存开销 | 引用没有额外的内存开销,它是一个别名 | 指针有额外的内存开销(存储指针本身的地址) |
使用场景 | 更常用于函数参数传递、返回引用等 | 更灵活,可以指向不同的对象,但需要管理内存 |
常量引用 | const type & |
const type* |
5. 注意:
- No reference to reference
- No pointers to reference:
int& * p;//illegal;
- No array of references
左右值引用
C中,左右值区别简单:左值可以用于赋值语句左侧,右值不行。 C++中:
- 左值:有持久化内存,对象作为一个左值时使用的是它的内存中的位置。
- 右值:临时的不可持久化的对象,对象作为右值时使用的是其值,内存中的内容。要么是字面常量,要么是表达式求值过程中创建的临时对象。
原则:左值可被当做右值使用(使用其内容),反之不可。
const i=0;
中i是常量左值。
左值引用
也就是默认的引用,如int &
。
右值引用
为支持对象的移动(而非拷贝),引入右值引用:只能绑定到右值的引用,也就是绑定到临时的、即将销毁的对象。
形式:T&&
即在类型后通过&&
获得对象引用。
例:
std::move
utility 标准库中的std::move
函数可以获得绑定到左值上的右值引用,它接受左值,返回右值引用。
右值引用主要用于两种情况:移动语义 和 完美转发
移动语义
右值引用实现的移动语义使得临时对象的资源(如动态分配的内存)可以从一个对象“转移”到另一个对象,而不是进行拷贝,提升了性能。
具体包括移动构造函数和移动赋值运算符。 注意:在移动操作完成后,原对象的指针应该被置为空,或者使其进入一个有效的、空的状态。这样,原对象的析构函数不会释放已经转移的资源,并且原对象可以安全地被销毁。
#include <iostream>
#include <cstring>
class MyClass {
private:
char* data;
public:
// 构造函数
MyClass(const char* str = "") {
if (str) {
data = new char[strlen(str) + 1];
strcpy(data, str);
} else {
data = nullptr;
}
std::cout << "Constructor: " << data << std::endl;
}
// 移动构造函数
MyClass(MyClass&& other) noexcept : data(other.data) {
other.data = nullptr; // 置空原对象的指针
std::cout << "Move Constructor: " << data << std::endl;
}
// 移动赋值运算符
MyClass& operator=(MyClass&& other) noexcept {
if (this != &other) { // 防止自赋值
delete[] data; // 释放当前对象的资源
data = other.data; // 转移资源
other.data = nullptr; // 将原对象的指针置为空
std::cout << "Move Assignment: " << data << std::endl;
}
return *this; // 支持链式赋值
}
// 拷贝构造函数(为了演示)
MyClass(const MyClass& other) {
if (other.data) {
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
} else {
data = nullptr;
}
std::cout << "Copy Constructor: " << data << std::endl;
}
// 析构函数
~MyClass() {
delete[] data;
std::cout << "Destructor: " << (data ? data : "nullptr") << std::endl;
}
// 显示数据
void show() const {
std::cout << "Data: " << (data ? data : "nullptr") << std::endl;
}
};
int main() {
MyClass obj1("Hello");
MyClass obj2("World");
obj2 = std::move(obj1); // 移动赋值
obj1.show(); // 应该显示 nullptr,因为资源已转移
obj2.show(); // 应该显示 "Hello"
return 0;
}
引用折叠(Reference Collapsing)
引用折叠用来处理引用类型之间的 组合和转换,特别是在模板和万能引用的上下文中。引用折叠规则在多重引用类型结合时会产生一个简化后的引用类型。
引用折叠规则
C++ 中的引用折叠规则指出,当 两个引用类型 结合在一起时,引用会被折叠为一种较为简单的类型。具体规则如下:
T& &, T& &&, T&& &
都折叠为T&
。T&& &&
折叠为T&&
。
万能引用(Universal Reference)
万能引用,又叫做 转发引用(Forwarding Reference),用来描述既可以绑定 左值 也可以绑定 右值 的引用类型。 它的主要特点是:在模板中,引用可以通过 右值引用 或 左值引用 来推导,这取决于传入的参数类型。
万能引用的定义是 通过模板类型推导出的右值引用,即类型为 T&&
,但是又能够接受 左值 和 右值。
也就是说,T&&
在模板中不仅仅代表右值引用,还可以代表 万能引用。
万能引用的示例
#include <iostream>
#include <utility> // std::forward
template <typename T>
void f(T&& arg) {
std::cout << "Received value: " << arg << std::endl;
}
int main() {
int x = 5;
f(x); // 传递左值
f(10); // 传递右值
return 0;
}
分析:
- 在模板中,
T&&
是一个万能引用(转发引用),能够接受x
(左值)和10
(右值)。 - 编译器会根据传递的实际参数类型推导
T
的类型。 - 当传递
x
时,T
被推导为int&
,所以T&&
会变成int& &&
,折叠为int&
。 - 当传递右值
10
时,T
被推导为int
,所以T&&
变成int&&
。
万能引用和引用折叠的结合
在模板中,T&&
类型的参数可以绑定到左值或右值。当参数是左值时,T&&
会被折叠为左值引用(T&
),而当参数是右值时,T&&
会保留为右值引用(T&&
)。这使得我们可以在函数中正确地转发传入的左值和右值。
转发引用的一个常见例子:完美转发
#include <iostream>
#include <utility> // for std::forward
template <typename T>
void forwarder(T&& arg) {
// 完美转发,保持传入参数的值类别(左值/右值)
someFunction(std::forward<T>(arg));
}
void someFunction(int& x) {
std::cout << "Left value: " << x << std::endl;
}
void someFunction(int&& x) {
std::cout << "Right value: " << x << std::endl;
}
int main() {
int a = 5;
forwarder(a); // 调用左值版本
forwarder(10); // 调用右值版本
return 0;
}
分析:
std::forward<T>(arg)
是为了实现 完美转发。它根据T
的类型决定传递arg
时是否保持其左值或右值的性质。- 如果
T
是左值类型(即T&
),std::forward<T>(arg)
会传递左值引用。 - 如果
T
是右值类型(即T&&
),std::forward<T>(arg)
会传递右值引用。
4. 总结:
- 万能引用 是指一个模板参数
T&&
,它可以绑定到左值或右值。它在模板推导中非常有用,可以用于完美转发和其他高级用途。 - 引用折叠 是指两个引用类型结合时会简化为一种类型的规则。引用折叠是 C++ 中模板推导的一部分,确保了引用类型的简化,避免了复杂的类型组合。
通过合理运用 万能引用 和 引用折叠,我们可以编写更高效、灵活的模板代码,避免不必要的拷贝,减少性能开销。