【C++】抱C++中的函数式编程:使用`std::function`和Lambda表达式简化代码

在这里插入图片描述

C++自C++11标准引入了lambda表达式、std::functionstd::bind,为开发者带来了强大的函数式编程特性。函数式编程让代码更加灵活、简洁、可重用,并使得开发者可以轻松处理回调、事件驱动编程和更复杂的函数组合。本文将详细介绍C++中函数式编程的关键工具,重点展示std::function、lambda表达式以及std::bind的使用。通过代码示例,读者将学习如何用这些工具来简化代码并提升代码的表达能力,最终提高开发效率。


引言

C++作为一门多范式编程语言,自C++11以来,逐步引入了更多函数式编程的特性,为开发者提供了丰富的工具来编写简洁而灵活的代码。虽然C++在其起源时并不是一门以函数式编程为导向的语言,但通过lambda表达式、std::functionstd::bind等特性,C++已经能够支持函数式编程风格。函数式编程强调使用不可变数据、表达式的组合以及将函数作为一等公民的思想,在复杂应用中,这种编程范式能够显著提升代码的可读性和可维护性。

本文将详细介绍C++中的函数式编程工具,展示如何通过lambda表达式、std::function以及std::bind来编写更灵活、更易维护的代码,并结合实际场景分析这些特性在提高开发效率和减少代码复杂性方面的优势。


函数式编程的基本概念

在深入探讨C++中的函数式编程之前,我们首先需要理解函数式编程的一些基本概念。函数式编程是一种以函数为核心的编程范式,核心思想包括:

  1. 不可变性:数据在被创建之后不能被修改,状态变化是通过返回新的数据来实现的。
  2. 函数作为一等公民:函数可以作为参数传递,作为返回值,或者存储在变量中。
  3. 高阶函数:接受其他函数作为参数或返回函数的函数称为高阶函数。
  4. 函数组合:可以将多个函数组合起来,使得程序逻辑更加灵活。

C++通过lambda表达式、std::function和其他标准库工具,使得这些函数式编程的概念在C++中得以实现。下面,我们将详细探讨这些工具的使用。


Lambda表达式:函数式编程的基石

什么是Lambda表达式?

Lambda表达式是C++11引入的一种匿名函数,它允许开发者在任何需要函数的地方定义和使用函数,而不必显式声明一个命名函数。Lambda表达式的语法简洁,并且支持捕获外部变量,使其成为实现回调函数和短小函数的理想工具。

Lambda表达式的基本语法如下:

[capture](parameters) -> return_type {// function body
};
  • 捕获列表(capture):决定了lambda表达式中哪些外部变量可以被捕获以及如何捕获(值捕获或引用捕获)。
  • 参数列表(parameters):与普通函数的参数列表相同。
  • 返回类型(return_type):可以显式指定,也可以省略,由编译器根据函数体自动推导。
  • 函数体(function body):lambda表达式的具体逻辑。

Lambda表达式的使用

  1. 无参无返回值的Lambda表达式

最简单的Lambda表达式可以没有参数,也没有返回值:

auto lambda = []() {std::cout << "Hello, Lambda!" << std::endl;
};
lambda();  // 输出:Hello, Lambda!
  1. 带参数的Lambda表达式

Lambda表达式可以接受参数,类似于普通函数:

auto add = [](int a, int b) -> int {return a + b;
};
std::cout << "Sum: " << add(3, 4) << std::endl;  // 输出:Sum: 7
  1. 捕获外部变量

Lambda表达式的一个重要特性是能够捕获其外部作用域的变量。捕获列表可以是按值捕获(=)或按引用捕获(&):

int x = 10;
auto capture_by_value = [x]() {std::cout << "Value captured: " << x << std::endl;
};x = 20;
capture_by_value();  // 输出:Value captured: 10auto capture_by_ref = [&x]() {std::cout << "Reference captured: " << x << std::endl;
};x = 30;
capture_by_ref();  // 输出:Reference captured: 30
  1. 通用Lambda表达式

C++14进一步增强了lambda表达式,允许在lambda中使用auto类型,定义通用的lambda表达式:

auto generic_lambda = [](auto x, auto y) {return x + y;
};std::cout << generic_lambda(3, 4) << std::endl;     // 输出:7
std::cout << generic_lambda(3.5, 4.5) << std::endl; // 输出:8.0

