📝 说明:本文为C++学习笔记,AI辅助系统整理
📅 整理时间:2025年9-10月
🎯 目的:系统梳理C++11核心特性,建立完整知识体系
💡 适用场景:面试复习、技术查阅


📚 本文内容

  1. auto和decltype(类型推导)
  2. 列表初始化
  3. 移动语义(右值引用)
  4. lambda表达式

本篇整理的知识点(上半部分)

本篇整理了C++11的前4个核心特性:

  1. auto和decltype(类型推导)
  2. 列表初始化
  3. 移动语义(右值引用)
  4. lambda表达式

这是上半部分,下半部分继续学习其他特性。


知识点1:auto和decltype

auto类型推导机制

auto类型推导
基本类型推导
引用推导
const推导
int, double等
auto& 保留引用
auto 丢失引用
const auto& 保留const
auto 丢失const

auto的作用

让编译器自动推导变量类型,简化复杂类型声明。

典型示例

// 复杂的STL迭代器类型
std::map<std::string, std::vector<int>> complex_map;

// 传统写法(太长了)
std::map<std::string, std::vector<int>>::iterator it1 = complex_map.begin();

// 用auto简化
auto it2 = complex_map.begin();  // 编译器自动推导

auto的推导规则

核心推导规则

// 1. const和引用的处理
const int ci = 42;
auto a = ci;        // a是int(丢失const)
const auto& b = ci; // b是const int&(保留const)

int x = 10;
int& rx = x;
auto c = rx;        // c是int(丢失引用)
auto& d = rx;       // d是int&(保留引用)

// 2. 数组退化
int arr[5];
auto g = arr;       // int*(数组退化为指针)

Mermaid图解:auto推导规则

原始类型
auto推导
丢失顶层const
丢失引用
数组退化为指针
需要保留?
const auto&
auto

decltype的精确推导

典型示例

int x = 5;
const int& cr = x;

decltype(x) y = 10;     // y是int
decltype(cr) z = x;     // z是const int&(完全保留)
decltype((x)) w = x;    // w是int&(括号表达式!)

重要规则:带括号的表达式会变成引用!

decltype在模板中的应用

典型示例

// 后置返回类型语法
template<typename T1, typename T2>
auto add(T1 a, T2 b) -> decltype(a + b) {
    return a + b;
}

// 编译器会根据a+b的类型推导返回类型
auto result1 = add(3.14, 2);     // double
auto result2 = add(1, 2);        // int

学习中的疑问1:decltype为什么适合模板?

疑问:decltype和auto有什么区别?为什么说decltype适合模板编程?

解答

  • auto:简化代码,但会丢失const、引用等修饰符
  • decltype:完全保留类型信息,包括const、引用等
  • 模板场景:返回类型可能依赖参数类型,decltype能精确推导

例子对比

const int x = 5;
const int& rx = x;

auto a = rx;        // a是int(丢了const和引用)
decltype(rx) b = x; // b是const int&(完全保留)

知识点2:列表初始化

统一初始化语法

典型示例

// 基本类型
int a{42};
double b{3.14};

// 容器初始化
std::vector<int> vec{1, 2, 3, 4, 5};
std::map<std::string, int> ages{{"Alice", 25}, {"Bob", 30}};

// 对象初始化
class Point {
public:
    Point(int x, int y) : x_(x), y_(y) {}
private:
    int x_, y_;
};
Point p{10, 20};

防止窄化转换

安全性优势

// 传统初始化(危险)
int x = 3.14;    // 允许,但截断小数 → x=3
char c = 256;    // 允许,但值溢出

// 列表初始化(安全)
int y{3.14};     // ❌ 编译错误!防止窄化
char d{256};     // ❌ 编译错误!防止溢出

我的理解:列表初始化在编译期就能发现类型转换问题,更安全。

initializer_list

典型示例

class MyContainer {
public:
    MyContainer(std::initializer_list<int> list) {
        for (auto item : list) {
            data_.push_back(item);
        }
    }
private:
    std::vector<int> data_;
};

MyContainer container{1, 2, 3, 4, 5};  // 直接用{}初始化

知识点3:移动语义

移动语义核心概念

移动语义
左值 lvalue
右值 rvalue
有名字
可取地址
临时对象
即将销毁
移动构造
转移资源
拷贝构造
复制资源

左值和右值

核心概念

int x = 10;        // x是左值(有名字,可取地址)
int y = x + 5;     // x+5是右值(临时值,不能取地址)
int z = func();    // func()返回值是右值

右值引用语法

典型示例

int&& rref = 20;            // 绑定到右值
int&& rref2 = std::move(x); // move将左值转为右值

移动构造函数

课上详细讲解的例子

class Resource {
private:
    int* data;
    size_t size;
    
public:
    // 构造函数
    Resource(size_t s) : size(s) {
        data = new int[size];
        cout << "构造:分配了" << size << "个int" << endl;
    }
    
    // 拷贝构造(深拷贝)
    Resource(const Resource& other) : size(other.size) {
        data = new int[size];
        memcpy(data, other.data, size * sizeof(int));
        cout << "拷贝构造:复制了" << size << "个int" << endl;
    }
    
    // 移动构造(资源转移)
    Resource(Resource&& other) noexcept 
        : data(other.data), size(other.size) {
        other.data = nullptr;  // "偷"资源
        other.size = 0;
        cout << "移动构造:转移了资源" << endl;
    }
    
    ~Resource() {
        delete[] data;
        cout << "析构" << endl;
    }
};

