跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

C++11 核心特性详解:列表初始化、新式声明、范围 for 与 STL 变化

介绍 C++11 标准的核心新特性。包括统一的列表初始化语法,简化了对象构造;引入 auto、decltype 和 nullptr 关键字,提升类型推导的安全性与代码简洁度;新增基于范围的 for 循环,优化遍历逻辑;以及 STL 容器的更新,如 initializer_list 支持、emplace 系列函数和移动语义。这些改进旨在提高开发效率、增强代码安全性并优化性能,是现代 C++ 编程的基础。

古灵精怪发布于 2026/3/30更新于 2026/7/2652 浏览
C++11 核心特性详解:列表初始化、新式声明、范围 for 与 STL 变化

C++11 新特性

C++ 的发展简史

C++ 最初由 Bjarne Stroustrup 在 1979 年提出,作为 C 语言的扩展,它引入了 面向对象编程 的概念,使得 C++ 在保持高效执行性能的同时,具备了更强的抽象和封装能力。

  • 1998 年:第一个 C++ 国际标准正式发布,被称为 C++98。这是 C++ 发展史上里程碑式的事件,确立了语言的基本框架和标准库体系。
  • 2003 年:标准委员会发布了一份技术勘误表(TC1),修复了 C++98 标准中的一些缺陷。修订后的版本被称为 C++03。由于核心语言未做改动,通常人们将其统称为 C++98/03。
  • 2000 年代初:委员会计划在 2007 年发布新标准,最初称之为 C++07,但由于进度拖延,后来被称为 C++0x(x 表示不确定是哪一年完成)。
  • 2011 年:经过长达 10 年的准备与争论,第二个真正意义上的 C++ 标准终于落地,正式定名为 C++11。

C++11 的意义

相比于 C++98/03,C++11 是一次真正意义上的'语言进化',它不仅修复了前标准中的大量缺陷(约 600 处),还引入了 超过 140 个新特性。这些变化使得 C++11 更加现代化,甚至有人称它为'一种新的语言'。

C++11 的主要改进体现在以下几个方面:

  1. 语法层面更简洁:引入了自动类型推导、范围 for 循环、lambda 表达式等,让代码更简洁直观。
  2. 性能与效率提升:通过右值引用和移动语义,减少了不必要的拷贝,显著提升运行效率。
  3. 并发支持:C++11 标准库首次引入了多线程支持,为现代并发编程提供了统一接口。
  4. 更强的库支持:新增了智能指针、正则表达式、哈希容器等工具,进一步增强了标准库的实用性。
  5. 安全性与可维护性:通过 nullptr、显式删除函数(=delete)、强枚举类型等机制,使得代码更加严谨和健壮。

总的来说,C++11 不仅是 C++98/03 的自然延续,更是一次 跨越式升级。它让 C++ 更加适用于 系统开发、库开发、并发编程 等复杂领域,同时提升了开发效率,降低了代码维护成本。

C++11 命名的由来

1998 年是 C++ 标准化的起点,委员会原计划 每五年更新一次标准。在讨论 C++03 后的下一个版本时,最初的目标是 2007 年发布,所以称为 C++07。然而由于标准制定的复杂性,2007 年未能完成,2008 年也无望,于是改名为 C++0x,其中 x 表示未知数。直到 2011 年才最终完成,因此正式定名为 C++11。

统一的列表初始化

我们用 Point 这个结构体来进行初始化的演示:

// 结构体 Point
struct Point {
    Point(int x, int y) : _x(x), _y(y) {}
    int _x;
    int _y;
};

C++98 中传统的{}初始化

在 C++98 中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定:

// C++98 花括号{} 初始化数组
int arr[] = {1, 2, 3};
int arr1[6] = {0};

// C++98 花括号{} 初始化结构体,属于聚合初始化
Point p = {1, 2};

C++11 中统一的列表初始化

