C++ 元编程

news/2024/9/19 12:27:31/ 标签: c++, 开发语言

目录

  • C++ 元编程
    • 1. 术语
    • 2. 元函数
      • 1. 数值元函数
        • 示例:阶乘计算
      • 2. 类型元函数
        • 示例:类型选择
    • 3. 混合编程
      • 1. 常规的计算点积范例
      • 2. 混合元编程计算点积
    • 4. typelist实现
      • 设计和基本操作接口(算法)
      • 完整代码
    • 5. tuple 实现
      • 基础知识
        • 1. 左值、右值、左值引用、右值引用
        • 2. std::move 究竟做了什么
        • 3. std::forward 究竟做了什么
        • 4. 万能引用(转发引用)
        • 5. 完美转发
      • tuple 实现示例

C++ 元编程

元编程的主要目的在于将各种计算从运行期提前至编译期进行,以实现程序运行时的性能提升。也正因如此,元编程是一种增加程序的编译时间,从而提升程序运行效率的编程技术。

在元编程中,涉及许多与循环相关的代码。传统编程中,循环通常采用 forwhile 等语句实现,这些语句一般针对的是运行期的条件变量;而在元编程中,更多的操作其实是针对类型或常量的。这种循环的实现往往会采用递归的手段,使得编译器能够在编译期间完成某些计算。

1. 术语

元编程的英文名称是 Meta Programming,有时也称为 模板元编程(Template Metaprogramming)。可以理解为一种编程手法,用于实现一些比较特殊的功能。元编程与“递归”这一概念紧密相连,代表着在元编程中,大多数情况下都会使用递归编程技术。

模板编程主要应用在泛型编程元编程

泛型编程(Generic Programming)强调的是“通用”的概念,旨在通过抽象和参数化来实现代码的复用与灵活性。在泛型编程中,程序员可以编写与类型无关的算法和数据结构,使得同一段代码能够适用于多种类型。模板的设计初衷正是为了满足这一需求。

元编程(Meta Programming)则是一种更高级的编程技巧,旨在通过编译期间的计算和推导来实现某些功能。元编程允许程序员在编译期进行复杂的逻辑处理,通常这些逻辑在运行时才能完成。

2. 元函数

传统的函数都是在程序运行期间被调用和执行的函数,而元函数是能在程序编译期间被调用和执行的函数(编译期间就能得到结果)。引入元函数概念的目的是支持元编程,而元编程的核心也正是元函数。

1. 数值元函数

数值元函数是在编译期间对数值进行计算的元函数。这类元函数可以接收整型常量作为模板参数,并根据这些常量进行编译时计算。数值元函数通常用于实现一些数学运算,如阶乘、斐波那契数列等。

示例:阶乘计算
template<int N>
struct Factorial {static const int value = N * Factorial<N - 1>::value;
};template<>
struct Factorial<0> {static const int value = 1;
};// 使用
constexpr int fact5 = Factorial<5>::value; // fact5 的值为 120
std::cout << fact5 << std::endl; // 输出 120

2. 类型元函数

类型元函数则是针对类型进行操作的元函数。它们允许程序员在编译期间对类型进行推导、选择和变换。这类元函数非常适合于类型特征提取和类型转换等场景。

示例:类型选择
template<bool Condition, typename TrueType, typename FalseType>
struct Conditional;template<typename TrueType, typename FalseType>
struct Conditional<true, TrueType, FalseType> {using type = TrueType;
};template<typename TrueType, typename FalseType>
struct Conditional<false, TrueType, FalseType> {using type = FalseType;
};// 使用
using ResultType = Conditional<true, int, double>::type; // ResultType 的类型为 int

3. 混合编程

混合编程是结合了运行时和编译时计算的技术,允许程序员在编译期间进行一些运算,同时时间复杂度也得到优化。混合编程的关键在于利用模板和递归来处理类型和数值,使得某些操作可以在编译期完成,从而提高运行时性能。

