【C++】 C++11 知识点梳理(上)


一、C++11 发展概述

版本 发布年份 核心新增特性
C++98 1998 模板、STL、容器、算法、字符串、流
C++11 2011 列表初始化、auto/decltype、Lambda、右值引用 / 移动语义、constexpr、多线程、内存模型、正则、智能指针、哈希容器、std::array
C++14 2014 读写锁、泛型 Lambda
C++17 2017 折叠表达式、constexpr if、结构化绑定、string_view、文件系统、std::any/optional/variant
C++20 2020 协程、模块、Concept、Ranges 库
C++23 2023 类模板参数推导、标准库模块化、打印接口、扁平容器等

二、列表初始化 & std::initializer_list

2.1 C++98 旧式 {} 初始化

仅支持数组、结构体使用大括号初始化,语法零散:

cpp 复制代码
struct Point
{
    int _x;
    int _y;
};

int main()
{
    int arr1[] = {1,2,3,4,5};
    int arr2[5] = {0};
    Point p = {1, 2};
    return 0;
}

2.2 C++11 统一列表初始化

C++11 实现万物可用 {} 初始化,支持内置类型、自定义类、容器,且可省略 =;同时会做窄转换检查。

核心用法
cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

struct Point
{
    int _x;
    int _y;
};

class Date
{
public:
    Date(int year = 1, int month = 1, int day = 1)
        : _year(year), _month(month), _day(day)
    {}
private:
    int _year, _month, _day;
};

int main()
{
    // 1. 内置类型
    int x1 = {2};
    int x2{2};       // 省略 =

    // 2. 自定义结构体/类
    Point p1{1, 2};
    Date d1{2025, 1, 1};
    const Date& d2{2024, 7, 25}; // 引用绑定临时对象

    // 3.需要注意的是C++98⽀持单参数时类型转换,也可以不⽤{}
    Date d3 = { 2025};
    Date d4 = 2025;

    // 4. 容器便捷构造/插入
    vector<Date> v;
    v.push_back({2025, 1, 1}); // 直接传初始化列表,简化代码

    return 0;
}
特性
  1. 编译器会优化:Date d = {a,b,c} 不会产生额外临时对象 + 拷贝构造,直接构造。
  2. 禁止隐式窄转换:int a{3.14}; 编译报错,int a = 3.14; C++98 允许截断。

2.3 std::initializer_list

解决容器批量初始化问题,STL 所有容器都增加了接收 initializer_list 的构造函数与赋值重载。

底层原理
  • std::initializer_list 内部仅保存两个指针(起始、末尾),不拷贝数据,开销极小。
  • ⭐底层指向栈上数组,支持迭代器遍历。
示例
cpp 复制代码
#include <iostream>
#include <vector>
#include <map>
using namespace std;

int main()
{
    // 1. 容器直接批量初始化
    vector<int> v1{1,2,3,4,5};
    vector<int> v2 = {10,20,30};

    // 2. map 结合列表初始化
    map<string, string> dict{ {"name", "zhangsan"}, {"age", "20"} };

    // 3. 列表赋值
    v1 = {100, 200, 300};

    return 0;
}
自定义类支持 initializer_list
cpp 复制代码
#include <initializer_list>
#include <vector>

class MyVector
{
private:
    vector<int> data;
public:
    // 接收初始化列表构造函数
    MyVector(initializer_list<int> il)
    {
        for (auto val : il)
            data.push_back(val);
    }
};

// 使用
MyVector mv{1,2,3,4};

std::initializer_list<int> mylist;
mylist = { 10, 20, 30 };
cout << sizeof(mylist) << endl;
// 这⾥begin和end返回的值initializer_list对象中存的两个指针
// 这两个指针的值跟i的地址跟接近,说明数组存在栈上
int i = 0;
cout << mylist.begin() << endl;
cout << mylist.end() << endl;
cout << &i << endl;
// {}列表中可以有任意多个值
// 这两个写法语义上还是有差别的,第⼀个v1是直接构造,
// 第⼆个v2是构造临时对象+临时对象拷⻉v2+优化为直接构造
vector<int> v1({ 1,2,3,4,5 });
vector<int> v2 = { 1,2,3,4,5 };
const vector<int>& v3 = { 1,2,3,4,5 };
// 这⾥是pair对象的{}初始化和map的initializer_list构造结合到⼀起⽤了
map<string, string> dict = { {"sort", "排序"}, {"string", "字符串"}};

三、右值引用 & 移动语义(C++11 性能核心)

