C++学习:六个月从基础到就业——C++17:std::optional/variant/any
C++学习:六个月从基础到就业——C++17:std::optional/variant/any
本文是我C++学习之旅系列的第四十七篇技术文章,也是第三阶段"现代C++特性"的第九篇,主要介绍C++17引入的三个重要工具类型:std::optional、std::variant和std::any。查看完整系列目录了解更多内容。
引言
C++17引入了三个非常有用的工具类型:std::optional
、std::variant
和std::any
。这些类型为C++程序员提供了更安全、更灵活的数据处理方式,弥补了C++标准库中的一些长期存在的缺陷。这些工具可以帮助我们编写更加健壮和表达力更强的代码,避免许多常见的编程陷阱。
std::optional
提供了一种表示"可能存在或不存在的值"的方法,是处理可选数据的优雅解决方案。std::variant
实现了类型安全的联合体,可以安全地存储多种可能类型中的一种。std::any
则提供了一种完全动态的类型擦除容器,可以存储任意类型的单个值。
本文将深入探讨这三种类型的设计理念、用法、性能特性以及实际应用场景,帮助你充分利用这些强大工具,编写更加现代化的C++代码。
目录
- C++17:std::optional/variant/any
- 引言
- 目录
- std::optional
- 基本概念
- 创建和访问optional
- 与指针和特殊值的对比
- 常见操作和方法
- 实际应用场景
- std::variant
- 类型安全的联合体
- 创建和访问variant
- 使用访问者模式
- 错误处理:valueless_by_exception
- 实际应用场景
- std::any
- 动态类型容器
- 创建和访问any
- 类型安全考虑
- 性能开销
- 实际应用场景
- 性能与设计考量
- 内存布局
- 异常安全
- 何时选择哪种工具
- 最佳实践与使用技巧
- API设计策略
- 与其他C++17特性结合
- 常见错误与避坑指南
- 总结
std::optional
基本概念
std::optional
是一个模板类,定义在<optional>
头文件中,用于表示一个可能有值也可能没有值的对象。它的概念类似于许多现代编程语言中的"Maybe"或"Option"类型,提供了一种比空指针或特殊值更安全、更明确的方式来表示可选值。
std::optional<T>
可以处于两种状态:
- 包含一个类型为T的值
- 不包含值(空状态)
这种设计解决了以下问题:
- 避免使用特殊值(如-1, 0, nullptr)来表示"无值"
- 避免使用指针及其相关的内存管理问题
- 使API的意图更加明确
- 提供类型安全的检查机制
创建和访问optional
以下是如何创建和使用std::optional
的基本示例:
#include <iostream>
#include <optional>
#include <string>std::optional<std::string> createGreeting(bool includeGreeting) {if (includeGreeting) {return "Hello, World!";}return std::nullopt; // 表示无值
}int main() {// 创建optionalstd::optional<int> opt1; // 空optionalstd::optional<int> opt2 = 42; // 包含值的optionalstd::optional<int> opt3 = std::nullopt; // 显式指定为空std::optional<std::string> opt4{"text"}; // 直接初始化// 检查是否有值if (opt1.has_value()) {std::cout << "opt1 has value: " << opt1.value() << std::endl;} else {std::cout << "opt1 has no value" << std::endl;}// 更简洁的检查语法if (opt2) {std::cout << "opt2 has value: " << *opt2 << std::endl;}// 使用value_or提供默认值std::cout << "opt3 value: " << opt3.value_or(0) << std::endl;// 函数返回optionalauto greeting = createGreeting(true);if (greeting) {std::cout << *greeting << std::endl;}// 访问不存在的值会抛出异常try {std::cout << opt1.value() << std::endl;} catch (const std::bad_optional_access& e) {std::cout << "Exception: " << e.what() << std::endl;}return 0;
}
与指针和特殊值的对比
为什么要使用std::optional
而不是其他替代方案?以下是对比分析:
-
与nullptr或指针相比:
optional
不涉及动态内存分配- 不需要担心内存释放和所有权问题
- 语义更明确(指针可能表示可选,也可能表示所有权)
- 更安全,不会导致空指针解引用
-
与特殊返回值相比(如-1表示失败):
- 不需要为每种类型定义特殊值
- 不需要文档说明哪个值是特殊值
- 不会与有效的数据值混淆
- 统一的检查方式,无需记忆每种情况的特殊值
示例对比:
// 使用特殊值的旧方法
int findUserOld(const std::string& name) {// 假设-1表示未找到if (name.empty()) return -1;return 42; // 假设这是用户ID
}// 使用指针的旧方法
int* findUserPtr(const std::string& name) {if (name.empty()) return nullptr;static int id = 42; // 静态变量避免返回悬垂指针return &id;
}// 使用optional的现代方法
std::optional<int> findUser(const std::string& name) {if (name.empty()) return std::nullopt;return 42;
}void example() {// 使用特殊值int idOld = findUserOld("Alice");if (idOld == -1) {// 处理未找到的情况}// 使用指针int* idPtr = findUserPtr("Alice");if (idPtr != nullptr) {// 使用*idPtr}// 使用optionalstd::optional<int> idOpt = findUser("Alice");if (idOpt) {// 使用*idOpt或idOpt.value()}
}
使用std::optional
的代码意图更明确,且不会与有效数据混淆。
常见操作和方法
std::optional
提供了一系列有用的方法:
#include <iostream>
#include <optional>
#include <string>void demonstrateOptionalMethods() {std::optional<std::string> opt;// 赋值操作opt = "Hello";std::cout << "After assignment: " << *opt << std::endl;// 重置为空opt.reset();std::cout << "Has value after reset: " << opt.has_value() << std::endl;// emplace构造新值opt.emplace("Constructed in-place");std::cout << "After emplace: " << opt.value() << std::endl;// 交换两个optionalstd::optional<std::string> other = "Other value";opt.swap(other);std::cout << "After swap: " << *opt << " and " << *other << std::endl;// 比较操作std::optional<int> a = 1;std::optional<int> b = 2;std::optional<int> empty;std::cout << "a < b: " << (a < b) << std::endl; // truestd::cout << "a > empty: " << (a > empty) << std::endl; // truestd::cout << "empty == std::nullopt: " << (empty == std::nullopt) << std::endl; // truestd::cout << "a == 1: " << (a == 1) << std::endl; // true// C++20: contains方法// if (a.contains(1)) { // 检查是否包含特定值// std::cout << "a contains 1" << std::endl;// }
}
实际应用场景
std::optional
在以下场景特别有用:
- 函数可能无法返回有效结果:
std::optional<double> safeSqrt(double x) {if (x >= 0) {return std::sqrt(x);}return std::nullopt;
}void useSafeSqrt() {auto result = safeSqrt(-4.0);if (result) {std::cout << "Square root: " << *result << std::endl;} else {std::cout << "Cannot compute square root of negative number" << std::endl;}
}
- 数据库查询或资源查找:
class UserRepository {
public:std::optional<User> findById(int userId) {// 数据库查询逻辑if (userExists(userId)) {return User(userId, "Username");}return std::nullopt;}private:bool userExists(int userId) {// 实际实现中会检查数据库return userId > 0 && userId < 1000;}
};
- 可选配置参数:
struct Configuration {std::string appName;std::optional<std::string> logFile;std::optional<int> maxConnections;std::optional<bool> debugMode;
};void setupApp(const Configuration& config) {std::cout << "App name: " << config.appName << std::endl;if (config.logFile) {std::cout << "Logging to: " << *config.logFile << std::endl;} else {std::cout << "Using default logging" << std::endl;}int connections = config.maxConnections.value_or(10);std::cout << "Max connections: " << connections << std::endl;if (config.debugMode && *config.debugMode) {std::cout << "Debug mode enabled" << std::endl;}
}
- 延迟初始化:
class ExpensiveResource {
private:std::optional<LargeObject> resource;public:LargeObject& getResource() {if (!resource) {resource.emplace(/* 昂贵的初始化参数 */);}return *resource;}
};
std::variant
类型安全的联合体
std::variant
定义在<variant>
头文件中,是C++17引入的类型安全的联合体(union)。与传统的C++联合体不同,variant
可以安全地存储包括类和结构体在内的任何类型,同时跟踪当前正在存储的类型。
std::variant<Types...>
能够保存指定类型集合中的任意一个类型的值,并且提供了类型安全的访问方式,避免了传统联合体使用不当导致的未定义行为。
传统联合体与std::variant
的主要区别:
- 类型安全:
variant
知道它当前持有的具体类型,而传统联合体不知道 - 支持的类型:
variant
可以存储任何类型,包括有构造函数和析构函数的类 - 内存管理:
variant
自动处理构造和析构,避免内存泄漏 - 访问方式:
variant
提供类型安全的访问方法,避免类型错误
创建和访问variant
以下是std::variant
基本用法的示例:
#include <iostream>
#include <variant>
#include <string>int main() {// 创建variantstd::variant<int, double, std::string> var; // 默认构造为第一个类型(int)的默认值std::cout << "Initially contains int: " << std::get<int>(var) << std::endl;// 赋予不同类型的值var = 3.14;std::cout << "Now contains double: " << std::get<double>(var) << std::endl;var = std::string("Hello, variant!");std::cout << "Now contains string: " << std::get<std::string>(var) << std::endl;// 检查当前保存的类型if (std::holds_alternative<std::string>(var)) {std::cout << "Variant currently holds a string" << std::endl;}// 尝试获取错误类型会抛出异常try {std::cout << std::get<int>(var) << std::endl;} catch(const std::bad_variant_access& e) {std::cout << "Exception: " << e.what() << std::endl;}// 使用std::get_if安全地获取值if (auto pval = std::get_if<std::string>(&var)) {std::cout << "Retrieved string: " << *pval << std::endl;} else {std::cout << "Variant doesn't contain a string" << std::endl;}// 替换为另一种类型var = 42;std::cout << "Back to int: " << std::get<int>(var) << std::endl;return 0;
}
使用访问者模式
访问std::variant
的最灵活和类型安全的方法是使用std::visit
和访问者模式:
#include <iostream>
#include <variant>
#include <string>// 方法1:访问者类
struct PrintVisitor {void operator()(int i) const {std::cout << "int: " << i << std::endl;}void operator()(double d) const {std::cout << "double: " << d << std::endl;}void operator()(const std::string& s) const {std::cout << "string: " << s << std::endl;}
};// 方法2:通用lambda (C++17)
auto printVisitor = [](const auto& value) {using T = std::decay_t<decltype(value)>;if constexpr (std::is_same_v<T, int>) {std::cout << "int: " << value << std::endl;} else if constexpr (std::is_same_v<T, double>) {std::cout << "double: " << value << std::endl;} else if constexpr (std::is_same_v<T, std::string>) {std::cout << "string: " << value << std::endl;}
};// 方法3:重载lambda (C++17)
auto overloadedVisitor = [](auto&& arg) -> decltype(auto) {using T = std::decay_t<decltype(arg)>;if constexpr (std::is_same_v<T, int>)return "int: " + std::to_string(arg);else if constexpr (std::is_same_v<T, double>)return "double: " + std::to_string(arg);else if constexpr (std::is_same_v<T, std::string>)return "string: " + arg;
};int main() {std::variant<int, double, std::string> var = 3.14;// 使用访问者类std::visit(PrintVisitor{}, var);// 使用通用lambdastd::visit(printVisitor, var);// 使用重载lambda并返回结果std::string result = std::visit(overloadedVisitor, var);std::cout << "Result: " << result << std::endl;// 更简洁的方式:使用重载运算符(辅助模板)var = std::string("Hello, visit!");auto printer = overload {[](int i) { std::cout << "int: " << i << std::endl; },[](double d) { std::cout << "double: " << d << std::endl; },[](const std::string& s) { std::cout << "string: " << s << std::endl; }};std::visit(printer, var);return 0;
}// 辅助模板:重载运算符
template<class... Ts> struct overload : Ts... { using Ts::operator()...; };
template<class... Ts> overload(Ts...) -> overload<Ts...>; // C++17 deduction guide
错误处理:valueless_by_exception
在极少数情况下,std::variant
可能处于一种特殊状态:valueless_by_exception
。当某些异常发生时,variant可能无法保持有效状态:
#include <iostream>
#include <variant>
#include <string>
#include <exception>class ThrowingClass {
public:ThrowingClass() { throw std::runtime_error("Construction failed"); }
};int main() {std::variant<int, std::string, ThrowingClass> var = 10;try {var = ThrowingClass{}; // 尝试分配一个会抛出异常的对象} catch (const std::exception& e) {std::cout << "Caught exception: " << e.what() << std::endl;}// 检查variant是否处于valueless_by_exception状态if (var.valueless_by_exception()) {std::cout << "Variant is valueless by exception" << std::endl;} else {std::cout << "Variant has a value" << std::endl;}return 0;
}
这种情况在实践中很少见,但处理它是健壮代码的重要部分。
实际应用场景
std::variant
在以下场景特别有用:
- 表示多种可能的类型:
// 解析配置文件中的值
using ConfigValue = std::variant<std::string, int, double, bool>;std::map<std::string, ConfigValue> parseConfig(const std::string& filename) {std::map<std::string, ConfigValue> result;// 实际解析逻辑...result["server"] = std::string("localhost");result["port"] = 8080;result["timeout"] = 30.5;result["debug"] = true;return result;
}void useConfig() {auto config = parseConfig("config.ini");if (auto value = std::get_if<std::string>(&config["server"])) {std::cout << "Server: " << *value << std::endl;}// 使用访问者处理所有可能的类型auto printer = [](const auto& value) {using T = std::decay_t<decltype(value)>;if constexpr (std::is_same_v<T, std::string>)return "string: " + value;else if constexpr (std::is_same_v<T, int>)return "int: " + std::to_string(value);else if constexpr (std::is_same_v<T, double>)return "double: " + std::to_string(value);else if constexpr (std::is_same_v<T, bool>)return std::string("bool: ") + (value ? "true" : "false");};for (const auto& [key, value] : config) {std::cout << key << " = " << std::visit(printer, value) << std::endl;}
}
- 状态机实现:
#include <iostream>
#include <variant>
#include <string>// 定义状态机的状态
struct Idle {void handle() { std::cout << "Idle state" << std::endl; }
};struct Processing {int progress;Processing(int p = 0) : progress(p) {}void handle() { std::cout << "Processing state: " << progress << "% complete" << std::endl; }
};struct Error {std::string message;Error(std::string msg) : message(std::move(msg)) {}void handle() { std::cout << "Error state: " << message << std::endl; }
};// 状态机类
class StateMachine {
private:std::variant<Idle, Processing, Error> state;public:StateMachine() : state(Idle{}) {}void nextState() {std::visit([this](auto& currentState) {using T = std::decay_t<decltype(currentState)>;if constexpr (std::is_same_v<T, Idle>) {state = Processing{0};}else if constexpr (std::is_same_v<T, Processing>) {auto& p = currentState;p.progress += 25;if (p.progress >= 100) {state = Idle{};}}else if constexpr (std::is_same_v<T, Error>) {state = Idle{};}}, state);}void processError(const std::string& message) {state = Error{message};}void handleCurrentState() {std::visit([](auto& currentState) {currentState.handle();}, state);}
};int main() {StateMachine machine;machine.handleCurrentState(); // Idlemachine.nextState();machine.handleCurrentState(); // Processing 0%machine.nextState();machine.handleCurrentState(); // Processing 25%machine.processError("Connection lost");machine.handleCurrentState(); // Errormachine.nextState();machine.handleCurrentState(); // Back to Idlereturn 0;
}
- 类型安全的消息传递:
#include <iostream>
#include <variant>
#include <vector>
#include <string>// 定义不同类型的消息
struct TextMessage {std::string sender;std::string text;
};struct ImageMessage {std::string sender;std::string imageUrl;int width;int height;
};struct StatusUpdate {std::string user;std::string newStatus;
};// 统一消息类型
using Message = std::variant<TextMessage, ImageMessage, StatusUpdate>;// 消息处理函数
void processMessages(const std::vector<Message>& messages) {for (const auto& msg : messages) {std::visit(overload{[](const TextMessage& m) {std::cout << "Text from " << m.sender << ": " << m.text << std::endl;},[](const ImageMessage& m) {std::cout << "Image from " << m.sender << ": " << m.imageUrl << " (" << m.width << "x" << m.height << ")" << std::endl;},[](const StatusUpdate& m) {std::cout << "Status update: " << m.user << " is now " << m.newStatus << std::endl;}}, msg);}
}// 辅助模板
template<class... Ts> struct overload : Ts... { using Ts::operator()...; };
template<class... Ts> overload(Ts...) -> overload<Ts...>;int main() {std::vector<Message> messages = {TextMessage{"Alice", "Hello, how are you?"},ImageMessage{"Bob", "vacation.jpg", 1024, 768},StatusUpdate{"Charlie", "On vacation"},TextMessage{"David", "Has anyone seen my keys?"}};processMessages(messages);return 0;
}
std::any
动态类型容器
std::any
定义在<any>
头文件中,是一个类型安全的容器,可以存储任何类型的单一值。与std::variant
不同,它不需要预先指定可能的类型集合,因此提供了完全的类型擦除功能。
std::any
主要用途:
- 当需要存储不确定类型的值时
- 当与动态类型语言交互时
- 当类型集合不能在编译时确定时
它与void*
指针或传统的类型擦除技术相比有以下优势:
- 类型安全(检索时需要指定正确的类型)
- 自动内存管理(无需担心资源泄漏)
- 支持任何可复制构造的类型
- 无需手动类型转换
创建和访问any
以下是std::any
基本用法的示例:
#include <iostream>
#include <any>
#include <string>
#include <vector>int main() {// 创建空的anystd::any a;std::cout << "a.has_value(): " << a.has_value() << std::endl;// 赋值不同类型a = 42;std::cout << "a contains int: " << std::any_cast<int>(a) << std::endl;a = std::string("Hello, any!");std::cout << "a contains string: " << std::any_cast<std::string>(a) << std::endl;// 也可以存储自定义类型struct Point { int x, y; };a = Point{10, 20};// 获取类型信息std::cout << "Type: " << a.type().name() << std::endl;// 安全的转换(使用指针)if (auto pval = std::any_cast<Point>(&a)) {std::cout << "Point: (" << pval->x << ", " << pval->y << ")" << std::endl;}// 不安全的转换(可能抛出异常)try {std::cout << std::any_cast<int>(a) << std::endl;} catch (const std::bad_any_cast& e) {std::cout << "Exception: " << e.what() << std::endl;}// 重置为空a.reset();std::cout << "After reset, has_value(): " << a.has_value() << std::endl;return 0;
}
类型安全考虑
std::any
提供类型安全,但与std::variant
相比有一些权衡:
#include <iostream>
#include <any>
#include <string>class MyClass {
public:MyClass(int v) : value(v) { std::cout << "MyClass constructed with " << v << std::endl; }~MyClass() { std::cout << "MyClass destroyed with " << value << std::endl; }int getValue() const { return value; }private:int value;
};int main() {// 自动资源管理{std::any a = MyClass(42);std::cout << "any created" << std::endl;// 使用any_cast访问try {const MyClass& mc = std::any_cast<MyClass&>(a);std::cout << "Value: " << mc.getValue() << std::endl;} catch (const std::bad_any_cast& e) {std::cout << "Bad cast: " << e.what() << std::endl;}} // 析构函数在这里自动调用// 类型安全检查std::any a = 42;if (a.type() == typeid(int)) {std::cout << "Contains int: " << std::any_cast<int>(a) << std::endl;}// 类型检查并转换的安全方式if (auto ptr = std::any_cast<int>(&a)) {std::cout << "Contains int: " << *ptr << std::endl;} else {std::cout << "Does not contain int" << std::endl;}return 0;
}
性能开销
std::any
提供了极大的灵活性,但有性能代价:
-
内存开销:
- 通常在堆上分配内存存储大对象
- 额外存储类型信息
- 小型值可能使用小对象优化
-
运行时开销:
- 类型检查发生在运行时
- 可能需要类型转换
- 堆分配和释放的成本
#include <iostream>
#include <any>
#include <chrono>
#include <vector>// 测量性能的辅助函数
template<typename Func>
long long measureExecutionTime(Func func) {auto start = std::chrono::high_resolution_clock::now();func();auto end = std::chrono::high_resolution_clock::now();return std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
}int main() {const int iterations = 1000000;// 测试直接使用原始类型auto rawTest = [iterations]() {int sum = 0;for (int i = 0; i < iterations; ++i) {int value = i;sum += value;}return sum;};// 测试使用std::anyauto anyTest = [iterations]() {int sum = 0;for (int i = 0; i < iterations; ++i) {std::any value = i;sum += std::any_cast<int>(value);}return sum;};std::cout << "Raw type execution time: " << measureExecutionTime(rawTest) << " microseconds" << std::endl;std::cout << "std::any execution time: " << measureExecutionTime(anyTest) << " microseconds" << std::endl;return 0;
}
实际应用场景
std::any
在以下场景特别有用:
- 插件系统和扩展点:
#include <iostream>
#include <any>
#include <map>
#include <string>
#include <functional>class PluginSystem {
private:std::map<std::string, std::any> extensionPoints;public:// 注册任意类型的扩展点template<typename T>void registerExtension(const std::string& name, const T& implementation) {extensionPoints[name] = implementation;}// 检查扩展点是否存在bool hasExtension(const std::string& name) const {return extensionPoints.find(name) != extensionPoints.end();}// 获取指定类型的扩展点template<typename T>T getExtension(const std::string& name) {if (!hasExtension(name)) {throw std::runtime_error("Extension point not found: " + name);}try {return std::any_cast<T>(extensionPoints[name]);} catch (const std::bad_any_cast& e) {throw std::runtime_error("Invalid extension type for: " + name);}}
};// 示例插件函数和类
std::string formatText(const std::string& text) {return "Formatted: " + text;
}class ImageProcessor {
public:std::string process(const std::string& imagePath) {return "Processed image: " + imagePath;}
};int main() {PluginSystem plugins;// 注册各种类型的扩展点plugins.registerExtension("text_formatter", std::function<std::string(const std::string&)>(formatText));plugins.registerExtension("image_processor", ImageProcessor{});// 使用扩展点try {auto formatter = plugins.getExtension<std::function<std::string(const std::string&)>>("text_formatter");std::cout << formatter("Hello, world!") << std::endl;auto processor = plugins.getExtension<ImageProcessor>("image_processor");std::cout << processor.process("photo.jpg") << std::endl;// 尝试获取不存在的扩展点auto unknown = plugins.getExtension<int>("unknown");} catch (const std::exception& e) {std::cout << "Error: " << e.what() << std::endl;}return 0;
}
- 脚本语言和配置系统:
#include <iostream>
#include <any>
#include <map>
#include <vector>
#include <string>// 简单的配置系统
class ConfigSystem {
private:std::map<std::string, std::any> values;public:template<typename T>void setValue(const std::string& key, const T& value) {values[key] = value;}template<typename T>T getValue(const std::string& key, const T& defaultValue) const {auto it = values.find(key);if (it != values.end()) {try {return std::any_cast<T>(it->second);} catch (const std::bad_any_cast&) {return defaultValue;}}return defaultValue;}bool hasKey(const std::string& key) const {return values.find(key) != values.end();}// 序列化配置std::string serialize() const {std::string result;for (const auto& [key, value] : values) {result += key + ": ";if (value.type() == typeid(int))result += "int=" + std::to_string(std::any_cast<int>(value));else if (value.type() == typeid(double))result += "double=" + std::to_string(std::any_cast<double>(value));else if (value.type() == typeid(std::string))result += "string=\"" + std::any_cast<std::string>(value) + "\"";else if (value.type() == typeid(bool))result += "bool=" + std::string(std::any_cast<bool>(value) ? "true" : "false");elseresult += "unknown_type";result += "\n";}return result;}
};int main() {ConfigSystem config;// 设置不同类型的配置值config.setValue("server_name", std::string("localhost"));config.setValue("port", 8080);config.setValue("timeout", 30.5);config.setValue("debug", true);config.setValue("allowed_ips", std::vector<std::string>{"127.0.0.1", "192.168.1.1"});// 读取配置值std::string server = config.getValue<std::string>("server_name", "default");int port = config.getValue<int>("port", 80);bool debug = config.getValue<bool>("debug", false);std::cout << "Server: " << server << ":" << port << std::endl;std::cout << "Debug mode: " << (debug ? "enabled" : "disabled") << std::endl;// 序列化配置std::cout << "\nConfiguration:\n" << config.serialize() << std::endl;return 0;
}
- 混合类型容器:
#include <iostream>
#include <any>
#include <vector>
#include <string>struct Event {std::string name;std::any data;
};void processEvent(const Event& event) {std::cout << "Event: " << event.name << ", Data type: " << event.data.type().name() << std::endl;if (event.data.type() == typeid(int)) {std::cout << " Integer value: " << std::any_cast<int>(event.data) << std::endl;} else if (event.data.type() == typeid(std::string)) {std::cout << " String value: " << std::any_cast<std::string>(event.data) << std::endl;} else if (event.data.type() == typeid(std::vector<int>)) {const auto& vec = std::any_cast<const std::vector<int>&>(event.data);std::cout << " Vector values: ";for (int val : vec) {std::cout << val << " ";}std::cout << std::endl;}
}int main() {std::vector<Event> events = {{"button_click", 42},{"text_input", std::string("Hello, world!")},{"data_update", std::vector<int>{1, 2, 3, 4, 5}}};for (const auto& event : events) {processEvent(event);}return 0;
}
性能与设计考量
内存布局
理解这三种类型的内存布局可以帮助我们更好地选择合适的工具:
-
std::optional:
- 通常有一个标志位和T的存储空间
- 大小约为:
sizeof(T) + sizeof(bool)
(可能有额外的对齐要求) - 没有动态内存分配(除非T本身分配动态内存)
-
std::variant<Ts…>:
- 存储类型索引和足够大的空间来容纳任何类型
- 大小约为:
max(sizeof(Ts)...) + sizeof(index)
(可能有额外的对齐要求) - 没有动态内存分配(除非存储的类型分配动态内存)
-
std::any:
- 包含类型信息和值存储
- 小值可能使用小对象优化(SBO)直接存储
- 大对象通常存储在堆上
- 需要额外的类型擦除和虚函数调用
#include <iostream>
#include <optional>
#include <variant>
#include <any>
#include <string>int main() {// 基本类型int intValue = 42;std::string strValue = "Hello";// 各种包装类型std::optional<int> optInt = 42;std::optional<std::string> optStr = "Hello";std::variant<int, std::string> varInt = 42;std::variant<int, std::string> varStr = std::string("Hello");std::any anyInt = 42;std::any anyStr = std::string("Hello");// 打印大小std::cout << "sizeof(int): " << sizeof(intValue) << std::endl;std::cout << "sizeof(string): " << sizeof(strValue) << std::endl;std::cout << "sizeof(optional<int>): " << sizeof(optInt) << std::endl;std::cout << "sizeof(optional<string>): " << sizeof(optStr) << std::endl;std::cout << "sizeof(variant<int,string>) as int: " << sizeof(varInt) << std::endl;std::cout << "sizeof(variant<int,string>) as string: " << sizeof(varStr) << std::endl;std::cout << "sizeof(any) with int: " << sizeof(anyInt) << std::endl;std::cout << "sizeof(any) with string: " << sizeof(anyStr) << std::endl;return 0;
}
异常安全
三种类型都设计为异常安全的,但有不同的处理方式:
-
std::optional:
- 如果值构造抛出异常,optional保持为空状态
value()
在optional为空时抛出std::bad_optional_access
-
std::variant:
- 如果值构造抛出异常,variant可能进入
valueless_by_exception
状态 std::get<T>()
在类型不匹配时抛出std::bad_variant_access
- 如果值构造抛出异常,variant可能进入
-
std::any:
- 如果值构造抛出异常,any保持为空状态
std::any_cast<T>()
在类型不匹配时抛出std::bad_any_cast
#include <iostream>
#include <optional>
#include <variant>
#include <any>
#include <string>
#include <stdexcept>// 可能抛出异常的类
class ThrowOnCopy {
public:ThrowOnCopy() = default;ThrowOnCopy(const ThrowOnCopy&) {throw std::runtime_error("Copy construction failed");}
};int main() {try {ThrowOnCopy original;std::optional<ThrowOnCopy> opt;// 尝试赋值可能抛出异常的对象try {opt = original; // 这会抛出异常} catch (const std::exception& e) {std::cout << "Optional caught: " << e.what() << std::endl;std::cout << "Optional has value: " << opt.has_value() << std::endl;}// variant示例std::variant<int, ThrowOnCopy> var = 10;try {var = original; // 这会抛出异常} catch (const std::exception& e) {std::cout << "Variant caught: " << e.what() << std::endl;std::cout << "Variant is valueless: " << var.valueless_by_exception() << std::endl;}// any示例std::any a = 10;try {a = original; // 这会抛出异常} catch (const std::exception& e) {std::cout << "Any caught: " << e.what() << std::endl;std::cout << "Any has value: " << a.has_value() << std::endl;}} catch (const std::exception& e) {std::cout << "Unexpected exception: " << e.what() << std::endl;}return 0;
}
何时选择哪种工具
这三种类型各有优缺点,适用于不同场景:
-
使用std::optional时机:
- 当函数可能返回"没有值"
- 表示可选参数
- 延迟初始化对象
- 需要表示"空"状态但又不想使用指针或特殊值
-
使用std::variant时机:
- 当值可能是几种已知类型之一
- 实现类型安全的状态机
- 需要强类型区分不同情况
- 处理有限集合的不同类型
-
使用std::any时机:
- 当类型在编译时未知
- 实现类型擦除
- 与动态类型系统交互
- 当类型集合太大或不可能预先定义
选择指南:
- 尽可能选择最受限制的类型(
optional
<variant
<any
) - 始终考虑性能影响和编译时类型安全性
- 考虑代码的可读性和可维护性
最佳实践与使用技巧
API设计策略
使用这些类型设计API时的建议:
-
返回值策略:
// 使用optional表示可能失败的操作 std::optional<User> findUser(int userId);// 使用variant返回成功结果或错误 std::variant<Success, Error> performOperation();// 谨慎使用any作为返回类型 std::any getRuntimeDeterminedValue(); // 调用者需要知道期望什么类型
-
参数策略:
// 可选参数 void processData(Data data, std::optional<Config> config = std::nullopt);// 可以是多种类型的参数 void handleValue(const std::variant<int, std::string, double>& value);// 仅在必要时使用any作为参数 void processAnyType(const std::any& value); // 调用者可以传入任何类型
-
Error-or-value模式:
template<typename T, typename E> class Result { private:std::variant<T, E> data;public:Result(const T& value) : data(value) {}Result(const E& error) : data(error) {}bool isSuccess() const { return std::holds_alternative<T>(data); }bool isError() const { return std::holds_alternative<E>(data); }const T& value() const { return std::get<T>(data); }const E& error() const { return std::get<E>(data); } };// 使用示例 struct Error { std::string message; };Result<int, Error> divide(int a, int b) {if (b == 0) {return Error{"Division by zero"};}return a / b; }
与其他C++17特性结合
这些类型与其他C++17特性结合使用时特别强大:
-
与结构化绑定:
// variant与结构化绑定 std::variant<int, std::string> var = getVariant();if (std::holds_alternative<std::string>(var)) {auto& [str] = std::get<std::string>(var);// 使用str }// optional与结构化绑定(需要自定义支持) template <typename T> struct optional_wrapper {T& value; };template <typename T> auto as_tuple(std::optional<T>& opt) {if (opt) {return optional_wrapper<T>{*opt};}throw std::bad_optional_access(); }namespace std {template <typename T>struct tuple_size<optional_wrapper<T>> : std::integral_constant<size_t, 1> {};template <typename T>struct tuple_element<0, optional_wrapper<T>> {using type = T&;}; }template <typename T> T& get(optional_wrapper<T>& w, size_t) {return w.value; }
-
与if初始化语句:
// optional与if初始化 if (auto opt = findUser(userId); opt) {processUser(*opt); }// variant与if初始化 if (auto result = performOperation(); std::holds_alternative<Success>(result)) {processSuccess(std::get<Success>(result)); } else {handleError(std::get<Error>(result)); }// any与if初始化 if (std::any value = getValue(); value.has_value()) {if (auto intPtr = std::any_cast<int>(&value)) {processInt(*intPtr);} }
-
与constexpr if:
template<typename T> void process(const std::variant<int, std::string, double>& var) {if constexpr (std::is_same_v<T, int>) {// 处理int类型} else if constexpr (std::is_same_v<T, std::string>) {// 处理string类型} else if constexpr (std::is_same_v<T, double>) {// 处理double类型} }
常见错误与避坑指南
使用这些类型时应避免的常见错误:
-
使用std::optional的陷阱:
// 反面示例:不检查值是否存在 std::optional<int> opt = getValue(); int value = *opt; // 如果opt为空,这将导致未定义行为// 正确做法 std::optional<int> opt = getValue(); if (opt) {int value = *opt; // 安全访问 }// 或者使用value_or int value = opt.value_or(defaultValue);
-
使用std::variant的陷阱:
// 反面示例:不检查类型就访问 std::variant<int, std::string> var = getValue(); std::string str = std::get<std::string>(var); // 如果var包含int,会抛出异常// 正确做法 std::variant<int, std::string> var = getValue(); if (std::holds_alternative<std::string>(var)) {std::string str = std::get<std::string>(var); }// 或者使用get_if if (auto str = std::get_if<std::string>(&var)) {// 使用*str }
-
使用std::any的陷阱:
// 反面示例:假设any中的类型 std::any a = getValue(); int value = std::any_cast<int>(a); // 如果类型不匹配,会抛出异常// 正确做法 std::any a = getValue(); try {int value = std::any_cast<int>(a); } catch (const std::bad_any_cast&) {// 处理类型不匹配 }// 或者使用指针版本 if (auto value = std::any_cast<int>(&a)) {// 使用*value }
-
性能陷阱:
// 反面示例:在性能关键代码中过度使用std::any void processMillionsOfItems(const std::vector<std::any>& items) {for (const auto& item : items) { // 这将导致大量的类型检查和可能的转换if (auto value = std::any_cast<int>(&item)) {// 处理int} else if (auto value = std::any_cast<double>(&item)) {// 处理double}} }// 更好的做法:如果类型在编译时已知,使用variant void processMillionsOfItems(const std::vector<std::variant<int, double>>& items) {for (const auto& item : items) {std::visit([](const auto& value) {// 处理value}, item);} }
总结
C++17引入的std::optional
、std::variant
和std::any
是现代C++工具箱中的强大成员,它们为处理不同类型的不确定性提供了类型安全和表达性强的解决方案:
-
std::optional 提供了优雅地表示"可能没有值"的方式,是处理可选数据、函数可能失败情况的理想选择。
-
std::variant 实现了类型安全的联合体,适用于需要在固定的几种类型之间选择一种的场景,如状态机或错误处理。
-
std::any 提供了完全的类型擦除功能,允许存储任何类型的值,适用于类型集合在编译时未知的情况。
这些类型解决了C++程序员长期面临的问题,例如如何安全地表示可选值、如何实现类型安全的联合体以及如何实现类型擦除。它们使代码更加安全、可读,并减少了常见错误。
在实际应用中,应根据具体需求选择最合适的工具。一般来说,应优先考虑更受限制的类型(optional
> variant
> any
),以获得更好的类型安全性和性能。与其他C++17特性(如结构化绑定和if初始化语句)结合使用时,这些类型可以进一步提高代码的表达力和简洁性。
在下一篇文章中,我们将探讨C++17的另一组重要特性:string_view
和新的文件系统库,这些特性如何提高字符串处理效率和简化文件操作。
这是我C++学习之旅系列的第四十七篇技术文章。查看完整系列目录了解更多内容。
相关文章:
C++学习:六个月从基础到就业——C++17:std::optional/variant/any
C学习:六个月从基础到就业——C17:std::optional/variant/any 本文是我C学习之旅系列的第四十七篇技术文章,也是第三阶段"现代C特性"的第九篇,主要介绍C17引入的三个重要工具类型:std::optional、std::varia…...
Go语言中函数 vs 方法
函数(Function):不属于任何类型,是全局可调用的。 方法(Method):绑定在某个类型上的函数,调用时依赖于这个类型的值或指针。 一、函数(Function) func 函数…...
代码随想录算法训练营第六十五天| 图论10—卡码网94. 城市间货物运输 I,95. 城市间货物运输 II
被学校课程轰炸了一周,回过头发现训练营已经要结束了,抓紧时间补完。不过算法这边也很难,感觉每天都是勉强理解在干什么的状态。 94. 城市间货物运输 I 94. 城市间货物运输 I SPFA算法,也是Bellman_ford 队列优化算法 优化原理…...
TDengine 在新能源领域的价值
能源数据的定义 能源数据是指记录和描述能源产业各个方面的信息,包括能源生产、供应、消费、储备、价格、排放以及相关政策和技术的数据。这些数据可以通过各种途径收集和整理,如能源企业的统计报表、政府部门的调查和监测、国际组织的发布数据等。 能…...
浅谈Frida 检测与绕过
目录 ptrace 占位与进程名检测端口检测与 D-Bus 协议通信扫描 /proc 目录(maps、task、fd)定位 so 中的 SVC syscall内存动态释放代码 1. ptrace 占位与进程名检测 检测方式 遍历运行进程列表,检查是否存在 frida-server 或相关进程名&…...
WaterStamp —— 一个实用的网页水印生成器开发记
我正在参加CodeBuddy「首席试玩官」内容创作大赛,本文所使用的 CodeBuddy 免费下载链接:腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴 最近,我和 CodeBuddy 一起完成了一个名为 WaterStamp 的网页水印生成器项目。这个小工具主要用于给…...
【MySQL】存储过程,存储函数,触发器
目录 准备工作 一. 存储过程 1.1.什么是存储过程 1.2.创建存储过程 1.3.创建只显示大于等于指定值的记录的存储过程 1.4.显示,删除存储过程 二. 存储函数 2.1.什么是存储函数 2.2.使用存储函数 2.2.1.使用存储函数之前 2.2.2.使用存储函数计算标准体重 …...
python打卡第29天
知识点回顾 类的装饰器装饰器思想的进一步理解:外部修改、动态类方法的定义:内部定义和外部定义 作业:复习类和函数的知识点,写下自己过去29天的学习心得,如对函数和类的理解,对python这门工具的理解等&…...
vim - v
在 Vim 中,使用 可视模式(Visual Mode) 可以选中文本并进行复制、剪切、粘贴等操作。以下是详细的使用方法: 1. 进入可视模式 命令功能v字符可视模式(按字符选择)V(大写)行可视模式…...
Linux 线程(上)
前言:大家早上中午晚上好!!今天来学习一下linux系统下所谓的线程吧!!! 一、重新理解进程,什么是进程? 1.1 图解 其中黑色虚线部分一整块就是进程,注意:一整…...
# 终端执行 java -jar example.jar 时(example.jar为项目jar包)报错:“没有主清单属性” 的解决方法
终端执行 java -jar example.jar 时(example.jar为项目jar包)报错:“没有主清单属性” 的解决方法 在Java中,一个JAR文件必须包含一个主清单属性(Main-Class属性)才能在命令行中直接运行。如果你在尝试运行…...
4:OpenCV—保存图像
将图像和视频保存到文件 在许多现实世界的计算机视觉应用中,需要保留图像和视频以供将来参考。最常见的持久化方法是将图像或视频保存到文件中。因此,本教程准备解释如何使用 OpenCV C将图像和视频保存到文件中。 将图像保存到文件 可以学习如何保存从…...
[C++面试] const相关面试题
1、非 const 的引用必须指向一个已存在的变量 int main() {int &a 20; // 错误const int &b 30; } 字面量 20 是临时值(右值),没有明确的内存地址。非常量引用(左值引用)不能直接绑定到右值(如…...
#Redis黑马点评#(六)Redis当中的消息队列
目录 Redis当中的消息队列 一 基于List 二 基于PubSub 三 基于Stream 单消费模式 消费者组 Redis当中的消息队列 消息队列,字面意思就是存放消息的队列。最简单的消息队列模型包括3个角色: 消息队列:存储和管理消息,也称为…...
Git基础原理和使用
Git 初识 一、版本管理痛点 在日常工作和学习中,我们经常遇到以下问题: - 通过不断复制文件来保存历史版本(如报告-v1、报告-最终版等) - 版本数量增多后无法清晰记住每个版本的修改内容 - 项目代码管理存在同样问题 二、版本控…...
Java程序员学AI(一)
一、前言 最近刷技术圈,满眼都是 GPT、DeepSeek、QWen 这些 AI 名词。看着同行们在群里聊 AI 写代码、做数据分析,我这个摸了 Java 老程序员突然慌了 —— 再不出手,怕是真要被时代落下了! 作为一个 Java 死忠粉,学 …...
《Python星球日记》 第91天:端到端 LLM 应用(综合项目:医疗文档助手)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 一、项目概述与需求分析1. 项目背景2. 项目目标3. 技术栈概览二、数据准备与处理1. 文档收集策略2. 文本预处理流程3. 向量化与知识库构建三、模…...
目前主流的AI测试工具推荐
以下是目前备受关注的AI测试工具及平台,涵盖功能测试、视觉测试、性能测试及国产化解决方案等多个领域,结合其核心特性与适用场景进行综合推荐: 一、主流AI测试工具推荐 Testim 核心功能:基于AI的动态元素定位技术,…...
vscode优化使用体验篇(快捷键)
本文章持续更新中 最新更新时间为2025-5-18 1、方法查看方法 1.1当前标签跳到新标签页查看方法实现 按住ctrl 鼠标左键点击方法。 1.2使用分屏查看方法实现(左右分屏) 按住ctrl alt 鼠标左键点击方法。...
uniprot中PTM数据的下载
首先是PTM的介绍: 参考:https://en.wikipedia.org/wiki/Post-translational_modification 蛋白质的翻译后修饰(PTM)通过改变氨基酸残基的化学结构,显著影响其带电性质,从而调控蛋白质的功能、定位和相互作…...
【QGIS二次开发】地图编辑-04
系列目录: 【QGIS二次开发】地图显示与交互-01_qgis二次开发加载地图案例-CSDN博客 【QGIS二次开发】地图显示与交互-02_setlayerlabeling-CSDN博客 【QGIS二次开发】地图符号与色表-03-CSDN博客 4 地图编辑 4.1 添加点要素 功能演示: 运行程序后…...
Qt 信号和槽-核心知识点小结(11)
目录 小结表格索引 disconnect函数 lambda表达式 啥是耦合,啥是内聚 简介:这是Qt信号和槽的最后一篇文章,最主要的是总结该信号和槽的核心知识点。以及该核心知识点的文章索引(表格太长了,手机可能看不完整&#…...
React响应事件中onClick={handleClick} 的结尾有没有小括号的区别
你可以通过在组件中声明 事件处理 函数来响应事件: function MyButton() {function handleClick() {alert(You clicked me!);}return (<button onClick{handleClick}>点我</button>);} 注意,onClick{handleClick} 的结尾没有小括号&#x…...
React-Query使用react-testing-library进行测试
1.测试react-query首先我们必须得拥有queryClient,所以我们初始化queryClient,因为默认是重试三次,这意味着如果想测试错误的查询,测试可能会超时。所以可以在初始化时关闭 const createWrapper () > {const queryClient new…...
软件设计师CISC与RISC考点分析——求三连
一、考点分值占比与趋势分析(CISC与RISC) 综合知识分值统计表 年份考题数量分值分值占比考察重点2018111.33%指令特征对比2019111.33%控制器实现方式2020222.67%寄存器数量/流水线技术2021111.33%寻址方式对比2022222.67%指令复杂度/译码方式2023111.3…...
GO语言(一期)常用关键字总结
GO语言(主题一)常用关键字总结 我们这里列出一些go语言关键字,方便各位友友们检查一下自己的学习效果,也方便友友们学习查询。 break default func interface select case defer go map …...
Ubuntu搭建NFS服务器的方法
0 工具 Ubuntu 18.041 Ubuntu搭建NFS服务器的方法 在Ubuntu下搭建NFS(网络文件系统)服务器可以让我们像访问本地文件一样访问Ubuntu上的文件,例如可以把开发板的根文件系统放到NFS服务器目录下方便调试。 1.1 安装nfs-kernel-server&#…...
京东商品详情API接口开发指南(含Java/Python实现)
接口概述 京东开放平台提供了商品详情查询接口,开发者可以通过SKUID获取商品的详细信息,包括标题、价格、图片、促销信息等。该接口需要申请API权限和认证密钥。 点击获取key和secret 接口特点 支持批量查询(最多20个SKU)返回J…...
二叉树构造:从前序、中序与后序遍历序列入手
目录 引言 从前序与中序遍历序列构造二叉树(题目 105) 解题思路 举例说明 从中序与后序遍历序列构造二叉树(题目 106) 解题思路 举例说明 总结 引言 二叉树的遍历与构造是算法领域中的经典问题。LeetCode 上的“从前序与中…...
GEE谷歌地球引擎批量下载逐日ERA5气象数据的方法
本文介绍在谷歌地球引擎(Google Earth Engine,GEE)中,批量下载逐日的ERA5土壤湿度数据(或者是其他气象数据、遥感影像数据等)的方法。 首先,明确一下本文的需求。我们希望在GEE中,下…...
C#接口(Interface)全方位讲解:定义、特性、应用与实践
引言 在面向对象编程(OOP)中,接口(Interface)是一种重要的结构,它定义了某一类对象或类应遵循的行为规范。接口强调“做什么(What)”,而非“怎么做(How&…...
索引与数据结构、并行算法
3. 索引与数据结构 索引类比目录:类似于书籍目录,帮助我们快速定位信息。索引的核心目的:提升数据查找效率,优化增删改查性能。实际应用广泛:MySQL、Redis、搜索引擎、分布式系统、中间件等。 3.1. 索引设计中的需求…...
GC全场景分析
GC全场景分析 文章目录 GC全场景分析标记-清除法**标记 - 清除法核心流程与 STW 机制****标记 - 清除法四步流程****1. STW 启动(暂停用户线程)****2. 标记可达对象(从根集合出发)****3. 清除未标记对象(回收堆内存&am…...
OSI七层模型和TCP/IP的五层(四层模型)
分层 1.什么是分层 我理解是对同一相同或者相似的事务或者操作功能进行分类,比如我们去餐厅吃饭,就可以分为好多层,客户层,服务员层,前台层,后厨层,每一层都专注自己的事情,客户层…...
MouseDown,MouseUp,LostMouseCapture的先后顺序
本文目标是实现如下功能: 按下一个按钮后置位某变量;鼠标松开后复位某个变量? 看似简单,但是一般来说会存在如下两种现象: 鼠标移出按钮:默认会丢失鼠标事件跟踪,即MouseLeftButtonUp事件并不会被触发。 焦点切换:Tab 键切换焦点会干扰按钮的事件捕获 本文通过几个…...
第8章 常用实用类
8.1 String类 在java.lang包(默认引入)中,可直接使用。 定义为final类,不能扩展String类,不可以继承,不可以有子类。 8.1.1 构造String对象 常量对象: 英文双引号括起来 String常量放入常…...
视差场(disparity field)
视差场(disparity field)是立体视觉中的一个重要概念,用于描述两幅立体图像之间像素的对应关系。以下是对视差场的详细解释: 1. 视差(Disparity)的定义 视差是指同一场景点在两幅立体图像中的像素位置差异…...
AI:OpenAI论坛分享—《AI重塑未来:技术、经济与战略》
AI:OpenAI论坛分享—《AI重塑未来:技术、经济与战略》 导读:2025年4月24日,OpenAI论坛全面探讨了 AI 的发展趋势、技术范式、地缘政治影响以及对经济和社会的广泛影响。强调了 AI 的通用性、可扩展性和高级推理能力,以…...
【已经解决诸多问题】Mamba安装
mamba被称为新一代的计算架构,因此在CV和时序领域存在诸多的方案开始采用这一新架构,但是这个架构的安装过程中存在诸多问题!!!!为了更好帮助大家理解我们给出一个统一的安装流程!!&…...
计算机的基本组成与性能
1. 冯诺依曼体系结构:计算机组成的金字塔 1.1. 计算机的基本硬件组成 1.CPU - 中央处理器(Central Processing Unit)。 2.内存(Memory)。 3.主板(Motherboard)。主板的芯片组(Ch…...
“绿色邮政,智能九识”——呼和浩特邮政无人快递车发车,驶向智慧物流新时代!
5月12日,“绿色邮政,智能九识”呼和浩特邮政无人驾驶快递车发车。 此次投运的邮政无人驾驶快递车实力惊人:单车运量超1000件,时速达40公里,通过智能路径规划实现24小时作业,与传统运输相比,运转…...
AGI大模型(24):通过LangChain的接口来调用OpenAI对话
1 创建对话 使用langchain库中的ChatOpenAI类来创建一个对话模型。 from dotenv import load_dotenvload_dotenv()import os from langchain_openai import ChatOpenAIllm = ChatOpenAI(api_key=os.getenv("DEEPSEEK_API_KEY"),base_url="https://api.deepsee…...
大模型中的Token机制深度解析
目录 大模型中的Token机制深度解析 一、Token的本质与核心作用 二、主流分词算法对比 三、GPT-3分词机制详解 四、分词策略对模型性能的影响 五、工程实践建议 六、未来演进方向 一、Token的本质与核心作用 Token是大模型处理文本的最小语义单元,类似于人类语…...
【MySQL】库与表的操作
一、库的操作 1. 查看数据库 语法:show databases;这里的database是要加s的 查看当前自己所处的数据库:select database(); 例如下图,我当前所处的数据库就是在class1数据库 2. 创建数据库 语法:create database [if not e…...
创建指定版本的vite项目
1、获取vite的版本号 npm view create-vite versions 注:4.4.1版本即对应着node16版本的项目 2、创建制定版本的vite项目 npm init vite<version>...
java中的Servlet3.x详解
Servlet 3.x 是 Java Web 开发的重要里程碑,包含 Servlet 3.0(2009年发布)和 Servlet 3.1(2013年发布)两个主要版本。它通过多项革新优化了开发效率、性能及扩展性,成为现代 Java Web 应用的核心技术基础。…...
单目测距和双目测距 bev 3D车道线
单目视觉测距原理 单目视觉测距有两种方式。 第一种,是通过深度神经网络来预测深度,这需要大量的训练数据。训练后的单目视觉摄像头可以认识道路上最典型的参与者——人、汽车、卡车、摩托车,或是其他障碍物(雪糕桶之类…...
weibo_comment_pc_tool | 我于2025.5月用python开发的评论采集软件,根据帖子链接爬取评论的界面工具
本工具仅限学术交流使用,严格遵循相关法律法规,符合平台内容的合法及合规性,禁止用于任何商业用途! 一、背景分析 1.1 开发背景 微博(以下简称wb)是国内极具影响力的社交媒体平台,具有内容形式…...
ubuntu防火墙命令和放行ssh端口
一、关闭UFW防火墙(Ubuntu默认工具) 1. 临时关闭防火墙 sudo ufw disable sudo ufw status # 显示 Status: inactive 表示已关闭 2. 永久禁用防火墙(禁用系统服务) sudo systemctl stop ufw # 立即停止服务 sudo sy…...
PWM讲解+STM32任意频率、占空比、脉宽生成函数介绍
1.PWM讲解 脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制。 脉宽调制 最开始使用PWM时,是做智能车时使用的舵机打角,电机驱动。这都属于比较浅显,普通的应用。下面和大家简单分享一下PWM的…...