1. 常规的计算点积范例

混合元编程方面,一个比较典型的案例是计算两个向量(数组)点积。

(1)数组a有3个元素a[0]、a[1]、a[2],值分别为1、2、3;

(2)数组b有3个元素b[0]、b[1]、b[2],值分别为4、5、6;

(3)a和b的点积是一个数值,结果为a[0]×b[0] + a[1] ×b[1] + a[2] ×b[2] =1×4+2×5+3×6=32。

在传统编程中,计算两个数组(向量)的点积通常使用循环结构,如forwhile。以下是一个简单的点积计算示例:

#include <iostream>template<typename T, int U>
auto DotProduct(T* array1, T* array2)
{T dpresult = T{};for (int i = 0; i < U; ++i){dpresult += array1[i] * array2[i];}return dpresult;
}int main()
{int a[] = { 1,2,3 };int b[] = { 4,5,6 };int result = DotProduct<int, 3>(a, b);std::cout << result << std::endl; // 输出 32return 0;
}

2. 混合元编程计算点积

通过元编程,可以在编译期间计算点积。下面的代码展示了如何使用模板和递归实现这一点:

#include <iostream>//泛化版本
template<typename T, int U> //T:元素类型,U:数组大小
struct DotProduct
{static T result(const T* a, const T* b){return (*a) * (*b) +DotProduct<T, U - 1>::result(a + 1, b + 1);}
};
//特化版本,作为递归调用的出口
template<typename T>
struct DotProduct<T, 0>
{static T result(const T *,const T*){return T{};}
};int main()
{int a[] = { 1,2,3 };int b[] = { 4,5,6 };int result = DotProduct<int, 3>::result(a, b);std::cout << result << std::endl;return 0;
}
  1. 泛化版本:
    • DotProduct 是一个模板类,它接受元素类型 T 和数组大小 U 作为模板参数。
    • result 函数是一个静态成员函数,通过递归调用来计算点积。它取当前元素 *a*b 的乘积,并加上对下一个元素的递归调用。
  2. 特化版本:
    • U0 时,递归调用的出口到达。此时返回一个默认初始化的值 T{},表示点积计算的结束。

4. typelist实现

typelist的解释为:用来操作一大堆类型的C++容器,就像C++标准库中的list容器能够为数值提供各种基本操作一样(只不过这里操作的不是数值,而是类型)。

从实现上来讲,typelist是一个类模板,译为“类型列表”,这个类模板用来表示一个列表,这个列表中存放着一堆类型。

设计和基本操作接口(算法)

Typelist 的基本定义

template<typename... Types>
struct TypeList {};// 基本类型操作
using EmptyTypeList = TypeList<>; // 空类型列表

1. 取得typelist中的第1个元素(front)

这个操作返回 typelist 中的第一个类型。通过模板特化,可以在编译时获取到这个类型。该操作通常用于获取类型链的起始类型,以便后续处理。

template<typename TList>
struct Front;template<typename Head, typename... Tail>
struct Front<TypeList<Head, Tail...>> {using type = Head; // 返回第一个元素
};

2. 取得typelist容器中元素的数量(size)

这个操作计算并返回 typelist 中包含的类型数量。它使用可变参数模板的特性,通过 sizeof... 来计算类型的数量。

template<typename TList>
struct Size;template<typename... Types>
struct Size<TypeList<Types...>> {static const size_t value = sizeof...(Types); // 元素计数
};

3. 从typelist中移除第1个元素(pop_front)

此操作会返回一个新的 typelist,该列表中不再包含第一个类型。它通过递归去掉开头的类型,为后续操作提供了简化的列表。

template<typename TList>
struct PopFront;template<typename Head, typename... Tail>
struct PopFront<TypeList<Head, Tail...>> {using type = TypeList<Tail...>; // 移除第一个元素
};

