目录
std::optional
下面来介绍一下我们的std::optional这个类,std::optional
是 C++17 引入的一个标准库类型,它是一个轻量级的容器,能够表示一个可能没有值的对象。std::optional
用于表示某个值可能存在也可能不存在的情况,它允许我们避免使用特殊的值(如 nullptr
或 -1
)来表示缺失的值,提供了更安全、清晰的代码结构。
struct factor_t {
bool is_prime;
long factor;
};
factor_t factor(long n) {
factor_t r{};
for(long i = 2; i <= n / 2; ++i) {
if (n % i == 0) {
r.is_prime = false;
r.factor = i;
return r;
}
}
r.is_prime = true;
return r;
}
比如说,我们想要生成一个有特殊含义的值,为了表达它是否有效,一个经典的做法要么是返回一个非法值(类似于-1),要么就是返回一个结构体存储其是否有效(但是受困于其信息存在潜在的不同步性)
这个玩意就是解决这类问题的:
-
函数返回值 :当函数可能无法返回有效值时,
std::optional
是一个非常合适的返回类型。它避免了使用指针(例如nullptr
)或错误代码来表示失败状态。 -
存储可选值 :在数据结构中,某些字段可能是可选的,使用
optional
可以简洁明了地表示这种情况。 -
避免使用特殊标记值 :例如,用
-1
或NULL
表示一个缺失的整数值时,可能会与有效值发生冲突。std::optional
可以避免这种情况。
std::optional
可以通过两种方式创建:
-
通过值初始化:
std::optional<int> opt(10);
-
通过无参构造:
std::optional<int> opt;
(表示"空"的optional
)
#include <iostream>
#include <optional>
int main() {
std::optional<int> opt1(42); // 包含值 42
std::optional<int> opt2; // 不包含值,默认构造为空
if (opt1) {
std::cout << "opt1 has value: " << *opt1 << std::endl;
}
if (!opt2) {
std::cout << "opt2 does not have value" << std::endl;
}
return 0;
}
输出:
opt1 has value: 42
opt2 does not have value
可以通过 has_value()
或 operator bool()
来检查 optional
是否包含值。如果 optional
包含值,has_value()
返回 true
,否则返回 false
。operator bool()
返回 true
表示包含值,false
表示没有值。
#include <iostream>
#include <optional>
int main() {
std::optional<int> opt1(42);
std::optional<int> opt2;
if (opt1.has_value()) {
std::cout << "opt1 has value: " << *opt1 << std::endl;
}
if (!opt2) {
std::cout << "opt2 does not have value" << std::endl;
}
return 0;
}
访问 optional
中的值可以通过解引用(*
)或 value()
成员函数。需要注意的是,如果 optional
中没有值,直接解引用会导致未定义行为,因此应先检查值是否存在。
#include <iostream>
#include <optional>
int main() {
std::optional<int> opt1(42);
std::optional<int> opt2;
if (opt1) {
std::cout << "opt1 contains: " << *opt1 << std::endl; // 解引用
}
if (!opt2) {
std::cout << "opt2 is empty" << std::endl;
}
return 0;
}
std::optional
提供了 value_or()
函数,可以为缺失的值提供一个默认值。
#include <iostream>
#include <optional>
int main() {
std::optional<int> opt1(42);
std::optional<int> opt2;
std::cout << "opt1 value: " << opt1.value_or(0) << std::endl; // 42
std::cout << "opt2 value: " << opt2.value_or(0) << std::endl; // 0
return 0;
}
输出:
opt1 value: 42
opt2 value: 0
std::optional
常用于表示函数的返回值,尤其是在函数可能无法成功返回时。例如,查找操作可能找不到目标值,此时可以返回一个空的 optional
。
#include <iostream>
#include <optional>
#include <vector>
std::optional<int> find_index(const std::vector<int>& vec, int target) {
for (int i = 0; i < vec.size(); ++i) {
if (vec[i] == target) {
return i; // 找到目标,返回索引
}
}
return std::nullopt; // 未找到目标,返回空的 optional
}
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
auto index = find_index(vec, 3);
if (index) {
std::cout << "Found 3 at index: " << *index << std::endl;
} else {
std::cout << "3 not found!" << std::endl;
}
auto missing = find_index(vec, 10);
if (!missing) {
std::cout << "10 not found!" << std::endl;
}
return 0;
}
输出:
Found 3 at index: 2
10 not found!
std::any
刚刚我们擦除的是类型的值,现在我们关心一下,可不可以存储任意类型呢?答案是可以的,std::any
是 C++17 引入的一个标准库类型,用于存储任何类型的单一值。它是一个类型安全的容器,允许在运行时动态地存储任意类型的数据,并且能够在需要时访问这些数据。与 std::variant
和 std::optional
不同,std::any
允许存储任何类型,而无需在编译时确定类型。
std::any
是一个类模板,用于存储任何类型的对象。在存储值时,std::any
会自动进行类型擦除,即它会存储值的类型信息,并使得我们能够稍后重新获取该值。std::any
提供了一些常用的操作,包括:
-
存储值 :将一个值存储到
std::any
容器中。 -
访问值 :可以通过
std::any_cast
来访问存储的值。 -
检查类型 :
std::any
提供了has_value()
方法,可以检查是否存储了值。
std::any
主要用于以下场景:
-
存储不同类型的数据 :当我们需要存储多种不同类型的数据时,
std::any
提供了一个统一的容器,可以不关心具体类型。 -
类型擦除 :在某些情况下,我们需要存储不同类型的对象,而又不希望每种类型都有自己的容器,这时可以使用
std::any
。 -
与反射相关的操作 :
std::any
提供了一种方式,可以动态地操作不同类型的值,类似于反射。
std::any
可以通过构造函数来初始化并存储任何类型的值。可以直接存储类型,也可以通过 std::make_any
工具函数来构造 std::any
对象。
#include <iostream>
#include <any>
int main() {
std::any a1 = 42; // 存储一个 int 值
std::any a2 = std::string("hello"); // 存储一个 string 值
std::cout << "Stored an int and a string." << std::endl;
return 0;
}
std::any
提供了 has_value()
方法来检查是否存储了一个有效的值。如果 std::any
存储了一个值,has_value()
返回 true
,否则返回 false
。
#include <iostream>
#include <any>
int main() {
std::any a1; // 未初始化的 any 对象
if (!a1.has_value()) {
std::cout << "a1 does not contain a value." << std::endl;
}
a1 = 42; // 给 any 赋一个 int 值
if (a1.has_value()) {
std::cout << "a1 contains a value." << std::endl;
}
return 0;
}
访问 std::any
中存储的值需要使用 std::any_cast
。如果类型匹配,std::any_cast
会返回对应类型的引用或指针;如果类型不匹配,std::any_cast
会抛出 std::bad_any_cast
异常。
#include <iostream>
#include <any>
#include <string>
#include <exception>
int main() {
std::any a = 42; // 存储一个 int 类型的值
try {
int val = std::any_cast<int>(a); // 正确:类型匹配
std::cout << "Stored value: " << val << std::endl;
// 错误:类型不匹配
std::string str = std::any_cast<std::string>(a);
} catch (const std::bad_any_cast& e) {
std::cout << "Failed to cast: " << e.what() << std::endl;
}
return 0;
}
输出:
Stored value: 42
Failed to cast: bad any cast
std::any
允许在同一个容器中存储不同类型的值。这使得它非常适合存储不确定类型的数据。例如,在某些情况下,程序可能需要处理不同类型的数据并统一存储。
#include <iostream>
#include <any>
#include <string>
void print_value(const std::any& a) {
try {
if (a.type() == typeid(int)) {
std::cout << "Integer: " << std::any_cast<int>(a) << std::endl;
} else if (a.type() == typeid(std::string)) {
std::cout << "String: " << std::any_cast<std::string>(a) << std::endl;
} else {
std::cout << "Unknown type" << std::endl;
}
} catch (const std::bad_any_cast& e) {
std::cout << "Error: " << e.what() << std::endl;
}
}
int main() {
std::any a1 = 42; // 存储 int
std::any a2 = std::string("hello"); // 存储 string
print_value(a1); // 输出:Integer: 42
print_value(a2); // 输出:String: hello
return 0;
}
std::any
和 std::variant
都用于存储多种类型的数据,但它们的使用场景有所不同:
-
std::any
是完全类型擦除的,它允许存储任何类型的对象,无需在编译时指定类型。但是,它的访问需要使用std::any_cast
,并且在运行时进行类型检查。 -
std::variant
是一个类型安全的联合体,它必须在编译时指定所有可能的类型。访问时通过std::get
或std::visit
来安全地获取存储的值,std::variant
在访问时提供了编译时类型检查。
#include <iostream>
#include <variant>
#include <string>
using VariantType = std::variant<int, std::string>;
int main() {
VariantType v1 = 42;
VariantType v2 = std::string("hello");
std::cout << "v1 contains: " << std::get<int>(v1) << std::endl;
std::cout << "v2 contains: " << std::get<std::string>(v2) << std::endl;
return 0;
}
与 std::any
相比,std::variant
的类型是固定的,可以在编译时确保类型安全,而 std::any
更灵活,但也需要在运行时进行类型检查。
std::any
不仅可以存储基本数据类型,还可以存储自定义类型。为了访问存储的自定义类型,我们同样使用 std::any_cast
,并确保类型匹配。
#include <iostream>
#include <any>
class MyClass {
public:
void hello() { std::cout << "Hello from MyClass!" << std::endl; }
};
int main() {
MyClass obj;
std::any a = obj; // 存储自定义类型
try {
MyClass& ref = std::any_cast<MyClass&>(a); // 访问存储的 MyClass 对象
ref.hello(); // 输出:Hello from MyClass!
} catch (const std::bad_any_cast& e) {
std::cout << "Error: " << e.what() << std::endl;
}
return 0;
}
处理我们的时间
std::chrono
是 C++11 引入的一个用于处理时间和时间间隔的库,它提供了一些类和工具来处理时间点、时间段、时钟等。下面是 std::chrono
中一些常见的类及其使用方法:
时钟类用于获取当前时间点。常见的时钟有:
-
std::chrono::system_clock
:-
这是与系统时间相关的时钟。它通常用于获取当前的时间戳。
-
它的时间表示的是从某个特定的纪元(通常是 1970 年 1 月 1 日 UTC)到当前的时间点。
示例:
#include <iostream> #include <chrono> int main() { auto now = std::chrono::system_clock::now(); auto duration = now.time_since_epoch(); // 获取自纪元以来的时间 auto seconds = std::chrono::duration_cast<std::chrono::seconds>(duration); std::cout << "Seconds since epoch: " << seconds.count() << std::endl; return 0; }
-
-
std::chrono::steady_clock
:-
该时钟与系统时间无关,保证其每次获取的时间点之间的间隔是恒定的(即没有跳跃)。
-
适用于测量程序运行时间和间隔。
示例:
#include <iostream> #include <chrono> int main() { auto start = std::chrono::steady_clock::now(); // 执行一些操作 auto end = std::chrono::steady_clock::now(); auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); std::cout << "Elapsed time: " << duration.count() << " milliseconds" << std::endl; return 0; }
-
-
std::chrono::high_resolution_clock
:- 这是精度最高的时钟,通常作为
steady_clock
的实现。它的分辨率取决于系统。
- 这是精度最高的时钟,通常作为
时间点表示某一特定的时刻,通常由时钟与其起始点结合定义。时间点通过 std::chrono::time_point
表示。
-
获取时间点:
auto now = std::chrono::system_clock::now();
-
转换为其他单位:
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch());
std::chrono::duration
表示时间间隔,是对两个时间点之差的描述。它通常通过一个整数和一个单位(如秒、毫秒、微秒等)表示。
常见的时间间隔类:
-
std::chrono::duration<int>
:表示整数类型的时间间隔。 -
std::chrono::duration<float>
:表示浮动类型的时间间隔。
常见的时间单位:
-
std::chrono::seconds
:表示秒。 -
std::chrono::milliseconds
:表示毫秒。 -
std::chrono::microseconds
:表示微秒。 -
std::chrono::nanoseconds
:表示纳秒。
示例:
#include <iostream>
#include <chrono>
int main() {
std::chrono::duration<int> d1(5); // 5 秒
std::chrono::duration<double> d2(3.14); // 3.14 秒
std::cout << "Duration 1: " << d1.count() << " seconds" << std::endl;
std::cout << "Duration 2: " << d2.count() << " seconds" << std::endl;
// 转换时间单位
auto d1_in_ms = std::chrono::duration_cast<std::chrono::milliseconds>(d1);
std::cout << "Duration 1 in milliseconds: " << d1_in_ms.count() << " ms" << std::endl;
return 0;
}
std::chrono::duration_cast
用于在不同的时间单位之间进行转换。注意,转换时会丢失一些精度。
示例:
#include <iostream>
#include <chrono>
int main() {
std::chrono::milliseconds ms(1500); // 1500毫秒
std::chrono::seconds sec = std::chrono::duration_cast<std::chrono::seconds>(ms); // 转换为秒
std::cout << sec.count() << " seconds" << std::endl; // 输出1秒
return 0;
}
std::chrono
可以与定时器结合使用,提供程序执行过程中的延时和超时功能。
示例:
#include <iostream>
#include <chrono>
#include <thread>
int main() {
std::cout << "Start waiting..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2)); // 程序暂停2秒
std::cout << "Waited for 2 seconds" << std::endl;
return 0;
}
你可以定义自己的时间单位,例如 1 毫秒等价于 1000 微秒。
using namespace std::chrono;
using milliseconds = duration<int, std::milli>; // 定义1毫秒
int main() {
milliseconds ms(500); // 500毫秒
std::cout << ms.count() << " milliseconds" << std::endl; // 输出500
return 0;
}