std::function:存储函数的通用容器

什么是std::function

std::function是C++标准库中的一个函数对象包装器,它可以存储任意可调用对象,包括普通函数、lambda表达式、函数指针和仿函数。std::function的灵活性使得它非常适合用于回调函数、函数组合等场景。

std::function的基本定义如下:

#include <functional>
std::function<返回类型(参数类型...)> func;

例如,定义一个接受两个整数并返回它们之和的std::function对象:

std::function<int(int, int)> add = [](int a, int b) {return a + b;
};
std::cout << "Sum: " << add(3, 4) << std::endl;  // 输出:Sum: 7

std::function的应用场景

  1. 存储Lambda表达式

std::function可以存储lambda表达式,尤其是在需要将lambda表达式作为回调函数时非常有用:

std::function<void()> callback = []() {std::cout << "Callback executed!" << std::endl;
};
callback();  // 输出:Callback executed!
  1. 函数组合

通过std::function,我们可以灵活地组合多个函数,形成复杂的调用链。例如,下面的代码展示了如何将多个函数组合成一个复杂的操作:

std::function<int(int)> multiply_by_two = [](int x) { return x * 2; };
std::function<int(int)> add_five = [](int x) { return x + 5; };std::function<int(int)> combined = [multiply_by_two, add_five](int x) {return multiply_by_two(add_five(x));
};std::cout << combined(3) << std::endl;  // 输出:16

std::bind:绑定函数与参数

什么是std::bind

std::bind是一个用于绑定函数与参数的工具,它允许我们将一个函数的一部分参数提前绑定,生成一个新的可调用对象。std::bind结合了高阶函数的思想,能够极大地提高代码的复用性。

std::bind的基本语法如下:

#include <functional>
auto bound_func = std::bind(原函数, 参数1, 参数2, ...);

std::bind中的占位符_1_2等用于表示绑定时未提供的参数,将在调用时提供。

std::bind的实际应用

  1. 绑定普通函数

假设我们有一个接受两个整数的函数,我们可以使用std::bind提前绑定一个参数:

int add(int a, int b) {return a + b;
}auto add_five = std::bind(add, _1, 5);
std::cout << add_five(3) << std::endl;  // 输出:8
  1. 结合成员函数使用

std::bind还可以用于绑定类的成员函数。通过传递对象实例,可以生成一个可调用对象:

class MyClass {
public:void print(int x) {std::cout << "Value: " << x << std::endl;}
};MyClass obj;
auto bound_method = std::bind(&MyClass::print, &obj, _1);
bound_method(10);  // 输出:Value: 10

实际场景中的函数式编程

函数式编程在实际应用中有很多场景可以极大地提高代码的灵活性和可维护性。通过std::functionstd::bind以及lambda表达式,我们可以在事件驱动编程、回调机制、算法组合等领域显著简化代码逻辑。以下是几个实际场景的例子,展示了如何将函数式编程应用到C++项目中。

场景1:回调机制与事件驱动编程

在现代C++应用中,回调函数是事件驱动编程的核心。通过lambda表达式和std::function,我们可以为某些事件绑定回调函数,从而实现灵活的事件处理机制。

#include <iostream>
#include <functional>
#include <vector>// 一个简单的事件调度器类
class EventDispatcher {
public:void addListener(const std::function<void(int)>& listener) {listeners.push_back(listener);}void dispatch(int eventData) {for (const auto& listener : listeners) {listener(eventData);}}private:std::vector<std::function<void(int)>> listeners;
};int main() {EventDispatcher dispatcher;// 添加回调,处理事件dispatcher.addListener([](int eventData) {std::cout << "Listener 1 received event with data: " << eventData << std::endl;});dispatcher.addListener([](int eventData) {std::cout << "Listener 2 received event with data: " << eventData << std::endl;});// 触发事件dispatcher.dispatch(42);  // 输出:Listener 1 和 Listener 2 都会收到事件数据 42return 0;
}

在这个例子中,EventDispatcher类利用std::function存储回调函数,并在事件发生时依次调用这些回调。通过使用lambda表达式,开发者可以简洁地定义事件处理逻辑,而不需要显式定义额外的回调函数。

场景2:延迟执行与任务调度

在异步编程中,延迟执行和任务调度是常见需求。使用std::functionstd::bind可以轻松创建可重用的任务调度器。