4. 向typelist的开头和结尾插入一个元素(push_front和push_back)

  • push_front: 将新类型插入到 typelist 的开头,产生一个新的 typelist
  • push_back: 将新类型插入到 typelist 的结尾,同样产生一个新的 typelist
template<typename TList, typename NewType>
struct PushFront;template<typename... Types, typename NewType>
struct PushFront<TypeList<Types...>, NewType> {using type = TypeList<NewType, Types...>; // 插入到开头
};template<typename TList, typename NewType>
struct PushBack;template<typename... Types, typename NewType>
struct PushBack<TypeList<Types...>, NewType> {using type = TypeList<Types..., NewType>; // 插入到结尾
};

5. 替换typelist的开头元素(replace_front)

此操作允许替换 typelist 的第一个类型为一个新的类型,并返回一个新的 typelist。这对于动态更改类型列表的开头非常有用。

template<typename TList, typename NewType>
struct ReplaceFront;template<typename... Tail, typename NewType>
struct ReplaceFront<TypeList<Tail...>, NewType> {using type = TypeList<NewType, Tail...>; // 替换开头元素
};

6. 判断typelist是否为空(is_empty)

此操作检查 typelist 是否包含任何类型。如果列表为空,返回 true,否则返回 false。这是确保操作安全性的重要步骤。

template<typename TList>
struct IsEmpty;template<>
struct IsEmpty<EmptyTypeList> {static const bool value = true; // 空类型列表
};template<typename... Types>
struct IsEmpty<TypeList<Types...>> {static const bool value = false; // 非空类型列表
};

7. 根据索引号查找typelist的某个元素(find)

此操作通过索引查找 typelist 中的特定类型。它使用递归进行索引减小,直到找到目标索引的类型。这使得可以根据位置快速访问特定类型。

template<typename TList, size_t Index>
struct Find;template<typename Head, typename... Tail>
struct Find<TypeList<Head, Tail...>, 0> {using type = Head; // 找到第一个元素
};template<typename Head, typename... Tail, size_t Index>
struct Find<TypeList<Head, Tail...>, Index> {using type = typename Find<TypeList<Tail...>, Index - 1>::type; // 递归查找下一个元素
};

8. 遍历typelist找到sizeof值最大的元素(get_maxsize_type)

该操作遍历 typelist 中的所有类型,并返回 sizeof 值最大的类型。它利用条件选择,递归比较每个类型的大小,确定最大值。这在需要根据类型大小进行处理时非常有用。

template<typename TList>
struct GetMaxSizeType;template<typename Head, typename... Tail>
struct GetMaxSizeType<TypeList<Head, Tail...>> {using type = typename std::conditional<(sizeof(Head) >= sizeof(typename GetMaxSizeType<TypeList<Tail...>>::type)),Head,typename GetMaxSizeType<TypeList<Tail...>>::type>::type; // 使用 std::conditional 比较
};// 特化版本,处理空类型列表
template<>
struct GetMaxSizeType<EmptyTypeList> {using type = void; // 空类型列表没有最大类型
};

完整代码