列表初始化
  • C++11 扩大了用大括号括起的列表 { } 的使用范围,使其可用于所有的内置类型和用户自定义的类型的初始化。
    • 使用列表初始化时,可添加等号 =,也可不添加。
  • C++11 以后是想统一初始化方式,试图实现一切对象皆可用{}初始化,{}初始化也叫做**{}列表初始化**。
  • 内置类型支持,自定义类型也支持,自定义类型本质是类型转换,中间会产生临时对象,最后编译器优化了以后变成直接构造。
  • 列表初始化带来的便利:

内置类型的初始化对比:

C++98:

// C++98 初始化内置类型
int x = 1; // 初始化
double y = 2.2;
int y(2); // C++ 中内置类型 的 构造函数 初始化
int a1[] = {1, 2, 3}; // C++98 初始化一个数组

C++11:

// C++11 {}列表初始化 内置类型
int z = {3};
int w{4};
int a2[] = {1, 2, 3}; // C++11 可以不写赋值的等号

初始化自定义类型:以下写法 本质都会调用构造函数:

// 以下写法 本质都会调用构造函数
// C++98
Point p0(0, 0);
Point p1 = {1, 1};

// C++11
Point p2{2, 2};

C++11{}列表初始化可以去掉=,更好地支持以下写法:

// C++ 11 更好地支持了这种写法
int* p1 = new int[3]{1, 2, 3};
int* p2 = new int[4]{2, 4, 6, 8};
int* p3 = new int[5]{0};

可以认为**{}列表初始化是支持了多参数的构造函数的隐式类型转换**:

  • C++98 中,单参数的构造函数支持隐式类型转换
Point ptr1 = {1, 1}; // 调用构造函数*1
Point* ptr2 = new Point[2]{Point(1, 2), Point(3, 4)}; // 调用构造函数*2
Point* ptr3 = new Point[2]{ptr1, ptr1}; // 这里没有调用构造函数
// 下面这行这里语法的本质是 支持了 多参数构造函数的隐式类型转换
Point* ptr4 = new Point[2]{{2, 2}, {3, 3}}; // 调用构造函数 *2

{}也可以构造临时对象:

// Point& rp0 = { 1, 8 }; // {1, 8}会生成一个 Point 的临时对象,临时对象具有常性,需要用常引用
const Point& rp = {1, 8};

总结:

  • 实际在使用时,可以多使用**{}列表初始化**,但不建议去掉初始化用的=,因为以下两种写法容易混淆
Point ptr1(0, 0); // 调用构造函数
Point ptr2{0, 0}; // 调用构造函数
std::initializer_list
引入
  • 有了统一的列表初始化,思考以下场景,以下两行代码使用的语法一样吗?
vector<int> v = {1, 2, 3};
Point p = {1, 2};

看起来都是**{}列表初始化**,但其实是完全不同的语法。

  • vector<int> v = { 1, 2, 3 };这里的语法不是 {}列表初始化,调用的是构造函数,这里会先调用initializer_list的构造函数,再调用C++11为vector新增的构造函数。
  • Point p = { 1, 2 };,直接调用两个参数的构造函数,支持了多参数的构造函数隐式类型转换。

vector<int> v = { 1, 2, 3 }的语法是调用C++11中新增的构造函数。

initializer_list 介绍

C++11设计了一个新的类型,为initializer_list,用**{}括起来的,逗号分隔的常量列表**,就是initializer_list。

  • 可以看到,auto il = {1, 2 ,5};,变量il的类型就是initializer_list。
  • 标准库中initializer_list的基本定义:
template<class T>
class initializer_list {
    const T* _start;
    const T* _finish;
};
  • 可以看到,标准库中将const T的列表称为initializer_list。
  • initializer_list可以当成 C++11 中新增的类来使用,有相应的构造函数和迭代器。
  • 但initializer_list只支持读数据,不支持写入,因为initializer_list的iterator和const_iterator的类型都是const T*。
  • 定义一个initializer_list对象,本质是调用initializer_list的构造函数:
