C++17新特性:optional/variant/any/string_view

本期我们就来介绍一下C++17中新增的标准库的接口

相关代码上传到gitee:楼田莉子/Linux学习

目录

optional

接口

构造与赋值

状态观测:询问"有值吗?"

值访问:安全地获取数据

内容修改

工具函数与算法支持

代码示例:

variant

接口

构造与赋值

状态观测:询问"现在是谁?"

值访问:安全地提取数据

比较与修改

代码示例

any

接口

构造与赋值 (Construction & Assignment)

状态观测 (Observers)

值访问 (Value Access)

修改器与工具函数 (Modifiers & Utility Functions)

代码示例

string_view

接口

注意事项

代码示例


optional

官方文档:Standard library header <optional> (C++17) - cppreference.com

<optional> 是C++17引入的一个关键头文件,它提供了一个名为 std::optional 的类模板,主要用于表达一个值"可能存在,也可能不存在"的语义。常用于替代传统的指针或特殊值(如-1)来表示函数的返回值可能无效,或者类中的某个成员变量可能未被设置的情况

类别 接口名称 功能描述
主类模板 std::optional<T> 核心类,管理一个可能存在的 T 类型值。
空状态 std::nullopt_t / std::nullopt std::nullopt 是一个常量,用来显式地将 optional 对象置为空。
异常类 std::bad_optional_access 当对一个空的 optional 调用 .value() 时,会抛出此异常。

接口

std::optional 的大部分操作都围绕"构造-检查-访问-修改"这个生命周期展开。

构造与赋值

提供了丰富的构造方式,支持值初始化、原位构造和柯里化构造。

  • constexpr optional(): 默认构造,对象为空。

  • constexpr optional(std::nullopt_t): 显式构造为空。

  • constexpr optional(const T& value): 通过拷贝/移动直接包含一个值。

  • template<...> constexpr explicit optional(std::in_place_t, Args&&...) : 使用 std::in_place 进行原位构造,直接在内部构建对象,避免临时对象拷贝,效率更高。

  • template<...> constexpr optional& operator=(U&& value): 通过赋值操作符包含新值,会正确析构旧值。

状态观测:询问"有值吗?"

在访问值之前,必须先检查其存在性。if (opt) 是C++工程师最常用的快捷方式。

  • constexpr explicit operator bool() const noexcept : 提供 bool 语境转换,允许 if (opt) 的简洁写法。

  • constexpr bool has_value() const noexcept : 显式查询,if (opt.has_value()) 语义更明确。

值访问:安全地获取数据

这些方法提供了不同安全级别的访问,体现了"先检查,后使用"的设计哲学。

  • constexpr T& operator*() / constexpr const T* operator->() : 类似指针的解引用。务必确保对象非空,否则行为未定义。

  • constexpr T& value() : 安全访问。若对象为空,会抛出 std::bad_optional_access 异常,适合RAII风格。

  • template<...> constexpr T value_or(U&& default_value): 提供带默认值的安全访问。若对象为空则返回默认值,非常适合容错场景。

内容修改

对已存在的值进行原地修改,避免额外的构造开销。

  • constexpr void reset() noexcept: 清空对象,销毁内部值。

  • template<...> constexpr T& emplace(Args&&...): 原地构造新值。无论对象之前是否持有值,都能安全地重建,是替换值的最优方式。

  • constexpr void swap(optional& other) : 高效地交换两个 optional 对象的内容。

工具函数与算法支持

  • std::make_optional : 便捷的工厂函数,自动推导类型,可直接从值构造 optional

  • 比较运算符: 所有运算符都进行了重载,逻辑直观:两个空对象相等;空对象被认为"小于"任何有值对象。

  • std::swap 重载 : 标准 swap 算法可以直接用于 optional 对象,高效交换内容。

  • std::hash 特化 : optional 对象支持哈希计算,可被用作 unordered_map 等容器的键,哈希值在对象为空时是未指定的。

  • 迭代器支持 (C++26起):C++26标准为std::optional添加了begin()end()成员函数,使其能像容器一样被迭代,支持范围for循环。

代码示例:

cpp 复制代码
#include <optional>
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <cassert>

// ============================================================
// std::optional<T> --- C++17 引入的"可能没有值"的容器
//
// 核心语义: 要么包含一个 T 类型的值,要么为空 (nullopt)。
// 值存储在线 (inline),不涉及堆分配。
// 不能存储引用 (T& 是非法的)。
// ============================================================

// ============================================================
// 1. 构造: 创建 optional 的所有方式
// ============================================================

void demo_construction() {
    std::cout << "======== 1. 构造 ========\n";

    // --- 1a. 默认构造 / nullopt --- 创建空的 optional ---
    std::optional<int> empty1;               // 空
    std::optional<int> empty2 = std::nullopt; // 空
    std::optional<int> empty3(std::nullopt);  // 空
    std::cout << "默认构造: has_value = " << empty1.has_value() << '\n';

    // --- 1b. 从值构造 (隐式) ---
    std::optional<int> opt_int = 42;
    std::optional<std::string> opt_str = "hello";
    std::cout << "从值构造: opt_int = " << *opt_int
              << ", opt_str = " << *opt_str << '\n';

    // --- 1c. make_optional --- 工厂函数,免写模板参数 ---
    auto opt1 = std::make_optional(42);           // optional<int>
    auto opt2 = std::make_optional<std::string>("xyz");
    auto opt3 = std::make_optional<double>(3.14);
    std::cout << "make_optional: " << *opt1 << ", " << *opt2 << ", " << *opt3 << '\n';

    // --- 1d. std::in_place --- 就地构造,避免临时对象 ---
    // 如果 T 不支持拷贝/移动,这是唯一构造方式
    // 方式 A: make_optional + initializer_list
    auto opt_vec = std::make_optional<std::vector<int>>({1, 2, 3});

    // 方式 B: 直接用 optional 构造函数
    std::optional<std::vector<int>> opt_vec2(std::in_place, {4, 5, 6});
    std::cout << "in_place vector A: ";
    for (int x : *opt_vec) std::cout << x << ' ';
    std::cout << "| size = " << opt_vec->size() << '\n';
    std::cout << "in_place vector B: ";
    for (int x : *opt_vec2) std::cout << x << ' ';
    std::cout << "| size = " << opt_vec2->size() << '\n';

    // --- 1e. in_place + initializer_list + 额外参数 ---
    // 构造 std::vector<int>({1,2,3}, allocator)
    auto opt_vec3 = std::make_optional<std::vector<int>>({1, 2, 3}, std::allocator<int>());
    std::cout << "in_place+alloc: size = " << opt_vec3->size() << '\n';

    // --- 1f. 拷贝/移动构造 ---
    auto opt_copy = opt_int;           // 拷贝
    auto opt_move = std::move(opt_int); // 移动 (opt_int 仍然有值,但 int 是基础类型无影响)
    std::cout << "拷贝: " << *opt_copy << ", 移动后原值(仍可访问): " << *opt_int << '\n';
}

// ============================================================
// 2. 观察者: 获取值 / 检查是否为空
// ============================================================

void demo_observers() {
    std::cout << "\n======== 2. 观察者 (observers) ========\n";

    std::optional<int> present = 100;
    std::optional<int> absent;

    // --- 2a. has_value() --- 检查是否含有值 ---
    std::cout << "present.has_value() = " << present.has_value() << '\n';
    std::cout << "absent.has_value()  = " << absent.has_value() << '\n';

    // --- 2b. operator bool --- 隐式转换为 bool (explicit) ---
    // 可以直接在 if 中使用
    if (present) {
        std::cout << "present 为 true\n";
    }
    if (!absent) {
        std::cout << "absent 为 false\n";
    }

    // --- 2c. value() --- 安全访问,空时抛出 std::bad_optional_access ---
    std::cout << "present.value() = " << present.value() << '\n';
    try {
        std::cout << absent.value() << '\n'; // 抛出异常
    } catch (const std::bad_optional_access& e) {
        std::cout << "absent.value() 抛出 bad_optional_access: " << e.what() << '\n';
    }

    // --- 2d. operator* / operator-> --- 不检查的访问 (未定义行为) ---
    std::cout << "*present  = " << *present << '\n';
    struct Point { int x, y; };
    std::optional<Point> opt_pt{Point{3, 4}};
    std::cout << "opt_pt->x = " << opt_pt->x << ", opt_pt->y = " << opt_pt->y << '\n';
    // 注意: *absent 是 UB,不要这么做!

    // --- 2e. value_or() --- 提供默认值,空时返回默认值 ---
    int val1 = present.value_or(-1);  // 100 (有值)
    int val2 = absent.value_or(-1);   // -1  (无值)
    std::cout << "present.value_or(-1) = " << val1 << '\n';
    std::cout << "absent.value_or(-1)  = " << val2 << '\n';

    // value_or 支持右值引用:
    std::optional<std::string> absent_str;
    std::string s = absent_str.value_or(std::string("default"));
    std::cout << "absent_str.value_or(\"default\") = " << s << '\n';
}

// ============================================================
// 3. 修改器: 修改 optional 的值或状态
// ============================================================