#include <iostream>
#include <functional>
#include <chrono>
#include <thread>// 一个简单的任务调度器
class TaskScheduler {
public:void schedule(const std::function<void()>& task, int delayInSeconds) {std::this_thread::sleep_for(std::chrono::seconds(delayInSeconds));task();  // 延迟执行任务}
};int main() {TaskScheduler scheduler;// 使用lambda表达式定义任务scheduler.schedule([]() {std::cout << "Task executed after delay!" << std::endl;}, 3);return 0;
}

这个简单的任务调度器类使用std::function存储任务,并通过std::this_thread::sleep_for实现任务的延迟执行。在实际应用中,这种模式可以被扩展到更复杂的调度系统中,支持异步任务的管理。

场景3:算法组合与策略模式

策略模式是一个经典的设计模式,常用于根据不同策略选择不同的算法。在C++中,我们可以通过std::function结合lambda表达式实现灵活的算法组合。

#include <iostream>
#include <functional>// 定义策略接口
std::function<int(int, int)> addStrategy = [](int a, int b) { return a + b; };
std::function<int(int, int)> multiplyStrategy = [](int a, int b) { return a * b; };// 策略选择器
int executeStrategy(const std::function<int(int, int)>& strategy, int a, int b) {return strategy(a, b);
}int main() {int a = 5, b = 3;// 使用加法策略std::cout << "Add strategy: " << executeStrategy(addStrategy, a, b) << std::endl;// 使用乘法策略std::cout << "Multiply strategy: " << executeStrategy(multiplyStrategy, a, b) << std::endl;return 0;
}

通过std::function,我们可以灵活地传递不同的策略,而无需对算法进行硬编码。这种方法使得算法的扩展变得简单,并且支持在运行时动态选择策略,极大地提高了代码的灵活性。


性能与开销分析

尽管std::function、lambda表达式和std::bind为我们带来了极大的代码灵活性和简洁性,但在使用这些工具时,也必须注意其性能开销。函数式编程的灵活性通常伴随着一定的运行时开销,这在某些对性能敏感的场景中需要谨慎处理。

std::function的性能

std::function是一个泛型的函数容器,它通过类型擦除(type erasure)机制来存储不同类型的可调用对象。这意味着std::function在使用时通常比直接调用函数指针或内联函数有额外的开销,因为其需要通过间接调用的方式实现。

例如,在回调函数或频繁调用的场景中,std::function的开销可能会对性能产生一定影响:

#include <functional>
#include <iostream>
#include <chrono>void directFunction(int x) {std::cout << x << std::endl;
}int main() {std::function<void(int)> func = directFunction;auto start = std::chrono::high_resolution_clock::now();for (int i = 0; i < 1000000; ++i) {func(42);}auto end = std::chrono::high_resolution_clock::now();std::cout << "Time taken by std::function: "<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()<< "ms" << std::endl;return 0;
}

在这个例子中,频繁调用std::function可能比直接调用函数有略微的性能损耗。因此,在对性能敏感的代码路径中,开发者需要权衡std::function带来的灵活性和它的额外开销。

Lambda表达式的性能

相比于std::function,lambda表达式的性能开销较小。lambda表达式在编译时生成内联函数,其性能与普通函数基本一致。事实上,C++编译器可以对lambda表达式进行优化,使其性能与显式定义的函数相当。因此,在大多数情况下,lambda表达式不会成为性能瓶颈。

然而,当lambda表达式捕获外部变量时,捕获的方式(按值或按引用)可能会影响性能。例如,按值捕获可能导致不必要的拷贝,而按引用捕获则会避免这一开销。

int x = 42;
auto by_value = [x]() { return x; };  // 按值捕获,拷贝x
auto by_reference = [&x]() { return x; };  // 按引用捕获,不拷贝

在性能敏感的场景中,开发者需要仔细选择捕获方式,避免不必要的拷贝操作。


性能表现。在函数式编程的背景下,C++的lambda表达式、std::functionstd::bind等特性,为开发者提供了灵活的工具,使得编写高效、可维护的代码成为可能。

