C++20新特性
作者:billy
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处
前言
C++20 是 C++ 标准中的一个重要版本,引入了许多新特性和改进,包括模块(Modules)、协程(Coroutines)、概念(Concepts)、三向比较运算符(<=>)、范围(Ranges)、日期时间库(Date and Time)、数字分隔符(Digit Separators)等等。
有的小伙伴可能会需要参考手册的链接,这里给一个网页版的:C++ 参考手册 - 网页版
一. 语言特性
1. 模块(Modules)
C++20 的模块通过引入 “导入(import)” 和 “导出(export)” 概念,替代传统的头文件机制,减少了编译时间,避免头文件互相包含的问题,使代码组织更清晰,提高了封装性。
// example_module.cppm (在 .cppm 文件中定义模块)
export module example_module;import <string>; // 使用 import 代替 #include 来导入 <string>
export namespace Example
{// 定义一个学生类class Student {public:Student(std::string _name, int _age) : name(_name), age(_age) {}std::string getName() const { return name; }int getAge() const { return age; }private:std::string name;int age;};
}
// main.cpp
import example_module; // 导入自定义的模块 example_module
#include <iostream>
//import <iostream>; 这里用 import <iostream> 失败了,可能 vs2019 中支持还不够,还是用的原始的 #includeint main()
{Example::Student stu("billy", 18);std::cout << "Student: " << stu.getName() << ", " << stu.getAge() << std::endl;return 0;
}
在这个例子中,example_module.cppm 定义了一个名为 Example 的命名空间,并在其中导出了 Student 类。main.cpp 通过 import 语句导入了example_module,然后直接使用了 Student。这种清晰的模块边界和导入机制,使得代码更加整洁、易于管理和维护。
模块化编程是 C++ 语言发展的重要一步,它解决了长期困扰 C++ 开发者的编译时间和代码组织问题。虽然在实际应用中可能会遇到一些挑战,但通过合理的规划和实践,开发者可以充分利用这一特性,提升开发效率和代码质量。随着编译器对 C++20 标准支持的不断成熟,模块化编程将成为现代 C++ 开发不可或缺的一部分。
2. 协程(Coroutines)
协程就是一个特殊的函数,可以在执行过程中挂起(suspend)并在稍后恢复(resume)。你可以暂停执行去做其他事情,然后在适当的时候恢复到暂停的位置继续执行。协程让异步编程更接近同步编程风格,减少了异步操作的复杂度,在游戏脚本、网络通信、高并发服务器等领域有很大作用。
C++ 提供了三个方法挂起协程:co_await, co_yield 和 co_return。如果一个函数中存在这三个关键字之一,那么它就是一个协程。
- co_await:用于暂停协程的执行,等待一个可等待对象(awaitable)完成。
- co_yield:用于将一个值返回给协程的调用者,并暂停协程的执行。
- co_return:用于从协程中返回一个值,并终止协程的执行。
// co_await 和 co_return 示例
#include <iostream>
#include <future>
#include <thread>// 协程函数
std::future<int> coroutine()
{// 创建一个异步任务,休眠 2 秒后返回 42auto task = std::async(std::launch::async, []() {std::this_thread::sleep_for(std::chrono::seconds(2));return 42;});// 使用 co_await 等待异步任务完成int result = co_await std::move(task);co_return result;
}int main()
{auto fut = coroutine();// 获取协程的结果int value = fut.get();std::cout << "Result: " << value << std::endl;return 0;
}
// co_yield 示例
#include <iostream>
#include <coroutine>
#include <optional>// 自定义的协程返回类型,包含一个 promise_type
template <typename T>
struct Generator
{struct promise_type;using handle_type = std::coroutine_handle<promise_type>;struct promise_type {T value_;std::exception_ptr exception_;Generator get_return_object() {return Generator{handle_type::from_promise(*this)};}std::suspend_always initial_suspend() { return {}; }std::suspend_always final_suspend() noexcept { return {}; }void unhandled_exception() { exception_ = std::current_exception(); }template <std::convertible_to<T> From>std::suspend_always yield_value(From&& from) {value_ = std::forward<From>(from);return {};}void return_void() {}};handle_type h_;Generator(handle_type h) : h_(h) {}~Generator() { if (h_) h_.destroy(); }std::optional<T> operator()() {if (!h_ || h_.done()) return {};h_.resume();if (h_.promise().exception_)std::rethrow_exception(h_.promise().exception_);return h_.promise().value_;}
};// 生成斐波那契数列的协程
Generator<int> fibonacci()
{int a = 0, b = 1;while (true) {co_yield a;int temp = a;a = b;b = temp + b;}
}int main()
{// 调用 fibonacci 协程,并通过 Generator 的 operator() 方法获取生成的值auto gen = fibonacci();for (int i = 0; i < 10; ++i) {auto value = gen();if (value) {std::cout << *value << " ";}}std::cout << std::endl;return 0;
}
3. 概念(Concepts)
在传统的模板编程中,模板参数的类型约束通常依赖于隐式接口,即模板代码在实例化时才会检查类型是否满足要求。这可能导致在编译过程中出现复杂且难以理解的错误信息。C++20 引入的概念(Concepts)是一项强大的新特性,它为模板编程提供了更强大的类型约束机制,使得代码更加清晰、易读,并且能够在编译期更早地发现错误。概念通过显式地定义模板参数必须满足的条件,使得模板的使用更加安全和可维护。
#include <iostream>
#include <concepts>// 定义一个概念,检查类型是否为整数类型
template <typename T>
concept Integral = std::is_integral_v<T>;// 方式一:使用 Integral 概念约束模板参数
template <Integral T>
T add(T a, T b)
{return a + b;
}// 方式二:使用 requires 子句
template <typename T>
requires Integral<T>
T subtract(T a, T b)
{return a - b;
}int main()
{int x = 5, y = 3;std::cout << "Addition: " << add(x, y) << std::endl;std::cout << "Subtraction: " << subtract(x, y) << std::endl;// 以下代码会导致编译错误,因为 double 不满足 Integral 概念// double d1 = 5.5, d2 = 3.3;// std::cout << "Addition: " << add(d1, d2) << std::endl;return 0;
}
// 复杂概念约束示例
#include <iostream>
#include <concepts>// 定义一个概念,检查类型是否为整数类型
template <typename T>
concept Integral = std::is_integral_v<T>;// 定义一个概念,要求类型支持 ++ 运算符
template <typename T>
concept Incrementable = requires(T t)
{{ ++t } -> std::same_as<T&>;
};// 定义一个复杂概念,要求类型既是整数类型,又支持 ++ 运算符
template <typename T>
concept IntegralAndIncrementable = Integral<T> && Incrementable<T>;// 使用复杂概念约束模板参数
template <IntegralAndIncrementable T>
void incrementAndPrint(T& value)
{++value;std::cout << "Incremented value: " << value << std::endl;
}int main()
{int num = 10;incrementAndPrint(num);return 0;
}
4. 三向比较运算符(<=>)
也叫太空船运算符,用于比较两个对象,返回小于、等于、大于三种结果。类似于 C 的 strcmp 函数返回 -1, 0, 1。
int a = 5, b = 3;
auto result = a <=> b;
// result > 0 表示 a > b
// result == 0 表示 a == b
// result < 0 表示 a < b
可以通过下面的代码自动生成 ==、!=、<、<=、>、>= 等运算符,它简化了多重比较运算的实现过程。
auto X::operator<=>(const Y&) = default;
如果对象是结构体则会逐个比较
#include <iostream>
#include <compare>struct Point
{int x, y;// 自动生成所有比较运算符auto operator<=>(const Point&) const = default;
};int main()
{Point p1 = { 1, 2 };Point p2 = { 1, 3 };if (p1 < p2) {std::cout << "p1 is less than p2\n";}if (p1 != p2) {std::cout << "p1 is not equal to p2\n";}
}
5. 范围 for 循环的语句初始化
从 C++ 17 开始支持 if 和 switch 的语句初始化,现在 C++20 中也可以在范围循环中进行初始化。
for ( std::vector v {1, 2, 3}; auto& e : v )
{ std::cout << e << std::endl;
}
二. 标准库特性
1. Ranges 库
Ranges 库是对标准模板库(STL)的一个重要扩展,它提供了一种现代、简洁的方式来处理序列(如数组、容器等)上的操作。范围库的主要特点是允许通过组合函数和适配器链接操作,使代码更直观和易读。
#include <iostream>
#include <ranges>
#include <vector>int main()
{std::vector<int> numbers = { 1, 9, 6, 7, 5, 8, 4, 3, 2, 10, 16, 14, 13, 12, 15, 11 };// 使用 ranges 进行惰性计算auto result = numbers| std::ranges::views::filter([](int n) { return n % 2 == 0; }) // 只保留偶数| std::ranges::views::transform([](int n) { return n * 2; }) // 每个数乘以2| std::ranges::views::take(6) // 获取范围内前6个数| std::ranges::views::drop(2) // 丢弃范围内前2个数| std::ranges::views::reverse; // 反转范围内元素的顺序// 遍历输出结果for (int n : result){std::cout << n << std::endl;}
}
2. 日历和时区库
C++20 引入了强大的日历和时区库,提供了处理日期、时间、日历系统和时区的功能,使得在 C++ 中进行日期和时间的操作变得更加方便和直观。
- std::chrono::year_month_day:表示公历中的一个具体日期,由年、月、日组成。
- std::chrono::year_month_weekday:表示公历中一个月内的第几个星期几,例如 2025 年 2 月的第 2 个星期五。
- std::chrono::day、std::chrono::month、std::chrono::year:分别表示日、月、年的类型。
- std::chrono::time_zone:表示一个时区,包含时区的名称、偏移量等信息。
- std::chrono::zoned_time:表示一个带有时区信息的时间点。
// 时间操作示例
#include <iostream>
#include <chrono>int main()
{using namespace std::chrono;// 创建一个具体日期year_month_day date = 2025y / February / 7d;// 输出日期信息std::cout << "Year: " << static_cast<int>(date.year()) << std::endl;std::cout << "Month: " << static_cast<unsigned>(date.month()) << std::endl;std::cout << "Day: " << static_cast<unsigned>(date.day()) << std::endl;// 检查日期是否有效if (date.ok()) {std::cout << "The date is valid." << std::endl;} else {std::cout << "The date is invalid." << std::endl;}return 0;
}
// 时区操作示例
#include <iostream>
#include <chrono>int main()
{using namespace std::chrono;// 获取当前系统时钟的时间点auto now = system_clock::now();// 获取本地时区const time_zone* local_tz = current_zone();// 创建带有时区信息的时间点zoned_time local_time(local_tz, now);// 输出本地时间std::cout << "Local time: " << local_time << std::endl;// 获取另一个时区(例如纽约)const time_zone* ny_tz = locate_zone("America/New_York");zoned_time ny_time(ny_tz, now);// 输出纽约时间std::cout << "New York time: " << ny_time << std::endl;return 0;
}
3. std::span
在 C++20 中引入的 std::span 是一个轻量级、非拥有的视图,它可以表示连续的对象序列。它提供了一种安全且高效的方式来处理数组、std::array、std::vector 等连续存储的数据结构,而无需复制数据。
主要特点
- 轻量级:std::span 仅包含指向数据的指针和长度信息,没有内存所有权,因此创建和复制 std::span 的开销非常小。
- 通用性:可以与各种连续存储的数据结构(如 C 风格数组、std::array、std::vector 等)一起使用。
- 安全性:std::span 会跟踪所引用数据的长度,避免越界访问。
#include <iostream>
#include <span>
#include <vector>
#include <array>int main()
{// 从 std::vector 创建 std::spanstd::vector<int> vec = {1, 2, 3, 4, 5};std::span<int> vecSpan(vec);// 从 std::array 创建 std::spanstd::array<int, 5> arr = {1, 2, 3, 4, 5};std::span<int> arrSpan(arr);// 从 C 风格数组创建 std::spanint cArr[] = {1, 2, 3, 4, 5};std::span<int> cArrSpan(cArr, std::size(cArr));// 获取子视图std::span<int> subSpan = vecSpan.subspan(1, 3);// 输出子视图中的元素for (int element : subSpan) {std::cout << element << " ";}std::cout << std::endl;return 0;
}
4. std::jthread
std::jthread 是 C++20 标准库中引入的一个新线程类,std::jthread 与 std::thread 相比安全性更高,std::jthread 会自动处理线程的清理工作,避免了可能出现的资源泄漏问题。并且提供了内置的停止机制,使得线程的取消操作更加方便和安全。
#include <iostream>
#include <thread>
#include <chrono>void cancellableThread(std::stop_token st)
{while (!st.stop_requested()) {std::cout << "Thread is working..." << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));}std::cout << "Thread is stopping." << std::endl;
}int main()
{std::jthread jt(cancellableThread);// 模拟一段时间后请求线程停止std::this_thread::sleep_for(std::chrono::seconds(3));jt.request_stop();return 0;
}
5. std::format
C++20 引入了 std::format 库,提供了一种功能强大且灵活的字符串格式化方式。与传统的 printf 和 std::stringstream 方法相比,std::format 更加简洁且安全,支持类型安全的格式化,并且可以更方便地与现代 C++ 标准库集成。
#include <iostream>
#include <format>int main()
{std::string name = "billy";int age = 18;double pi = 3.1415926;std::cout << std::format("Name: {}, Age: {}", name, age) << std::endl;std::cout << std::format("Pi: {:.4f}", pi) << std::endl;std::cout << std::format("{:>10}", age) << std::endl; // 右对齐,宽度为10std::cout << std::format("{:<10}", age) << std::endl; // 左对齐,宽度为10std::cout << std::format("{:0>10}", age) << std::endl; // 用0填充,宽度为10std::cout << std::endl;std::cout << std::format("Decimal: {}", age) << std::endl;std::cout << std::format("Hexadecimal: {:#x}", age) << std::endl; // 带有前缀0xstd::cout << std::format("Octal: {:#o}", age) << std::endl; // 带有前缀0std::cout << std::format("Binary: {:#b}", age) << std::endl; // 带有前缀0breturn 0;
}
// 自定义类型的格式化
#include <iostream>
#include <format>
#include <string>struct Point {int x;int y;
};template<>
struct std::formatter<Point>
{constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); }auto format(const Point& p, std::format_context& ctx) {return std::format_to(ctx.out(), "({}, {})", p.x, p.y);}
};int main()
{Point p{3, 4};std::string result = std::format("The point is {}.", p);std::cout << result << std::endl;return 0;
}
6. std::source_location
std::source_location 是 C++20 标准库引入的一个新特性,它允许在代码中获取当前代码的源文件位置信息,包括文件名、行号、列号以及函数名等。这对于调试、日志记录和错误报告等场景非常有用。
// 日志记录
#include <iostream>
#include <source_location>void log(const std::string& message, const std::source_location& location = std::source_location::current())
{std::cout << "File: " << location.file_name() << '\n'<< "Line: " << location.line() << '\n'<< "Column: " << location.column() << '\n'<< "Function: " << location.function_name() << '\n'<< "Message: " << message << '\n';
}int main()
{log("This is a log message.");return 0;
}
// 错误处理
#include <iostream>
#include <source_location>
#include <stdexcept>void divide(int a, int b, const std::source_location& location = std::source_location::current())
{if (b == 0) {throw std::runtime_error(std::format("Division by zero at {}:{}", location.file_name(), location.line()));}std::cout << "Result: " << a / b << std::endl;
}int main()
{try {divide(10, 0);} catch (const std::exception& e) {std::cerr << "Exception: " << e.what() << std::endl;}return 0;
}
7. std::map 和 std::set 的 contains 函数
在 C++20 中,std::map 和 std::set 引入了 contains 函数,这为判断容器中是否包含某个特定元素提供了更加简洁和直观的方式。在 C++20 之前,要判断 std::set 或 std::map 中是否包含某个元素或键,通常使用 find 函数。虽然 find 函数也能实现相同的功能,但 contains 函数的语义更加清晰,代码也更加简洁。
#include <iostream>
#include <set>
#include <map>
#include <string>int main()
{std::set<int> mySet = { 1, 2, 3, 4, 5 };// 检查集合中是否包含元素 3// if (mySet.find(3) != mySet.end()) { // find 函数的判断方式if (mySet.contains(3)) {std::cout << "Set contains 3." << std::endl;}else {std::cout << "Set does not contain 3." << std::endl;}std::map<std::string, int> myMap = {{"apple", 1},{"banana", 2},{"cherry", 3}};// 检查映射中是否包含键 "banana"// if (myMap.find("banana") != myMap.end()) {if (myMap.contains("banana")) {std::cout << "Map contains key 'banana'." << std::endl;}else {std::cout << "Map does not contain key 'banana'." << std::endl;}return 0;
}
8. 栅栏(barriers)、闩锁(latches)
C++20 中引入了 std::latch(闩锁)和 std::barrier(栅栏)这两个同步原语,它们用于在多线程环境中协调线程的执行,确保线程在特定的点上进行同步。
std::latch 是一种一次性的同步原语,它允许一个或多个线程等待,直到达到预先设定的计数值。一旦计数值达到零,所有等待的线程都会被释放,并且之后不能再使用这个 std::latch 进行同步。
#include <iostream>
#include <latch>
#include <thread>
#include <vector>// 模拟一些工作
void worker(std::latch& latch)
{std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << "Worker thread finished its work." << std::endl;// 完成工作后,减少闩锁的计数,将计数值减 1latch.count_down();
}int main()
{const int numThreads = 3;// 初始化闩锁,预期计数值为 3,表示需要等待 3 个线程完成工作std::latch latch(numThreads);std::vector<std::jthread> threads;for (int i = 0; i < numThreads; ++i) {threads.emplace_back(worker, std::ref(latch));}// 主线程调用 latch.wait() 阻塞,直到计数值变为 0,即所有工作线程都完成了工作std::cout << "Main thread waiting for workers..." << std::endl;latch.wait();std::cout << "All workers finished, main thread can continue." << std::endl;return 0;
}
std::barrier 是一种可重复使用的同步原语,它允许一组线程在某个点上进行同步。当所有线程都到达栅栏时,会执行一个可选的完成函数,然后所有线程继续执行。之后这个 std::barrier 可以再次用于同步。
#include <iostream>
#include <barrier>
#include <thread>
#include <vector>// 栅栏的完成函数
void onCompletion()
{std::cout << "All threads reached the barrier, continue..." << std::endl;
}// 模拟一些工作
void worker(std::barrier<>& barrier)
{std::cout << "Worker thread starting work." << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << "Worker thread reached the barrier." << std::endl;// 到达栅栏并等待其他线程barrier.arrive_and_wait();std::cout << "Worker thread continues after the barrier." << std::endl;
}int main()
{const int numThreads = 3;// 初始化栅栏,预期线程数量为 3,并指定完成函数std::barrier barrier(numThreads, onCompletion);// 启动 3 个工作线程,每个线程在完成一部分工作后调用 barrier.arrive_and_wait() 到达栅栏并等待其他线程。// 当所有线程都到达栅栏时,会执行完成函数 onCompletion,然后所有线程继续执行后续代码。std::vector<std::jthread> threads;for (int i = 0; i < numThreads; ++i) {threads.emplace_back(worker, std::ref(barrier));}return 0;
}
9. std::future 和 std::promise
std::promise 和 std::future 通常一起使用来实现线程间的同步和数据传递。std::promise 用于存储一个值或异常,供后续的 std::future 对象获取;而 std::future 则用于异步地获取 std::promise 所存储的值或异常,通过这种方式可以方便地在不同线程之间共享数据和状态。
std::future 主要成员函数:
- get():阻塞当前线程,直到关联的 std::promise 设置了值或异常,然后返回该值或抛出异常。
- valid():检查 std::future 对象是否有效,即是否与一个 std::promise 或 std::packaged_task 关联。
- wait():阻塞当前线程,直到关联的 std::promise 设置了值或异常,但不获取该值。
- wait_for():在指定的时间内等待关联的 std::promise 设置值或异常,如果超时则返回一个状态。
- wait_until():等待直到指定的时间点,如果在该时间点前关联的 std::promise 设置了值或异常,则返回。
std::promise 主要成员函数:
- set_value():设置存储的值,一旦调用此函数,关联的 std::future 对象就可以安全地获取该值。
- set_exception():设置存储的异常,关联的 std::future 对象在获取值时会抛出该异常。
- get_future():返回一个与该 std::promise 关联的 std::future 对象,用于获取存储的值或异常。
#include <iostream>
#include <future>
#include <thread>
#include <vector>// 工作线程函数
void multipleWorkers(std::promise<int>& prom, int id)
{std::this_thread::sleep_for(std::chrono::seconds(id));prom.set_value(id * 10);
}int main()
{// 创建了一个包含多个 std::promise 和 std::future 的向量,以及一个线程向量const int numPromises = 3;std::vector<std::promise<int>> promises(numPromises);std::vector<std::future<int>> futures;std::vector<std::thread> threads;// 遍历 std::promise 向量,为每个 std::promise 获取对应的 std::future,并启动一个工作线程处理该 std::promisefor (int i = 0; i < numPromises; ++i) {futures.emplace_back(promises[i].get_future());threads.emplace_back(multipleWorkers, std::ref(promises[i]), i);}// 主线程遍历 std::future 向量,调用 get() 方法获取每个 std::future 的结果并输出for (int i = 0; i < numPromises; ++i) {int result = futures[i].get();std::cout << "Result from promise " << i << ": " << result << std::endl;}// 等待所有工作线程完成for (auto& t : threads) {t.join();}return 0;
}
10. std::is_constant_evaluated
std::is_constant_evaluated 是 C++20 引入的一个函数,用于在编译时判断当前代码是否在常量求值上下文中执行。这可以让函数根据不同的上下文执行不同的逻辑。
#include <iostream>
#include <type_traits>constexpr int compute(int x)
{if (std::is_constant_evaluated()) {// 在编译时执行的逻辑return x * x;} else {// 在运行时执行的逻辑return x + x;}
}int main()
{// 编译时计算constexpr int compileTimeResult = compute(5);std::cout << "Compile-time result: " << compileTimeResult << std::endl;// 运行时计算int num = 10;int runtimeResult = compute(num);std::cout << "Runtime result: " << runtimeResult << std::endl;return 0;
}
三. 其他改进
1. Lambda 表达式的增强
1)模板 Lambda
在 C++20 之前,Lambda 表达式的参数类型必须是具体的类型。C++20 引入了模板 Lambda,允许在 Lambda 表达式中使用模板参数,使得 Lambda 可以处理不同类型的参数。
#include <iostream>int main()
{// 模板Lambda,使用 auto 关键字auto add = [](auto a, auto b) {return a + b;};std::cout << add(1, 2) << std::endl; // 处理整数std::cout << add(1.5, 2.5) << std::endl; // 处理浮点数return 0;
}
2)约束 Lambda
C++20 引入了概念(Concepts),可以将其应用于 Lambda 表达式,对 Lambda 的参数类型进行约束。
#include <iostream>
#include <concepts>int main()
{// 约束Lambda,要求参数是整数类型auto printInt = []<std::integral T>(T value) {std::cout << value << std::endl;};printInt(10); // 可以正常调用// printInt(3.14); // 编译错误,因为 3.14 不是整数类型return 0;
}
3)Lambda 默认构造函数
在 C++20 之前,lambda 表达式没有默认构造函数,这意味着你不能默认初始化一个 lambda 对象。而从 C++20 开始,无捕获的 lambda 表达式可以默认构造,这为代码的编写和使用带来了更多的灵活性。
#include <iostream>// 定义一个函数,接受一个无捕获的 lambda 类型的参数
template<typename Func>
void callFunction(Func func) {func();
}int main()
{// 定义一个无捕获的 lambda 类型using MyLambda = void(*)();// C++20 中,无捕获的 lambda 可以默认构造MyLambda lambda{};// 为 lambda 赋值一个无捕获的 lambda 表达式lambda = []() {std::cout << "Hello, C++20 Lambda Default Constructor!" << std::endl;};// 调用 lambda 表达式lambda();// 也可以将 lambda 传递给模板函数callFunction(lambda);return 0;
}
4)使用模板形参
C++20 允许在 Lambda 表达式中显式使用模板形参,这使得 Lambda 更加灵活和强大,就像普通的模板函数一样。
#include <iostream>
#include <concepts>// 定义一个概念,要求类型为算术类型
template<typename T>
concept Arithmetic = std::is_arithmetic_v<T>;int main()
{// 带有模板形参和概念约束的 Lambdaauto arithmeticLambda = []<Arithmetic T>(T value) {std::cout << "The arithmetic value is: " << value << std::endl;};// 调用 Lambda 处理算术类型的数据arithmeticLambda(10);arithmeticLambda(3.14);// 下面这行代码会编译错误,因为字符串不是算术类型// arithmeticLambda("Not an arithmetic type");return 0;
}
2. constexpr 改进
1)constexpr 动态内存分配
C++20 允许在 constexpr 函数中进行动态内存分配和释放,这意味着在编译时可以使用 new 和 delete 运算符。不过,这些动态分配的内存必须在编译时释放,否则会导致编译错误。
#include <iostream>constexpr int* allocateAndFill(int value)
{int* ptr = new int(value);return ptr;
}constexpr void deletePtr(int* ptr)
{delete ptr;
}int main()
{// 在编译时分配内存,并在编译时访问该内存中的值constexpr int* ptr = allocateAndFill(42);constexpr int value = *ptr;// 在编译时释放内存deletePtr(ptr);std::cout << "Value: " << value << std::endl;return 0;
}
2)constexpr std::vector 和其他标准库容器
C++20 允许在 constexpr 上下文中使用一些标准库容器,如 std::vector、std::string 等。这使得在编译时可以创建和操作容器。
#include <iostream>
#include <vector>constexpr std::vector<int> createVector()
{std::vector<int> vec;vec.push_back(1);vec.push_back(2);vec.push_back(3);return vec;
}int main()
{// 在编译时创建一个 std::vector,并遍历该容器输出其中的元素constexpr auto vec = createVector();for (const auto& value : vec) {std::cout << value << " ";}std::cout << std::endl;return 0;
}
3)constexpr 虚函数调用的限制放宽
在 C++20 之前,constexpr 函数不能调用虚函数。C++20 放宽了这一限制,只要虚函数调用在编译时可以解析,就可以在 constexpr 函数中进行虚函数调用。
4)constexpr std::initializer_list
C++20 允许 std::initializer_list 在 constexpr 上下文中使用,这使得在编译时可以使用初始化列表来初始化对象。
#include <iostream>
#include <initializer_list>constexpr int sum(std::initializer_list<int> list)
{int result = 0;for (int value : list) {result += value;}return result;
}int main()
{// 使用初始化列表调用 sum 函数,并将结果存储在 constexpr 变量 result 中。constexpr int result = sum({1, 2, 3, 4});std::cout << "Sum: " << result << std::endl;return 0;
}
3. using 扩展
在 C++11 及以后版本就支持使用 using 定义模板别名,C++20 进一步增强了模板别名在泛型编程中的灵活性,允许使用非类型模板参数。
#include <iostream>
#include <vector>
#include <array>// c++11 定义一个模板别名
template<typename T>
using Vec = std::vector<T>;// c++20 定义一个模板别名,使用了非类型模板参数 std::size_t
template<typename T, std::size_t N>
using FixedArray = std::array<T, N>;int main()
{Vec<int> vec = {1, 2, 3};for (int num : vec) {std::cout << num << " ";}std::cout << std::endl;FixedArray<int, 5> arr = {1, 2, 3, 4, 5};for (int num : arr) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
4. 即时函数(Immediate Functions)
即时函数(Immediate Functions)是一种特殊类型的函数,与常量表达式密切相关,使用 consteval 关键字来声明即时函数。
主要特性
- 即时函数必须在编译时求值,如果无法在编译时计算函数的结果,编译器会报错。
- 即时函数只能在常量表达式的上下文中调用,并且调用的结果也会作为常量表达式使用。
- 即时函数可以进行递归调用,但递归调用也必须在编译时能够终止。
// constexpr 函数与 consteval 函数比较示例
#include <iostream>// constexpr 函数:可以在编译时或运行时求值,具体取决于调用的上下文。
// 如果调用 constexpr 函数的参数是常量表达式,那么函数会在编译时求值;否则,函数会在运行时求值。
constexpr int add(int a, int b)
{return a + b;
}// consteval 即时函数
consteval int multiply(int a, int b)
{return a * b;
}int main()
{// 编译时求值constexpr int compileTimeSum = add(2, 3);constexpr int compileTimeProduct = multiply(2, 3);int x = 4, y = 5;// 运行时求值int runtimeSum = add(x, y);// 错误:不能在运行时上下文调用 consteval 函数// int runtimeProduct = multiply(x, y); std::cout << "Compile-time sum: " << compileTimeSum << std::endl;std::cout << "Compile-time product: " << compileTimeProduct << std::endl;std::cout << "Runtime sum: " << runtimeSum << std::endl;return 0;
}
5. char8_t 类型
在 C++20 之前,UTF - 8 编码的字符串通常使用 char 类型来表示。然而,char 类型既可以用于表示有符号整数,也可以用于表示无符号整数,这取决于编译器的实现,这就导致了在处理 UTF - 8 字符串时可能会出现一些未定义行为或移植性问题。为了解决这些问题,C++20 引入了 char8_t 类型,它是无符号的,专门用于表示 UTF - 8 编码的字符,从而提高了代码的安全性和可移植性。
char8_t 的特点
- 无符号类型:char8_t 是无符号类型,这意味着它的取值范围是从 0 到 255,避免了有符号 char 可能带来的符号扩展问题。
- 与 UTF - 8 编码紧密相关:char8_t 类型专门用于处理 UTF - 8 编码的字符和字符串,使得代码更加清晰和安全。
- 标准库支持:C++ 标准库提供了对 char8_t 的支持,例如 std::u8string 用于表示 UTF - 8 字符串。
#include <iostream>
#include <string>int main()
{// 使用 u8 前缀定义一个 char8_t 字符char8_t ch = u8'A';// 使用 u8 前缀定义一个 char8_t 字符串const char8_t* str = u8"Hello, 世界!";// 使用 u8 前缀定义一个 std::u8string 对象std::u8string u8str = u8"你好,C++20!";// 注意:std::cout 不能直接输出 char8_t 字符串,需要将其转换为 char 类型std::cout << "Character: " << static_cast<char>(ch) << std::endl;// 输出字符串std::cout << "String: ";for (const char8_t* p = str; *p; ++p) {std::cout << static_cast<char>(*p);}std::cout << std::endl;// 输出 std::u8string 的长度std::cout << "Length of u8string: " << u8str.length() << std::endl;// 遍历 std::u8string 中的每个字符for (char8_t ch : u8str) {std::cout << static_cast<char>(ch);}std::cout << std::endl;return 0;
}
6. 智能指针优化
在 C++20 中,智能指针(如 std::unique_ptr、std::shared_ptr 和 std::weak_ptr)本身没有语法上的重大改变,但 C++20 引入的一些新特性可以帮助我们更好地使用和优化智能指针的使用。
1)使用consteval和constexpr进行编译时计算
constexpr 和 consteval 可以在编译时执行代码,对于智能指针相关的常量初始化和编译时计算非常有用。
#include <iostream>
#include <memory>// 编译时函数,返回一个 unique_ptr
consteval auto createUniquePtr()
{return std::make_unique<int>(42);
}int main()
{// 在编译时创建 unique_ptrconstexpr auto ptr = createUniquePtr();static_assert(*ptr == 42);std::cout << *ptr << std::endl;return 0;
}
2)使用 std::span 和智能指针结合
将智能指针管理的数组与 std::span 结合使用,避免不必要的复制。
#include <iostream>
#include <memory>
#include <span>void printSpan(std::span<int> sp)
{for (int num : sp) {std::cout << num << " ";}std::cout << std::endl;
}int main()
{auto arr = std::make_unique<int[]>(5);for (int i = 0; i < 5; ++i) {arr[i] = i;}// 使用 std::span 包装智能指针管理的数组std::span<int> sp(arr.get(), 5);printSpan(sp);return 0;
}
3)使用 std::jthread 和智能指针
使用智能指针管理 std::jthread 对象,确保线程资源的正确释放。
#include <iostream>
#include <memory>
#include <thread>void threadFunction()
{std::cout << "Thread is running." << std::endl;
}int main()
{auto threadPtr = std::make_unique<std::jthread>(threadFunction);// 当 threadPtr 离开作用域时,线程会自动加入return 0;
}
4)使用 std::atomic 和智能指针
C++20 对 std::atomic 进行了改进,可以使用 std::atomic<std::shared_ptr>
实现线程安全的共享指针操作。
#include <iostream>
#include <memory>
#include <atomic>
#include <thread>std::atomic<std::shared_ptr<int>> atomicPtr;void writer()
{auto newPtr = std::make_shared<int>(42);atomicPtr.store(newPtr, std::memory_order_release);
}void reader()
{std::shared_ptr<int> localPtr = atomicPtr.load(std::memory_order_acquire);if (localPtr) {std::cout << "Read value: " << *localPtr << std::endl;}
}int main()
{std::thread t1(writer);std::thread t2(reader);t1.join();t2.join();return 0;
}
5)使用概念(Concepts)来约束智能指针的使用
C++20 引入的概念可以用于约束模板参数,确保智能指针操作的类型安全。
#include <iostream>
#include <memory>
#include <concepts>// 定义一个概念,要求类型可以解引用
template <typename T>
concept Dereferenceable = requires(T t)
{*t;
};// 接受任何可解引用的类型
template <Dereferenceable Ptr>
void printValue(Ptr ptr)
{std::cout << *ptr << std::endl;
}int main() {auto ptr = std::make_unique<int>(42);printValue(ptr);return 0;
}
7. constinit 变量
constinit 是 C++20 引入的一个新的变量声明说明符,用于确保变量在程序启动时进行常量初始化。与 constexpr 不同,constinit 不要求变量本身是常量,只要求它的初始化表达式是常量表达式。
#include <iostream>// 使用 constinit 声明变量,虽然 globalValue 不是常量,但它的初始化表达式 10 * 2 是常量表达式
constinit int globalValue = 10 * 2;int main()
{// 访问 constinit 变量std::cout << "Global value: " << globalValue << std::endl;// 修改 constinit 变量的值globalValue = 30;std::cout << "Modified global value: " << globalValue << std::endl;return 0;
}
8. 哈希查找优化
1)自定义哈希函数
对于自定义类型,需要提供自定义的哈希函数和相等比较函数。在 C++20 中,可以使用 std::hash 模板来简化哈希函数的定义。
#include <iostream>
#include <unordered_map>
#include <string>// 自定义类型
struct Person
{std::string name;int age;// 重载相等比较运算符bool operator==(const Person& other) const {return name == other.name && age == other.age;}
};// 自定义哈希函数
namespace std
{template <>struct hash<Person> {std::size_t operator()(const Person& p) const {// 使用 std::hash 组合多个成员的哈希值auto nameHash = std::hash<std::string>{}(p.name);auto ageHash = std::hash<int>{}(p.age);return nameHash ^ (ageHash << 1);}};
} // namespace stdint main()
{std::unordered_map<Person, std::string> personMap = {{{ "Alice", 25 }, "Engineer"},{{ "Bob", 30 }, "Doctor"}};Person target = { "Alice", 25 };auto it = personMap.find(target);if (it != personMap.end()) {std::cout << target.name << " is a " << it->second << std::endl;} else {std::cout << "Person not found" << std::endl;}return 0;
}
2)std::erase_if
C++20 引入了 std::erase_if 函数,可用于删除满足特定条件的元素,避免了手动遍历和删除元素时的迭代器失效问题,同时也可能间接影响哈希表的性能。
#include <iostream>
#include <unordered_map>int main()
{std::unordered_map<int, std::string> myMap = {{1, "Apple"},{2, "Banana"},{3, "Cherry"}};// 删除值为 "Banana" 的元素std::erase_if(myMap, [](const auto& pair) {return pair.second == "Banana";});for (const auto& pair : myMap) {std::cout << pair.first << ": " << pair.second << std::endl;}return 0;
}
3)调整哈希表参数
std::unordered_map 和 std::unordered_set 提供了一些成员函数来调整哈希表的参数,如 rehash 和 reserve,可以在插入大量元素之前预先分配足够的空间,减少哈希冲突的概率。
#include <iostream>
#include <unordered_map>int main()
{std::unordered_map<int, std::string> myMap;// 预先分配足够的空间myMap.reserve(100);// 插入元素for (int i = 0; i < 100; ++i) {myMap[i] = "Value " + std::to_string(i);}// 查找元素auto it = myMap.find(50);if (it != myMap.end()) {std::cout << "Found: " << it->second << std::endl;} else {std::cout << "Not found" << std::endl;}return 0;
}
相关文章:
C++20新特性
作者:billy 版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处 前言 C20 是 C 标准中的一个重要版本,引入了许多新特性和改进,包括模块(Modules)、协程…...
[LUA ERROR] bad light userdata pointer
Cocos2d项目,targetSdkVersion30,在 android 13 设备运行报错: [LUA ERROR] bad light userdata pointer ,导致黑屏。 参考 cocos2dx 适配64位 arm64-v8a 30 lua 提示 bad light userdata pointer 黑屏-CSDN博客的方法 下载最新的Cocos2dx …...
Maven 安装配置(完整教程)
文章目录 一、Maven 简介二、下载 Maven三、配置 Maven3.1 配置环境变量3.2 Maven 配置3.3 IDEA 配置 四、结语 一、Maven 简介 Maven 是一个基于项目对象模型(POM)的项目管理和自动化构建工具。它主要服务于 Java 平台,但也支持其他编程语言…...
JAVA:CloseableHttpClient 进行 HTTP 请求的技术指南
1、简述 CloseableHttpClient 是 Apache HttpComponents 提供的一个强大 HTTP 客户端库。它允许 Java 程序与 HTTP/HTTPS 服务交互,可以发送 GET、POST 等各种请求类型,并处理响应。该库广泛用于 REST API 调用、文件上传和下载等场景。 2、特性 Close…...
WebRTC 客户端与ZLMediaKit通讯
1 web浏览器js方式 要使用 WebRTC 客户端与 ZLMediaKit 通讯,您需要设置一个 WebRTC 客户端并与 ZLMediaKit 进行连接。以下是一个基本的步骤和示例代码,帮助您实现这一目标。 ### 步骤 1. **安装 ZLMediaKit**:确保您已经在服务器上安装并…...
openssl使用
openssl使用 提取密钥对 数字证书pfx包含公钥和私钥,而cer证书只包含公钥。提取需输入证书保护密码 openssl pkcs12 -in xxx.pfx -nocerts -nodes -out pare.key提取私钥 openssl rsa -in pare.key -out pri.key提取公钥 openssl rsa -in pare.key -pubout -ou…...
stm32小白成长为高手的学习步骤和方法
我们假定大家已经对STM32的书籍或者文档有一定的理解。如不理解,请立即阅读STM32的文档,以获取最基本的知识点。STM32单片机自学教程 这篇博文也是一篇不错的入门教程,初学者可以看看,讲的真心不错。 英文好的同学…...
支持多种网络数据库格式的自动化转换工具——VisualXML
一、VisualXML软件介绍 对于DBC、ARXML……文件的编辑、修改等繁琐操作,WINDHILL风丘科技开发的总线设计工具——VisualXML,可轻松解决这一问题,提升工作效率。 VisualXML是一个强大且基于Excel表格生成多种网络数据库文件的转换工具&#…...
Linux学习笔记16---高精度延时实验
延时函数是很常用的 API 函数,在前面的实验中我们使用循环来实现延时函数,但是使用循环来实现的延时函数不准确,误差会很大。虽然使用到延时函数的地方精度要求都不会很严格( 要求严格的话就使用硬件定时器了 ) ,但是延时函数肯定…...
【kafka实战】05 Kafka消费者消费消息过程源码剖析
1. 概述 Kafka消费者(Consumer)是Kafka系统中负责从Kafka集群中拉取消息的客户端组件。消费者消费消息的过程涉及多个步骤,包括消费者组的协调、分区分配、消息拉取、消息处理等。本文将深入剖析Kafka消费者消费消息的源码,并结合…...
20240817 联想 笔试
文章目录 1、选择题1.11.21.31.41.51.61.71.81.91.101.111.121.131.141.151.161.171.181.191.202、编程题2.12.2岗位:Linux开发工程师 题型:20 道选择题,2 道编程题 1、选择题 1.1 有如下程序,程序运行的结果为 (D) #include <stdio.h>int main() {int k = 3...
Maven 中常用的 scope 类型及其解析
在 Maven 中,scope 属性用于指定依赖项的可见性及其在构建生命周期中的用途。不同的 scope 类型能够影响依赖项的编译和运行阶段。以下是 Maven 中常用的 scope 类型及其解析: compile(默认值): 这是默认的作用域。如果…...
【电机控制器】STC8H1K芯片——低功耗
【电机控制器】STC8H1K芯片——低功耗 文章目录 [TOC](文章目录) 前言一、芯片手册说明二、IDLE模式三、PD模式四、PD模式唤醒五、实验验证1.接线2.视频(待填) 六、参考资料总结 前言 使用工具: 1.STC仿真器烧录器 提示:以下是本…...
PHP 运算符
PHP 运算符 概述 PHP 是一种广泛使用的开源服务器端脚本语言,它具有丰富的运算符集,这些运算符是编写 PHP 程序的基础。运算符用于执行各种数学、逻辑和比较操作。本篇文章将详细介绍 PHP 中常用的运算符,包括算术运算符、比较运算符、逻辑运算符、赋值运算符等。 算术运…...
【自然语言处理】利用Memory Layer替换Transformer中的FFN
论文地址:https://arxiv.org/pdf/2412.09764 相关博客 【自然语言处理】利用Memory Layer替换Transformer中的FFN 【自然语言处理】【大模型】BitNet:用1-bit Transformer训练LLM 【自然语言处理】BitNet b1.58:1bit LLM时代 【自然语言处理】…...
【设计模式】【行为型模式】策略模式(Strategy)
👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD 🔥 2025本人正在沉淀中… 博客更新速度 📫 欢迎V: flzjcsg2,我们共同讨论Java深渊的奥秘 …...
报错:no matching host key type found
no matching host key type found. Their offer: ssh-rsa,ssh-dss scp: Connection closed 可能发生在scp或其他方式连接服务器时 报错原因: 服务器只支持较老的加密算法(如 ssh-rsa 或 ssh-dss),而本地客户端由于安全原因默认禁…...
LVGL4种输入设备详解(触摸、键盘、实体按键、编码器)
lvgl有触摸、键盘、实体按键、编码器四种输入设备 先来分析一下这四种输入设备有什么区别 (1)LV_INDEV_TYPE_POINTER 主要用于触摸屏 用到哪个输入设备保留哪个其他的也是,保留触摸屏输入的任务注册,其它几种种输入任务的注册&…...
VirtualBox中Ubuntu 22.04网卡配置以及解决过程中遇到的问题
1.添加网卡(仅主机) 2.启动虚拟机,查看新添加网卡信息 #查看网卡 ip addr # 查看网络信息,发现新网卡(enp0s8)未分配 ifconfig -a3.使用netplan进行网络配置 3.1 配置 DHCP获取IP # 进入netplan 文件夹 cd /etc/netplan #查看文件夹下yaml ls -al # 编…...
【Vue】在Vue3中使用Echarts的示例 两种方法
文章目录 方法一template渲染部分js部分方法一实现效果 方法二template部分js or ts部分方法二实现效果 贴个地址~ Apache ECharts官网地址 Apache ECharts示例地址 官网有的时候示例显示不出来,属于正常现象,多进几次就行 开始使用前,记得先…...
【在线优化】【有源程序】基于遗传算法(GA)和粒子群优化(PSO)算法的MPPT控制策略
目录 一、背景 二、源程序及结果 2.1 simulink仿真程序 2.2 GA模块源程序 2.3 PSO模块源程序 三、程序运行结果 3.1 基于GA优化的MPPT 3.2 基于PSO优化的MPPT 一、背景 MPPT策略能够显著提高光伏、风电等发电效率,节省大量成本。该策略的经典算法是…...
Excel大数据量导入导出
github源码 地址(更详细) : https://github.com/alibaba/easyexcel 文档:读Excel(文档已经迁移) B 站视频 : https://www.bilibili.com/video/BV1Ff4y1U7Qc 一、JAVA解析EXCEL工具EasyExcel Java解析、生成Excel比较…...
Blocked aria-hidden on an element because its descendant retained focus.
在使用el-popover和el-radio-group实现弹窗选择数据后调用el-popover的doClose()方法时一直报错! 经过分析发现el-popover及el-radio__original有aria-hidden属性,具体aria-hidden属性应用自行搜索了解。既然是这个玩意引起的,则在显示时将a…...
MIT开源7B推理模型Satori:用行动思维链进行强化学习,增强自回归搜索
自OpenAI的o1发布以来,研究社区为提升开源LLM的高级推理能力做出了诸多努力,包括使用强大的教师模型进行蒸馏、蒙特卡洛树搜索(MCTS)以及基于奖励模型的引导搜索等方法。 本研究旨在探索一个新的研究方向:使LLM具备自回…...
神经网络|(九)概率论基础知识-泊松分布及python仿真
【1】引言 在前序学习进程中,我们已经知晓二项分布是多重伯努利分布,二伯努利分布对应的是可以无限重复、结果只有两种可能的随机试验。 相关文章链接为: 神经网络|(八)概率论基础知识-二项分布及python仿真-CSDN博客 上述文章还调用nump…...
机器学习 —— 深入剖析线性回归模型
一、线性回归模型简介 线性回归是机器学习中最为基础的模型之一,主要用于解决回归问题,即预测一个连续的数值。其核心思想是构建线性方程,描述自变量(特征)和因变量(目标值)之间的关系。简单来…...
vs封装dll 给C#使用
一,vs创建控制台应用 创建控制台应用得好处时,我们可以自己测试接口,如果接口没有问题,改成dll重新编译一遍就可以。 二, 创建一个c 类,将所需提供得功能 封装到类中。 这样可以将 所有功能,进…...
V8 引擎:深入理解 JavaScript 的执行环境
🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…...
JDK 9新特性学习大纲
第1部分:引言与背景 第1章:JDK 9的诞生与目标 1.1 JDK 9的核心目标与设计哲学 1.2 JDK 9的重要更新概览 1.3 兼容性与升级策略 第2部分:模块化系统(Project Jigsaw) 第2章:模块化基础 2.1 模块化的背景…...
DeepSeek从入门到精通:全面掌握AI大模型的核心能力
文章目录 一、DeepSeek是什么?性能对齐OpenAI-o1正式版 二、Deepseek可以做什么?能力图谱文本生成自然语言理解与分析编程与代码相关常规绘图 三、如何使用DeepSeek?四、DeepSeek从入门到精通推理模型推理大模型非推理大模型 快思慢想&#x…...
MySQL数据库(七)SQL 优化
目录 一 插入数据 1 批量插入 2 手动提交事务 3 主键顺序插入 4* 使用load插入指令数据 二 主键优化 1 数据组织方式 2 页分裂 编辑3 页合并 4* 主键设计原则 三 order by 优化 四 group by 优化 五 limit 优化 六 count优化 七 update优化 一…...
Oracle数据连接 Dblink
拓展: oracle远程登陆数据库 1.oracle客户端或者服务端 2.修改你的电脑如下路径文件(服务器IP,服务器的数据库名,服务器的数据库端口号) c:\oracle\product\10.2.0\db_1\NETWORK\ADMIN\tnsnames.ora orcl_109 (DESCRIPTION …...
基于 Nginx 的 CDN 基础实现
概览 本文是对基于Nginx的CDN网络的学习笔记,阅读的代码为:https://github.com/leandromoreira/cdn-up-and-running 其中,先确定CDN中的一些基础概念: Balancer:负载均衡,即请求数据的流量最开始打到Bal…...
网络编程基础1
七层协议模型和四层协议模型 七层协议模型:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层 四层协议模型:链路层、网络层、传输层、应用层 TCP通信流程 服务器端 (1)创建socket(socket) (2)绑定自己的IP(bind) (3)监听客户端连接(liste…...
Web3 跨链技术:构建互联互通的虚拟世界
随着区块链技术的蓬勃发展,我们正站在 Web3 时代的门槛上。Web3 不仅仅是技术的革新,它更是一场关于数据所有权和互联网自由的革命。然而,区块链技术的一个核心挑战是如何打破不同链之间的壁垒,实现信息和资源的自由流动。跨链技术…...
【专题】2025年我国机器人产业发展形势展望:人形机器人量产及商业化关键挑战报告汇总PDF洞察(附原数据表)
原文链接:https://tecdat.cn/?p39668 机器人已广泛融入我们生活的方方面面。在工业领域,它们宛如不知疲倦的工匠,精准地完成打磨、焊接等精细工作,极大提升了生产效率和产品质量;在日常生活里,它们是贴心…...
FPGA VGA timing
概念 VGA(Video Graphics Array)时序是控制VGA接口显示图像的关键参数,它主要包括行时序和场时序两部分。以下是对VGA时序的详细解释: 一、VGA接口简介 VGA接口是IBM公司在1987年推出的一种使用模拟信号的视频传输标准,具有成本低、结构简单、应用灵活等优点,至今仍被广…...
[7] 游戏机项目说明
[7] 游戏机项目说明 在这节课中,我们将学习如何基于FreeRTOS开发一个简单的游戏项目。我们会使用一个开源项目nwatch,它是一个基于STM32的开源手表,包含了三个游戏。我们的目标是将这个游戏移植到我们的开发板上,并逐步使用FreeR…...
UE学习日志#25、26 C++笔记#11 智能指针
注:本篇内容主要为《C20高级编程》的学习笔记 当智能指针离开作用域或被重置时,会自动释放所占用的资源。智能指针可用于管理在函数作用域内(或作为类的数据成员)动态分配的资源。也可以通过函数实参来传递动态分配的资源的所有权…...
Spring AI -使用Spring快速开发ChatGPT应用
前言 Spring在Java生态中一直占据大半江山。最近我发现Spring社区推出了一个Spring AI项目,目前该项目还属于Spring实验性项目,但是我们可以通过该项目,可以非常快速的开发出GPT对话应用。 本篇文章将会对SpringAI进行简单的介绍和使用&#…...
windows通过网络向Ubuntu发送文件/目录
由于最近要使用树莓派进行一些代码练习,但是好多东西都在windows里或虚拟机上,就想将文件传输到树莓派上,但试了发现u盘不能简单传送,就在网络上找到了通过windows 的scp命令传送 前提是树莓派先开启ssh服务,且Window…...
大语言模型需要的可观测性数据的关联方式
可观测性数据的关联方式及其优缺点 随着现代分布式架构和微服务的普及,可观测性(Observability)已经成为确保系统健康、排查故障、优化性能的重要组成部分。有效的可观测性数据关联方式不仅能够帮助我们实时监控系统的运行状态,还…...
python连点器
要实现一个用于抖音点赞的鼠标连点工具,可以通过编程或现有软件实现。以下是两种常见方法(但请注意:频繁自动化操作可能违反平台规则,需谨慎使用): 方法 1:使用现成工具(如 AutoClic…...
Nginx部署Umi React前端项目标准配置
文章目录 概要前端Umi项目配置文件请求后端Api打包 后端项目Nginx配置配置文件 错误信息 概要 使用UmiJs开发的前端项目打包部署在Nginx,主要是Umi中项目的配置和Nginx的配置 前端Umi项目 基于"umijs/max": "^4.3.24", "react": &…...
Ubuntu20.4软件应用打不开
安装 snap-store: 确保 Snap 已安装: Snap 是一个包管理系统,需要先确保 snapd 已经安装。如果系统中没有安装,可以通过以下命令来安装 Snap: sudo apt update sudo apt install snapd安装 snap-store: 使…...
如何在Vscode中接入Deepseek
一、获取Deepseek APIKEY 首先,登录Deepseek官网的开放平台:DeepSeek 选择API开放平台,然后登录Deepseek后台。 点击左侧菜单栏“API keys”,并创建API key。 需要注意的是,生成API key复制保存到本地,丢失…...
apisix的real-ip插件使用说明
k8s集群入口一般都需要过负载均衡,然后再到apisix。 这时候如果后台业务需要获取客户端ip,可能拿到的是lb或者网关的内网ip。 这里一般要获取真实ip需要做几个处理。 1. 负载均衡上,一般支持配置获取真实ip参数,需要配置上。然…...
基于 Ollama+Docker+OpenWebUI 的本地化部署deepseek流程
搭建deepseek 安装Ollama Ollama官方下载地址 下载完成后双击打开Ollama进行安装,点击install 安装完成后系统会弹出下图提示代表安装成功并且已启动 验证安装 ollama -v安装完成后,cmd 打开命令行窗口,输入 “ollama -v” 测试,显示 olla…...
打家劫舍3
今天和打家讲一下打家劫舍3 题目: 题目链接:337. 打家劫舍 III - 力扣(LeetCode) 小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为root。 除了 root 之外,每栋房子有且只有一个“父“…...
第三个Qt开发实例:利用之前已经开发好的LED驱动在Qt生成的界面中控制LED2的亮和灭
前言 上一篇博文 https://blog.csdn.net/wenhao_ir/article/details/145459006 中,我们是直接利用GPIO子系统控制了LED2的亮和灭,这篇博文中我们利用之前写好的LED驱动程序在Qt的生成的界面中控制LED2的亮和灭。 之前已经在下面两篇博文中实现了LED驱动…...