当前位置: 首页 > news >正文

C++11: STL之bind

C++11: STL之bind

  • 引言
  • 可调用对象的绑定
    • 绑定普通函数
    • 绑定静态函数
    • 绑定类成员函数
    • 绑定仿函数
    • 绑定Lambda
  • 占位符std::placeholders的应用
    • 嵌套绑定
    • 参数重排序
    • 结合 STL 算法
    • 占位符传递到嵌套函数
    • 混合占位符与默认值
    • 复杂占位符组合
  • std::bind的原理
    • std::bind 的设计思路
    • 简化实现示例
  • Bind的陷阱
  • 总结

引言

std::bind 是 C++11 引入的一种函数适配器,能够将一个可调用对象(如函数、成员函数、仿函数或 lambda 表达式)与其支持的部分(或全部)参数绑定生成一个新的可调用对象。基于std::bind,开发者可调整目标函数的参数列表顺序或绑定部分参数,这极大地增强了函数调用的灵活性。

std::bind 的函数声明位于头文件 ,其形式如下:

template< class F, class... Args > 
/* unspecified */ bind( F&& f, Args&&... args );template< class R, class F, class... Args >
/* unspecified */ bind( F&& f, Args&&... args );

参数说明:
• F: 可调用对象(普通函数、静态函数、类成员函数、仿函数或 lambda)。
• Args: 要绑定的参数列表。可以传递具体值或占位符 std::placeholders::_1, std::placeholders::_2, … (表示待传入的参数)。

可调用对象的绑定

Bind可支持普通函数、静态函数、类成员函数、仿函数或 lambda等多种可以调用对象的绑定。

绑定普通函数

对于普通函数,我们可以直接利用 std::bind 绑定部分参数,进而生成一个新的可调用对象。例如:

#include <iostream>
#include <functional>void printMessage(const std::string& message, int repeatCount) 
{for (int i = 0; i < repeatCount; ++i){std::cout << message << std::endl;}
}int main() 
{auto bindPrintMessage = std::bind(printMessage, "Hello, World!", 3);bindPrintMessage(); // 输出三行 "Hello, World!"return 0;
}

绑定静态函数

静态成员函数与普通函数在绑定方式上较为相似,可以直接进行绑定操作。

#include <iostream>
#include <functional>class Logger 
{
public:static void logInfo(const std::string& message) {std::cout << "[INFO]: " << message << std::endl;}
};int main() 
{auto bindLogInfo = std::bind(&Logger::logInfo, "Application started");bindLogInfo(); // 输出: [INFO]: Application startedreturn 0;
}

绑定类成员函数

当绑定类的成员函数时,需要额外传递对象实例的指针作为绑定的一部分。

#include <iostream>
#include <functional>class Calculator 
{
public:void add(int a, int b) const {std::cout << "Sum: " << (a + b) << std::endl;}
};int main() 
{Calculator calculator;auto bindAdd = std::bind(&Calculator::add, &calculator, 5, 7);bindAdd(); // 输出: Sum: 12return 0;
}

绑定仿函数

仿函数是通过重载 operator() 实现的对象行为,亦可以与 std::bind 结合。

#include <iostream>
#include <functional>struct Multiply 
{void operator()(int a, int b) const {std::cout << "Product: " << (a * b) << std::endl;}
};int main() 
{Multiply multiply;auto bindMultiply = std::bind(multiply, 4, 5);bindMultiply(); // 输出: Product: 20return 0;
}

绑定Lambda

std::bind 也可以绑定 lambda 表达式,这种方式很灵活。

#include <iostream>
#include <functional>int main() 
{auto divide = [](double a, double b) {if (b != 0) {std::cout << "Result: " << (a / b) << std::endl;} else {std::cout << "Error: Division by zero!" << std::endl;}};auto bindDivide = std::bind(divide, 10.0, 2.0);bindDivide(); // 输出: Result: 5return 0;
}

占位符std::placeholders的应用

std::placeholders 是 std::bind 中的重要组成部分,允许用户在绑定函数时动态指定参数位置、调整参数顺序或实现嵌套绑定。通过使用std::placeholders::_1, std::placeholders::_2 等占位符,可以构建灵活的函数调用方式,尤其适合处理动态输入和函数组合逻辑。