C++函数式编程的价值总结

  1. 简化代码结构:使用lambda表达式和std::function可以避免大量显式声明的命名函数或函数指针,减少了代码的冗长,使得代码逻辑更加清晰和简洁。

  2. 提高代码的可复用性:通过std::functionstd::bind,函数可以被更加灵活地传递和绑定。这使得开发者可以创建更具通用性和模块化的代码,尤其在需要将函数作为参数传递的场景中。

  3. 增强可读性:函数式编程将函数作为一等公民,配合lambda表达式的使用,可以将小型、局部化的操作封装到匿名函数中,避免定义过多的辅助函数,从而提升代码的整体可读性。

  4. 适用于多种应用场景:C++的函数式编程工具非常适合用于事件驱动编程、回调机制、异步任务调度、函数组合等。函数式编程范式能够灵活地处理复杂逻辑,并将这些操作拆解为简洁的、可重用的小型函数。

  5. 减少错误:通过高阶函数和函数组合的思想,函数式编程能够减少重复代码,降低了手动处理相同逻辑时可能出现的错误。这对大型代码库中的代码维护有着重要意义。

劣势与权衡

虽然函数式编程带来了许多好处,但其引入的间接性也可能会带来一些缺点,尤其是在以下场景中需要权衡:

  1. 性能开销std::function虽然提供了很大的灵活性,但由于其背后的类型擦除机制,相比直接调用函数或内联函数,可能会带来一些额外的性能开销。在性能至关重要的部分代码中,建议谨慎使用std::function

  2. 代码的调试复杂性:由于lambda表达式和std::bind的匿名性,调试这些代码时可能会更加复杂。调试工具可能无法轻松展示lambda表达式的具体内容,特别是当lambda捕获外部变量或使用std::bind组合多个函数时。

  3. 可读性的权衡:虽然函数式编程能减少冗长的类型声明,但过度使用匿名函数、嵌套的lambda表达式或复杂的std::bind操作,可能会让代码的逻辑变得晦涩难懂。在这些情况下,显式声明函数名或分解复杂逻辑,可能更有助于提升代码的可读性。


未来的展望:C++中的函数式编程与现代C++特性

C++在持续演进中不断吸收其他编程范式的优点,函数式编程特性就是其中的重要组成部分。从C++11引入lambda表达式和std::function,到C++20引入的协程(coroutines),C++中的函数式编程正在不断扩展应用场景,特别是在异步编程和并发编程中。

协程与异步函数

C++20引入的协程使得编写异步代码变得更加容易和直观。协程可以被看作是对函数式编程的一种扩展,它允许函数在执行过程中暂停,并在需要时恢复,尤其适合需要等待I/O操作的异步任务。这种设计使得复杂的异步操作变得更加简洁,类似于函数式编程中高阶函数的思想,将复杂的异步逻辑封装为一个简单的函数调用。

#include <iostream>
#include <coroutine>
#include <future>struct Awaitable {bool await_ready() const { return false; }void await_suspend(std::coroutine_handle<>) const {}void await_resume() const {}
};std::future<void> asyncTask() {co_await Awaitable();std::cout << "Task completed!" << std::endl;
}int main() {auto task = asyncTask();task.wait();return 0;
}

在未来的C++版本中,函数式编程与协程的结合将极大地简化异步编程模型,使得开发者可以在复杂的异步操作中依然保持代码的简洁和直观。

模板编程与函数式编程的结合

C++模板的强大能力使得函数式编程中的许多思想可以以泛型方式实现。函数式编程依赖于高阶函数、不可变性以及函数组合,而这些理念可以通过模板和SFINAE(Substitution Failure Is Not An Error)等技术在编译期得以实现。这种编译期的优化可以消除函数式编程带来的部分运行时开销。

例如,函数组合可以通过模板来实现,从而使得不同的函数在编译期进行组合,而不引入运行时的性能损耗:

template <typename F1, typename F2>
auto compose(F1 f1, F2 f2) {return [f1, f2](auto x) {return f1(f2(x));};
}int main() {auto multiplyBy2 = [](int x) { return x * 2; };auto add3 = [](int x) { return x + 3; };auto composed = compose(multiplyBy2, add3);std::cout << composed(5) << std::endl;  // 输出 16,等于 (5 + 3) * 2return 0;
}

这种编译期函数组合的方式不仅能够提高代码的可读性,还能通过模板进行强类型检查,避免运行时错误。


结论