auto il_1 = {1, 2, 3};
initializer_list<int> il_2 = {10, 20, 30};
  • 根据initializer_list成员函数的设计,我们可以简单推断initializer_list的底层实现:
template<class T>
class initializer_list {
    const T* _start;
    const T* _finish;
};
  • 但由于有了initializer_list,C 语言中的这种写法不被支持了,不然和initializer_list的设计相冲突,不能发生隐式类型转换:
// 以下写法 C++11 中不支持
const int* arr = {1, 2, 3};
int* arr = {1, 2, 3};
vector 补充支持 initializer_list 的构造
// 补充支持 initializer_list 的构造函数
vector(std::initializer_list<T> il) {
    reserve(il.size());
    for(auto& e : il) push_back(e);
}
map 相关
map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };

C++11 的新声明

1. auto

1. C++ 类型系统演进
1.1 从 C 到 C++ 的类型困境

传统 C 风格代码中,复杂的类型声明严重阻碍了代码可读性。以 STL 容器迭代器为例:

std::map<std::string, std::vector<std::pair<int, double>>>::iterator it = data.begin();

这种冗长的类型声明带来两个个主要问题:

  1. 类型拼写错误风险增加
  2. 代码维护成本指数级上升

聪明的宝子已经想到了,我们可以尝试用 typedef 解决问题,但 typedef 也有其缺陷和局限性。

1.2 typedef 的局限性

虽然 typedef 能缓解部分问题,但存在严重缺陷:

typedef char* pstring;
int main() {
    const pstring p1; // 编译成功还是失败?
    const pstring* p2; // 编译成功还是失败?
    return 0;
}

在 C++ 中,const 的修饰规则取决于它出现的位置和类型别名的展开方式。

1. const pstring p1

• pstring 的类型是 char*(指针类型)。
• const 修饰的是 变量 p1 本身,即 p1 是一个 常量指针(指针本身不可变,但指向的字符可变)。
• 展开后等价于:char* const p1;。
• 错误原因:常量指针 p1 必须在声明时初始化(否则编译失败)。

2. const pstring* p2

• pstring 的类型是 char*。
• const 修饰的是 pstring 类型的对象,即 p2 是一个 指向常量指针的指针。
• 展开后等价于:char* const* p2;。
• 正确:p2 本身是一个普通指针,可以指向其他 const pstring 类型的对象(无需初始化)。


关键总结
声明const 修饰的对象展开后的等价形式编译结果
const pstring p1;指针 p1 本身(常量指针)char* const p1;失败
const pstring* p2;pstring 类型的对象(指向的指针是常量)char* const* p2;成功

对比其他写法

• 若想修饰 指向的字符(而不是指针本身),应使用 const char*:

// 指向常量字符的指针(指针可变,字符不可变)
const char* p3;
char const* p4; // 同上

• 若想同时修饰 指针和指向的字符:

const char* const p5; // 常量指针指向常量字符

在*左边的 const,修饰的是指针指向的对象 在*右边的 const,修饰的是对象本身


核心规则

• const 修饰的是其右侧的符号(若右侧无符号,则修饰左侧的符号)。
• typedef 定义的别名会保留原始类型的修饰关系,const 直接修饰别名类型本身。

可以看到,const在和 typedef 联合使用时,有这么多的注意事项,那有没有什么好的解决方案呢?
有的有的!

2. auto 关键字的革命性意义

在早期 C/C++ 中 auto 的含义是:使用 auto 修饰的变量,是具有 自动存储器的局部变量,但遗憾的是一直没有人去使用它。

C++11 中,标准委员会赋予了 auto 全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由 编译器在编译时期推导而得。

2.1 auto 的用法
int TestAuto() {
    return 10;
}
int main() {
    int a = 10;
    auto b = a;
    auto c = 'a';
    auto d = TestAuto();
    cout << typeid(b).name() << endl;
    cout << typeid(c).name() << endl;
    cout << typeid(d).name() << endl;
    //auto e; 无法通过编译,使用 auto 定义变量时必须对其进行初始化
    return 0;
}