void demo_modifiers() {
    std::cout << "\n======== 3. 修改器 (modifiers) ========\n";

    // --- 3a. emplace() --- 销毁旧值,在原位构造新值 ---
    std::optional<std::string> opt("hello");
    std::cout << "原始值: " << *opt << '\n';

    opt.emplace("world");  // 销毁 "hello",构造 "world"
    std::cout << "emplace(\"world\") 后: " << *opt << '\n';

    // emplace 也可以给空 optional 构造值:
    std::optional<std::string> empty_opt;
    empty_opt.emplace("constructed!");
    std::cout << "emplace 到空 optional: " << *empty_opt << '\n';

    // emplace 返回新构造值的引用:
    std::string& ref = opt.emplace("new");
    ref[0] = 'N';  // 修改 "new" → "New"
    std::cout << "emplace 返回引用: " << *opt << '\n';

    // 就地构造带多个参数的类型:
    std::optional<std::vector<int>> opt_vec;
    opt_vec.emplace(5, 99);  // vector<int>(5, 99) → {99,99,99,99,99}
    std::cout << "emplace(5, 99): size=" << opt_vec->size()
              << ", [0]=" << (*opt_vec)[0] << '\n';

    // --- 3b. reset() --- 销毁值,变为空 ---
    opt.reset();
    std::cout << "reset() 后: has_value = " << opt.has_value() << '\n';
    // 对空的 optional 调用 reset() 是安全且无操作的

    // --- 3c. swap() --- 交换两个 optional 的内容 ---
    std::optional<int> a = 10;
    std::optional<int> b = 20;
    a.swap(b);
    std::cout << "a.swap(b) 后: a = " << *a << ", b = " << *b << '\n';

    // 与空值交换:
    std::optional<int> c;
    b.swap(c);
    std::cout << "b.swap(空) 后: b.has_value = " << b.has_value()
              << ", c = " << *c << '\n';

    // --- 3d. operator= --- 各种赋值 ---
    std::optional<int> opt_int;

    // 从值赋值:
    opt_int = 42;
    std::cout << "= 42: " << *opt_int << '\n';

    // 从 nullopt 赋值 (变为空):
    opt_int = std::nullopt;
    std::cout << "= nullopt 后: has_value = " << opt_int.has_value() << '\n';

    // 从另一个 optional 赋值:
    std::optional<int> src = 99;
    opt_int = src;
    std::cout << "= optional<int> 后: " << *opt_int << '\n';
}

// ============================================================
// 4. 比较运算: ==, !=, <, <=, >, >=
// ============================================================

void demo_comparison() {
    std::cout << "\n======== 4. 比较运算 ========\n";

    std::optional<int> a = 10;
    std::optional<int> b = 20;
    std::optional<int> empty;

    // --- 与 optional 比较 ---
    std::cout << "a == b  : " << (a == b) << '\n';
    std::cout << "a != b  : " << (a != b) << '\n';
    std::cout << "a <  b  : " << (a < b)  << '\n';

    // 空 optional 参与的规则:
    //   nullopt 被认为比任何有值 optional 都"小"
    //   两个空 optional 相等
    std::cout << "empty < a     : " << (empty < a) << '\n';     // true
    std::cout << "empty == nullopt : " << (empty == std::nullopt) << '\n'; // true
    std::cout << "a == nullopt     : " << (a == std::nullopt) << '\n';     // false
    std::cout << "a != nullopt     : " << (a != std::nullopt) << '\n';     // true

    // --- 与底层值类型直接比较 ---
    std::cout << "a == 10 : " << (a == 10) << '\n'; // true
    std::cout << "a < 100 : " << (a < 100) << '\n'; // true
    // 注意: 空 optional 与值比较总是 false (空 != 任何值)
    std::cout << "empty == 0 : " << (empty == 0) << '\n'; // false
}

// ============================================================
// 5. 实际应用场景
// ============================================================

// 场景 1: 查找可能不存在的键
std::optional<std::string> find_value(const std::map<int, std::string>& m, int key) {
    auto it = m.find(key);
    if (it != m.end()) {
        return it->second;       // 找到了
    } else {
        return std::nullopt;     // 没找到
    }
}

// 场景 2: 可能失败的解析 --- 不需要异常或 out-parameter
std::optional<int> parse_int(const std::string& s) {
    try {
        return std::stoi(s);
    } catch (const std::invalid_argument&) {
        return std::nullopt;
    } catch (const std::out_of_range&) {
        return std::nullopt;
    }
}

// 场景 3: 延迟初始化 --- 成员变量可能稍后才赋值
class Config {
    std::optional<std::string> cached_path_;
public:
    void set_path(const std::string& p) { cached_path_ = p; }
    std::string get_path() const {
        return cached_path_.value_or("/etc/default");
    }
};

void demo_real_world() {
    std::cout << "\n======== 5. 实际应用场景 ========\n";

    // --- 场景 1: 查找 ---
    std::map<int, std::string> m{{1, "one"}, {2, "two"}, {3, "three"}};
    auto found = find_value(m, 2);
    if (found) {
        std::cout << "找到 key=2: " << *found << '\n';
    }
    auto missing = find_value(m, 99);
    std::cout << "key=99: " << missing.value_or("(未找到)") << '\n';

    // --- 场景 2: 解析 ---
    auto n1 = parse_int("12345");
    auto n2 = parse_int("not_a_number");
    std::cout << "parse \"12345\"       = " << n1.value_or(-1) << '\n';
    std::cout << "parse \"not_a_number\" = " << n2.value_or(-1) << " (fallback)\n";

    // --- 场景 3: 延迟初始化 ---
    Config cfg;
    std::cout << "默认路径: " << cfg.get_path() << '\n';
    cfg.set_path("/custom/path");
    std::cout << "设置后路径: " << cfg.get_path() << '\n';
}

// ============================================================
// 6. optional 与 std::nullopt / std::bad_optional_access
// ============================================================

void demo_helpers() {
    std::cout << "\n======== 6. 辅助类型 ========\n";

    // --- std::nullopt --- 空 optional 的"空值"常量 ---
    // 类型为 std::nullopt_t,常用于:
    //   1. 创建空 optional
    //   2. 将 optional 置空
    //   3. 函数返回空值

    std::optional<int> opt = 42;
    opt = std::nullopt;  // 置空
    std::cout << "置 nullopt 后: has_value = " << opt.has_value() << '\n';

    // 函数返回空值:
    auto create = [](bool ok) -> std::optional<std::string> {
        if (ok) return "success";
        else    return std::nullopt;  // 空
    };
    std::cout << "create(true) = " << *create(true) << '\n';
    std::cout << "create(false).has_value = " << create(false).has_value() << '\n';

    // --- std::bad_optional_access --- 访问空 optional 抛出的异常 ---
    // 继承自 std::logic_error,提供 what() 信息
    std::optional<int> empty;
    try {
        empty.value();  // 抛出 std::bad_optional_access
    } catch (const std::bad_optional_access& e) {
        std::cout << "捕获 bad_optional_access: " << e.what() << '\n';
    }
}
int main() {
    demo_construction();
    demo_observers();
    demo_modifiers();
    demo_comparison();
    demo_real_world();
    demo_helpers();

    std::cout << "\n所有演示完毕。\n";
    return 0;
}

variant

官方文档:Standard library header <variant> (C++17) - cppreference.com

<variant> 在 C++17 标准库中是实现类型安全联合体(Type-safe Union)的核心头文件。它比传统的 union 更强大和安全,不仅知道当前存储的值是哪个类型,还能管理非平凡(non-trivial)类型的生命周期(构造/析构)。

组件 功能描述
std::variant<Types...> 核心类模板,它是一个类型安全的联合体,可以持有其类型列表中任意一种类型的值。
std::monostate 一个空类型,用于使 variant 可以默认构造,或表示一个"空"状态。
std::bad_variant_access 异常类,当错误地访问 variant 对象的值时(例如,用 get 获取一个不匹配的类型)会抛出此异常。
辅助类型特征 std::variant_sizestd::variant_alternative 用于在编译时查询 variant 的可选类型数量和特定索引对应的类型。
std::variant_npos 一个常量,表示 variant 处于非法状态(即 valueless_by_exception 返回 true 时的索引)。

接口

std::variant 的大部分操作都围绕着"构造/赋值 -> 检查 -> 访问 -> 修改"这个生命周期展开。

构造与赋值

提供了非常灵活的构造方式,可以直接赋值,也可以原位构造。

  • 默认/值构造 :可以直接用 variant<int, std::string> v; 默认构造(若第一个类型支持),或用 v = 10; 直接赋值。

  • 原位构造 (emplace) :使用 v.emplace<std::string>("hello")v.emplace<1>("hello"),直接在内部构建新值,避免临时对象拷贝,效率更高。

  • std::in_place_type / std::in_place_index :用于在构造时显式指定要激活的类型或索引,防止歧义,例如 std::variant<std::string, int> v(std::in_place_type<std::string>, "hello");

状态观测:询问"现在是谁?"

在访问值之前,必须先知道它当前存储的是哪个类型的值。

  • constexpr size_t index() const noexcept :返回当前存储的值的类型在 Types... 列表中的零基索引

  • constexpr bool valueless_by_exception() const noexcept :检查 variant 是否处于因异常导致的"无值"非法状态(极少发生)。

值访问:安全地提取数据

这些方法提供了不同安全级别的访问,完美体现了"先检查,后使用"的设计哲学。

  • std::get<T>(v) / std::get<Index>(v) :不安全访问,但代码简洁。若类型或索引不匹配,会抛出 std::bad_variant_access 异常。

  • std::get_if<T>(&v) / std::get_if<Index>(&v) :安全访问,返回一个指针 。若类型/索引匹配,返回指向值的指针;否则返回 nullptr。这是编写健壮代码的首选。

  • std::visit:最强大的工具,允许你传入一个可调用对象(如泛型 lambda),其内部会自动根据当前激活的类型调用对应的重载或模板实例。

比较与修改

  • constexpr void swap(variant& other) :交换两个 variant 对象的内容。

  • 比较运算符variant 对象可以像普通值一样进行所有比较操作(==, !=, <, <=, >, >=)。比较规则是:先比较哪个类型的索引小,索引相同再比值。

  • std::hash 特化variant 支持哈希,因此可以作为 std::unordered_map 等容器的键。

