现代 C++ 特性
现代 C++ 指的是 C++11 及之后标准引入的一系列新特性。这些特性极大地提升了 C++ 的表达能力、安全性和性能。本章将系统介绍 C++11/14/17/20 的核心新特性。
C++11 核心特性
C++11 是 C++ 历史上最重要的更新之一,引入了大量现代编程特性。
auto 类型推导
auto 让编译器自动推导变量类型,简化代码书写:
#include <vector>
#include <map>
#include <memory>
int main() {
// 基本用法
auto i = 42; // int
auto d = 3.14; // double
auto s = "hello"; // const char*
auto str = std::string("hello"); // std::string
// 简化复杂类型
std::map<std::string, std::vector<int>> data;
// 传统写法
std::map<std::string, std::vector<int>>::iterator it1 = data.begin();
// auto 简化
auto it2 = data.begin(); // 简洁得多
// 与智能指针配合
auto ptr = std::make_shared<int>(42);
// 注意事项
auto x = 10; // int(拷贝)
auto& ref = x; // int&(引用)
const auto& cref = x; // const int&
auto* ptr = &x; // int*
return 0;
}
decltype 类型推导
decltype 用于获取表达式的类型:
#include <type_traits>
int main() {
int x = 10;
decltype(x) y = 20; // y 是 int
decltype((x)) z = x; // z 是 int&(注意双层括号)
// 用于返回类型
auto add = [](int a, int b) -> decltype(a + b) {
return a + b;
};
// decltype(auto) - C++14
// 保持引用和 cv 限定符
return 0;
}
范围 for 循环
更简洁的遍历语法:
#include <vector>
#include <map>
#include <iostream>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
// 遍历元素(只读)
for (int n : nums) {
std::cout << n << " ";
}
std::cout << std::endl;
// 遍历并修改
for (int& n : nums) {
n *= 2; // 修改原元素
}
// 使用 auto
for (const auto& n : nums) {
std::cout << n << " ";
}
std::cout << std::endl;
// 遍历 map
std::map<std::string, int> ages = {
{"Alice", 25},
{"Bob", 30}
};
for (const auto& [name, age] : ages) { // C++17 结构化绑定
std::cout << name << ": " << age << std::endl;
}
// 遍历数组
int arr[] = {1, 2, 3, 4, 5};
for (int n : arr) {
std::cout << n << " ";
}
// 遍历初始化列表
for (int n : {10, 20, 30}) {
std::cout << n << " ";
}
return 0;
}
初始化列表
统一的初始化语法:
#include <vector>
#include <map>
#include <iostream>
class Point {
public:
int x, y;
Point(int x, int y) : x(x), y(y) {}
// 初始化列表构造函数
Point(std::initializer_list<int> list) {
auto it = list.begin();
x = *it++;
y = *it;
}
};
int main() {
// 基本类型
int a{10}; // 直接初始化
int b = {20}; // 拷贝初始化
int c{}; // 值初始化为 0
// 数组
int arr[] = {1, 2, 3, 4, 5};
// 容器
std::vector<int> vec = {1, 2, 3, 4, 5};
std::map<std::string, int> map = {
{"one", 1},
{"two", 2}
};
// 自定义类型
Point p1{10, 20};
Point p2 = {30, 40};
// 防止窄化转换
// int x = 3.14; // 警告或错误(取决于编译器)
int y{3.14}; // 编译错误!防止精度丢失
return 0;
}
nullptr
类型安全的空指针常量:
#include <iostream>
void f(int) {
std::cout << "f(int)" << std::endl;
}
void f(int*) {
std::cout << "f(int*)" << std::endl;
}
int main() {
// 传统方式的问题
// f(NULL); // 有歧义!NULL 可能是 0 或 (void*)0
// nullptr 解决歧义
f(nullptr); // 调用 f(int*)
// nullptr 的类型
auto p = nullptr; // std::nullptr_t
// 与旧代码兼容
int* ptr = nullptr;
if (ptr == nullptr) {
std::cout << "空指针" << std::endl;
}
return 0;
}
强类型枚举
#include <iostream>
// 传统枚举
enum Color {
Red, Green, Blue
};
// 强类型枚举(enum class)
enum class Direction {
Up, Down, Left, Right
};
// 指定底层类型
enum class Status : char {
OK = 'O',
Error = 'E',
Pending = 'P'
};
int main() {
// 传统枚举:隐式转换为整数
Color c = Red;
int n = c; // OK,n = 0
// 强类型枚举:不隐式转换
Direction d = Direction::Up;
// int x = d; // 错误!
int x = static_cast<int>(d); // 需要显式转换
// 作用域
// Direction d2 = Up; // 错误!必须使用 Direction::Up
Direction d2 = Direction::Up;
return 0;
}
constexpr
编译时常量表达式:
#include <array>
// constexpr 变量
constexpr int MAX_SIZE = 100;
constexpr double PI = 3.14159265358979;
// constexpr 函数
constexpr int square(int x) {
return x * x;
}
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
// C++14: constexpr 函数可以有局部变量和循环
constexpr int sum(int n) {
int result = 0;
for (int i = 1; i <= n; ++i) {
result += i;
}
return result;
}
// constexpr 类
class Point {
int x_, y_;
public:
constexpr Point(int x, int y) : x_(x), y_(y) {}
constexpr int x() const { return x_; }
constexpr int y() const { return y_; }
};
int main() {
// 编译时计算
constexpr int sq = square(5); // 编译时求值
constexpr int fact = factorial(5); // 120
// 运行时也可以调用
int n;
std::cin >> n;
int result = factorial(n); // 运行时计算
// 用于数组大小
std::array<int, square(3)> arr; // 大小为 9
// constexpr 对象
constexpr Point p(10, 20);
static_assert(p.x() == 10, "x should be 10");
return 0;
}
override 和 final
明确表示虚函数重写和禁止继承:
#include <iostream>
class Base {
public:
virtual void foo() { std::cout << "Base::foo" << std::endl; }
virtual void bar() { std::cout << "Base::bar" << std::endl; }
virtual void baz() final { std::cout << "Base::baz" << std::endl; }
};
class Derived : public Base {
public:
// override: 确保正确重写基类虚函数
void foo() override { std::cout << "Derived::foo" << std::endl; }
// 错误检测
// void bar(int) override { } // 编译错误!签名不匹配
// void qux() override { } // 编译错误!基类没有此函数
// final: 禁止进一步重写
void bar() override final { std::cout << "Derived::bar" << std::endl; }
// baz 不能重写,因为它在 Base 中是 final
};
// final 类:禁止继承
class FinalClass final {
// ...
};
// class CannotInherit : public FinalClass { }; // 编译错误!
C++14 新特性
C++14 是 C++11 的完善和扩展,主要是一些改进。
二进制字面量
int main() {
// 二进制字面量
int binary = 0b1010; // 10
int mask = 0b11110000; // 240
// 数字分隔符(提高可读性)
int million = 1'000'000;
int hex = 0xFF'FF'FF'FF;
int bin = 0b1111'0000'1111'0000;
return 0;
}
泛型 Lambda
#include <iostream>
int main() {
// C++11: 需要指定参数类型
auto add11 = [](int a, int b) { return a + b; };
// C++14: 使用 auto 参数
auto add14 = [](auto a, auto b) {
return a + b;
};
std::cout << add14(1, 2) << std::endl; // int + int
std::cout << add14(1.5, 2.5) << std::endl; // double + double
// 泛型 lambda 的更多用法
auto print = [](const auto& value) {
std::cout << value << std::endl;
};
print(42);
print(3.14);
print("hello");
return 0;
}
返回类型推导
#include <vector>
// 函数返回类型推导
auto add(int a, int b) {
return a + b; // 返回 int
}
// 递归函数(需要至少一个 return 语句来推导类型)
auto factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
// decltype(auto) - 保持引用和 cv 限定符
template<typename Container>
decltype(auto) getElement(Container& c, size_t index) {
return c[index]; // 返回引用
}
std::make_unique
#include <memory>
int main() {
// C++11 有 make_shared,但没有 make_unique
// C++14 添加了 make_unique
auto ptr = std::make_unique<int>(42);
auto arr = std::make_unique<int[]>(10);
return 0;
}
C++17 核心特性
C++17 是一次重要更新,添加了许多实用特性。
结构化绑定
#include <map>
#include <tuple>
#include <utility>
#include <iostream>
int main() {
// 绑定 pair
std::pair<int, std::string> p = {1, "one"};
auto [id, name] = p;
std::cout << id << ": " << name << std::endl;
// 绑定 tuple
std::tuple<int, double, std::string> t = {1, 3.14, "pi"};
auto [a, b, c] = t;
// 绑定数组
int arr[] = {1, 2, 3};
auto [x, y, z] = arr;
// 遍历 map
std::map<std::string, int> scores = {
{"Alice", 90},
{"Bob", 85}
};
for (const auto& [name, score] : scores) {
std::cout << name << ": " << score << std::endl;
}
// 绑定结构体
struct Point {
int x, y;
};
Point pt{10, 20};
auto [px, py] = pt;
return 0;
}
if 和 switch 的初始化语句
#include <map>
#include <iostream>
int main() {
// if 初始化
std::map<std::string, int> scores = {{"Alice", 90}};
// 传统方式
auto it1 = scores.find("Alice");
if (it1 != scores.end()) {
std::cout << it1->second << std::endl;
}
// C++17: 在 if 中初始化
if (auto it2 = scores.find("Alice"); it2 != scores.end()) {
std::cout << it2->second << std::endl;
}
// it2 在此处不可见
// switch 初始化
switch (auto x = getValue(); x) {
case 1:
std::cout << "one" << std::endl;
break;
default:
std::cout << "other: " << x << std::endl;
}
return 0;
}
if constexpr
编译时条件分支:
#include <type_traits>
#include <iostream>
template<typename T>
void process(T value) {
if constexpr (std::is_integral_v<T>) {
std::cout << "整数: " << value << std::endl;
} else if constexpr (std::is_floating_point_v<T>) {
std::cout << "浮点数: " << value << std::endl;
} else if constexpr (std::is_pointer_v<T>) {
std::cout << "指针: " << *value << std::endl;
} else {
std::cout << "其他类型" << std::endl;
}
}
// 用于递归模板
template<typename T, typename... Args>
void printAll(T first, Args... rest) {
std::cout << first;
if constexpr (sizeof...(rest) > 0) {
std::cout << ", ";
printAll(rest...);
} else {
std::cout << std::endl;
}
}
int main() {
process(42); // 整数: 42
process(3.14); // 浮点数: 3.14
int x = 10;
process(&x); // 指针: 10
printAll(1, 2.0, "three"); // 1, 2, three
return 0;
}
std::optional
表示可能存在的值:
#include <optional>
#include <string>
#include <iostream>
// 返回可能不存在的值
std::optional<int> findUserAge(const std::string& name) {
if (name == "Alice") return 25;
if (name == "Bob") return 30;
return std::nullopt; // 不存在
}
int main() {
// 创建 optional
std::optional<int> opt1; // 空
std::optional<int> opt2 = 42; // 有值
std::optional<int> opt3 = std::nullopt; // 空
// 检查是否有值
if (opt2.has_value()) {
std::cout << opt2.value() << std::endl; // 42
}
// 使用 value_or 提供默认值
std::cout << opt1.value_or(0) << std::endl; // 0
// 使用示例
if (auto age = findUserAge("Alice")) {
std::cout << "Alice 的年龄: " << *age << std::endl;
} else {
std::cout << "未找到 Alice" << std::endl;
}
// 结构化绑定友好
auto age = findUserAge("Unknown");
std::cout << (age ? std::to_string(*age) : "未知") << std::endl;
return 0;
}
std::variant
类型安全的联合体:
#include <variant>
#include <string>
#include <iostream>
int main() {
// 创建 variant
std::variant<int, double, std::string> v;
v = 42; // 存储 int
v = 3.14; // 存储 double
v = "hello"; // 存储 string
// 获取值
std::cout << std::get<std::string>(v) << std::endl; // hello
// std::get<int>(v); // 抛出 std::bad_variant_access
// 安全访问
if (auto* p = std::get_if<std::string>(&v)) {
std::cout << *p << std::endl;
}
// 获取当前类型的索引
std::cout << v.index() << std::endl; // 2 (string 是第三个类型)
// 使用 std::visit
auto visitor = [](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>) {
std::cout << "int: " << arg << std::endl;
} else if constexpr (std::is_same_v<T, double>) {
std::cout << "double: " << arg << std::endl;
} else if constexpr (std::is_same_v<T, std::string>) {
std::cout << "string: " << arg << std::endl;
}
};
std::visit(visitor, v);
return 0;
}
std::any
可以存储任何类型的值:
#include <any>
#include <string>
#include <iostream>
int main() {
std::any a;
a = 42; // int
a = 3.14; // double
a = std::string("hello"); // string
// 访问值
std::cout << std::any_cast<std::string>(a) << std::endl;
// 检查是否有值
if (a.has_value()) {
std::cout << "类型: " << a.type().name() << std::endl;
}
// 安全访问
try {
std::cout << std::any_cast<int>(a) << std::endl;
} catch (const std::bad_any_cast& e) {
std::cout << "类型不匹配" << std::endl;
}
return 0;
}
std::string_view
非拥有的字符串视图:
#include <string_view>
#include <string>
#include <iostream>
// 高效的字符串参数传递
void process(std::string_view sv) {
std::cout << sv << std::endl;
}
int main() {
std::string s = "Hello, World!";
// 从 string 创建
std::string_view sv1(s);
// 从 C 字符串创建
std::string_view sv2("Hello");
// 从部分字符串创建
std::string_view sv3(s.data(), 5); // "Hello"
// 操作(不复制)
sv1.remove_prefix(7); // 去掉 "Hello, "
sv1.remove_suffix(1); // 去掉 "!"
std::cout << sv1 << std::endl; // "World"
// 比较
std::cout << (sv2 == "Hello") << std::endl; // 1
// 搜索
auto pos = sv1.find("o");
// 注意:string_view 不拥有数据
// 不要返回指向临时 string 的 string_view
// std::string_view bad() {
// return std::string("temp"); // 危险!
// }
process("hello"); // 不需要构造 string
process(s); // 不需要复制
return 0;
}
文件系统库
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
// 路径操作
fs::path p = "/home/user/documents/file.txt";
std::cout << "文件名: " << p.filename() << std::endl;
std::cout << "扩展名: " << p.extension() << std::endl;
std::cout << "父路径: " << p.parent_path() << std::endl;
// 创建目录
fs::create_directory("test_dir");
fs::create_directories("a/b/c"); // 递归创建
// 检查文件状态
if (fs::exists("test_dir")) {
std::cout << "目录存在" << std::endl;
}
if (fs::is_directory("test_dir")) {
std::cout << "是目录" << std::endl;
}
// 遍历目录
for (const auto& entry : fs::directory_iterator(".")) {
std::cout << entry.path() << std::endl;
}
// 文件大小
std::cout << "大小: " << fs::file_size(p) << std::endl;
// 复制/移动/删除
fs::copy_file("src.txt", "dst.txt");
fs::rename("old.txt", "new.txt");
fs::remove("test_dir");
return 0;
}
C++20 核心特性
C++20 是又一次重大更新,引入了概念、协程等重要特性。
Concepts(概念)
对模板参数的约束:
#include <concepts>
#include <iostream>
// 定义概念
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
// 使用概念约束模板
template<Numeric T>
T add(T a, T b) {
return a + b;
}
// 或使用 requires
template<typename T>
requires Numeric<T>
T multiply(T a, T b) {
return a * b;
}
// 简洁语法
auto divide(Numeric auto a, Numeric auto b) {
return a / b;
}
// 自定义概念
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::convertible_to<T>;
};
template<Addable T>
T sum(const std::vector<T>& v) {
T result{};
for (const auto& x : v) {
result = result + x;
}
return result;
}
int main() {
std::cout << add(1, 2) << std::endl; // OK
std::cout << add(1.5, 2.5) << std::endl; // OK
// std::cout << add("a", "b") << std::endl; // 编译错误!
return 0;
}
Ranges 库
更现代的算法和视图:
#include <ranges>
#include <vector>
#include <iostream>
namespace rv = std::views;
namespace rg = std::ranges;
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 过滤偶数
auto evens = nums | rv::filter([](int n) { return n % 2 == 0; });
// 转换
auto squares = nums | rv::transform([](int n) { return n * n; });
// 组合
auto result = nums
| rv::filter([](int n) { return n % 2 == 0; })
| rv::transform([](int n) { return n * n; });
for (int n : result) {
std::cout << n << " "; // 4 16 36 64 100
}
std::cout << std::endl;
// 取前 N 个
auto first3 = nums | rv::take(3);
// 跳过前 N 个
auto skip2 = nums | rv::drop(2);
// 反转
auto reversed = nums | rv::reverse;
// 排序
std::vector<int> unsorted = {3, 1, 4, 1, 5};
rg::sort(unsorted);
return 0;
}
三向比较运算符(宇宙飞船运算符)
#include <compare>
#include <iostream>
struct Point {
int x, y;
// 自动生成所有比较运算符
auto operator<=>(const Point&) const = default;
};
int main() {
Point p1{1, 2};
Point p2{1, 3};
// 所有比较操作自动可用
std::cout << (p1 < p2) << std::endl; // 1
std::cout << (p1 == p2) << std::endl; // 0
std::cout << (p1 <= p2) << std::endl; // 1
// 三向比较结果
auto cmp = p1 <=> p2;
if (cmp < 0) std::cout << "p1 < p2" << std::endl;
else if (cmp > 0) std::cout << "p1 > p2" << std::endl;
else std::cout << "p1 == p2" << std::endl;
return 0;
}
指定初始化器
#include <iostream>
struct Config {
int width = 800;
int height = 600;
bool fullscreen = false;
std::string title = "Window";
};
int main() {
// 指定初始化
Config cfg = {
.width = 1920,
.height = 1080,
.title = "My Window"
// fullscreen 使用默认值
};
std::cout << cfg.width << "x" << cfg.height << std::endl;
return 0;
}
std::format
类型安全的格式化:
#include <format>
#include <iostream>
#include <string>
int main() {
std::string s = std::format("Hello, {}!", "World");
std::cout << s << std::endl; // Hello, World!
// 位置参数
std::string s2 = std::format("{0} {1} {0}", "Hello", "World");
std::cout << s2 << std::endl; // Hello World Hello
// 格式说明符
std::cout << std::format("{:.2f}", 3.14159) << std::endl; // 3.14
std::cout << std::format("{:10}", 42) << std::endl; // " 42"
std::cout << std::format("{:0>10}", 42) << std::endl; // "0000000042"
// 打印到 stdout
std::print("Hello, {}!\n", "World");
return 0;
}
std::span
对连续序列的非拥有视图:
#include <span>
#include <vector>
#include <array>
#include <iostream>
// 接受任意连续容器
void process(std::span<int> s) {
for (int& n : s) {
n *= 2; // 可以修改
}
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
std::vector<int> vec = {1, 2, 3, 4, 5};
std::array<int, 5> stdArr = {1, 2, 3, 4, 5};
// 都可以传入
process(arr);
process(vec);
process(stdArr);
// 创建子范围
std::span<int> s(arr);
auto sub = s.subspan(1, 3); // 元素 1, 2, 3
// 大小
std::cout << s.size() << std::endl; // 5
return 0;
}
版本特性速查
C++11 关键特性
| 特性 | 说明 |
|---|---|
| auto | 自动类型推导 |
| decltype | 表达式类型推导 |
| 范围 for | 简洁的遍历语法 |
| 初始化列表 | 统一初始化语法 |
| nullptr | 类型安全的空指针 |
| enum class | 强类型枚举 |
| constexpr | 编译时常量表达式 |
| Lambda | 匿名函数 |
| 右值引用 | 移动语义 |
| 智能指针 | RAII 内存管理 |
| std::thread | 标准线程库 |
C++14 关键特性
| 特性 | 说明 |
|---|---|
| 二进制字面量 | 0b 前缀 |
| 数字分隔符 | 单引号分隔 |
| 泛型 Lambda | auto 参数 |
| 返回类型推导 | auto 返回类型 |
| make_unique | 智能指针工厂 |
C++17 关键特性
| 特性 | 说明 |
|---|---|
| 结构化绑定 | auto [a, b] = ... |
| if 初始化 | if (auto x = f(); x > 0) |
| if constexpr | 编译时条件 |
| optional | 可选值 |
| variant | 类型安全联合体 |
| any | 任意类型值 |
| string_view | 字符串视图 |
| filesystem | 文件系统库 |
C++20 关键特性
| 特性 | 说明 |
|---|---|
| Concepts | 模板约束 |
| Ranges | 现代算法库 |
| 宇宙飞船运算符 | 三向比较 |
| 指定初始化器 | 命名成员初始化 |
| std::format | 格式化字符串 |
| std::span | 序列视图 |
| 协程 | 无栈协程 |
| 模块 | 替代头文件 |
小结
本章学习了:
- C++11 特性:auto、范围 for、初始化列表、nullptr、constexpr 等
- C++14 特性:二进制字面量、泛型 Lambda、返回类型推导
- C++17 特性:结构化绑定、if constexpr、optional、variant、string_view
- C++20 特性:Concepts、Ranges、宇宙飞船运算符、format
现代 C++ 特性极大地提高了代码的可读性、安全性和性能。掌握这些特性对于编写高质量的 C++ 代码至关重要。