3.1 左值 & 右值

  • 左值 (lvalue):有名字、可寻址、能出现在赋值号左边(普通变量、解引用指针、引用)。

  • 右值 (rvalue):无名字、临时对象、字面量、表达式结果,不可寻址。

    int a = 10; // a 左值
    int b = a + 20; // a+20 右值
    string s = "hello"; // "hello" 字符串字面量是右值


3.2 左值引用 & 右值引用

语法:

  • 左值引用:类型&,只能绑定左值;const 左值引用 可绑定右值。
  • 右值引用:类型&&,只能绑定右值;想要绑定左值需要 move 。
cpp 复制代码
int main()
{
    int a = 10;
    int& r1 = a;        // 左值引用绑定左值
    const int& r2 = 20; // const 左值引用绑定右值

    int&& rr1 = 20;     // 右值引用绑定右值
    // int&& rr2 = a;   // 报错:右值引用不能直接绑左值
    int&& rr3 = move(a);// std::move 将左值转为右值

    // 重点:右值引用变量本身是**左值**
    int&& rr4 = 100;
    // int&& rr5 = rr4; // 报错
    int&& rr6 = move(rr4);
    return 0;
}

3.3 引用延长生命周期

const 左值引用右值引用 都可以延长临时对象生命周期:

cpp 复制代码
#include <string>
using namespace std;

int main()
{
    string s1 = "test";
    const string& r1 = s1 + s1; // 延长生命周期,不可修改
    string&& r2 = s1 + s1;      // 延长生命周期,可修改
    r2 += "end";
    return 0;
}

3.4 重载匹配规则

同时提供三种重载时:

  1. 普通左值 → 匹配 T&
  2. const 左值 → 匹配 const T&
  3. 右值 / move(左值) → 匹配 T&&
cpp 复制代码
#include <iostream>
using namespace std;

void f(int& x)       { cout << "左值引用\n"; }
void f(const int& x) { cout << "const 左值引用\n"; }
void f(int&& x)      { cout << "右值引用\n"; }

int main()
{
    int a = 1;
    const int ca = 2;
    f(a);         // 左值引用
    f(ca);        // const 左值引用
    f(100);       // 右值引用
    f(move(a));   // 右值引用
    return 0;
}

3.5 移动构造 & 移动赋值

针对深拷贝类(string/vector)设计,转移资源所有权,替代深拷贝,提升性能。

函数原型

cpp 复制代码
// 移动构造
类名(类名&& 源) noexcept;
// 移动赋值
类名& operator=(类名&& 源) noexcept;

模拟简易 string(完整示例)

cpp 复制代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

class String
{
private:
    char* _str = nullptr;
    size_t _size = 0;
    size_t _capacity = 0;
public:
    String(const char* str = "")
    {
        _size = strlen(str);
        _capacity = _size;
        _str = new char[_capacity + 1];
        strcpy(_str, str);
    }

    // 拷贝构造(深拷贝)
    String(const String& s)
    {
        _size = s._size;
        _capacity = s._capacity;
        _str = new char[_capacity + 1];
        strcpy(_str, s._str);
    }

    // 移动构造(窃取资源)
    String(String&& s) noexcept
    {
        swap(_str, s._str);
        swap(_size, s._size);
        swap(_capacity, s._capacity);
    }

    // 拷贝赋值
    String& operator=(const String& s)
    {
        if (this != &s)
        {
            delete[] _str;
            _size = s._size;
            _capacity = s._capacity;
            _str = new char[_capacity + 1];
            strcpy(_str, s._str);
        }
        return *this;
    }

    // 移动赋值
    String& operator=(String&& s) noexcept
    {
        if (this != &s)
        {
            swap(_str, s._str);
            swap(_size, s._size);
            swap(_capacity, s._capacity);
        }
        return *this;
    }

    ~String()
    {
        delete[] _str;
        _str = nullptr;
    }
    const char* c_str() const { return _str; }
};

int main()
{
    String s1("hello");
    String s2 = s1;             // 拷贝构造
    String s3 = move(s1);       // 移动构造
    String s4("world");
    s4 = move(s3);              // 移动赋值
    return 0;
}

3.6 移动语义两大应用场景

  1. 函数传值返回局部对象:局部对象作为右值,触发移动构造,减少拷贝。
  2. 容器接口重载:STL push_back/insert 都提供 const T&T&& 两个版本:
    • 传入左值 → 拷贝构造
    • 传入右值 /move 对象 → 移动构造