嵌套绑定

使用 std::bind将一个函数绑定为另一个函数的参数,我们称之为嵌套绑定。

#include <iostream>
#include <functional>void multiplyAndPrint(int a, int b) 
{std::cout << "Product: " << (a * b) << std::endl;
}void wrapperFunction(std::function<void(int)> func, int value) 
{func(value);
}int main() 
{// 绑定 multiplyAndPrint 的第一个参数为 5,第二个参数为动态传递的值auto multiplyByFive = std::bind(multiplyAndPrint, 5, std::placeholders::_1);// 绑定 wrapperFunction,将 multiplyByFive 作为其固定参数auto bindWrapper = std::bind(wrapperFunction, multiplyByFive, std::placeholders::_1);// 动态传递参数bindWrapper(10); // 输出: Product: 50return 0;
}

参数重排序

通过占位符调整参数的传递顺序。

#include <iostream>
#include <functional>void calculate(int a, int b, int c) 
{std::cout << "Result: " << (a + b * c) << std::endl;
}int main() 
{// 调整参数顺序://  1. std::placeholders::_3表示将调用的第二个参数传递给binder的第三个参数,//  2. std::placeholders::_2表示将调用的第三个参数传递给binder的第二个参数auto reorderedCalculate = std::bind(calculate, std::placeholders::_1, std::placeholders::_3, std::placeholders::_2);reorderedCalculate(2, 3, 4); // 实际调用: calculate(2, 4, 3),输出: Result: 14return 0;
}

结合 STL 算法

将 std::bind 与 STL 算法结合,在容器中处理动态参数。

#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>void printIfGreater(int value, int threshold) 
{if (value > threshold) {std::cout << value << " ";}
}int main() 
{std::vector<int> numbers = {1, 3, 5, 7, 9, 11};// 绑定动态阈值auto bindPrintIfGreater = std::bind(printIfGreater, std::placeholders::_1, 5);std::for_each(numbers.begin(), numbers.end(), bindPrintIfGreater);// 输出: 7 9 11return 0;
}

占位符传递到嵌套函数

占位符可以在多层绑定中传递,适用于复杂嵌套调用场景。

#include <iostream>
#include <functional>void outerFunction(int x, std::function<void(int)> innerFunc) 
{std::cout << "Outer: " << x << std::endl;innerFunc(x + 1);
}void innerFunction(int y) 
{std::cout << "Inner: " << y << std::endl;
}int main() 
{// 绑定 outerFunction 的第一参数为动态传递,第二参数绑定 innerFunctionauto bindOuter = std::bind(outerFunction, std::placeholders::_1, std::bind(innerFunction, std::placeholders::_1));bindOuter(10); // 输出:// Outer: 10// Inner: 11return 0;
}

混合占位符与默认值

结合固定值和动态参数。

#include <iostream>
#include <functional>void greet(const std::string& greeting, const std::string& name, int repeat) 
{for (int i = 0; i < repeat; ++i) {std::cout << greeting << ", " << name << "!" << std::endl;}
}int main() 
{// 固定 greeting 为 "Hello",repeat 为 2,动态传递 nameauto bindGreet = std::bind(greet, "Hello", std::placeholders::_1, 2);bindGreet("Alice"); // 输出:// Hello, Alice!// Hello, Alice!bindGreet("Bob");   // 输出:// Hello, Bob!// Hello, Bob!return 0;
}

复杂占位符组合

结合静态绑定、动态参数和参数顺序调整,展示复杂调用逻辑。

#include <iostream>
#include <functional>void processValues(int x, int y, int z) 
{std::cout << "Processed: " << (x + y - z) << std::endl;
}int main() 
{// 固定 x 为 10,z 为动态传递,y 和 z 的顺序调整auto bindProcess = std::bind(processValues, 10, std::placeholders::_2, std::placeholders::_1);bindProcess(5, 20); // 实际调用: processValues(10, 20, 5)// 输出: Processed: 25return 0;
}

std::bind的原理

std::bind 的设计思路