代码示例

cpp 复制代码
#include <variant>
#include <iostream>
#include <string>
#include <vector>
#include <cmath>
#include <iomanip>

// ============================================================
// std::variant<Types...> --- C++17 引入的类型安全的联合体
//
// 核心语义: 任意时刻持有 Types 中 **某一种** 类型的值。
// 替代 C 的 union (union 只能存 POD, variant 能存任何类型)。
// 所有值存于栈上, 不涉及动态分配。
// ============================================================

// ============================================================
// 1. 构造: 创建 variant 的所有方式
// ============================================================

void demo_construction() {
    std::cout << "======== 1. 构造 (Construction) ========\n";

    // --- 1a. 默认构造:持有第一种类型的值初始化 (值初始化) ---
    // 要求第一种类型 (int) 必须可默认构造
    std::variant<int, double, std::string> v1;
    std::cout << "默认构造: v1.index() = " << v1.index()
              << " (索引0=int), value = " << std::get<int>(v1) << '\n';
    // v1 持有 int{} → 0

    // --- 1b. 从值构造 (直接推导类型) ---
    std::variant<int, double, std::string> v2 = 42;
    std::variant<int, double, std::string> v3 = 3.14;
    std::variant<int, double, std::string> v4 = std::string("hello");
    std::cout << "从值构造: v2=" << std::get<int>(v2)
              << ", v3=" << std::get<double>(v3)
              << ", v4=" << std::get<std::string>(v4) << '\n';

    // --- 1c. std::in_place_type<T> --- 明确构造哪个类型 ---
    // 当多个类型都能从同一实参构造时, 需要消除歧义
    // 例如 variant<string, vector<char>> --- 两者都能从 const char* 构造
    std::variant<std::string, std::vector<char>> v_str(
        std::in_place_type<std::string>, "hello");
    std::cout << "in_place_type<string> = " << std::get<std::string>(v_str) << '\n';

    // --- 1d. std::in_place_index<I> --- 按索引构造 ---
    std::variant<int, double, std::string> v_idx(
        std::in_place_index<2>, "world");  // 索引2 = std::string
    std::cout << "in_place_index<2> = " << std::get<2>(v_idx) << '\n';

    // --- 1e. std::monostate --- 让 variant 成为 optional-like ---
    // variant<int, float> 无法"为空"。用 monostate 作为第一个类型
    // 可以表示"无值"状态, 类似 optional
    std::variant<std::monostate, int, std::string> maybe_int;
    std::cout << "monostate variant: index = " << maybe_int.index()
              << " (monostate), 可以用它表示空状态\n";
}

// ============================================================
// 2. 获取值: index / get / get_if / holds_alternative
// ============================================================

void demo_access() {
    std::cout << "\n======== 2. 获取值 (Access) ========\n";

    std::variant<int, double, std::string> v(3.14);

    // --- 2a. index() --- 获取当前值的类型索引 (0-based) ---
    std::cout << "v.index() = " << v.index() << " (1 = double)\n";

    // --- 2b. std::get<T>() --- 按类型取值 (失败时抛异常) ---
    std::cout << "std::get<double>(v) = " << std::get<double>(v) << '\n';
    try {
        std::get<int>(v);  // v 当前存 double, 不是 int
    } catch (const std::bad_variant_access& e) {
        std::cout << "std::get<int>(v) 抛出 bad_variant_access: "
                  << e.what() << '\n';
    }

    // --- 2c. std::get<I>() --- 按索引取值 (编译时类型安全) ---
    std::cout << "std::get<1>(v) = " << std::get<1>(v) << '\n';
    // std::get<0>(v); // 编译失败: 索引0是int, 而v当前存double
    // 但 get<I> 仍然会抛 bad_variant_access 如果运行时索引不匹配

    // --- 2d. std::get_if<T>() --- 按类型取指针 (失败返回 nullptr) ---
    // 不抛异常, 用指针语义
    if (auto* pval = std::get_if<double>(&v)) {
        std::cout << "get_if<double> 成功: " << *pval << '\n';
    }
    if (auto* pval = std::get_if<int>(&v)) {
        std::cout << "get_if<int> 成功: " << *pval << '\n';
    } else {
        std::cout << "get_if<int> 返回 nullptr (v 当前不是 int)\n";
    }

    // --- 2e. std::get_if<I>() --- 按索引取指针 ---
    if (auto* pval = std::get_if<1>(&v)) {
        std::cout << "get_if<1> 成功: " << *pval << '\n';
    }

    // --- 2f. std::holds_alternative<T>() --- 仅判断类型, 不取值 ---
    std::cout << "holds_alternative<int>(v)    = "
              << std::holds_alternative<int>(v) << '\n';
    std::cout << "holds_alternative<double>(v) = "
              << std::holds_alternative<double>(v) << '\n';
}

// ============================================================
// 3. 修改: emplace / operator= / swap / valueless_by_exception
// ============================================================

void demo_modifiers() {
    std::cout << "\n======== 3. 修改 (Modifiers) ========\n";

    std::variant<int, double, std::string> v(10);
    std::cout << "初始值: " << std::get<int>(v) << '\n';

    // --- 3a. emplace<T>() --- 就地构造新类型, 销毁旧值 ---
    v.emplace<std::string>("replaced!");
    std::cout << "emplace<std::string> 后: "
              << std::get<std::string>(v) << '\n';

    // --- 3b. emplace<I>() --- 按索引就地构造 ---
    v.emplace<0>(99);  // 索引0 = int
    std::cout << "emplace<0> 后: " << std::get<0>(v) << '\n';

    // --- 3c. operator= --- 从值赋值 (自动切换类型) ---
    v = 3.14159;                            // → 变成 double
    std::cout << "v = 3.14159: " << std::get<double>(v) << '\n';
    v = std::string("assigned");           // → 变成 string
    std::cout << "v = \"assigned\": " << std::get<std::string>(v) << '\n';

    // --- 3d. swap() --- 交换两个 variant ---
    std::variant<int, double, std::string> a(100);
    std::variant<int, double, std::string> b(200);
    std::cout << "交换前: a=" << std::get<int>(a)
              << ", b=" << std::get<int>(b) << '\n';
    a.swap(b);
    std::cout << "交换后: a=" << std::get<int>(a)
              << ", b=" << std::get<int>(b) << '\n';

    // --- 3e. valueless_by_exception() --- 异常安全标记 ---
    // 正常情况下永远为 false。只有当 emplace/赋值
    // 过程中抛出异常且无法恢复原值时才会变为 true。
    // 处于 valueless 状态时, index() 返回 variant_npos。
    std::cout << "valueless_by_exception = "
              << v.valueless_by_exception() << '\n';
}

// ============================================================
// 4. std::visit --- variant 的"瑞士军刀"
// ============================================================

// 访问者: 重载的 operator() --- 每个类型一个重载
struct Stringifier {
    std::string operator()(int i)          const {
        return "int: " + std::to_string(i);
    }
    std::string operator()(double d)       const {
        std::ostringstream oss;
        oss << std::fixed << std::setprecision(2) << d;
        return "double: " + oss.str();
    }
    std::string operator()(const std::string& s) const {
        return "string: \"" + s + "\"";
    }
};

// C++17 惯用法: 用泛型 lambda 或模板化的 operator()
// overloaded 模式 --- 组合多个 lambda (P0218不需要额外库!)
template <typename... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
// C++20 起有类模板实参推导 (CTAD) 可省略下面这行
template <typename... Ts> overloaded(Ts...) -> overloaded<Ts...>;

void demo_visit() {
    std::cout << "\n======== 4. std::visit (访问者) ========\n";

    std::vector<std::variant<int, double, std::string>> items = {
        42, 3.14159, std::string("world"), 7, 2.71828
    };

    // --- 4a. visit + 有名字的访问者 (struct) ---
    std::cout << "--- 使用 struct 访问者 ---\n";
    for (const auto& v : items) {
        std::cout << "  " << std::visit(Stringifier{}, v) << '\n';
    }

    // --- 4b. visit + overloaded lambda (最常用的惯用法) ---
    std::cout << "\n--- 使用 overloaded lambda ---\n";
    auto visitor = overloaded{
        [](int i)          { std::cout << "  int: " << i << '\n'; },
        [](double d)       { std::cout << "  double: " << d << '\n'; },
        [](const std::string& s) { std::cout << "  string: " << s << '\n'; }
    };

    for (const auto& v : items) {
        std::visit(visitor, v);
    }

    // --- 4c. visit + 返回值 ---
    auto get_type_name = overloaded{
        [](int)           -> std::string { return "int"; },
        [](double)        -> std::string { return "double"; },
        [](const std::string&) -> std::string { return "string"; }
    };
    for (const auto& v : items) {
        std::cout << "  当前类型: " << std::visit(get_type_name, v) << '\n';
    }

    // --- 4d. visit 可以同时访问多个 variant ---
    std::variant<int, double> a(10);
    std::variant<int, double> b(3.5);
    auto sum_visitor = overloaded{
        [](int x, int y)     -> double { return x + y; },
        [](double x, double y) -> double { return x + y; },
        [](int x, double y)  -> double { return x + y; },
        [](double x, int y)  -> double { return x + y; }
    };
    std::cout << "\n多variant visit: visit(visitor, a=10, b=3.5) = "
              << std::visit(sum_visitor, a, b) << '\n';
}

// ============================================================
// 5. 比较运算
// ============================================================