C++中的函数式编程工具为开发者带来了更多的灵活性和简洁性。通过lambda表达式、std::functionstd::bind,开发者可以编写更加简洁、模块化和可扩展的代码。尽管这些特性带来了额外的性能开销,但它们为编写复杂应用程序提供了重要的工具,特别是在回调、事件驱动编程和异步任务管理中。

随着C++标准的不断演进,函数式编程的思想在C++中将会得到进一步扩展,特别是结合协程、模板元编程等特性,函数式编程的灵活性与高效性将在未来发挥更大的作用。

通过理解并合理使用这些现代C++特性,开发者可以在编写简洁、灵活代码的同时,保持代码的高性能和可维护性。这种编程范式的优势将在未来的C++开发中继续彰显,帮助开发者应对越来越复杂的软件开发需求。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/457490.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解码专业术语——应用系统开发项目中的专业词汇解读

文章目录 引言站点设置管理具体要求包括&#xff1a; Footer管理基于URL的权限控制利用数据连接池优化数据库操作什么是数据连接池&#xff1f;优化的优势 利用反射改造后端代码&#xff0c;AJAX反射的作用及其在后端代码中的应用AJAX 实现前后端无刷新交互 引言 创新实践项目二…

Linux常用命令1

切换目录 cd [rootlocalhost menge]# cd /[rootlocalhost /]# cd: cd [-L|[-P [-e]] [-]] [目录] 查看当前的目录 pwd 浏览目录内容 ls ls浏览后颜色表示 白色&#xff1a;普通文件 蓝色&#xff1a;目录 红色&#xff1a;压缩包文件 黄色&#xff1a;设备文件 绿…

Python浪漫之画一个圆月亮

效果图&#xff1a; 完整代码&#xff1a; import turtle import time# 创建一个画布 screen turtle.Screen() screen.bgcolor("darkblue") # 设置背景为深蓝色# 创建一个海龟&#xff08;turtle&#xff09;用于绘制月亮 moon turtle.Turtle() moon.color("…

Axure设置面板状态——元件动作二

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01;因为有事断更了三天&#xff0c;从今天开始又回来了&#xff0c;继续为大家分享Axure相关知识点。 课程主题&#xff1a;设置面板状态 主要内容&#xff1a;State状态、推…

UML 总结(基于《标准建模语言UML教程》)

定义 UML 又称为统一建模语言或标准建模语言&#xff0c;是一种标准的图形化建模语言&#xff0c;它是面向对象分析与设计的一种标准表示。尽管UML 本身没有对过程有任何定义&#xff0c;但UML 对任何使用它的方法&#xff08;或过程&#xff09;提出的要求是&#xff1a;支持用…

springboot入门学习笔记

在我们创建了一个Springboot项目之后&#xff0c;我们会看到有很多文件夹和文件 Springboot程序中各类文件的意义 一.pom.xml 在 Spring Boot 项目中&#xff0c;pom.xml&#xff08;Project Object Model&#xff09;文件是 Maven 构建工具的核心配置文件。起到项目信息定义…

S-Function

目录 S-Function介绍 生成S-Function的三种常用手段 使用手写S-函数合并定制代码 使用S-Function Builder块合并定制代码 使用代码继承工具合并定制代码 S-Function介绍 我们可以使用S-Function扩展Simulink对仿真和代码生成的支持。例如&#xff0c;可以使用它们&#xf…

ELK之路第一步——Elasticsearch集群的搭建以及踩坑记录

elasticSearch集群 前言一、架构二、下载三、虚拟机相关设置3.1 创建es用户3.2 为建es用户赋权sudo3.3 更换es目录所属用户 四、Elasticsearch配置文件修改4.1 修改elasticsearch.yml4.2 修改jvm.options4.3 修改jdk路径 五、启动六、启动报错七、可视化界面cerebro 前言 Elk&…

二进制方式部署k8s集群

目标任务: 1、Kubernetes集群部署架构规划 2、部署Etcd数据库集群 3、在Node节点安装Docker 4、部署Flannel网络插件 5、在Master节点部署组件(api-server,schduler,controller-manager) 6、在Node节点部署组件(kubelet,kube-proxy) 7、查看集群状态 8、运行⼀个测…

【有啥问啥】DINO:一种改进的去噪锚框的端到端目标检测器