可以看到,auto 对类型进行了自动推导。

2.2 auto 使用时的注意细节

使用 auto 定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导 auto 的实际类型。因此 auto 并非是一种'类型'的声明,而是一个类型声明时的'占位符',编译器在编译期会将 auto 替换为变量实际的类型。

//无法通过编译,使用 auto 定义变量时必须对其进行初始化
auto e;
1. auto 与指针和引用结合起来使用

用 auto 声明指针类型时,用 auto 和 auto* 没有任何区别,但用 auto 声明引用类型时则必须 加 &。

int main() {
    int x = 10;
    auto a = &x;
    auto* b = &x;
    auto& c = x;
    auto d = x;
    cout << typeid(a).name() << endl;
    cout << typeid(b).name() << endl;
    cout << typeid(c).name() << endl;
    cout << typeid(d).name() << endl;
    *a = 20;
    *b = 30;
    c = 40;
    return 0;
}

可以看到:

  • c 是 x 的别名,d 是 x 的赋值
  • 用 auto 声明指针类型时,用 auto 和 auto* 没有任何区别。
  • 用 auto 声明引用类型时则必须加 &。
2. 在同一行定义多个变量

当在 同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译
器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

int main() {
    auto a = 1, b = 2; //该行代码会编译失败,因为 c 和 d 的初始化表达式类型不同
    auto c = 3, d = 4.0;
    return 0;
}
  • 可以看到第五行有相应的报错信息。
3. auto 不能推导的场景
1. auto 不能作为函数的参数
// 此处代码编译失败,auto 不能作为形参类型,因为编译器无法对 a 的实际类型进行推导
void TestAuto(auto a) {}
  • 形参在传参前没有初始值,编译器无法根据其初始值进行推导类型。
2. auto 不能直接用来声明数组
  • 为了避免与 C++98 中的 auto 发生混淆,C++11 只保留了 auto 作为类型指示符的用法。
  • auto 在实际中最常见的优势用法就是和 C++11 提供的新式 for 循环 和 lambda 表达式 等进行配合使用。

3. auto 核心机制深度剖析
3.1 类型推导规则

推导规则遵循模板参数推导的黄金法则:

const int cx = 42;
auto v1 = cx; // 推导为 int (去除 const)
auto& v2 = cx; // 推导为 const int&

特殊情况处理:

int arr[5];
auto arr1 = arr; // 推导为 int*
auto& arr2 = arr; // 推导为 int(&)[5]
void func(int);
auto f1 = func; // void(*)(int),此处为函数指针
auto& f2 = func; // void(&)(int)

编译器处理 auto 变量的步骤:

  1. 解析初始化表达式
  2. 推导表达式类型(去除引用 和 const 限定)
  3. 应用类型修饰符(& * 等)
  4. 生成最终变量类型
3.2 auto 推导时的类型退化(Type Decay)

auto 推导时的类型退化机制:

const char* const str = "hello";
auto s1 = str; // 推导为:const char*
auto* s2 = str; // 推导为:const char*
auto& s3 = str; // 推导为:const char* const&

退化规则:

  1. 去除顶层 const
  2. 数组退化为指针
  3. 函数退化为函数指针
推导函数指针的场景:
int i = 10;
auto p = &i;
auto pf = malloc; // 函数指针类型 也可以 自动识别
cout << typeid(p).name() << endl;
cout << typeid(pf).name() << endl;
map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
//map<string, string>::iterator it = dict.begin();
auto it = dict.begin();

2. decltype

  • 关键字 decltype 的作用:将变量的类型声明为表达式指定的类型

引入:

使用 auto 定义一个函数指针:

auto pf = malloc; // 函数指针类型 auto 也可以 自动识别
cout << typeid(pf).name() << endl;