void demo_comparison() {
    std::cout << "\n======== 5. 比较运算 (Comparison) ========\n";

    std::variant<int, double, std::string> v1(10);
    std::variant<int, double, std::string> v2(20);
    std::variant<int, double, std::string> v3(10);
    std::variant<int, double, std::string> v4(3.14);  // 不同类型!

    // 同一个类型, 同一个值 → 相等
    std::cout << "v1(10) == v3(10): " << (v1 == v3) << '\n';
    std::cout << "v1(10) != v2(20): " << (v1 != v2) << '\n';
    std::cout << "v1(10) <  v2(20): " << (v1 < v2)  << '\n';

    // 不同类型的比较: 按 index() 比较!
    // index 0 (int) < index 1 (double), 因此 v1 < v4 为 true
    std::cout << "v1(index=0, int) < v4(index=1, double): "
              << (v1 < v4) << " (按索引比较, 而非按值!)\n";
    // 规则: 先比较 index(), index 不同→返回 index 的比较结果
    //       index 相同→比较底层类型的值

    // 注意: variant 不能直接与底层值比较 (GCC12), 需用 get 或构造同类型 variant
    // 方式A: 取出值比较
    if (std::holds_alternative<int>(v1)) {
        std::cout << "v1 (int) == 10: " << (std::get<int>(v1) == 10) << '\n';
    }
    // 方式B: 构造同类型 variant 比较
    std::cout << "v1 == variant(10): " << (v1 == std::variant<int, double, std::string>(10)) << '\n';
}

// ============================================================
// 6. 辅助工具: monostate / bad_variant_access / variant_npos
// ============================================================

void demo_helpers() {
    std::cout << "\n======== 6. 辅助类型 ========\n";

    // --- std::monostate: variant 的"空"状态 ---
    // 用途1: 让 variant 可以表示"没有值" (类似 optional)
    std::variant<std::monostate, int, std::string> maybe;
    std::cout << "默认: index=" << maybe.index() << " (monostate)\n";
    maybe = 42;
    std::cout << "赋值后: index=" << maybe.index() << " (int)\n";

    // 用途2: 在 std::expected-like 用法中表示成功(无额外数据)
    // variant<monostate, error_code> --- monostate=成功, error=出错

    // --- std::bad_variant_access ---
    std::variant<int, double> v(3.14);
    try {
        std::get<int>(v);  // 当前是 double
    } catch (const std::bad_variant_access& e) {
        std::cout << "bad_variant_access: " << e.what() << '\n';
    }

    // --- std::variant_npos ---
    // 当 valueless_by_exception() == true 时,
    // index() 返回 std::variant_npos 而不是合法索引
    std::cout << "variant_npos = " << std::variant_npos << '\n';
    std::cout << "当前 v.index() = " << v.index()
              << " (正常值, != variant_npos)\n";

    // --- std::variant_size_v / std::variant_alternative_t ---
    using V = std::variant<int, double, std::string>;
    std::cout << "variant_size_v<V> = " << std::variant_size_v<V> << '\n';
    // variant_alternative_t<1, V> 即 double
    using SecondType = std::variant_alternative_t<1, V>;
    std::cout << "alternative<1,V>::type double? "
              << std::is_same_v<SecondType, double> << '\n';

    // --- std::hash ---
    // variant<Types...> 可哈希 (从 C++20 开始可选, 取决于所含类型)
    std::variant<int, std::string> v1_hash(42);
    std::variant<int, std::string> v2_hash(42);
    std::cout << "hash(v1)=hash(v2)? "
              << (std::hash<decltype(v1_hash)>{}(v1_hash)
                  == std::hash<decltype(v2_hash)>{}(v2_hash)) << '\n';
}

// ============================================================
// 7. 实际应用: 状态机 / 消息传递
// ============================================================

struct ConnectReq  { std::string host; int port; };
struct DisconnectReq {};
struct SendDataReq  { std::vector<unsigned char> data; };
struct TimeoutEv    { int ms; };

using NetworkMsg = std::variant<ConnectReq, DisconnectReq, SendDataReq, TimeoutEv>;

void handle_message(const NetworkMsg& msg) {
    std::visit(overloaded{
        [](const ConnectReq& c) {
            std::cout << "  连接请求: " << c.host << ":" << c.port << '\n';
        },
        [](const DisconnectReq&) {
            std::cout << "  断开连接请求\n";
        },
        [](const SendDataReq& s) {
            std::cout << "  发送数据: " << s.data.size() << " bytes\n";
        },
        [](const TimeoutEv& t) {
            std::cout << "  超时事件: " << t.ms << "ms\n";
        }
    }, msg);
}

void demo_state_machine() {
    std::cout << "\n======== 7. 实际应用: 消息状态机 ========\n";

    std::vector<NetworkMsg> messages = {
        ConnectReq{"localhost", 8080},
        SendDataReq{{0x48, 0x65, 0x6c, 0x6c, 0x6f}},
        TimeoutEv{5000},
        DisconnectReq{}
    };

    for (const auto& msg : messages) {
        handle_message(msg);
    }
}
int main() {
    demo_construction();
    demo_access();
    demo_modifiers();
    demo_visit();
    demo_comparison();
    demo_helpers();
    demo_state_machine();

    std::cout << "\n所有演示完毕。\n";
    return 0;
}

any

官方文档:Standard library header <any> (C++17) - cppreference.com

<any> 是 C++17 标准库引入的一个关键头文件,它定义了 std::any 类,为C++提供了真正的类型安全擦除容器

<any> 头文件提供了三个核心组件,其中最主要的是 std::any 类。

  • std::any:核心容器,可持有任意可复制构造类型(CopyConstructible)的值,也可以是空状态。

  • std::bad_any_cast 异常类 :当 std::any_cast 进行类型转换失败时抛出的异常。

  • std::make_any / std::any_cast 工具函数 :用于创建和访问 any 对象的非成员函数,提供了极大的便利性和安全性。

接口

std::any 的操作接口紧密围绕其"存储任意值"这一核心能力展开。

构造与赋值 (Construction & Assignment)

提供了非常灵活的构造方式,可以直接赋值,也可以原位构造,以追求最佳效率。

  • any() / any(std::nullopt_t) :默认构造,创建空 any 对象。

  • any(const T& value) / any(T&& value) :通过拷贝或移动直接包含一个值,例如 std::any a = 42;

  • any(std::in_place_type<T>, args...) :使用 std::in_place_type 进行原位构造,直接在 any 内部构建对象,能有效避免不必要的拷贝。

  • operator=(const T& rhs) / operator=(T&& rhs):通过赋值操作符包含新值,会正确析构旧值。

  • emplace<T>(args...) :原位构造新值,直接替换 any 对象持有的值,无论其之前是否为空。

状态观测 (Observers)

在访问值之前,检查其存在性和类型是非常关键的安全实践。

  • has_value() :返回 bool 值,指示对象当前是否持有值。

  • type() :返回当前持有值的 std::type_info 引用。如果对象为空,返回 typeid(void)。此方法常用于类型分发,如 if (a.type() == typeid(int))

值访问 (Value Access)

这些方法提供了不同安全级别的访问,是 std::any 类型安全的核心体现。

  • std::any_cast<T>(any_obj) :安全访问的值返回形式 。若类型匹配则返回值的拷贝,否则抛出 std::bad_any_cast 异常

  • std::any_cast<T&>(any_obj) :安全访问的引用返回形式。可以避免拷贝,直接获取内部值的引用。类型不匹配时同样抛出异常。

  • std::any_cast<T*>(any_obj_ptr) :安全访问的指针形式 。这是最安全 的方式。接收一个 std::any 的指针,若类型匹配则返回指向内部值的指针,否则返回 nullptr不抛出异常

修改器与工具函数 (Modifiers & Utility Functions)

  • reset() :销毁持有的对象,将 any 变为空状态。

  • swap() :交换两个 any 对象的内容。

  • std::make_any<T>(args...) :一个便捷的工厂函数,相当于 std::any(std::in_place_type<T>, args...),用于原位构造 any 对象。

代码示例

cpp 复制代码
#include <any>
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <cassert>
#include <typeinfo>
#include <iomanip>

// ============================================================
// std::any --- C++17 引入的类型安全的"万能容器"
//
// 核心语义: 可以持有任意可拷贝构造类型的值, 类型信息在运行时保留。
//
// 与 void*  的区别: any 类型安全 (拒绝非法转换)
// 与 variant 的区别: any 的类型集是开放的 (不需要提前列举)
// 与 optional 的区别: any 没有"已知类型"的限制 (任何类型都能存)
//
// 代价: 使用类型擦除, 可能涉及堆分配 (小对象优化 SBO 除外)
// ============================================================

// ============================================================
// 1. 构造: 创建 any 的所有方式
// ============================================================