cpp 复制代码
int main()
{
    std::list<bit::string> lt;
    bit::string s1("111111111111111111111");
    lt.push_back(s1);
    cout << "*************************" << endl;
    lt.push_back(bit::string("22222222222222222222222222222"));
    cout << "*************************" << endl;
    lt.push_back("3333333333333333333333333333");
    cout << "*************************" << endl;
    lt.push_back(move(s1));
    cout << "*************************" << endl;
    return 0;
}

运⾏结果:
string(char* str)
string(const string& s) -- 拷⻉构造
*************************
string(char* str)
string(string&& s) -- 移动构造
~string() -- 析构
*************************
string(char* str)
string(string&& s) -- 移动构造
~string() -- 析构
*************************
string(string&& s) -- 移动构造
*************************
~string() -- 析构
~string() -- 析构
~string() -- 析构
~string() -- 析构
~string() -- 析构

3.7 值类别细分(C++11 扩展)

C++11 把右值细分为两类:

  1. 纯右值 (prvalue)****:字面量、表达式结果、传值返回临时对象(传统意义上的右值)。
  2. 将亡值 (xvalue)****:move 结果、右值引用转换结果。

合称:泛左值 (glvalue) = 左值 + 将亡值。


3.8 引用折叠 & 万能引用

C++ 不允许 "引用的引用",模板中出现时遵循引用折叠规则:

  • T& &T&
  • T& &&T&
  • T&& &T&
  • T&& &&T&&

万能引用:模板参数 T&&

  • 传入左值 → 推导 T = 类型&,折叠后为左值引用
  • 传入右值 → 推导 T = 类型&&,最终为右值引用
cpp 复制代码
template<class T>
void f1(T& x)
{}
// 由于引⽤折叠限定,f2实例化后可以是左值引⽤,也可以是右值引⽤
template<class T>
void f2(T&& x)
{}
int main()
{
typedef int& lref;
typedef int&& rref;
int n = 0;
lref& r1 = n; // r1 的类型是 int&
lref&& r2 = n; // r2 的类型是 int&
rref& r3 = n; // r3 的类型是 int&
rref&& r4 = 1; // r4 的类型是 int&&
// 没有折叠->实例化为void f1(int& x)
f1<int>(n);
f1<int>(0); // 报错
// 折叠->实例化为void f1(int& x)
f1<int&>(n);
f1<int&>(0); // 报错
// 折叠->实例化为void f1(int& x)
f1<int&&>(n);
f1<int&&>(0); // 报错
// 折叠->实例化为void f1(const int& x)
f1<const int&>(n);
f1<const int&>(0);
// 折叠->实例化为void f1(const int& x)
f1<const int&&>(n);
f1<const int&&>(0);
// 没有折叠->实例化为void f2(int&& x)
f2<int>(n); // 报错
f2<int>(0);
// 折叠->实例化为void f2(int& x)
f2<int&>(n);
f2<int&>(0); // 报错
// 折叠->实例化为void f2(int&& x)
f2<int&&>(n); // 报错
f2<int&&>(0);
return 0;

3.9 完美转发 std::forward

作用:保留参数原有左 / 右值属性,配合万能引用使用。

  • std::move:一律转为右值
  • std::forward<T>:原样转发(左值转左值,右值转右值)
cpp 复制代码
template<class T>
void wrapper(T&& t)
{
    // fun(t);        // t 是左值,永远调用左值版本
    fun(forward<T>(t)); // 完美转发,保留原值类别
}

相关推荐
Y_Bk1 小时前
第十七届蓝桥杯C/C++A组省赛
c语言·数据结构·c++·算法·蓝桥杯
飞天狗1111 小时前
零基础JavaWeb入门——第4课:表单处理 —— 浏览器怎么把数据发给服务器
java·开发语言·前端·后端·servlet
多彩电脑1 小时前
向AIDE(安卓设备上的Android Studio)导入aar库
android·java·开发语言·androidx
江屿风2 小时前
C++图论基础单源最短路-常规版dijkstra算法/堆优化版dijkstra算法/bellman-ford 算法/spfa 算法流食般投喂
开发语言·c++·笔记·算法·图论
摇滚侠2 小时前
MyBatis 入门到项目实战 MyBatis 逆向工程 62
java·开发语言·mybatis
ch.ju2 小时前
Java Programming Chapter 4——Multi-level inheritance
java·开发语言
Molesidy2 小时前
【Linux】【C++】Linux下的C++编程以及基于GDB的VSCode的C++调试
开发语言·c++
techdashen2 小时前
用 Rust 真正发出 Ping:FFI 类型、newtype 与 MaybeUninit
开发语言·后端·rust
塵觴葉2 小时前
基于Lua协程的简单任务管理
开发语言·lua