新特性 说明 应用举例
auto 自动类型推断,简化变量声明 auto x = 42; auto it = vec.begin();
范围for循环 简洁地遍历容器 for (auto& e : container) { /* … / }
右值引用(T&&) 支持移动语义,减少拷贝,提高性能 std::vector v2 = std::move(v1);
智能指针(unique_ptr、shared_ptr) 自动管理动态内存,防止内存泄漏 std::unique_ptr p(new MyClass());
初始化列表(花括号) 使用 {} 进行统一初始化,简化赋值 std::vector v = {1, 2, 3}; int arr[3] = {4, 5, 6};
constexpr 编译时常量表达式,提升性能和安全性 constexpr int square(int x) { return xx; }
nullptr 类型安全的空指针,替代NULL int* p = nullptr;
Lambda表达式 匿名函数,方便传递函数行为 auto f = [](int a){ return a+1; };
static_assert 编译时断言,用于类型检查和条件验证 static_assert(sizeof(int) == 4, “int必须是4字节”);
线程库 (, , ) 标准多线程支持 std::thread t([]{ /* … */ }); t.join();
新枚举类型 (enum class) 强类型枚举,避免命名冲突 enum class Color { Red, Green }; Color c = Color::Red;
模板别名 (using) 替代复杂的typedef,提高可读性 template using Vec = std::vector;
右值引用捕获的Lambda Lambda可以捕获并移动资源 auto lam = p = std::move(ptr) { return *p; };
统一初始化语法 支持用 {} 统一初始化对象,包括内置类型和类类型 int x{5}; std::vector v{1, 2, 3};
右值引用捕获(完美转发) 支持完美转发函数参数 template void f(T&& t) { g(std::forward(t)); }
std::tuple 支持多值返回和打包多个不同类型变量 auto t = std::make_tuple(1, “abc”, 3.14);
decltype 获取表达式的类型,用于模板和泛型编程 decltype(x+y) z;
override和final 显式标记重写函数和防止继承 void foo() override;
explicit 转换函数避免隐式类型转换引发的错误 explicit operator bool() const;
完美转发
std::make_shared / make_unique
可变参数模板 template<typename… Args
decltype
锁lock_guard
原子编程 autimic
C++11新引入的特性
1、auto关键字
自动类型推导
让编译器在编译期自动推导类型,让代码更简洁,尤其在迭代器和模板编程的场景下;
auto it = vec.begin();
2、智能指针
unique_ptr
share_ptr
weak_ptr
用于自动管理动态内存,从根本上避免内存泄漏和悬空指针的问题,unique_ptr用来 独占所有权,share_ptr用于共享所有权;
std::make_shared / make_unique 安全高效的创建方式 避免裸new 且make_share支持只在堆内存创建一次内存分配;
3、范围based for循环
提供了一种遍历容器的简洁语法
:
4、lambda表达式
允许在调用的地方内联定义匿名函数对象,极大简化标准库算法的使用,像soft,
find, for_each
std::soft(vec.begin(), vec.end(), [](int a, int b) { return a > b; })
std::for_each(vec.begin(), vec.end(), [](int x) {cout << x <<" ";});
第三个参数是一个可调用的对象:仿函数/函数指针/lambda表达式
每个Lambda表达式都有它自己唯一的、编译器生成的匿名类型。
// 使用方式
auto myLambda = [](int x) { std::cout << x << " "; };
std::for_each(vec.begin(), vec.end(), myLambda);
// 或者更常见的,直接内联定义
std::for_each(vec.begin(), vec.end(), [](int x) { std::cout << x << " "; });
// 此时,在模板实例化中:
// _Function 被推导为一个编译器生成的、独一无二的匿名类型。
// 比如,编译器可能内部给它起个名字叫 `__lambda_10_9`,但这个名字对我们是不可见的。
这种设计的威力在于其极致的通用性和抽象性。
抽象了操作:std::for_each 的代码只关心一件事:“遍历”和“调用”。它完全不关心你具体要执行什么操作。这个操作由调用者通过传入不同的 _Function 对象来决定。这使得算法和操作彻底解耦。
零成本抽象:由于模板是在编译时实例化的,编译器会为不同的 _Function 类型生成特化后的机器码。这意味着:
使用 std::for_each 和一个手写的 for 循环在性能上完全没有区别。编译器会把 __f(*__first) 这行调用内联展开,就像你直接写在循环里一样。
它比使用函数指针(运行时解析)效率更高。
类型安全:编译器会在编译期进行严格的类型检查。如果你的Lambda表达式期望一个 int 参数,但容器里装的是 std::string,编译器会立即报错,因为推导出的 _Function 类型与 *__first 的解引用类型不匹配。
正是通过这种模板参数设计,STL算法才变得如此强大和灵活,可以接受任何符合概念(Concept)的类型,实现了“编译期多态”,而不是依赖于传统的面向对象继承。这也是C++泛型编程哲学的核心体现。
编译器在背后会自动为我们生成一个类似于仿函数的匿名类。它可以看作是创建仿函数的一种“语法糖”,但因其强大的捕获能力和便利性,彻底改变了C++的编程风格。
就地定义:不需要像仿函数那样跑到外面去定义一个完整的类。
捕获能力:可以通过捕获列表 [=], [&], [a, &b] 方便地捕获所在作用域的变量,这是仿函数需要手动通过构造函数实现的功能。
Lambda表达式在编译后,本质上就是变成了一个仿函数。
5、noexcept
noexcept 是 C++11 引入的一个关键字,用来声明函数不会抛出异常,它告诉编译器和程序员这个函数是“异常安全”的。
noexcept 的核心含义:
程序员向编译器承诺:这个函数“按设计”不会抛出异常。
这个承诺是“人为保证”,而不是代码自动保证的。
如果函数真的抛出异常了,程序会调用 std::terminate(),直接终止,不会有异常传播。
注意:
如果一个被标记为 noexcept 的函数 抛出了异常,那么不管外面有没有 try-catch,程序都会直接调用:
std::terminate();
✅ 外层的 catch 无法捕获这个异常,因为异常不会被正常“抛出去”——它在函数内部就被拦截终止了。
目的:
为什么要用 noexcept?
性能优化:编译器能针对不会抛异常的函数做优化,比如减少异常处理代码开销。
异常安全保证:给调用者信号,告诉它这个函数是不会抛异常的,可以放心调用。
配合标准库:比如 STL 容器的某些操作会判断函数是否 noexcept,决定是否调用移动构造函数等。
详细:
编译器怎么“因为一个函数是 noexcept”就能优化性能?
🧠 背后原理:异常处理有运行时成本!
在 C++ 中,如果一个函数可能抛异常,编译器通常要:
生成异常处理表(EH 表);
保存栈帧以便异常时回滚;
插入隐藏的检查代码;
在某些平台使用 SJLJ(setjmp/longjmp)或 DWARF unwind 信息等机制。
这都会引入额外的代码体积和执行开销。
而如果函数标记为 noexcept,编译器就可以:
省略这些异常处理结构;
省下额外的检查和栈回滚指令;
内联更激进,因为不需要处理异常路径;
在生成机器码时使用更轻量的调用约定。
标准库会这样判断:
if (std::is_nothrow_move_constructible<T>::value) {
// 使用移动构造
} else {
// 使用拷贝构造(保险起见,防止移动中抛异常导致资源丢失)
}
“如果函数没有加 noexcept,也没写 throw,也没抛异常,编译器怎么判断它是否会抛异常?”
在没有 noexcept 时,编译器会“保守地假设函数可能抛异常”,
除非它能完全分析出所有调用路径都不会抛异常,否则它不会做 noexcept 优化。
📌 所以,没有 noexcept ≠ 不抛异常,
而是:我不知道,可能会抛,所以我不敢优化。
即使你自己没写 throw,但:
函数内可能调用了别的库函数;
这些库函数可能抛异常;
所以 编译器不能确定,这个函数到底安不安全;
它必须保守处理:按“可能抛异常”处理,为它生成异常处理代码(异常表、额外指令等);
“析构若抛异常,慎之又慎。两错相加,程序终断。”
6、完美转发 std::forward(param)
在模板中调用函数时,保持左右值参数完美对应
不论传入的是左值还是右值,转发时都保持原样,调用目标函数的合适版本。
#include <iostream>
#include <utility>
void foo(int& x) { std::cout << "左值引用foo: " << x << "\n"; }
void foo(int&& x) { std::cout << "右值引用foo: " << x << "\n"; }
// 完美转发函数模板
template<typename T>
void wrapper(T&& param) {
// 这里用std::forward完美转发param,保持左/右值属性
foo(std::forward<T>(param));
}
int main() {
int a = 5;
wrapper(a); // 传入左值,调用foo(int&)
wrapper(10); // 传入右值,调用foo(int&&)
}
7、explicit 禁止隐式转换
explicit 是 C++ 中用于修饰构造函数和类型转换运算符的关键字,它的主要作用是禁止隐式类型转换,防止编译器自动进行类型转换导致的潜在错误。
作用总结
1. 修饰构造函数
默认情况下,单参数的构造函数会被当作隐式转换函数,允许从参数类型自动转换为类类型。
如果你不希望这种隐式转换发生,可以用 explicit 修饰构造函数,只能显式调用,不能隐式转换。
2. 修饰转换运算符(C++11起支持)
修饰类型转换操作符,也能禁止隐式类型转换。
不加 explicit:允许你传普通类型参数,编译器自动转换为类对象;
加了 explicit:禁止自动转换,必须明确写构造调用,防止误用。
class A {
public:
A(int x) { }
};
void func(A a) { }
int main() {
func(10); // 这里会发生隐式转换,编译器把 int 10 自动转换成 A 类型
}
在这里插入代码片发生了什么?
func 需要一个 A 类型的参数;
你传了个 int(10);
编译器看到 A 有个单参数构造函数 A(int),就自动帮你把 10 转成了 A(10);
所以 func(10) 实际调用的是 func(A(10))。
这就是“隐式转换”:编译器帮你自动完成了从 int 到 A 的类型转换。
抓换运算符
class A {
public:
operator int() const {
return 42; // 把 A 类型转换成 int
}
};
8. constexpr 编译期运行关键字
constexpr 让你写出“编译期就能运行的代码”,提高性能、增强类型安全,是现代 C++ 的核心工具之一。
constexpr 修饰的变量/函数/常量,在编译器就可以执行,比宏更安全;
1)和宏对比
宏是预处理指令,由预处理器进行文本替换,比如 #define PI 3.14159,它只是简单地把所有出现 PI 的地方替换成 3.14159,没有类型检查,容易出错。
constexpr 是C++中的一个关键字,用于告诉编译器某个变量或函数在编译阶段求值。它是编译器层面的概念,具有类型检查和作用域。
2)为什么 constexpr 比宏更安全?
类型安全:constexpr 变量或函数是有类型的,编译器会进行类型检查,宏只是简单的文本替换,没有类型信息。
作用域明确:constexpr 遵循C++的作用域规则,而宏是全局替换,容易污染命名空间。
调试友好:constexpr 变量在调试时可以看到其值和类型,宏展开后调试时只剩下替换后的代码,难以理解。
避免副作用:宏参数可能会被多次展开,导致副作用(比如 #define SQUARE(x) ((x)*(x)),SQUARE(i++) 会导致i自增两次),而 constexpr 函数不会。
3) constexpr 主要用在哪?
常量定义:替代 const,用于定义编译时常量,比如数组大小、模板参数等。
constexpr int size = 10;
int arr[size]; // 合法,size 是编译时常量
编译时函数:允许函数在编译时求值,提高性能和表达能力。
constexpr int factorial(int n) {
return n <= 1 ? 1 : (n * factorial(n - 1));
}
constexpr int val = factorial(5); // 在编译时计算
模板参数:用作非类型模板参数,必须是编译时常量。
4.) 为什么它能在编译器运行?
constexpr 关键字告诉编译器,该表达式必须能在编译阶段求值。
编译器会尝试在编译时展开并计算这些表达式,如果表达式满足常量表达式要求(无副作用、仅调用其他 constexpr 函数、使用字面量类型等),则直接将结果写进目标代码。
这样做可以减少运行时开销,提高性能。
模板参数:用作非类型模板参数,必须是编译时常量。
5.) constexpr 怎么修饰常量?
用来修饰变量,表示变量是编译时常量:
constexpr int a = 42;
用来修饰函数,表示函数可以用于编译时求值:
constexpr int square(int x) { return x * x; }
还可以修饰构造函数,使得类的对象可以在编译时初始化:
struct Point {
int x, y;
constexpr Point(int x_, int y_) : x(x_), y(y_) {}
};
constexpr Point p(1, 2);
6、用途
constexpr目的是为了优化性能 让函数
✅ 总结记忆法
问题 使用哪个?
需要在编译时计算值吗? constexpr
是模板参数、数组大小? constexpr
值在运行时才能知道? const
函数可以在编译时被调用? constexpr 函数
constexpr 的目的是为了在编译阶段就计算出值,优化性能,减少运行时开销,并且使常量能够参与模板参数、数组大小等需要编译时常量的场景,从而提升代码的表达力和可读性。
模板参数的使用:
模板参数有两种:
类型 示例
类型模板参数(type) template<typename T>
非类型模板参数(value) template<int N>、template<char C>
非类型模板参数需要传递编译器常量
例:
🔧 示例 1:非类型模板参数 + constexpr
template<int N>
struct MyArray {
int data[N];
};
constexpr int len = 5;
MyArray<len> arr; // ✅ OK,len 是编译时常量
那普通的函数或全局定义的常量 用const还是constexpr好一点?
一般来说,如果变量的值可以在编译时确定,就推荐使用 constexpr;否则使用 const。
📍 默认用 constexpr 替代 const,只要你知道这个值不会变、也能在编译期确定。
📍 只有在你需要从运行时拿值时,再退一步用 const。
📍 C++17 后,constexpr 越来越强大,连 if 语句、循环、构造函数都能用了,所以几乎可以完全替代 const 的大部分用途(在可行的场景下)。
✅ 单个值的计算影响可以忽略,但 constexpr 的意义远不止于“计算一个值快不快”。
它提供的是:
明确的语义
强制的编译时计算
更强的类型安全
更好的编译优化空间
更广泛的适用场景(模板、数组大小、元编程等)
类型安全的体现:
const 可以用任何能初始化的值
constexpr 限定必须是编译时常量表达式
这就是类型安全的一种体现:
constexpr 限定了“这个值必须在编译期确定”,防止你误用运行时值当作常量使用。
二、 C++14(对C++11的完善和补充)
C++14可以看作是C++11的“bug修复”和功能增强版。
1、泛型 Lambda
说明:Lambda的参数可以使用auto关键字,使Lambda更像一个模板函数。
cpp
// C++11中,lambda参数必须明确类型
auto lambda = [](int x) { return x * 2; };
// C++14中,参数可以是泛型的
auto genericLambda = [](auto x) { return x * 2; };
std::cout << genericLambda(5); // 10 (int)
std::cout << genericLambda(3.14); // 6.28 (double)
2、std::make_unique
说明:为unique_ptr提供了配套的工厂函数,与std::make_shared对称,保证了异常安全,并且代码书写更一致。
cpp
// C++11中需要直接构造unique_ptr
std::unique_ptr<MyClass> ptr(new MyClass());
// C++14提供了更优的方式
auto ptr = std::make_unique<MyClass>();
三、 C++17(重要的功能增强)
1、std::optional
🔹 1. std::optional<T>:表示“可能有值,也可能没值”的变量(可选值)
📌 它是什么?
std::optional 是一个模板类,用来包装一个类型 T 的值,表示这个值可能存在也可能不存在。
它的目的是避免使用裸指针、magic 值(如 -1/null)来表示“无值”,提升类型安全。
例:
#include <optional>
#include <iostream>
std::optional<int> findEven(int x) {
if (x % 2 == 0)
return x;
else
return std::nullopt; // 表示“无值”
}
int main() {
auto result = findEven(5);
if (result.has_value()) {
std::cout << "Found even number: " << result.value() << "\n";
} else {
std::cout << "No even number found\n";
}
}
✅ 用处:
表示“可能没有值”的函数返回值(比如查找失败)
避免使用 NULL、特殊数值(如 -1、0) 来传达“无效值”
可读性好、语义清晰、类型安全
2、std::filesystem
std::filesystem 是 C++17 引入的文件系统库,提供跨平台的文件、路径、目录操作功能。
它包含在 <filesystem> 头文件中,所有功能都在 std::filesystem 命名空间下。
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::path p = "/path/to/some/file.txt";
if (fs::exists(p)) {
std::cout << "File exists: " << p << "\n";
std::cout << "File size: " << fs::file_size(p) << " bytes\n";
} else {
std::cout << "File does not exist.\n";
}
}
✅ 能干什么?
检查文件是否存在 (fs::exists)
遍历目录 (fs::directory_iterator)
获取文件大小 (fs::file_size)
创建/删除文件和目录
合并、拆分路径
✅ 小结:filesystem 提供的功能清单
功能 对应方法
构建/操作路径 fs::path, .filename(), .parent_path() 等
判断存在性/类型 fs::exists(), fs::is_directory() 等
文件大小/时间 fs::file_size(), fs::last_write_time()
遍历目录 directory_iterator, recursive_directory_iterator
创建/删除目录或文件 fs::create_directory(), fs::remove()
文件复制/重命名/移动 fs::copy(), fs::rename()
当前/临时路径 fs::current_path(), fs::temp_directory_path()
权限操作(C++20) fs::status().permissions()
std::filesystem::status() 和 std::filesystem::symlink_status() 可以获取类似 stat() 的信息,例
7540

被折叠的 条评论
为什么被折叠?