void demo_construction() {
    std::cout << "======== 1. 构造 (Construction) ========\n";

    // --- 1a. 默认构造 --- 空 any (没有值) ---
    std::any empty1;
    std::any empty2{};
    std::cout << "默认构造: has_value = " << empty1.has_value() << '\n';

    // --- 1b. 从任意值构造 --- 隐式转换 ---
    std::any a_int    = 42;
    std::any a_dbl    = 3.14;
    std::any a_str    = std::string("hello");
    std::any a_cstr   = "world";  // const char[6] → const char*
    std::any a_vec    = std::vector<int>{1, 2, 3};
    std::cout << "从值构造: int=" << std::any_cast<int>(a_int)
              << ", double=" << std::any_cast<double>(a_dbl)
              << ", string=" << std::any_cast<std::string>(a_str)
              << ", cstr=" << std::any_cast<const char*>(a_cstr)
              << ", vec[0]=" << std::any_cast<std::vector<int>>(a_vec)[0] << '\n';

    // --- 1c. std::in_place_type<T> --- 就地构造 (避免临时对象) ---
    std::any a_inplace(std::in_place_type<std::string>, "constructed in-place");
    std::cout << "in_place_type: " << std::any_cast<std::string>(a_inplace) << '\n';

    // --- 1d. in_place_type + initializer_list ---
    std::any a_vec2(std::in_place_type<std::vector<int>>, {10, 20, 30});
    auto& vec_ref = std::any_cast<std::vector<int>&>(a_vec2);
    std::cout << "in_place+init_list: size=" << vec_ref.size()
              << ", [1]=" << vec_ref[1] << '\n';

    // --- 1e. std::make_any --- 工厂函数 (免写模板参数) ---
    auto a1 = std::make_any<int>(100);
    auto a2 = std::make_any<std::string>("factory");
    auto a3 = std::make_any<std::vector<int>>(5, 7);  // 5 elements of 7
    std::cout << "make_any<int>: " << std::any_cast<int>(a1)
              << ", make_any<string>: " << std::any_cast<std::string>(a2)
              << ", make_any<vector>: size=" << std::any_cast<std::vector<int>&>(a3).size()
              << '\n';

    // --- 1f. make_any + initializer_list ---
    auto a4 = std::make_any<std::vector<int>>({1, 2, 3, 4, 5});
    std::cout << "make_any<vector>(init_list): size="
              << std::any_cast<std::vector<int>&>(a4).size() << '\n';

    // --- 1g. 拷贝/移动构造 ---
    std::any copy = a_int;           // 拷贝
    std::any moved = std::move(a_int);  // 移动 (a_int 变为空)
    std::cout << "拷贝后: " << std::any_cast<int>(copy)
              << ", 移动后原值 has_value = " << a_int.has_value() << '\n';
}

// ============================================================
// 2. 观察者: has_value / type
// ============================================================

void demo_observers() {
    std::cout << "\n======== 2. 观察者 (Observers) ========\n";

    std::any a_int = 42;
    std::any a_str = std::string("hello");
    std::any empty;

    // --- 2a. has_value() --- 是否持有值 ---
    std::cout << "a_int.has_value()  = " << a_int.has_value() << '\n';
    std::cout << "a_str.has_value()  = " << a_str.has_value() << '\n';
    std::cout << "empty.has_value()  = " << empty.has_value() << '\n';

    // --- 2b. type() --- 运行时获取类型信息 (返回 const std::type_info&) ---
    std::cout << "a_int.type().name() = " << a_int.type().name() << '\n';
    std::cout << "a_str.type().name() = " << a_str.type().name() << '\n';

    // 比较类型:
    std::cout << "a_int.type() == typeid(int)           : "
              << (a_int.type() == typeid(int)) << '\n';
    std::cout << "a_str.type() == typeid(std::string)  : "
              << (a_str.type() == typeid(std::string)) << '\n';
    std::cout << "a_int.type() == typeid(double)        : "
              << (a_int.type() == typeid(double)) << '\n';

    // 空 any 的 type(): 返回 typeid(void)
    std::cout << "empty.type() == typeid(void): "
              << (empty.type() == typeid(void)) << '\n';
}

// ============================================================
// 3. 取值: any_cast 的 5 种重载
// ============================================================

void demo_any_cast() {
    std::cout << "\n======== 3. any_cast --- 取值 ========\n";

    // --- 3a. any_cast<T>(any&) --- 返回 T 的拷贝 (类型不匹配时抛异常) ---
    std::any a = 42;
    int val = std::any_cast<int>(a);  // 拷贝
    std::cout << "any_cast<int>(a) = " << val << '\n';

    try {
        std::any_cast<double>(a);  // a 存的是 int, 不是 double
    } catch (const std::bad_any_cast& e) {
        std::cout << "any_cast<double>(int_any) 抛出 bad_any_cast: "
                  << e.what() << '\n';
    }

    // --- 3b. any_cast<T&>(any&) --- 返回引用, 可以修改原值 ---
    std::any a_str = std::string("hello");
    std::any_cast<std::string&>(a_str) += " world!";  // 修改内部值
    std::cout << "any_cast<string&> 修改后: "
              << std::any_cast<std::string>(a_str) << '\n';

    // --- 3c. any_cast<T&&>(any&&) --- 移动取出值 ---
    std::any a_tmp = std::string("will be moved");
    std::string stolen = std::any_cast<std::string>(std::move(a_tmp));
    std::cout << "移动取出: " << stolen << '\n';
    // a_tmp 不再持有完整的 string (被移走)

    // --- 3d. any_cast<T>(const any*) --- 安全访问 (const), 不匹配返回 nullptr ---
    const std::any* pa = &a;
    if (const int* pint = std::any_cast<int>(pa)) {
        std::cout << "any_cast<int>(const any*) 成功: " << *pint << '\n';
    }
    if (const double* pdbl = std::any_cast<double>(pa)) {
        std::cout << "this never prints\n";
    } else {
        std::cout << "any_cast<double>(const any*) 返回 nullptr (类型不匹配)\n";
    }

    // --- 3e. any_cast<T>(any*) --- 安全访问 (非 const), 可修改 ---
    if (int* pint = std::any_cast<int>(&a)) {
        *pint += 10;  // 修改 a 内部的值
    }
    std::cout << "any_cast<int>(any*) 修改后: "
              << std::any_cast<int>(a) << '\n';
}

// ============================================================
// 4. 修改器: emplace / reset / swap / operator=
// ============================================================

void demo_modifiers() {
    std::cout << "\n======== 4. 修改器 (Modifiers) ========\n";

    std::any a(10);
    std::cout << "初始值: " << std::any_cast<int>(a) << '\n';

    // --- 4a. operator= --- 从值赋值 (切换类型) ---
    a = 3.14159;                     // int → double
    std::cout << "a = 3.14: " << std::any_cast<double>(a) << '\n';
    a = std::string("changed");     // double → string
    std::cout << "a = \"changed\": " << std::any_cast<std::string>(a) << '\n';

    // --- 4b. emplace<T>() --- 就地构造, 避免临时对象 ---
    a.emplace<std::string>("emplaced value");
    std::cout << "emplace<string>: " << std::any_cast<std::string>(a) << '\n';

    // emplace 返回新值的引用:
    std::any vec_any = std::vector<int>{1, 2, 3};
    auto& vref = vec_any.emplace<std::vector<int>>(4, 99);  // 4 elements of 99
    vref.push_back(100);
    std::cout << "emplace<vector> + 修改: size=" << vref.size()
              << ", [0]=" << vref[0] << ", last=" << vref.back() << '\n';

    // --- 4c. reset() --- 清空为无值状态 ---
    std::any temp = 42;
    std::cout << "reset 前: has_value = " << temp.has_value() << '\n';
    temp.reset();
    std::cout << "reset 后: has_value = " << temp.has_value()
              << ", type = " << temp.type().name() << '\n';

    // --- 4d. swap() --- 交换两个 any 的内容 ---
    std::any x = 100;
    std::any y = std::string("two hundred");
    std::cout << "交换前: x=int(" << std::any_cast<int>(x)
              << "), y=string(" << std::any_cast<std::string>(y) << ")\n";
    x.swap(y);
    std::cout << "交换后: x=string(" << std::any_cast<std::string>(x)
              << "), y=int(" << std::any_cast<int>(y) << ")\n";

    // 与空值交换:
    std::any z;
    y.swap(z);
    std::cout << "与空交换后: y.has_value = " << y.has_value()
              << ", z = " << std::any_cast<int>(z) << '\n';
}

// ============================================================
// 5. 实际应用场景
// ============================================================

// 场景 1: 类型擦除的配置存储
using ConfigMap = std::map<std::string, std::any>;

ConfigMap make_config() {
    ConfigMap cfg;
    cfg["timeout"]    = 30;                         // int
    cfg["host"]       = std::string("localhost");   // string
    cfg["port"]       = 8080;                       // int
    cfg["verbose"]    = true;                       // bool
    cfg["retry_delay"] = 1.5;                       // double
    return cfg;
}

void demo_config_store() {
    std::cout << "\n======== 5. 实际应用: 配置存储 ========\n";

    auto cfg = make_config();

    // 读取时按类型取出:
    auto timeout = std::any_cast<int>(cfg["timeout"]);
    auto host    = std::any_cast<std::string>(cfg["host"]);
    auto verbose = std::any_cast<bool>(cfg["verbose"]);

    std::cout << "timeout = " << timeout << '\n';
    std::cout << "host    = " << host << '\n';
    std::cout << "verbose = " << std::boolalpha << verbose << '\n';
}

// 场景 2: 通用的"任意消息"队列 (调度事件)
struct EventBus {
    std::vector<std::any> events;

    template <typename T>
    void post(T&& event) {
        events.emplace_back(std::forward<T>(event));
    }

    template <typename T>
    void process_one() {
        for (auto& e : events) {
            if (e.type() == typeid(T)) {
                auto& typed_event = std::any_cast<T&>(e);
                std::cout << "  处理事件: " << typed_event << '\n';
            }
        }
    }
};

void demo_event_bus() {
    std::cout << "\n======== 6. 实际应用: 事件总线 ========\n";

    EventBus bus;
    bus.post(std::string("UserLoggedIn"));
    bus.post(404);
    bus.post(std::string("PageRequested"));
    bus.post(200);

    std::cout << "共 " << bus.events.size() << " 个事件\n";
    std::cout << "\n处理 string 事件:\n";
    bus.process_one<std::string>();
    std::cout << "\n处理 int 事件:\n";
    bus.process_one<int>();
}