#include <iostream>
#include <type_traits>template<typename... Types>
struct TypeList {};using EmptyTypeList = TypeList<>;// 取得第一个元素
template<typename TList>
struct Front;template<typename Head, typename... Tail>
struct Front<TypeList<Head, Tail...>> {using type = Head;
};// 取得元素数量
template<typename TList>
struct Size;template<typename... Types>
struct Size<TypeList<Types...>> {static const size_t value = sizeof...(Types);
};// 移除第一个元素
template<typename TList>
struct PopFront;template<typename Head, typename... Tail>
struct PopFront<TypeList<Head, Tail...>> {using type = TypeList<Tail...>;
};// 向开头插入一个元素
template<typename TList, typename NewType>
struct PushFront;template<typename... Types, typename NewType>
struct PushFront<TypeList<Types...>, NewType> {using type = TypeList<NewType, Types...>;
};// 向结尾插入一个元素
template<typename TList, typename NewType>
struct PushBack;template<typename... Types, typename NewType>
struct PushBack<TypeList<Types...>, NewType> {using type = TypeList<Types..., NewType>;
};// 替换开头元素
template<typename TList, typename NewType>
struct ReplaceFront;template<typename... Tail, typename NewType>
struct ReplaceFront<TypeList<Tail...>, NewType> {using type = TypeList<NewType, Tail...>;
};// 判断是否为空
template<typename TList>
struct IsEmpty;template<>
struct IsEmpty<EmptyTypeList> {static const bool value = true;
};template<typename... Types>
struct IsEmpty<TypeList<Types...>> {static const bool value = false;
};// 根据索引查找元素
template<typename TList, size_t Index>
struct Find;template<typename Head, typename... Tail>
struct Find<TypeList<Head, Tail...>, 0> {using type = Head; // 找到第一个元素
};template<typename Head, typename... Tail, size_t Index>
struct Find<TypeList<Head, Tail...>, Index> {using type = typename Find<TypeList<Tail...>, Index - 1>::type; // 递归查找下一个元素
};// 遍历找到最大 sizeof 的类型
template<typename TList>
struct GetMaxSizeType;template<typename Head, typename... Tail>
struct GetMaxSizeType<TypeList<Head, Tail...>> {using type = typename std::conditional<(sizeof(Head) >= sizeof(typename GetMaxSizeType<TypeList<Tail...>>::type)),Head,typename GetMaxSizeType<TypeList<Tail...>>::type>::type; // 使用 std::conditional 比较
};// 特化版本,处理空类型列表
template<>
struct GetMaxSizeType<EmptyTypeList> {using type = void; // 空类型列表没有最大类型
};// 测试
int main() {using MyTypes = TypeList<int, double, char>;std::cout << "Size: " << Size<MyTypes>::value << std::endl; // 输出 3std::cout << "Is empty: " << IsEmpty<MyTypes>::value << std::endl; // 输出 0std::cout << "First element type: " << typeid(Front<MyTypes>::type).name() << std::endl; // 输出 intusing Popped = typename PopFront<MyTypes>::type;std::cout << "Size after pop: " << Size<Popped>::value << std::endl; // 输出 2using PushedFront = typename PushFront<MyTypes, float>::type;std::cout << "Size after push front: " << Size<PushedFront>::value << std::endl; // 输出 4using PushedBack = typename PushBack<MyTypes, long>::type;std::cout << "Size after push back: " << Size<PushedBack>::value << std::endl; // 输出 4using FoundType = typename Find<MyTypes, 1>::type; // 查找索引1的类型std::cout << "Found type at index 1: " << typeid(FoundType).name() << std::endl; // 输出 doubleusing MaxSizeType = typename GetMaxSizeType<MyTypes>::type; // 查找最大 sizeof 的类型std::cout << "Max size type: " << typeid(MaxSizeType).name() << std::endl; // 输出 doublereturn 0;
}
  • TypeList: 用于存储类型的模板类。
  • 基本操作: 提供了如获取第一个元素、计算大小、移除元素、插入元素、替换元素和判断是否为空等基本操作。
  • Find: 通过递归查找指定索引的类型。
  • GetMaxSizeType: 通过比较各类型的 sizeof 值,找出最大类型。
  • 使用示例: 在 main 函数中演示了如何使用这些操作。

5. tuple 实现

基础知识

