c++注意点(7)----引用类型
引用可以理解为 “给已存在的变量起别名”,通过引用可以直接操作原变量。C++ 中引用主要分为左值引用和右值引用。
一、左值引用(Type&)
左值引用是对左值(可被取地址的变量、对象等持久化数据)的别名,声明形式为 Type& 引用名 = 原变量;。
特性
1.必须初始化,且初始化后不能更改指向(从一而终)。
2.对引用的操作等价于对原变量的操作。
int a = 1;
int& b = a; // b 是 a 的别名
b = 1001; // 等价于 a = 1001
cout << a; // 输出 1001
用途
(1)作为函数参数
1.直接修改实参:函数内部对引用的修改会直接影响外部实参。类似指针。
2.避免拷贝开销:对于大型对象,传递引用可减少内存拷贝,提高效率。
void modify(int& x) { // x 是实参的别名
x = 999; // 直接修改实参
}
int main() {
int a = 1;
modify(a); // 传入 a 的引用
cout << a; // 输出 999(a 被修改)
}
(2)作为函数返回值
1.允许返回值作为左值:若返回的是全局 / 静态变量或类成员的引用,可直接赋值。
注意:禁止返回局部变量的引用(局部变量生命周期结束后会被销毁,引用会成为 “悬垂引用”,访问时 undefined behavior)。
int a = 1; // 全局/静态变量(生命周期长)
int& getValue() {
return a; // 返回 a 的引用(合法)
}
int main() {
getValue() = 10; // 合法:相当于 a = 10
cout << a; // 输出 10
}
(3) const 限定的左值引用(常引用)
1.用于引用常量或临时值(右值),避免被修改。
const int& xx = 13; // 合法:给常量 13 起别名(常引用)
经典错误
class Vector {
private:
int& x; // 引用成员
int& y; // 引用成员
public:
Vector(int& a, int& b) : x(a), y(b) {}
// 默认拷贝构造函数等价于:
// Vector(const Vector& other) : x(other.x), y(other.y) {} // 错误!
};
int main() {
int a = 1, b = 2;
Vector v1(a, b);
Vector v2 = v1; // 错误:无法复制引用成员
}
引用的本质是对象的别名,一旦初始化就不能更改其指向的对象。因此,当类中包含引用成员时,默认的拷贝构造函数会尝试重新初始化引用,但这是非法的。
举个简单示例理解一下这里的错误。
假设我们强行允许引用成员的拷贝:
int a = 1, b = 2;
Vector v1(a, b); // x 引用 a,y 引用 b
int c = 3, d = 4;
Vector v2(c, d); // x 引用 c,y 引用 d
v2 = v1; // 假设允许拷贝,会发生什么?
1.若 x 和 y 重新绑定到 v1.x 和 v1.y 引用的对象(即 a 和 b),则违反引用不可重新绑定的规则。2.若 x 和 y 保持对 c 和 d 的引用,则拷贝操作没有意义。
解决办法:
class Vector {
private:
int& x;
int& y;
public:
Vector(int& a, int& b) : x(a), y(b) {}
Vector(const Vector&) = delete; // 禁用拷贝构造函数
Vector& operator=(const Vector&) = delete; // 禁用赋值运算符
};
二、右值引用
右值引用是 C++11 引入的重要特性,用于实现移动语义和完美转发。
右值
只能出现在赋值号右边的临时表达式,即没有持久内存地址的对象。
特点:没有名字、不可取地址、生命周期短(当前表达式结束后销毁)。
纯右值(prvalue):基本类型的字面量、临时对象。
42; // 整数字面量是右值
x + 5; // 表达式结果是右值
std::string("tmp"); // 临时对象是右值
将亡值:通过 std::move 或右值引用生成的对象。
std::move(x); // 将左值转为右值
int&& rr = 100; // rr 是右值引用,但 100 是右值
特性
右值引用是一种只能绑定到临时对象(右值)的引用,语法为 T&&。
用途
1.延长临时对象的生命周期。(就是说,这一块内存已经要释放了,这里的资源我暂存一会,延长其生命。)
2.区分左值和右值参数,实现移动语义。
右值引用的核心作用是在对象所有权转移时避免深拷贝,直接复用临时对象的资源。
为什么要有右值引用呢?
在传统的情况里,我们会遇到这种情况:即你有一个类class TEMP,你实现了TEMP A,然后你现在需要将其复制给TEMP B,平常会调用传统的拷贝构造函数。
这当然没什么问题,但是如果这个A已经没什么用了,你准备释放了,只是想把值给B,那么这时候,调用拷贝构造函数就会进行深拷贝(创建新对象时,为所有资源(如动态内存、文件句柄)分配新空间,并复制数据。),需执行耗时的内存分配和数据复制。
但是如果你使用右值引用,那么这时候,你就可以将值直接给B,通过右值引用识别临时对象,直接转移资源所有权(如指针),避免深拷贝。
移动拷贝的过程
- 临时对象(右值) 的资源(如动态分配的内存)被直接转移到新对象。
- 原对象被置空(如指针设为
nullptr),确保析构时不会释放已转移的资源。
接下来举个例子:
#include <iostream>
#include <utility>
class MyString {
private:
char* data; // 指向动态分配的内存
size_t length;
public:
// 构造函数
MyString(const char* str = "") {
length = strlen(str);
data = new char[length + 1];
std::strcpy(data, str);
std::cout << "构造: " << data << std::endl;
}
// 拷贝构造函数(深拷贝)
MyString(const MyString& other) {
length = other.length;
data = new char[length + 1];
std::strcpy(data, other.data);
std::cout << "拷贝构造: " << data << std::endl;
}
// 移动构造函数(资源转移)
MyString(MyString&& other) noexcept {
data = other.data; // 直接接管资源
length = other.length;
other.data = nullptr; // 清空原对象
other.length = 0;
std::cout << "移动构造: " << data << std::endl;
}
// 析构函数
~MyString() {
delete[] data;
}
};
int main() {
// 1. 创建临时对象(右值)
MyString temp = MyString("temporary"); // 临时对象
// 2. 通过右值引用转移资源
MyString str = std::move(temp); // 调用移动构造函数
// 此时 temp 的 data 已被清空(nullptr)
return 0;
}
更多推荐


所有评论(0)