// 场景 3: 通用回调参数 (替代 void*)
void invoke_with_args(const std::vector<std::any>& args) {
    std::cout << "\n======== 7. 通用参数传递 ========\n";
    for (size_t i = 0; i < args.size(); ++i) {
        const auto& arg = args[i];
        // 运行时类型分发
        if (arg.type() == typeid(int))
            std::cout << "  [" << i << "] int    : " << std::any_cast<int>(arg) << '\n';
        else if (arg.type() == typeid(double))
            std::cout << "  [" << i << "] double : " << std::any_cast<double>(arg) << '\n';
        else if (arg.type() == typeid(std::string))
            std::cout << "  [" << i << "] string : " << std::any_cast<std::string>(arg) << '\n';
        else if (arg.type() == typeid(const char*))
            std::cout << "  [" << i << "] cstr   : " << std::any_cast<const char*>(arg) << '\n';
        else
            std::cout << "  [" << i << "] (unknown type: " << arg.type().name() << ")\n";
    }
}

// ============================================================
// 8. any vs variant vs optional 对比
// ============================================================

void demo_comparison() {
    std::cout << "\n======== 8. any / variant / optional 对比 ========\n";
    std::cout << R"(
┌──────────────┬───────────────────┬───────────────────┬───────────────────┐
│ 特性          │ std::optional<T>  │ std::variant<Ts..>│ std::any           │
├──────────────┼───────────────────┼───────────────────┼───────────────────┤
│ 类型集        │ 1 种 (T 或空)     │ 多种(需提前列举)  │ 任意 (开放集合)    │
│ 编译时类型可达 │ 是                │ 是                │ 否 (运行时查询)    │
│ 内存分配      │ 栈上 (inline)     │ 栈上 (inline)     │ 可能堆分配         │
│ 空状态        │ 有 (nullopt)      │ 有 (monostate)    │ 有 (空 any)        │
│ 关键访问      │ value() / *       │ get<T>() / visit  │ any_cast<T>()      │
│ 适用场景      │ "可能没有值"       │ "N选1"            │ "任意一种"         │
└──────────────┴───────────────────┴───────────────────┴───────────────────┘
)";
}

int main() {
    demo_construction();
    demo_observers();
    demo_any_cast();
    demo_modifiers();
    demo_config_store();
    demo_event_bus();

    std::vector<std::any> args;
    args.push_back(42);
    args.push_back(3.14159);
    args.push_back(std::string("hello"));
    args.push_back("c-string");
    invoke_with_args(args);

    demo_comparison();

    std::cout << "\n所有演示完毕。\n";
    return 0;
}

string_view

官方文档:Standard library header <string_view> (C++17) - cppreference.com

<string_view> 是 C++17 标准库的重要组成部分,它通过 std::string_view 类模板,引入了一种非拥有(non-owning)、只读的"字符串视图" 的概念。

这个设计解决了在传统 C++ 中,为了传递只读字符串,我们不得不在 const std::string&const char* 之间做出选择的困扰,它兼具了两者的优点,同时避免了它们的短板。

string_view 的核心思想是它本身并不分配、管理或复制任何字符数据,而是像一个轻量级的窗口,为你提供一个观察、访问一个已存在的、连续字符序列的视角。这个"窗口"在内部通常只由两个核心元素构成:

  • 一个指向字符序列起始地址的指针 (const CharT*)。

  • 一个表示序列长度的整数值 (size_t)。

正是这种设计,使得创建、拷贝和传递一个 string_view 的开销变得非常低,且不涉及任何堆内存分配。

接口

string_view 提供了与 std::string 高度相似的一套只读接口,这使得学习和迁移成本非常低。

类别 常用成员/接口 功能描述
核心观测 data(), size(), empty(), length() 获取底层数据的指针、视图长度,或判断是否为空视图。
元素访问 operator[], at(), front(), back() 以只读方式访问指定位置的字符或首尾字符,与 std::string 用法一致。
子串操作 substr(pos, count) 关键方法 。返回一个新的 string_view,其内容是调用者的一个子串。此操作是 O(1) 复杂度的,因为它只调整了新视图的指针和长度,完全不涉及字符数据的拷贝。
修改视图 remove_prefix(n), remove_suffix(n) 从视图的前端或后端"裁剪"掉 n 个字符,这同样通过移动指针和减小长度实现,开销为 O(1)
查找与比较 find(), rfind(), compare(), 关系运算符 (==, <, ...) 提供了丰富的字符串查找和比较功能,其行为和 std::string 的同名方法一致。
构造与赋值 构造函数, operator= 可从 const char*std::string 等隐式构造,拷贝构造和赋值仅为浅拷贝,开销极低。
类型转换 operator std::string() 为了强调其非拥有性质,从 string_viewstd::string 的转换是显式 (explicit) 的,防止不经意间的昂贵拷贝。
字面量支持 operator""sv C++17 提供了字面量后缀,让你可以非常便捷地创建 string_view,如 "hello"sv

注意事项

string_view 的强大之处在于它的"轻",而它的所有使用陷阱也源于它的"轻"。

  • 🎯 核心陷阱:生命周期管理
    string_view 不拥有数据,因此它就像一块"借来"的手表,必须确保它指向的原始数据在其使用期间一直有效。

    cpp 复制代码
    std::string_view sv;
    {
        std::string temp = "I am temporary";
        sv = temp; // sv 现在指向 temp 的内部缓冲区
    } // temp 被销毁,其内部缓冲区失效
    std::cout << sv; // 严重错误:未定义行为 (悬垂指针)!

    核心原则 :永远不要返回指向局部 std::string 变量的 string_view,也避免将其存储在生命周期可能长于原始数据的地方。

  • 🚫 不以空终止符 '\0' 结尾

    std::string 不同,string_view 不保证其数据序列以空字符 \0 结尾。因此,绝对不能sv.data() 直接传递给像 printffopen 这类依赖 \0 的 C 风格 API,除非你明确知道其来源是保证空终止的。

  • 🔄 所有权与修改的界限

    • string_view 是只读的,你无法通过它修改原始字符串。

    • 当真正需要一份可以独立存在的、可修改的字符串拷贝时,务必显式地从 string_view 构造一个 std::string(例如 std::string owner(sv)),由此产生的开销应当是你深思熟虑后的选择。

代码示例

cpp 复制代码
#include <string_view>
#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>

// ============================================================
// std::string_view --- C++17 引入的"字符串只读视图"
//
// 核心语义: 不拥有数据的指针+长度组合 (non-owning reference to string).
//          可以指向 string、C-string、字符数组、甚至子串,
//          全过程不分配、不拷贝内存。
//
// 与 const string& 的区别:
//   - string_view 可以指向 C-string 而不构造临时 string
//   - string_view 做 substr 是 O(1) (只调整指针), string 是 O(n) 拷贝
//   - string_view 不保证 null-terminated
// ============================================================

// ============================================================
// 1. 构造: string_view 的所有创建方式
// ============================================================

void demo_construction() {
    std::cout << "======== 1. 构造 ========\n";

    // --- 1a. 默认构造 --- 空视图 ---
    std::string_view sv_empty;
    std::cout << "默认构造: size=" << sv_empty.size()
              << ", data()==nullptr? " << (sv_empty.data() == nullptr) << '\n';

    // --- 1b. 从 C-string 构造 (隐式) ---
    std::string_view sv_cstr = "hello world";
    std::cout << "从C-string: " << sv_cstr << ", size=" << sv_cstr.size() << '\n';

    // --- 1c. 从 C-string + 长度构造 (不需要 null 终止符!) ---
    const char* raw = "one\0two\0three";  // 嵌入 '\0'
    std::string_view sv_len(raw, 11);
    std::cout << "从原始指针+长度: size=" << sv_len.size();
    std::cout << ", 内容: ";
    for (char c : sv_len) std::cout << (c == '\0' ? '_' : c);
    std::cout << " (\\0 内部可见, 非 C-string 思维!)\n";

    // --- 1d. 从 std::string 构造 (隐式转换) ---
    std::string s = "std::string source";
    std::string_view sv_str = s;
    std::cout << "从string: " << sv_str << '\n';

    // --- 1e. 拷贝构造 / 赋值 (浅拷贝, 只拷指针+长度) ---
    std::string_view sv_copy = sv_cstr;
    std::cout << "拷贝: " << sv_copy << " (与源共享同一块内存)\n";
    // 没有移动构造的必要 --- string_view 本身已经是轻量级的

    // --- 1f. operator""sv --- 字面量后缀 (C++17) ---
    using namespace std::string_view_literals;
    auto sv_lit = "literal string view"sv;  // 类型: std::string_view
    std::cout << "字面量 sv: " << sv_lit << ", type size=" << sizeof(sv_lit) << " bytes\n";
    // sizeof(string_view) 通常 = sizeof(const char*) + sizeof(size_t) = 16 bytes

    // --- 1g. 从字符数组构造 ---
    char buf[] = {'a', 'b', 'c', 'd', 'e'};
    std::string_view sv_buf(buf, sizeof(buf));
    std::cout << "从字符数组: " << sv_buf << '\n';
}

// ============================================================
// 2. 元素访问: [] / at / front / back / data
// ============================================================