std::bind 的设计基于 函数对象封装 和 参数转发 的思想。 std::bind 会将目标函数及其参数封装为一个可调用对象(一般是一个类), 该可调用对象重载了 operator(),从而能够模拟函数调用;对于绑定的参数,std::bind 会存储其值,对于动态传递的参数,占位符(如 std::placeholders::_1、std::placeholders::_2)会在调用时将实际参数映射到正确的位置;std::bind 是高度模板化的,其实现依赖于 完美转发 和 类型擦除,从而能够适配不同类型的函数及参数。

简化实现示例

#include <tuple>
#include <utility>
#include <iostream>// 占位符实现
template <int Index>
struct Placeholder
{template <typename... CallArgs>auto operator()(CallArgs&&... callArgs) const{return std::get<Index>(std::forward_as_tuple(callArgs...));}
};// 定义全局占位符
constexpr Placeholder<0> _1{};
constexpr Placeholder<1> _2{};// 简化版 Bind 实现
template <typename Func, typename... BoundArgs>
class Bind
{
public:Bind(Func f, BoundArgs... boundArgs): m_func(std::move(f)), m_boundArgs(std::make_tuple(std::forward<BoundArgs>(boundArgs)...)){}template <typename... CallArgs>auto operator()(CallArgs&&... callArgs){return invoke(std::index_sequence_for<BoundArgs...>{}, std::forward<CallArgs>(callArgs)...);}private:Func m_func;                          // 存储目标函数std::tuple<BoundArgs...> m_boundArgs; // 存储绑定的参数// 调用目标函数template <std::size_t... Indexes, typename... CallArgs>auto invoke(std::index_sequence<Indexes...>, CallArgs&&... callArgs){return m_func(resolve(std::get<Indexes>(m_boundArgs), callArgs...)...);}// 如果参数是值,则直接返回template <typename Arg, typename... CallArgs>static auto resolve(Arg&& arg, CallArgs&&...){return std::forward<Arg>(arg);}// 如果参数是 _1,占位符对应第一个动态传入的参数template <typename... CallArgs>static auto resolve(const Placeholder<0>&, CallArgs&&... callArgs){return std::get<0>(std::forward_as_tuple(callArgs...));}// 如果参数是 _2,占位符对应第二个动态传入的参数template <typename... CallArgs>static auto resolve(const Placeholder<1>&, CallArgs&&... callArgs){return std::get<1>(std::forward_as_tuple(callArgs...));}
};// 辅助函数
template <typename Func, typename... Args>
auto bind(Func&& func, Args&&... args) 
{return Bind<Func, Args...>(std::forward<Func>(func), std::forward<Args>(args)...);
}// 测试普通函数
int add(int a, int b)
{return a + b;
}int main()
{// 绑定 add 函数,第二个参数固定为 10auto boundAdd = bind(add, _1, 10);std::cout << "Result: " << boundAdd(5) << std::endl; // 输出: Result: 15// 绑定 add 函数,两个参数动态传入auto dynamicAdd = bind(add, _2, _1);std::cout << "Result: " << dynamicAdd(10, 5) << std::endl; // 输出: Result: 15return 0;
}

Bind的陷阱

当使用std::bind与std::shared_ptr联合创建绑定对象时,若std::shared_ptr实例作为绑定参数直接传递,会增加其引用计数。若此绑定函数被存储在生命周期较长的对象中(如事件回调或任务队列),可能导致循环引用,进而无法释放相关内存,触发内存泄漏。

示例:异步任务与回调处理

