初始化列表
在 C++98 标准中,花括号 {} 仅允许用于数组或结构体元素的统一列表初始化。
例如:
struct Point { int a; int b; };
int main() {
int arry[] = {1,2,3,4}; // 数组初始化
Point p = {1,2}; // 结构体初始化
return 0;
}
C++11 扩大了花括号初始化的适用范围,使其可用于所有内置类型和用户自定义类型。
// 对数组初始化
int arry1[]{1,2,3,4,5};
// 对内置类型初始化
int x{9};
// 对用户自定义类型初始化
Point p{9,9};
// 对 vector 容器对象进行初始化
vector<int> vec = {1,2,3,4,5,6,7,8,9};
注意: 使用初始化列表时,等号 = 是可选的。
std::initializer_list
std::initializer_list 是一个轻量级的模板类,定义在 <initializer_list> 头文件中。它提供了一种只读的视图来访问初始化列表中的元素,常用于构造函数、函数参数以及返回类型,以支持统一初始化语法。
虽然名字像容器,但它本质上是一个指向常量数据的视图。当你在容器构造中看到花括号初始化时,底层往往依赖它。
基本形式:
std::initializer_list<T> init_list = { /* 初始化元素 */ };
并不是所有类型都有 std::initializer_list 的构造函数,但如果你显式定义了该构造函数,编译器在进行 {} 初始化时会优先调用它。
auto
在 C++98 中,auto 仅表示变量的存储类型(自动存储),意义不大。C++11 将其重新定义为自动类型推断关键字,极大地简化了复杂类型的声明。
示例:
std::pair<int, std::string> get_user_info() {
return {42, "Alice"};
}
int main() {
// 使用 auto 简化返回类型的声明
auto user = get_user_info();
return 0;
}
decltype
decltype 用于在编译时推导表达式的类型。与 auto 不同,decltype 关注的是表达式本身的类型属性,而非变量的初始化值。
基本语法:
decltype(expression) variable_name;
示例:
int a = 5;
double b = 3.14;
decltype(a) c = a; // c 的类型为 int
decltype(b+b) d = b; // b+b 结果为 double,则 d 的类型为 double
注意: decltype 不会实际计算表达式的值,仅在编译期工作。
nullptr
C++ 中传统的 NULL 通常被定义为字面量 0,这可能导致二义性,因为它既能表示指针也能表示整数。为了清晰和安全,C++11 引入了 nullptr 专门表示空指针。
左值引用和右值引用
C++11 新增了右值引用(Rvalue Reference),配合原有的左值引用(Lvalue Reference),使得资源管理更加灵活。
什么是左值和左值引用?
左值是表示数据实体的表达式,如变量名、指针等,通常出现在赋值符号左侧。
int* p = new int(0);
int b = 1;
const int c = 2;
左值引用给左值取别名,类型必须严格匹配(类型 + &)。
int*& rp = p;
int& rb = b;
const int& rc = c;
什么是右值和右值引用?
右值通常是临时对象、字面常量或表达式结果,只能出现在赋值符号右侧。
int&& rr1 = 10; // 字面常量
double&& rr2 = x + y; // 表达式
右值本身不能取地址,但通过右值引用绑定后,引用变量本身是左值,可以取地址并修改其绑定的内容(但这不改变原始右值的本质,只是延长了生命周期)。
误区纠正: 修改右值引用变量 rr1 并不会让原始字面量 10 变成 11,你修改的是引用所绑定的副本或临时存储区。
右值引用的意义
左值引用在某些场景下存在短板。例如,函数返回局部对象时,若没有移动语义,会触发拷贝构造,导致额外的内存分配和数据复制。
移动构造(Move Constructor)
右值引用解决了深拷贝开销问题。通过定义移动构造函数,我们可以'窃取'右值的资源,而不是复制它。
// 移动构造函数
MyString(MyString&& s) : _str(nullptr), _size(0), _capacity(0) {
swap(s); // 直接交换资源指针
}
当既有拷贝构造又有移动构造时,对于右值(临时对象),编译器会优先匹配更精确的移动构造,从而避免开辟新空间,显著提升性能。
万能引用与完美转发
在模板编程中,T&& 被称为万能引用(Universal Reference),它能同时绑定左值和右值。但在函数体内,它们默认被视为左值处理。
示例:
template<typename T> void CallFun(T&& arg) {
Fun(arg); // 这里 arg 被视为左值,无法触发右值重载
}
为了保持参数的原始值类别(左值还是右值)传递给目标函数,需要使用 std::forward 进行完美转发。
template<typename T> void CallFun(T&& arg) {
Fun(std::forward<T>(arg)); // 根据 T 的类型决定转发方式
}
这样,传入左值时调用左值版本,传入右值时调用右值版本,实现了真正的值类别保持。