void demo_element_access() {
    std::cout << "\n======== 2. 元素访问 ========\n";

    std::string_view sv = "abcdefghij";
    std::cout << "原始: " << sv << '\n';

    // --- 2a. operator[] --- 不检查边界 (O(1)) ---
    std::cout << "sv[0] = " << sv[0] << ", sv[5] = " << sv[5] << '\n';
    // sv[99];  // 未定义行为! 不要这样做

    // --- 2b. at() --- 越界时抛出 std::out_of_range ---
    std::cout << "sv.at(3) = " << sv.at(3) << '\n';
    try {
        sv.at(99);
    } catch (const std::out_of_range& e) {
        std::cout << "sv.at(99) 抛出 out_of_range: " << e.what() << '\n';
    }

    // --- 2c. front() / back() --- 第一个和最后一个字符 ---
    std::cout << "front() = " << sv.front() << '\n';
    std::cout << "back()  = " << sv.back() << '\n';
    // 对空视图调用 front/back 是 UB

    // --- 2d. data() --- 返回底层 const char* (不保证 null-terminated!) ---
    std::cout << "data() = " << (void*)sv.data()
              << " (不保证有 \\0 结尾!)\n";
    // 关键陷阱: string_view 的内部数据可能不以 '\0' 结尾!
    // 如果需要 null-terminated string, 用 to_string() 或自己保证源数据有 '\0'

    // 展示 data() 不保证 null-terminated:
    std::string_view sub = sv.substr(0, 3);  // "abc"
    // sub.data() 指向 "abcdefghij" 的起始位置, sub 长度为 3
    // 如果以 C-string 方式使用 sub.data(), 会读到 "abcdefghij"!
    std::cout << "注意: sub = \"" << sub << "\", 但 data() 指向原串, "
              << "不要当成 C-string 用!\n";
}

// ============================================================
// 3. 容量: size / length / empty / max_size / npos
// ============================================================

void demo_capacity() {
    std::cout << "\n======== 3. 容量 ========\n";

    std::string_view sv = "hello";

    // --- 3a. size() / length() --- 两者等价 ---
    std::cout << "size()   = " << sv.size() << '\n';
    std::cout << "length() = " << sv.length() << " (与 size() 完全等价)\n";

    // --- 3b. empty() --- 是否为空 ---
    std::cout << "empty()  = " << sv.empty() << '\n';
    std::string_view sv_empty;
    std::cout << "空视图.empty() = " << sv_empty.empty() << '\n';

    // --- 3c. max_size() --- 理论上最大能表示的字符数 ---
    std::cout << "max_size() = " << sv.max_size() << '\n';

    // --- 3d. npos --- "不存在" 的哨兵值 ---
    std::cout << "npos = " << std::string_view::npos
              << " (即 size_t 最大值)\n";
    // find 系列函数找不到时返回 npos
}

// ============================================================
// 4. 修改器: remove_prefix / remove_suffix / swap
// ============================================================

void demo_modifiers() {
    std::cout << "\n======== 4. 修改器 (不改变底层数据!) ========\n";

    // --- 4a. remove_prefix(n) --- 向前移动起始指针 (O(1)) ---
    std::string_view sv = "prefix_and_body";
    std::cout << "原始: " << sv << " (size=" << sv.size() << ")\n";
    sv.remove_prefix(7);  // 去掉 "prefix_"
    std::cout << "remove_prefix(7): " << sv << " (size=" << sv.size() << ")\n";
    // 注意: 底层数据没变, 只是 start 指针前移了

    // --- 4b. remove_suffix(n) --- 向前缩短尾部 (O(1)) ---
    sv.remove_suffix(5);  // 去掉 "_body"
    std::cout << "remove_suffix(5): " << sv << " (size=" << sv.size() << ")\n";
    // 结果只剩下 "and"

    // --- 4c. 链式调用前缀/后缀裁剪 ---
    auto trim = [](std::string_view sv) -> std::string_view {
        // 去掉首尾空格 (注意: string_view 的 trim 不影响原数据)
        while (!sv.empty() && sv.front() == ' ') sv.remove_prefix(1);
        while (!sv.empty() && sv.back() == ' ')  sv.remove_suffix(1);
        return sv;
    };
    std::string_view padded_sv = "   trimmed   ";
    std::cout << "trim 前: \"" << padded_sv << "\"\n";
    std::cout << "trim 后: \"" << trim(padded_sv) << "\"\n";

    // --- 4d. swap() --- 交换两个 view (O(1), 只交换指针+长度) ---
    std::string_view a = "hello";
    std::string_view b = "world";
    a.swap(b);
    std::cout << "a.swap(b) 后: a=" << a << ", b=" << b << '\n';
}

// ============================================================
// 5. 子串: substr (O(1)! 不拷贝数据)
// ============================================================

void demo_substr() {
    std::cout << "\n======== 5. substr --- O(1) 子串 ========\n";

    std::string_view sv = "0123456789abcdef";

    // --- substr(pos, count) --- 返回新 view, 不拷贝 ---
    auto sub1 = sv.substr(0, 5);   // "01234"
    auto sub2 = sv.substr(5, 3);   // "567"
    auto sub3 = sv.substr(8);      // "89abcdef" (pos=8, 省略 count → 到末尾)

    std::cout << "substr(0,5)  = " << sub1 << '\n';
    std::cout << "substr(5,3)  = " << sub2 << '\n';
    std::cout << "substr(8)    = " << sub3 << '\n';

    // 验证数据共享: 三个 view 的 data() 都指向同一块内存
    std::cout << "sub1.data() - sv.data() = " << (sub1.data() - sv.data()) << " bytes\n";
    std::cout << "sub2.data() - sv.data() = " << (sub2.data() - sv.data()) << " bytes\n";
    std::cout << "sub3.data() - sv.data() = " << (sub3.data() - sv.data()) << " bytes\n";
    std::cout << "(三个 view 指向同一块内存, 没有内存分配!)\n";
}

// ============================================================
// 6. 查找: find / rfind / find_first_of / find_last_of / find_first_not_of
// ============================================================

void demo_find() {
    std::cout << "\n======== 6. 查找 (find 系列) ========\n";

    std::string_view sv = "The quick brown fox jumps over the lazy dog";

    // --- 6a. find(string_view, pos) --- 查找子串 ---
    auto pos1 = sv.find("fox");
    std::cout << "find(\"fox\") = " << pos1
              << " → sv[" << pos1 << "] = '" << sv[pos1] << "'\n";

    // --- 6b. find(char, pos) --- 查找字符 ---
    auto pos2 = sv.find('e');
    std::cout << "find('e')   = " << pos2
              << " → 第一个 'e' 在位置 " << pos2 << '\n';

    // 从指定位置开始找:
    auto pos3 = sv.find('e', pos2 + 1);
    std::cout << "find('e', 3) = " << pos3 << " → 第二个 'e'\n";

    // --- 6c. 未找到时返回 npos ---
    auto pos_nf = sv.find("xyz");
    std::cout << "find(\"xyz\") = " << pos_nf
              << " (== npos? " << (pos_nf == std::string_view::npos) << ")\n";

    // --- 6d. rfind --- 反向查找 ---
    auto last_e = sv.rfind('e');
    std::cout << "rfind('e')  = " << last_e << " → 最后一个 'e'\n";

    // --- 6e. find_first_of --- 查找"集合中任意字符"首次出现 ---
    auto first_vowel = sv.find_first_of("aeiou");
    std::cout << "find_first_of(\"aeiou\") = " << first_vowel
              << " → 第一个元音 '" << sv[first_vowel] << "'\n";

    // --- 6f. find_last_of --- 查找"集合中任意字符"最后一次出现 ---
    auto last_digit = sv.find_last_of("0123456789");
    std::cout << "find_last_of(\"0-9\") = " << last_digit
              << " (" << (last_digit == std::string_view::npos ? "没找到" : "找到") << ")\n";

    // --- 6g. find_first_not_of --- 查找"不在集合中"的字符 ---
    // 跳过前缀:
    std::string_view hex = "   0xFF";
    auto data_start = hex.find_first_not_of(" ");
    std::cout << "hex 跳过空格: \"" << hex << "\" → 数据起始位置 " << data_start << '\n';

    // --- 6h. find_last_not_of --- 查找尾部"不在集合中"的字符 ---
    // 忽略尾随空格:
    std::string_view with_trail = "data   ";
    auto data_end = with_trail.find_last_not_of(" ");
    std::cout << "\"" << with_trail << "\" 有效长度: " << (data_end + 1) << '\n';

    // --- 6i. 查找 const char* 版本 ---
    auto pos_cstr = sv.find("brown", 0);
    std::cout << "find(\"brown\") = " << pos_cstr << '\n';
}

// ============================================================
// 7. 比较: compare / 运算符
// ============================================================

void demo_comparison() {
    std::cout << "\n======== 7. 比较 ========\n";

    std::string_view a = "apple";
    std::string_view b = "banana";
    std::string_view c = "apple";
    std::string_view d = "Apple";

    // --- 7a. 比较运算符 (字典序比较) ---
    std::cout << std::boolalpha;
    std::cout << "apple == apple  : " << (a == c) << '\n';
    std::cout << "apple != banana : " << (a != b) << '\n';
    std::cout << "apple <  banana : " << (a < b) << '\n';
    std::cout << "Apple <  apple  : " << (d < a)
              << " ('A' ascii=" << int('A') << " < 'a' ascii=" << int('a') << ")\n";

    // --- 7b. compare() --- 6 种重载 ---
    // 1: compare(string_view) --- 整体比较
    int cmp1 = a.compare(b);  // 负值 (a < b)
    std::cout << "a.compare(b) = " << cmp1 << " (负=小于, 0=等于, 正=大于)\n";

    // 2: compare(pos1, count1, string_view) --- 子串 vs 视图
    std::string_view sv = "prefix_apple_suffix";
    int cmp2 = sv.compare(7, 5, a);  // sv[7..11]="apple" vs a="apple"
    std::cout << "子串 vs 视图: sv[7..11]==\"apple\"? compare=" << cmp2 << '\n';

    // 3: compare(pos1, count1, sv, pos2, count2) --- 子串 vs 子串
    std::string_view sv2 = "xxxappleyyy";
    int cmp3 = sv.compare(7, 5, sv2, 3, 5);
    std::cout << "子串 vs 子串: compare=" << cmp3 << " (两个 \"apple\")\n";

    // 4: compare(C-string) --- 与 C-string 比较
    int cmp4 = a.compare("apricot");
    std::cout << "\"apple\".compare(\"apricot\") = " << cmp4 << " (负: apple<apricot)\n";

    // 5: compare(pos1, count1, C-string) --- 子串 vs C-string
    std::cout << "sv[7..11].compare(\"apple\") = " << sv.compare(7, 5, "apple") << '\n';

    // 6: compare(pos1, count1, C-string, count2) --- 子串 vs C-string+长度
    const char* raw = "apple_extra";
    std::cout << "sv[7..11].compare(\"apple_extra\", 5) = "
              << sv.compare(7, 5, raw, 5) << '\n';
}