DINO&#xff1a;一种改进的去噪锚框的端到端目标检测器 在目标检测领域&#xff0c;DINO&#xff08;DETR with Improved DeNoising Anchor Boxes for End-to-End Object Detection&#xff09;是一种创新的端到端目标检测模型&#xff0c;旨在解决传统目标检测算法中的一些关…

基于Multisim的音频放大电路设计与仿真

基本设计要求&#xff1a;设计并仿真实现一个音频功率放大器。功率放大器的电源电压为&#xff0b;5V&#xff08;电路其他部分的电源电压不限&#xff09;&#xff0c;负载为8Ω电阻。具体要求如下&#xff1a;1&#xff09;3dB通频带为300&#xff5e;3400Hz&#xff0c;输出…

AI智能爆发:从自动驾驶到智能家居,科技如何改变我们的日常?

内容概要 在这个瞬息万变的时代&#xff0c;AI智能以其惊人的速度崛起&#xff0c;正在以前所未有的方式改变我们的生活。从自动驾驶到智能家居&#xff0c;这一系列创新为我们的日常生活注入了新的活力和便利。从交通安全到居家体验&#xff0c;这些科技不仅仅是工具&#xf…

【Visual Studio】下载安装 Visual Studio Community 并配置 C++ 桌面开发环境的图文教程

引言 Visual Studio 是一个面向 .NET 和 C 开发人员的综合性 Windows 版 IDE&#xff0c;可用于构建 Web、云、桌面、移动应用、服务和游戏。 安装步骤 访问 Visual Studio 的官方下载页面&#xff1a; https://visualstudio.microsoft.com/zh-hans/downloads/运行已下载的 V…

【数据结构与算法】第4课—数据结构单链表OJ练习题

文章目录 1. 移除链表元素2. 反转链表3. 找链表中间节点4. 合并两个有序的链表5. 分割链表6. 链表的回文结构7. 相交链表8. 判断环形链表9. 返回环形链表的入环节点10. 随机链表的复制 1. 移除链表元素 题目 思路 #include <stdio.h> #include <stdlib.h> #include…

【功能安全】技术安全概念TSC

目录 01 TSC定义 02 TSC注意事项 03 TSC案例 01 TSC定义 所处位置 TSC:Technical safety concept技术安全概念 TSR:Technical safety requirement技术安全需求 在系统开发阶段属于安全活动4-6 系统层产品开发示例 TSC目的

传输层UDP

再谈端口号 端口号&#xff1a;标识了主机上进行通信的不同的应用程序 在TCP/IP 协议中我们用“源IP”"源端口号" “目的IP”“目的端口号” “协议号”五元组来标识一个通信 用netstat -n 查看 查看网络信息&#xff0c;我们有两种命令查看网络通信1.用netsta…

力扣刷题(sql)--零散知识点(1)

通过一段时间的刷题&#xff0c;感觉自己的sql能力逐渐上去&#xff0c;所以不会像前三道题一样讲那么详细了&#xff0c;这里主要会讲到一些特殊的知识点和方法。另外&#xff0c;我的建议是做完一个题有好的想法赶紧记录下来&#xff0c;不要想着最后汇总&#xff0c;不然会懒…

通过cv库智能切片 把不同的分镜切出来 自媒体抖音快手混剪

用 手机自动化脚本&#xff0c;从自媒体上获取视频&#xff0c;一个商品对应几百个视频&#xff0c;我们把这几百个视频下载下来&#xff0c;进行分镜 视频切片&#xff0c;从自媒体上下载视频&#xff0c;通过cv库用直方图识别每个镜头进行切片。 下载多个图片进行视频的伪原…

香橙派5(RK3588)使用npu加速yolov5推理的部署过程

香橙派5使用npu加速yolov5推理的部署过程 硬件环境 部署过程 模型训练(x86主机) 在带nvidia显卡(最好)的主机上进行yolo的配置与训练, 获取最终的best.pt模型文件, 详见另一篇文档 模型转换(x86主机) 下载airockchip提供的yolov5(从pt到onnx) 一定要下这个版本的yolov5, …

sass软件登录设定——未来之窗行业应用跨平台架构

一、saas软件开发中登录设计 以为大公司为参考思迅在登录时候需要录入商户号 二、独立商户商户好处 1.每个店铺的账户是独立的&#xff0c;保护商户职员账户信息的相对安全。 2.不同店铺可以试用相同用户名