1. 左值、右值、左值引用、右值引用
  • 左值和右值:

    • 左值 (lvalue): 表示一个可以被取地址的对象,具有持久的内存位置。例子:int i = 10; 这里的 i 是左值。
    • 右值 (rvalue): 表示一个临时的对象,通常是不能取地址的,存在于表达式的右边。例子:10 是右值。
  • 左值引用:

    • 通过左值引用 (&) 可以引用一个左值。例子:
      int &j = i; // j 是左值引用
      
    • 左值引用只能绑定到左值上。尝试将右值绑定到左值引用会导致编译错误,但 const 左值引用可以绑定到右值:
      const int &j = 10; // 合法
      
  • 右值引用:

    • 右值引用使用 && 进行声明,允许绑定到右值。
    • 例如:
      int &&k = 10; // k 是右值引用
      
    • 右值引用只能绑定到右值。尝试将左值绑定到右值引用会导致编译错误。
  • 普通变量:

    • 普通的变量(如 int m;)既不是左值引用也不是右值引用,因为引用必须带有 &&& 修饰符。
2. std::move 究竟做了什么
  • std::move 是一个标准库函数,它将左值转换为右值,从而允许使用右值引用。例如:

    int &&k = std::move(i); // 将左值 i 转换为右值
    
  • 重要的是,std::move 不会执行任何移动操作,它只是一种类型转换,标记一个对象可以被“移动”。

3. std::forward 究竟做了什么
  • std::forward 是用于实现完美转发的工具,它允许保持参数的值类别(lvalue 或 rvalue)。在模板中使用时,可以根据传入的参数类型决定是保持为左值还是右值。

    template<typename T>
    void func(T&& arg) {// 完美转发process(std::forward<T>(arg));
    }
    
4. 万能引用(转发引用)
  • 万能引用是指在模板参数中使用的引用类型,可以绑定到左值或右值(例如 T&&,其中 T 是模板参数)。这种引用在模板中被称为转发引用。
5. 完美转发
  • 完美转发使得函数可以根据实际传入的参数类型选择合适的引用类型,从而避免不必要的复制和保证性能。例如,结合 std::forward 和万能引用,可以实现完美转发。

tuple 实现示例

C++ 标准库中的 std::tuple 是一个可以存储不同类型的元素的容器,以下是一个简单的自定义 tuple 实现示例:

  • tuple 是一种灵活的数据结构,能够存储不同类型的元素。理解左值、右值及其引用非常重要,因为 tuple 的实现涉及到对象的生命周期管理、内存效率以及函数调用方式(如移动语义和转发)。
  • std::movestd::forward 是实现高效代码的重要工具,特别是在模板编程和泛型编程中。
#include <iostream>
#include <utility>
#include <type_traits>template<typename... Types>
class MyTuple;// 特化基础情况
template<>
class MyTuple<> {};// 递归定义
template<typename Head, typename... Tail>
class MyTuple<Head, Tail...> : private MyTuple<Tail...> {
public:Head head; // 当前元素using MyTuple<Tail...>::head; // 继承下一层的头部元素MyTuple(Head h, Tail... t): head(h), MyTuple<Tail...>(t...) {} // 构造函数
};// 获取元素
template<size_t Index, typename Tuple>
struct TupleElement;template<typename Head, typename... Tail>
struct TupleElement<0, MyTuple<Head, Tail...>> {using type = Head; // 返回当前头部元素
};template<size_t Index, typename Head, typename... Tail>
struct TupleElement<Index, MyTuple<Head, Tail...>> {using type = typename TupleElement<Index - 1, MyTuple<Tail...>>::type; // 递归获取
};// 获取元素的辅助函数
template<size_t Index, typename... Types>
typename TupleElement<Index, MyTuple<Types...>>::type& get(MyTuple<Types...>& tuple) {return static_cast<typename TupleElement<Index, MyTuple<Types...>>::type&>(tuple);
}int main() {MyTuple<int, double, char> myTuple(1, 2.5, 'c');std::cout << "First element: " << get<0>(myTuple) << std::endl; // 输出 1std::cout << "Second element: " << get<1>(myTuple) << std::endl; // 输出 2.5std::cout << "Third element: " << get<2>(myTuple) << std::endl; // 输出 creturn 0;
}

http://www.ppmy.cn/news/1527642.html

相关文章