// ============================================================
// 8. 与 string 互转: to_string / operator basic_string / copy
// ============================================================

void demo_conversion() {
    std::cout << "\n======== 8. 与 string 互转 ========\n";

    std::string_view sv = "sample text";

    // --- 8a. 显式转换为 std::string (explicit operator std::string) ---
    std::string s1(sv);          // 直接构造
    std::string s2 = std::string(sv);  // 显式转换
    std::cout << "显式转换: s1=" << s1 << ", s2=" << s2 << '\n';

    // --- 8b. to_string() --- 创建 string 副本 (C++26 起支持自定义 Allocator) ---
    // to_string() 在 C++17/20 中不存在! C++17 需用 std::string(sv)
    // 这里用 std::string(sv) 替代
    std::string s3(sv);
    std::cout << "std::string(sv): " << s3 << '\n';

    // --- 8c. copy(buf, n, pos) --- 拷贝字符到外部缓冲区 ---
    char buf[8] = {};
    sv.copy(buf, 6, 0);  // 从 pos=0 开始拷贝 6 个字符
    std::cout << "copy(buf, 6): buf = \"" << buf << "\"\n";
    // 注意: copy 不会添加 '\0'! 需要手动处理
}

// ============================================================
// 9. 性能对比: string_view vs string 的 substr 开销
// ============================================================

void demo_performance() {
    std::cout << "\n======== 9. string vs string_view 的 substr 性能 ========\n";

    // string::substr() --- 总是拷贝 (O(n)):
    std::string big_str(10000, 'x');
    big_str[5000] = 'T';  // 目标字符在中间

    // string_view::substr() --- 只调整指针 (O(1)):
    std::string_view big_sv(big_str);

    // 搜索 "T" 然后取周围子串:
    auto pos = big_sv.find('T');
    auto context = big_sv.substr(pos - 5, 11);  // 5 个前后字符
    std::cout << "搜索 'T' 位置 = " << pos << '\n';
    std::cout << "周围上下文: \"" << context << "\" (没有拷贝 10000 字符!)\n";

    // 等价于 string::substr (会拷贝):
    // std::string context_s = big_str.substr(pos - 5, 11);
    // string 版本会分配 11 字节 + 拷贝
}

// ============================================================
// 10. 实际应用场景
// ============================================================

// 场景 1: 函数参数 --- 接收任意字符串类型而零开销
// 可以接收: string, C-string, char array, string_view
void process_name(std::string_view name) {
    if (name.empty()) {
        std::cout << "  (空名字)\n";
        return;
    }
    std::cout << "  Hello, " << name << "!\n";
}

// 场景 2: 分词器 --- 不拷贝地切分字符串
std::vector<std::string_view> split(std::string_view sv, char delim) {
    std::vector<std::string_view> tokens;
    size_t start = 0;
    while (start <= sv.size()) {
        auto end = sv.find(delim, start);
        if (end == std::string_view::npos) {
            tokens.push_back(sv.substr(start));
            break;
        }
        tokens.push_back(sv.substr(start, end - start));
        start = end + 1;
    }
    return tokens;
}

// 场景 3: 解析 CSV 行 (零拷贝)
void parse_csv_line(std::string_view line) {
    auto fields = split(line, ',');
    std::cout << "  " << fields.size() << " 列: ";
    for (auto f : fields) {
        // 去除首尾空格
        while (!f.empty() && f.front() == ' ') f.remove_prefix(1);
        while (!f.empty() && f.back() == ' ')  f.remove_suffix(1);
        std::cout << '[' << f << "] ";
    }
    std::cout << '\n';
}

void demo_real_world() {
    std::cout << "\n======== 10. 实际应用场景 ========\n";

    // --- 场景 1: 通用字符串参数 ---
    std::cout << "--- 通用参数 ---\n";
    process_name("Alice");                    // const char[6]
    process_name(std::string("Bob"));         // std::string
    process_name(std::string_view("very long c-string").substr(0, 4));  // string_view, 无拷贝
    process_name("");                         // 空

    // --- 场景 2: 零拷贝分词 ---
    std::cout << "\n--- 零拷贝分词 ---\n";
    std::string line = "hello world foo bar baz";
    auto tokens = split(line, ' ');
    std::cout << line << " → " << tokens.size() << " tokens:\n";
    for (auto t : tokens) {
        std::cout << "  [" << t << "] (指向原串偏移 " << (t.data() - line.data()) << ")\n";
    }

    // --- 场景 3: CSV 解析 ---
    std::cout << "\n--- CSV 解析 ---\n";
    std::string csv = "  Alice  ,  Bob , Charlie ,  David  ";
    std::cout << "CSV: " << csv << '\n';
    parse_csv_line(csv);

    // --- 场景 4: 编译器词法分析 (经典场景) ---
    std::cout << "\n--- 简单词法分析 ---\n";
    const std::string source = "int main() { return 42; }";
    std::string_view rest = source;
    while (!rest.empty()) {
        // 跳过空格
        auto start = rest.find_first_not_of(" ");
        if (start == std::string_view::npos) break;
        rest.remove_prefix(start);

        // 找到 token 末尾
        auto end = rest.find_first_of(" ;(){}\r\n");
        auto token = rest.substr(0, end);
        std::cout << "  token: \"" << token << "\"\n";

        if (end == std::string_view::npos) break;
        rest.remove_prefix(end + 1);
    }
}

// ============================================================
// 11. 常见陷阱
// ============================================================

void demo_pitfalls() {
    std::cout << "\n======== 11. 常见陷阱 ========\n";

    // 陷阱 1: 悬垂引用 (dangling reference)
    std::cout << "陷阱 1: 指向临时对象的悬垂引用:\n";
    [[maybe_unused]] auto bad = []() -> std::string_view {
        std::string temp = "temporary";
        return temp;  // ⊗ 危险! temp 被销毁, view 悬垂
    };
    // std::cout << bad();  // 未定义行为!

    // 正确做法: 返回 string (拥有数据)
    [[maybe_unused]] auto good = []() -> std::string {
        std::string temp = "safe";
        return temp;  // ✓ 移动返回, 数据被移出
    };

    // 陷阱 2: data() 不一定 null-terminated
    std::string_view sub = std::string_view("hello world").substr(0, 5);  // "hello"
    // sub.data() 不是 "hello\0" 而是 "hello world\0"
    std::cout << "陷阱 2: sub.data() 不是独立 null-terminated 串\n  sub = \""
              << sub << "\", 但 data() 长这样: \"" << sub.data() << "\"\n";
    // 如果需要 C-string, 用 std::string(sub)

    // 陷阱 3: remove_prefix/suffix 不检查越界 (UB!)
    std::cout << "陷阱 3: remove_prefix 超过 size 是 UB\n";
    std::string_view sv = "short";
    sv.remove_prefix(3);  // OK, sv = "rt"
    sv.remove_prefix(1);  // OK, sv = "t"
    std::cout << "  (sub, 但在安全范围内)\n";
    // sv.remove_prefix(5); // UB! 只剩 3 字符却移了 5

    // 陷阱 4: string_view 只是视图, 不延长生命周期
    std::cout << "陷阱 4: string_view 不延长被指向对象的生命周期\n";
    std::string_view dangling;
    {
        std::string inner = "inner scope";
        dangling = inner;  // ✓ 在此作用域内有效
    }  // inner 被销毁!
    // std::cout << dangling;  // UB! dangling 指向已销毁的对象
    std::cout << "  (dangling 现在指向已被销毁的 string)\n";
}

int main() {
    demo_construction();
    demo_element_access();
    demo_capacity();
    demo_modifiers();
    demo_substr();
    demo_find();
    demo_comparison();
    demo_conversion();
    demo_performance();
    demo_real_world();
    demo_pitfalls();

    std::cout << "\n所有演示完毕。\n";
    return 0;
}

本期内容到这里就结束了。

封面图自取:

相关推荐
苍何8 小时前
终于找到解决手机消息轰炸的 AI 神器,有点离谱...
后端
I Promise348 小时前
多传感器融合&模型后处理C++工程师面试参考回答
开发语言·c++·面试
IT策士9 小时前
Django 从 0 到 1 打造完整电商平台:商品排序与浏览量统计
后端·python·django
霍霍的袁9 小时前
【C++初阶】函数重载详细讲解
开发语言·c++·算法
陌路209 小时前
详解C++ 高性能网络库 muduo 的精简日志模块
开发语言·c++·php
水木流年追梦9 小时前
大模型入门-DPO 直接偏好优化
人工智能·学习·算法·机器学习·正则表达式
网络与设备以及操作系统学习使用者9 小时前
vi与vim在openEuler中的差异及应用
linux·运维·网络·学习·vim
万少9 小时前
万少的 Claude Code 入门教程
前端·人工智能·后端
malog_9 小时前
Milvus向量数据库:AI时代的搜索革命
数据库·人工智能·后端·milvus