运行效果

Resource r1(100);                   // 构造
Resource r2 = r1;                   // 拷贝构造(深拷贝)
Resource r3 = std::move(r1);        // 移动构造(资源转移)
// r1现在被"掏空"了,data=nullptr

移动构造流程图解

r1对象 堆内存 r2对象 拷贝构造(深拷贝) 我的data指向内存A 分配新内存B 复制内存A的内容到B r2有自己的副本 移动构造(资源转移) 我的data指向内存A 给我你的data指针 给你(data=nullptr) r2接管内存A r1被掏空 r1对象 堆内存 r2对象

学习中的疑问2:移动语义

疑问:移动后的对象还能用吗?

解答

  • 移动后的对象处于"有效但未指定"状态
  • 可以给它重新赋值
  • 但不要访问它的数据(已经被"偷"走了)

安全做法

string s1 = "hello";
string s2 = std::move(s1);

// ❌ 危险
// cout << s1 << endl;  // s1的内容已被转移

// ✅ 安全
s1 = "new value";  // 重新赋值
cout << s1 << endl;  // OK

学习中的疑问3:noexcept的作用

疑问:为什么移动函数要加noexcept?

解答

  1. 性能优化:编译器可以进行更激进的优化
  2. 容器优化:vector扩容时优先用移动而不是拷贝
  3. 异常安全:告诉编译器不会抛异常

例子

// 无noexcept:vector扩容可能用拷贝
class WithoutNoexcept {
public:
    WithoutNoexcept(WithoutNoexcept&&) { }
};

// 有noexcept:vector扩容优先用移动
class WithNoexcept {
public:
    WithNoexcept(WithNoexcept&&) noexcept { }
};

知识点4:lambda表达式

基本语法

核心概念

// 最简单的lambda
auto add = [](int a, int b) { return a + b; };
int result = add(3, 5);  // 8

// 完整语法
[capture](parameters) mutable -> return_type { body }

捕获方式

课上详细讲解的例子

int x = 10;
int y = 20;

// 1. 值捕获(拷贝)
auto f1 = [x]() { 
    // x是副本,不能修改
    return x; 
};

// 2. 引用捕获(引用)
auto f2 = [&x]() { 
    x++;  // 可以修改外部的x
};

// 3. 混合捕获
auto f3 = [x, &y]() { 
    // x是副本,y是引用
};

// 4. 全部值捕获
auto f4 = [=]() { };  // 捕获所有变量的副本

// 5. 全部引用捕获
auto f5 = [&]() { };  // 捕获所有变量的引用

lambda与STL算法

课上的实用例子

std::vector<int> vec = {5, 2, 8, 1, 9};

// 降序排序
std::sort(vec.begin(), vec.end(), 
    [](int a, int b) { return a > b; });

// 查找大于5的元素
int threshold = 5;
auto it = std::find_if(vec.begin(), vec.end(), 
    [threshold](int x) { return x > threshold; });

// 统计偶数个数
int count = std::count_if(vec.begin(), vec.end(), 
    [](int x) { return x % 2 == 0; });

学习中的疑问4:lambda捕获

疑问:引用捕获变量在lambda返回时死亡,捕获了有什么用?

解答
引用捕获主要用于:

  1. 在同一作用域内使用(最常见,安全)
  2. 修改外部变量
  3. 避免大对象的拷贝开销

例子

void process() {
    std::vector<int> data{1, 2, 3, 4, 5};
    int sum = 0;
    
    // ✅ 安全:在同一作用域使用
    std::for_each(data.begin(), data.end(), 
        [&sum](int x) { sum += x; });
    
    cout << "总和:" << sum << endl;  // 15
}

❌ 危险的用法

auto makeLambda() {
    int x = 10;
    return [&x]() { return x; };  // ❌ x的引用会失效!
}

lambda捕获方式对比图

graph LR
    A[外部变量x] --> B{捕获方式}
    B -->|值捕获 x| C[lambda获得x的副本]
    B -->|引用捕获 &x| D[lambda获得x的引用]
    
    C --> E[不能修改x]
    C --> F[lambda可以返回]
    
    D --> G[可以修改x]
    D --> H[❌ 不能返回lambda]
    
    style E fill:#90EE90
    style F fill:#90EE90
    style G fill:#90EE90
    style H fill:#FFB6C1

学习中的疑问5:mutable关键字

疑问:lambda里值捕获的变量能修改吗?

解答

  • 默认不能修改(值捕获是const的)
  • 加上mutable关键字可以修改

例子

int count = 0;

// ❌ 编译错误
auto f1 = [count]() { 
    count++;  // 错误:count是const
};

// ✅ 加mutable
auto f2 = [count]() mutable { 
    count++;  // OK,但只修改副本
    return count;
};

cout << f2() << endl;  // 1
cout << f2() << endl;  // 2(副本累加)
cout << count << endl;  // 0(外部count没变)

学习收获(上半部分)

  1. auto能简化复杂类型声明,但要注意丢失const和引用
  2. decltype保留完整类型信息,适合模板编程
  3. 列表初始化更安全,能在编译期防止窄化转换
  4. 移动语义通过"偷"资源避免深拷贝,性能提升明显
  5. lambda表达式让代码更简洁,特别是配合STL算法
  6. 值捕获安全但不能修改,引用捕获能修改但要注意生命周期

💡 下一步:继续学习下半部分(nullptr、enum class、delete/default、using别名)


📝 本篇整理完成 - C++11新特性知识体系(上)

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