那么我们如何定义一个和 pf 同类型的变量呢?

// 以下这种写法不可取
typeid(pf).name _ptr; 
  • typeid().name 只能获取到变量的类型,不能用于声明/定义变量
  • 我们可以使用 auto 来定义变量,但 auto 定义时必须完成初始化
auto pf1 = pf; // 如果我只想声明一个变量,不定义,该如何做呢?
  • C++11 更新了 decltype 关键字来解决这里的问题。

关键字 decltype 的作用:将变量的类型声明为表达式指定的类型

场景一:类内的成员变量是个函数指针,但是声明函数指针类型太繁琐了,有没有什么简单的书写方式呢?

class A {
private:
    decltype(malloc) pf;
};
// 一些其他使用
// decltype 可以推导出变量的类型,再定义变量,或者作为模板实参
decltype(pf) _ptr = malloc;
decltype(malloc) _ptr2; // 单纯先声明一个变量,不初始化
  • decltype(malloc) pf; pf 是一个函数指针,类型和 malloc 的类型一致,这里 decltype 完成类型的自动推导。

场景二:类内的模板参数需要传入函数指针时,使用 decltype 简化书写

template<class Func>
class B {
private:
    Func _pf; // 可以传入函数指针
};
  • 实例化 B 类时,decltype 作为模板实参,显式实例化模板
auto pf = malloc; // 函数指针类型 也可以 自动识别
B<decltype(pf)> b1; // decltype 自动推导函数指针类型
B<decltype(malloc)> b2;

场景三: decltype 推理表达式的类型

int main() {
    const int x = 1;
    double y = 2.2;
    decltype(x * y) ret; // ret 的类型是 double
    decltype(&x) p; // p 的类型是 int const *
    cout << typeid(ret).name() << endl;
    cout << typeid(p).name() << endl;
    return 0;
}

3. nullptr

1. nullptr 的背景

在 C++ 中,NULL 被广泛用来表示空指针。NULL 实际是一个宏,在传统的 C 头文件 (stddef.h) 中,可以看到如下代码:

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void*)0)
#endif
#endif

这种实现方式存在一定问题:

可以看到,NULL 可能被定义为字面常量 0,或者被定义为无类型指针 (void*) 的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如:

  • 类型不安全:由于 NULL 实际上是 0,它可能被错误地解释为整数 0。在某些情况下,编译器可能无法正确区分 0 是空指针还是数字常量。
  • 重载函数冲突:在重载函数中,NULL 作为整数常量可能会导致不明确的重载匹配,编译器无法确定应该调用哪个版本的函数。

例如:有如下代码

//C++ 中的空指针
void f(int) { cout << "f(int)" << endl; }
void f(int*) { cout << "f(int*)" << endl; }
int main() {
    f(0);
    f(NULL);
    f(nullptr); // 空指针关键字
    cout << sizeof(nullptr) << endl; // 8 字节
    return 0;
}

程序本意是想通过 f(NULL) 调用指针版本的 f(int*) 函数,但是由于 NULL 被定义成 0,因此与程序的初衷相悖。
在 C++98 中,字面常量 0 既可以是一个整形数字,也可以是无类型的指针 (void*) 常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转 (void*)0。

为了避免这些问题,C++11 引入了 nullptr,它是一个具有明确类型的空指针常量。

2. nullptr 的特性
2.1 类型安全

nullptr 具有独特的类型 std::nullptr_t,这使得它与任何类型的指针都不相同。它是一个空指针常量,专门用来表示指针的空值。

int* p1 = nullptr; // 正确,p1 是一个空的 int 指针
int x = nullptr; // 错误,不能将 nullptr 赋给一个非指针类型
2.2 不能被隐式转换为整数

与 NULL 可能被隐式转换为 0 不同,nullptr 无法被转换为整数类型。这消除了类型不安全的隐患。