HTML讲解(一)body部分

目录 1.什么是HTML 2.HTML基本框架 3.标题声明 4.修改标题位置 5.段落声明 6.修改段落位置 7.超链接访问 8.图像访问 9.改变网页背景及文本颜色 10.添加网页背景图 11.超链接改变颜色 12.设置网页边距 小心&#xff01;VS2022不可直接接触&#xff0c;否则&#xff…

Linux文件IO(一)-open使用详解

在 Linux 系统中要操作一个文件&#xff0c;需要先打开该文件&#xff0c;得到文件描述符&#xff0c;然后再对文件进行相应的读写操作&#xff08;或其他操作&#xff09;&#xff0c;最后在关闭该文件&#xff1b;open 函数用于打开文件&#xff0c;当然除了打开已经存在的文…

最新EmlogPro影视主题模版/简约暗黑纯净Mould主题模板/博客网站源码

源码简介&#xff1a; 最新EmlogPro影视主题模版&#xff0c;它是一个简约暗黑纯净Mould主题模板&#xff0c;也能做博客网站源码。 Mould这个主题模板啊&#xff0c;真的是设计得特别有感觉。它的布局和设计都超级流畅&#xff0c;用起来特别直观&#xff0c;简单多了。不管是…

c++206 友元类

#include<iostream> using namespace std; class A { public://声明的位置 和public private无关friend void modifyA(A* pA, int _a);//函数modifyA是A的好朋友A(int a, int b){this->a a;this->b b;}int getA(){return this->a;} private:int a;int b; };vo…

力扣题解2332

大家好&#xff0c;欢迎来到无限大的频道。 今日继续给大家带来力扣题解。 题目描述&#xff08;中等&#xff09;​&#xff1a; 坐上公交的最晚时间 给你一个下标从 0 开始长度为 n 的整数数组 buses &#xff0c;其中 buses[i] 表示第 i 辆公交车的出发时间。同时给你一…

Spring Boot-Session管理问题

Spring Boot 中的 Session 管理问题及其解决方案 1. 引言 在 Spring Boot Web 应用中&#xff0c;Session 是用来维护用户状态的重要机制。由于 HTTP 协议本质上是无状态的&#xff0c;Session 提供了一种方式来存储和共享用户的会话数据。Spring Boot 提供了多种方式来管理 …

SpringMVC后续4

文件上传下载 导入maven <dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.2</version></dependency> 配置MultipartResolver <bean id"multipartRes…

PointNet2(一)分类

发现PVN3D中使用到了pointnet2和 densfusion等网络&#xff0c;为了看懂pvn3d&#xff0c;因此得看看pointnet2&#xff0c;然而带cpp&#xff0c;cu文件的程序一时办事编译不成功&#xff0c;因此找到了一个 Pointnet_Pointnet2_pytorch-master&#xff0c;里面有pointnet和po…

孙怡带你深度学习(2)--PyTorch框架认识

文章目录 PyTorch框架认识1. Tensor张量定义与特性创建方式 2. 下载数据集下载测试展现下载内容 3. 创建DataLoader&#xff08;数据加载器&#xff09;4. 选择处理器5. 神经网络模型构建模型 6. 训练数据训练集数据测试集数据 7. 提高模型学习率 总结 PyTorch框架认识 PyTorc…

java-springboot 实现文件 图片的上传 以及渲染

在 Java Spring Boot 应用中实现文件和图片的上传以及渲染&#xff0c;通常涉及以下几个步骤&#xff1a; 配置文件上传&#xff1a;使用 Spring Boot 的 MultipartResolver 来配置文件上传。 创建上传接口&#xff1a;创建一个 REST 控制器来处理上传请求。 保存文件到服务器&…

C#基础(14)冒泡排序