#include <iostream>
#include <functional>
#include <memory>
#include <thread>
#include <chrono>class Worker 
{
public:Worker(const std::string& name) : m_name(name) {}~Worker() {std::cout << "Worker [" << m_name << "] destroyed" << std::endl;}void doWork() {std::cout << "Worker [" << m_name << "] is working..." << std::endl;}void scheduleTask(std::function<void()> task) {// 模拟异步任务执行std::thread([task]() {std::this_thread::sleep_for(std::chrono::seconds(1));task(); // 执行回调}).detach();}private:std::string m_name;
};void example() 
{std::shared_ptr<Worker> worker = std::make_shared<Worker>("TaskWorker");// 使用std::bind捕获shared_ptr,导致引用计数增加worker->scheduleTask(std::bind(&Worker::doWork, worker));// std::bind内部增加了引用计数,阻止worker正常释放std::cout << "Worker scheduled, but not released yet..." << std::endl;std::this_thread::sleep_for(std::chrono::seconds(2));// worker未销毁
}

输出

TimerTask created
Running task...
(未输出 "TimerTask destroyed",表明未释放资源)

我们可通过std::weak_ptr替代std::shared_ptr,动态检查对象是否仍然有效,避免循环引用。

void exampleWithWeakPtr() 
{std::shared_ptr<Worker> worker = std::make_shared<Worker>("TaskWorker");std::weak_ptr<Worker> weakWorker = worker;worker->scheduleTask([weakWorker]() {if (auto sharedWorker = weakWorker.lock()) {sharedWorker->doWork();} else {std::cout << "Worker object no longer exists." << std::endl;}});std::cout << "Worker scheduled, attempting release..." << std::endl;worker.reset(); // 提前释放workerstd::this_thread::sleep_for(std::chrono::seconds(2));// 此时worker已销毁
}

输出

Worker scheduled, attempting release...
Worker [TaskWorker] destroyed
Worker object no longer exists.

总结

std::bind 是 C++11 提供的一个强大工具,用于灵活地处理函数调用。它能够绑定普通函数、成员函数、仿函数或 Lambda 表达式的部分参数,并通过占位符 std::placeholders 动态传递其他参数,从而在参数重排序、函数适配以及嵌套调用中发挥巨大作用。此外,std::bind 可以很好地与标准库算法结合,适用于高阶函数和动态参数传递的场景。

在使用 std::bind 时,需要注意以下几点:

  • 灵活性与复杂性:std::bind 提供了极大的灵活性,但在复杂绑定和嵌套场景中可能会使代码可读性下降。对于简单任务,优先考虑使用 Lambda 表达式。
  • 性能影响:std::bind 会生成额外的中间函数对象,在性能敏感的场景下,需要注意其开销。
  • 占位符顺序:通过 std::placeholders::_n 绑定动态参数时,需明确顺序映射关系,避免参数传递错误。

随着现代 C++ 标准的演化,std::bind 的使用场景逐渐被 Lambda 表达式取代,后者提供了更直观的语法和性能优化。然而,在某些需要函数适配器的场景中,std::bind 仍然是一个值得掌握的工具。

通过对 std::bind 的原理和实现机制的解析,可以更好地理解其设计思路和应用方式,这对深入学习现代 C++ 的函数式编程范式也具有重要意义。

相关文章:

C++11: STL之bind

C11: STL之bind 引言可调用对象的绑定绑定普通函数绑定静态函数绑定类成员函数绑定仿函数绑定Lambda 占位符std::placeholders的应用嵌套绑定参数重排序结合 STL 算法占位符传递到嵌套函数混合占位符与默认值复杂占位符组合 std::bind的原理std::bind 的设计思路简化实现示例 B…...

在线音乐播放器 —— 测试报告

自动化脚本源代码&#xff1a;Java: 利用Java解题与实现部分功能及小项目的代码集合 - Gitee.com 目录 前言 一、项目简介 1.项目背景 2.应用技术 &#xff08;1&#xff09;后端开发 &#xff08;2&#xff09;前端开发 &#xff08;3&#xff09;数据库 二、项目功能…...

等保测评讲解:安全管理中心

在数字化转型的背景下&#xff0c;网络安全的重要性愈发凸显&#xff0c;而作为中国边疆大省的黑龙江&#xff0c;其网络安全建设更是不可忽视。等保测评&#xff0c;即信息安全等级保护测评&#xff0c;是确保信息系统安全的关键环节。本文将详细讲解黑龙江等保测评中的安全管…...

vue3表单输入相关修饰符使用

在 Vue 3 中&#xff0c;.lazy、.number 和 .trim 是用于 v-model 指令的修饰符&#xff0c;它们可以帮助你在双向绑定时进行特定的处理。 1. .lazy 修饰符 .lazy 修饰符表示只在 input 事件之后触发更新&#xff0c;即输入框的内容发生变化后&#xff0c;只有在用户**失去焦…...

CSS笔记(二)类名复用

这里我通过两张不同位置的卡片来实现效果 代码 <!DOCTYPE html> <html><head><style>/*设置画布*/body{/* 方便排列与对齐*/display: flex; /*画布布满整个窗口*/height: 100vh;/*水平居中*/justify-content: center;/*垂直居中*/align-items: cente…...

TCP三次握手与四次挥手(TCP重传机制,2MSL)超详细!!!计算机网络

本篇是关于3次握手和四次挥手的详细解释~ 如果对你有帮助&#xff0c;请点个免费的赞吧&#xff0c;谢谢汪。&#xff08;点个关注也可以&#xff01;&#xff09; 如果以下内容需要补充和修改&#xff0c;请大家在评论区多多交流~。 目录 1. TCP头部&#xff1a; 2. 三次握手…...

LCR 006. 两数之和 II - 输入有序数组

一.题目&#xff1a; LCR 006. 两数之和 II - 输入有序数组 - 力扣&#xff08;LeetCode&#xff09; 二.我的原始解法-暴力解法超时&#xff1a; class Solution: def twoSum(self, numbers: List[int], target: int) -> List[int]: # 暴力解法 result [] for i in rang…...

网络安全在现代企业中的重要作用

网络安全是这个数字时代最令人担忧的事情之一。对技术的依赖性越来越强&#xff0c;使其同时面临多种网络威胁。其声誉和法律后果的大幅下降可能归因于一次妥协。 这使得良好的网络安全成为所有企业的选择和必需品。本文介绍了网络安全的重要性、企业中常见的网络威胁以及公司…...

关于 EKS Bottlerocket AMI 版本与 Karpenter 配置的说明

问题1: Bottlerocket AMI 版本问题 之前,后端团队发现在使用 Bottlerocket v1.26.2 AMI 版本时,存在某些问题。经过 Bottlerocket 团队调查,此行为是罕见的 race condition 导致的结果。 我们在环境中重现了此状况,并且关注到由于 kubelet device manager 的启动时间晚于 NVI…...

Python实现人生重开模拟器

目录 人生重开模拟器介绍 代码实现 打印初始界面 设置初始属性 设置角色性别 设置角色出生点 针对每一岁&#xff0c;生成人生经历 完整代码 人生重开模拟器介绍 人生重开模拟器 是之前比较火的一个小游戏&#xff0c;我们这里使用 Python 实现一个简化版的 人生重开模…...

java——Spring Boot的配置加载顺序和优先级

Spring Boot的配置加载顺序和优先级是确定应用程序如何读取和应用配置的关键。以下是对Spring Boot配置加载顺序和优先级的详细解释&#xff1a; 一、配置加载顺序 命令行参数&#xff1a; Spring Boot会首先加载命令行中指定的参数。这些参数可以通过在命令行中使用--keyval…...

【21-30期】Java技术深度剖析:从分库分表到微服务的核心问题解析

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;Java &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; 文章题目&#xff1a;Java技术深度剖析&#xff1a;从分库分表到微服务的核心问题解析 摘要&#xff1a; 本…...

CSS:怎么把网站都变成灰色

当大家看到全站的内容都变成了灰色&#xff0c;包括按钮、图片等等。这时候我们可能会好奇这是怎么做到的呢&#xff1f; 有人会以为所有的内容都统一换了一个 CSS 样式&#xff0c;图片也全换成灰色的了&#xff0c;按钮等样式也统一换成了灰色样式。但你想想这个成本也太高了…...

开发一个基于MACOS M1/2芯片的Android 12的模拟器

产品需求&#xff1a;MuMu模拟器Pro_率先适配Apple M系列芯片的安卓模拟器 苹果M芯片专属&#xff1a;产品专为苹果M系列芯片设计&#xff0c;意味着它需要能够充分利用M系列芯片的性能优势。 安卓模拟器&#xff1a;产品是一个安卓模拟器&#xff0c;允许用户在Mac设备上运行…...

Flink 中 JDBC Connector 使用详解

1. 背景 在实时计算或离线任务中&#xff0c;往往需要与关系型数据库交互&#xff0c;例如 MySQL、PostgreSQL 等。Apache Flink 提供了 JDBC Connector&#xff0c;可以方便地将流式数据写入或读取数据库。 本文将介绍 Flink JDBC Connector 的基础用法、配置方法以及注意事…...

【Linux打怪升级记 | 报错02】-bash: 警告:setlocale: LC_TIME: 无法改变区域选项 (zh_CN.UTF-8)

&#x1f5fa;️博客地图 &#x1f4cd;1、报错发现 &#x1f4cd;2、原因分析 &#x1f4cd;3、解决办法 &#x1f4cd;4、测试结果 1、报错发现 装好了CentOS操作系统&#xff0c;使用ssh远程登陆CentOS&#xff0c;出现如下告警信息&#xff1a; bash: 警告:setlocale…...

未来已来?AI技术革新改变我们的生活

在21世纪的今天&#xff0c;人工智能&#xff08;AI&#xff09;不再是一个遥远的概念&#xff0c;而是逐渐渗透到我们生活的方方面面。从智能家居到自动驾驶汽车&#xff0c;从个性化推荐系统到医疗诊断辅助&#xff0c;AI技术正在以惊人的速度发展&#xff0c;并深刻地影响着…...

【Linux】进程的生命之旅——诞生、消逝与守候(fork/exit/wait)

&#x1f3ac; 个人主页&#xff1a;谁在夜里看海. &#x1f4d6; 个人专栏&#xff1a;《C系列》《Linux系列》《算法系列》 ⛰️ 一念既出&#xff0c;万山无阻 目录 &#x1f4d6;一、进程创建 1.fork函数 &#x1f4da;高层封装特性 &#x1f4da;fork返回值 2.写时拷…...

使用vcpkg自动链接tinyxml2时莫名链接其他库(例如boost)

使用vcpkg自动链接tinyxml2时莫名链接其他库&#xff08;例如boost&#xff09; vcpkg的自动链接功能非常方便&#xff0c;但在某些情况下会出现过度链接的问题。 链接错误症状 以tinyxml2为例&#xff0c;程序中调用tinyxml2的函数后&#xff0c;若vcpkg中同时存在opencv和…...

【去毛刺】OpenCV图像处理基础:腐蚀与膨胀操作入门

在数字图像处理中&#xff0c;形态学操作是一种常用的技术&#xff0c;用于提取图像中的特定形状或特征。其中&#xff0c;腐蚀&#xff08;Erosion&#xff09;和膨胀&#xff08;Dilation&#xff09;是两种基本的形态学运算。本文将通过一个简单的例子来演示如何使用Python中…...

智慧医疗能源事业线深度画像分析(上)

引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

docker详细操作--未完待续

docker介绍 docker官网: Docker&#xff1a;加速容器应用程序开发 harbor官网&#xff1a;Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台&#xff0c;用于将应用程序及其依赖项&#xff08;如库、运行时环…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)

目录 1.TCP的连接管理机制&#xff08;1&#xff09;三次握手①握手过程②对握手过程的理解 &#xff08;2&#xff09;四次挥手&#xff08;3&#xff09;握手和挥手的触发&#xff08;4&#xff09;状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...

el-switch文字内置

el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

Python如何给视频添加音频和字幕

在Python中&#xff0c;给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加&#xff0c;包括必要的代码示例和详细解释。 环境准备 在开始之前&#xff0c;需要安装以下Python库&#xff1a;…...

有限自动机到正规文法转换器v1.0

1 项目简介 这是一个功能强大的有限自动机&#xff08;Finite Automaton, FA&#xff09;到正规文法&#xff08;Regular Grammar&#xff09;转换器&#xff0c;它配备了一个直观且完整的图形用户界面&#xff0c;使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

Docker 本地安装 mysql 数据库

Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker &#xff1b;并安装。 基础操作不再赘述。 打开 macOS 终端&#xff0c;开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

Selenium常用函数介绍

目录 一&#xff0c;元素定位 1.1 cssSeector 1.2 xpath 二&#xff0c;操作测试对象 三&#xff0c;窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四&#xff0c;弹窗 五&#xff0c;等待 六&#xff0c;导航 七&#xff0c;文件上传 …...

NPOI Excel用OLE对象的形式插入文件附件以及插入图片

static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...