nullptr_t nt = nullptr;
int x = nt; // 错误,无法将 nullptr_t 转换为 int
2.3 可以用于所有指针类型

nullptr 可以用于任何类型的指针,无论是普通指针、智能指针还是类类型指针。

int* p1 = nullptr; // 对于普通指针
std::shared_ptr<int> p2 = nullptr; // 对于智能指针
2.4 在重载函数中消除歧义

由于 nullptr 有明确的类型,它可以帮助解决重载函数中由于 NULL 造成的歧义问题。

void func(int* p) { std::cout << "Called func(int*)\n"; }
void func(double* p) { std::cout << "Called func(double*)\n"; }
int main() {
    func(nullptr); // 传递 nullptr,调用 func(int*)
    return 0;
}

在这个例子中,func(nullptr) 会调用 func(int*),而不会引发重载歧义问题。

3. nullptr extbackslash`与 NULL 比较
3.1 定义差异
  • NULL 是一个宏,通常被定义为 0,但它是整数类型,不具有指针类型的明确区分。
  • nullptr 是一个具有明确类型 std::nullptr_t 的常量,只有在指针上下文中才有效。
3.2 使用场景
  • 使用 NULL 时可能会引发一些类型混淆或重载解析问题,尤其是在复杂的函数重载中。
  • 使用 nullptr 能有效避免这些问题,因为它具有强类型系统,不会与整数类型混淆。
3.3 编译器支持

大部分现代编译器都已经支持 nullptr,因此可以放心使用。在 C++11 标准之前,C++ 编译器大多只能使用 NULL 来表示空指针。

4. nullptr 在实际编程中的使用
4.1 初始化指针

在初始化指针时,使用 nullptr 是更好的选择,因为它确保指针明确为空。

int* p = nullptr; // 更安全和明确的空指针初始化
4.2 在函数重载中避免歧义

在重载函数中,nullptr 的使用可以避免由于 NULL 引发的歧义问题。

void foo(int* p);
void foo(double* p);
foo(nullptr); // 调用 foo(int*)

范围 for 循环

该语法前文已经介绍过:

1. 语法

1.1 C++98 中遍历数组的方式
void TestFor() {
    int array[] = {1, 2, 3, 4, 5};
    for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
        array[i] *= 2;
    for (int* p = array; p < array + sizeof(array) / sizeof(array[0]); ++p)
        cout << *p << endl;
}

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此 C++11 中引入了基于范围的 for 循环。

1.2 C++11 范围 for 循环

for 循环后的括号由冒号' :'分为两部分:

  • 第一部分:范围内用于迭代的变量。
  • 第二部分:表示被迭代的范围。
void TestFor_2() {
    int array[] = {1, 2, 3, 4, 5};
    for (auto& e : array) e *= 2;
    for (auto& e : array) cout << e << endl;
}

范围 for 与普通循环类似,可以用 continue 来结束本次循环,也可以用 break 来跳出整个循环。

2. 范围 for 的使用条件

  1. for 循环迭代的范围必须是确定的
    • 对于数组而言,就是数组中第一个元素和最后一个元素的范围;
    • 对于类而言,应该提供 begin 和 end 的方法,begin 和 end 就是 for 循环 迭代的范围。
  2. 迭代的对象要实现++和==的操作。

以下代码就有错误,因为 for 的范围不确定

//不知道数组的长度
void TestFor(int array[]) {
    for (auto& e : array) cout << e << endl;
}

3. 范围 for 语法糖的底层实现

范围 for 循环的等价转换:

for (auto& elem : container) {
    ...
}
// 转换为
{
    auto&& __range = container;
    auto __begin = begin(__range);
    auto __end = end(__range);
    for (; __begin != __end; ++__begin) {
        auto& elem = *__begin;
        ...
    }
}

可以看到,范围 for 的底层依然是基本的 for 循环。

范围 for 底层的关键点:

  • 依赖 ADL 查找 begin/end 方法
  • 使用右值引用避免不必要的拷贝
  • 迭代器有效性要求与普通循环相同

4. auto 与范围 for 的协同效应

4.1 最佳实践模式
//对于 STL 中的长类型使用 auto
std::vector<std::vector<std::string>> complex_data;
for (const auto& inner_vec : complex_data) {
    // 对每个子向量,使用范围 for 遍历内层字符串
    for (const auto& str : inner_vec) {
        std::cout << str << ' ';
    }
    std::cout << '\n';
}

注意事项:

  • 若只需读取数据,使用 const 引用(const auto&)避免不必要的拷贝。
  • 若需修改字符串内容,可去掉 const 并使用普通引用(auto&)。
  • 避免在遍历过程中修改容器结构(如添加/删除元素),否则可能导致未定义行为。
4.2 性能优化要点

迭代器失效场景:

std::vector<int> vec{1, 2, 3};
for (auto& x : vec) {
    if (x == 2) vec.push_back(4); // 导致迭代器失效
}

右值容器处理:

for (auto&& x : get_temporary()) {} // 延长临时对象生命周期

避免隐式拷贝:

for (auto x : huge_container) {} // 拷贝开销
for (const auto& x : huge_container) {} // 正确方式
//使用 const 引用减少拷贝开销。

5. 性能分析

测试案例(循环 100 万次):

std::vector<int> data(1'000'000);
// 传统 for 循环
for (size_t i = 0; i < data.size(); ++i) {
    data[i] *= 2;
}
// 范围 for 循环
for (auto& x : data) {
    x *= 2;
}

GCC 12 优化结果:

循环类型指令缓存命中率分支预测失败率执行时间 (ms)
传统索引循环92%1.2%2.45
范围 for 循环95%0.8%2.38

结论:现代编译器对两种循环方式的优化能力相当,因此不用担心性能问题。

C++11 容器相关

新容器

  • C++11 中 STL 新增了四个容器,分别是 array(定长数组),forward_list(单链表),unordered_map(哈希),unordered_set(哈希)
  • 以上容器这里暂不做介绍

新接口

c 系列的迭代器
  • 新增了一系列c 开头的迭代器接口,这些是由于C++ 标准委员会认为普通对象和 const 对象都调用 begin 和 end 容易混淆
  • 其实不容易混淆:const 版本的 begin() 和 end() 中的 const修饰的是 this 指针(非静态成员函数隐藏的第一个参数),所以参数类型不同,构成函数重载,因此:
    • 普通对象调用普通的 begin() 和 end() 函数,const 对象调用 const 版本的 begin() 和 end() 函数,因此很容易区分
    • 在实际中 c 系列开头的迭代器返回函数使用并不多
{}列表初始化的构造函数
  • 所有容器均支持了{}列表初始化的构造函数
emplace 系列和右值引用
  • 所有容器新增了 emplace 系列,会涉及**&&右值引用和…模板的可变参数**,会带来性能上的提升
移动构造和移动赋值
  • 容器新增了移动构造(也叫移动拷贝构造)和移动赋值,同样也会带来性能上的提升

  • 右值引用和移动构造带来的性能提升,我们在之后的文章中做讲解

结语

从列表初始化到新式声明,从范围 for 到 STL 新接口,C++11 的诸多特性无不体现出一个目标:让开发者在保持高性能的同时,写出更简洁、更安全、更现代化的代码。

当然,C++11 并不是终点。之后的 C++14、C++17、C++20 都在不断丰富和强化语言本身,但 C++11 无疑是整个'现代 C++'时代的奠基石。如果你能熟练掌握这些特性,就已经站在了通往更高版本的桥梁上。

目录

  1. C++11 新特性
  2. C++ 的发展简史
  3. C++11 的意义
  4. C++11 命名的由来
  5. 统一的列表初始化
  6. C++98 中传统的{}初始化
  7. C++11 中统一的列表初始化
  8. 列表初始化
  9. std::initializer_list
  10. 引入
  11. initializer_list 介绍
  12. vector 补充支持 initializer_list 的构造
  13. map 相关
  14. C++11 的新声明
  15. 1\. auto
  16. 1\. C++ 类型系统演进
  17. 1.1 从 C 到 C++ 的类型困境
  18. 1.2 typedef 的局限性
  19. 1\. const pstring p1
  20. 2\. const pstring* p2
  21. 关键总结
  22. 对比其他写法
  23. 核心规则
  24. 2\. auto 关键字的革命性意义
  25. 2.1 auto 的用法
  26. 2.2 auto 使用时的注意细节
  27. 1\. auto 与指针和引用结合起来使用
  28. 2\. 在同一行定义多个变量
  29. 3\. auto 不能推导的场景
  30. 1\. auto 不能作为函数的参数
  31. 2\. auto 不能直接用来声明数组
  32. 3\. auto 核心机制深度剖析
  33. 3.1 类型推导规则
  34. 3.2 auto 推导时的类型退化(Type Decay)
  35. 推导函数指针的场景:
  36. 2\. decltype
  37. 3\. nullptr
  38. 1\. nullptr 的背景
  39. 2\. nullptr 的特性
  40. 2.1 类型安全
  41. 2.2 不能被隐式转换为整数
  42. 2.3 可以用于所有指针类型
  43. 2.4 在重载函数中消除歧义
  44. 3\. nullptr extbackslash`与 NULL 比较
  45. 3.1 定义差异
  46. 3.2 使用场景
  47. 3.3 编译器支持
  48. 4\. nullptr 在实际编程中的使用
  49. 4.1 初始化指针
  50. 4.2 在函数重载中避免歧义
  51. 范围 for 循环
  52. 1\. 语法
  53. 1.1 C++98 中遍历数组的方式
  54. 1.2 C++11 范围 for 循环
  55. 2\. 范围 for 的使用条件
  56. 3\. 范围 for 语法糖的底层实现
  57. 4\. auto 与范围 for 的协同效应
  58. 4.1 最佳实践模式
  59. 4.2 性能优化要点
  60. 5\. 性能分析
  61. C++11 容器相关
  62. 新容器
  63. 新接口
  64. c 系列的迭代器
  65. {}列表初始化的构造函数
  66. emplace 系列和右值引用
  67. 移动构造和移动赋值
  68. 结语
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • AIGC 时代 C++ 吞吐量优化技巧与性能提升实践
  • MySQL 库核心操作详解:创建、修改与备份实战
  • 大模型上下文窗口 200k 到底是什么
  • 机场出租车调度问题的数学建模与 Python 仿真实现
  • MySQL 常用函数整理与使用指南
  • LLM 微调:时机、方法与抉择
  • 基于 DeepSeek-R1-Distill-Llama-8B 的 OpenSpec 协议分析
  • 安卓手机通过 Termux 和 Alpine 部署 Docker 并实现外网访问
  • Unity Shader Graph Triplanar 节点原理解析与实战
  • STL 转 STEP 格式转换工具 stltostp 使用指南
  • 高效集成 Gemini API:Zotero 学术场景 AI 辅助分析指南
  • OpenClaw 智能体框架入门:环境搭建、模型配置与远程访问
  • HarmonyOS 6.0 Camera Kit 微距状态监听能力详解
  • 知网 AIGC 检测原理与降重实操指南
  • AI 编程工具深度对比:Cursor、Copilot、Trae 与 Claude Code
  • Spring Boot 接入淘宝猫超卡 TopAPI 经验记录
  • 开源逆袭!Wan2.1让家用电脑也能跑AI视频生成
  • Everything Claude Code:Claude Code 配置集合与 AI 开发工作流
  • HarmonyOS6 RcList 组件事件处理机制与实战示例
  • 二级 Python 考试真题及参考代码合集(基本操作题部分)

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online