前言 其实到上一节结构体我们就已经将c#的基础知识点大概讲完&#xff0c;接下来我们会讲解一些关于算法相关的东西。 我们一样来问一下gpt吧&#xff1a; Q:解释算法 A: 算法是一组有序的逻辑步骤&#xff0c;用于解决特定问题或执行特定任务。它可以是一个计算过程、一个…

linux-Shell 编程-常用 Shell 脚本技巧

Linux Shell 编程&#xff1a;常用 Shell 脚本技巧 一、概述 Shell 脚本是 Linux 系统管理员和开发人员日常自动化任务的重要工具。通过编写 Shell 脚本&#xff0c;用户可以自动化重复性工作、简化系统维护、管理服务器资源等。Shell 脚本的强大之处在于其简洁和灵活性&…

手势识别-Yolov5模型-自制数据集训练

1、源码下载&#xff1a; 大家可以直接在浏览器搜索yolov5即可找到官方链接&#xff0c;跳转进github进行下载&#xff1a; 这里对yolov5模型补充说明一下&#xff0c;它是存在较多版本的&#xff0c;具体信息可在master->tags中查看&#xff0c;大家根据需要下载。这些不同…

Golang如何优雅的退出程序

Golang如何优雅的退出程序 在 Go 中优雅地退出程序&#xff0c;通常需要处理一些清理工作&#xff0c;如关闭文件、网络连接、释放资源等。以下是一些常见的方法&#xff1a; 一、使用 os.Signal 和 signal.Notify 捕获系统信号&#xff1a;可以使用 os/signal 包来捕获中断…

Android中如何处理运行时权限?

在Android中&#xff0c;处理运行时权限是开发过程中一个至关重要的环节&#xff0c;它自Android 6.0&#xff08;API级别23&#xff09;引入&#xff0c;旨在提高用户隐私保护和应用的透明度。以下将详细阐述Android中处理运行时权限的方法、步骤、注意事项以及相关的最佳实践…

最优化理论与自动驾驶(十一):基于iLQR的自动驾驶轨迹跟踪算法(c++和python版本)

最优化理论与自动驾驶&#xff08;四&#xff09;&#xff1a;iLQR原理、公式及代码演示 之前的章节我们介绍过&#xff0c;iLQR&#xff08;迭代线性二次调节器&#xff09;是一种用于求解非线性系统最优控制最优控制最优控制和规划问题的算法。本章节介绍采用iLQR算法对设定…

使用阿里OCR身份证识别

1、开通服务 免费试用 2、获取accesskay AccessKeyId和AccessKeySecret 要同时复制保存下来 因为后面好像看不AccessKeySecret了 3.Api 参考 https://help.aliyun.com/zh/ocr/developer-reference/api-ocr-api-2021-07-07-recognizeidcard?spma2c4g.11186623.0.0.7a9f4b1e5C…

STM32快速复习(十二)FLASH闪存的读写

文章目录 一、FLASH是什么&#xff1f;FLASH的结构&#xff1f;二、使用步骤1.标准库函数2.示例函数 总结 一、FLASH是什么&#xff1f;FLASH的结构&#xff1f; 1、FLASH简介 &#xff08;1&#xff09;STM32F1系列的FLASH包含程序存储器、系统存储器和选项字节三个部分&…

AcWing算法基础课-789数的范围-Java题解

大家好&#xff0c;我是何未来&#xff0c;本篇文章给大家讲解《AcWing算法基础课》789 题——数的范围。本文详细解析了一个基于二分查找的算法题&#xff0c;题目要求在有序数组中查找特定元素的首次和最后一次出现的位置。通过使用两个二分查找函数&#xff0c;程序能够高效…

随机规划及其MATLAB实现

目录 引言 随机规划的基本模型 随机动态规划 随机动态规划建模实例​(随机动态规划)&#xff1a; MATLAB中的随机规划实现 示例&#xff1a;两阶段随机规划 表格总结&#xff1a;随机规划求解方法与适用场景 结论 引言 随机规划&#xff08;Stochastic Programming&…