易语言子程序高级应用:递归、回调与参数设计实战

易语言子程序高级应用:递归、回调与参数设计实战

易语言子程序高级应用:递归、回调与参数设计实战

在这里插入图片描述

一、学习目标与重点

💡学习目标:1. 深入理解易语言子程序的递归调用机制与实现条件;2. 掌握参数传递的高级形式(引用传递、数组参数、自定义数据类型参数);3. 学会使用回调函数实现程序模块间的解耦;4. 初步了解多线程环境下的子程序调用(线程同步基础);5. 通过真实案例(递归计算斐波那契数列、文件目录遍历、员工薪资计算系统的模块解耦)巩固所学知识。
⚠️学习重点:递归的退出条件设置、栈溢出风险规避、引用传递与值传递的区别、数组/UDT参数的类型声明、回调函数的指针实现方法、线程同步的临界区使用。


二、递归调用机制与实现

2.1 递归的基本概念与实现条件

2.1.1 递归的基本概念

递归调用是指子程序直接或间接调用自身的过程,常用于解决具有重复子问题的问题(如计算阶乘、斐波那契数列、遍历文件目录、二叉树操作等)。

2.1.2 递归的实现条件
  1. 终止条件(退出条件):必须存在至少一个条件,当满足该条件时,递归调用停止,返回具体值;
  2. 递归公式(递推关系):必须存在一个公式,将原问题分解为规模更小的子问题,并通过子问题的解得到原问题的解。

2.2 递归调用的经典案例

2.2.1 计算阶乘(非优化版)

阶乘的数学公式为:n! = 1×2×3×…×n(n≥0),其中0! = 1。

.版本 2 .子程序 计算阶乘_递归, 整数型, 公开, 递归计算阶乘,参数n≥0 .参数 n, 整数型, , 要计算阶乘的整数 .局部变量 结果, 整数型 ' 终止条件 如果真 (n = 0 或 n = 1) 结果 = 1 返回 (结果) 如果真结束 ' 递归公式:n! = n × (n-1)! 结果 = n × 计算阶乘_递归 (n - 1) 返回 (结果) 

测试结果:调试输出(计算阶乘_递归(5)),输出120,符合预期。

2.2.2 计算斐波那契数列(非优化版与记忆化搜索优化版)

斐波那契数列的数学公式为:F(n) = F(n-1) + F(n-2)(n≥2),其中F(0)=0,F(1)=1。

非优化版(时间复杂度O(2ⁿ),空间复杂度O(n))
.版本 2 .子程序 计算斐波那契_递归_非优化, 整数型, 公开, 递归计算斐波那契数列(非优化版),参数n≥0 .参数 n, 整数型, , 斐波那契数列的第n项 .局部变量 结果, 整数型 ' 终止条件 如果真 (n = 0) 结果 = 0 返回 (结果) 如果真结束 如果真 (n = 1) 结果 = 1 返回 (结果) 如果真结束 ' 递归公式:F(n) = F(n-1) + F(n-2) 结果 = 计算斐波那契_递归_非优化 (n - 1) + 计算斐波那契_递归_非优化 (n - 2) 返回 (结果) 

⚠️性能问题:当n=40时,计算时间约为1秒;当n=50时,计算时间约为100秒,因为存在大量重复计算。

记忆化搜索优化版(时间复杂度O(n),空间复杂度O(n))

通过创建一个全局或程序集变量的“记忆数组”,存储已经计算过的斐波那契数列项,避免重复计算。

.版本 2 ' 程序集数据段定义的记忆数组 .程序集变量 斐波那契记忆数组, 长整数型, 数组, , 用于存储已经计算过的斐波那契数列项 .子程序 计算斐波那契_递归_记忆化, 长整数型, 公开, 递归计算斐波那契数列(记忆化搜索优化版),参数n≥0 .参数 n, 整数型, , 斐波那契数列的第n项 .局部变量 结果, 长整数型 ' 预分配记忆数组的内存(根据n的大小动态调整) .如果 (取数组成员数 (斐波那契记忆数组) ≤ n) 重定义数组 (斐波那契记忆数组, 真, n + 1) .如果结束 ' 检查记忆数组中是否已经存在该值 .如果 (斐波那契记忆数组 [n + 1] ≠ 0) ' 数组索引从1开始,初始值为0 返回 (斐波那契记忆数组 [n + 1]) .如果结束 ' 终止条件 .如果 (n = 0) 结果 = 0 .否则 .如果 (n = 1) 结果 = 1 .否则 ' 递归公式:F(n) = F(n-1) + F(n-2) 结果 = 计算斐波那契_递归_记忆化 (n - 1) + 计算斐波那契_递归_记忆化 (n - 2) .如果结束 .如果结束 ' 将结果存入记忆数组中 斐波那契记忆数组 [n + 1] = 结果 返回 (结果) 

性能对比:当n=50时,非优化版计算时间约为100秒,优化版计算时间小于1毫秒。


2.3 递归调用的栈溢出风险与规避方法

递归调用是基于函数栈(stack)实现的,每次调用都会在栈中压入一个“栈帧”(包含参数、局部变量、返回地址等信息)。当递归深度过大(如n=10000)时,栈会溢出,导致程序崩溃。

2.3.1 栈溢出风险的示例
.版本 2 .子程序 模拟栈溢出_递归, , 公开 .参数 递归深度, 整数型, 可空 .局部变量 临时变量, 文本型, 静态, "1000" ' 静态数组,每个栈帧占用较大内存 ' 模拟递归深度的增加 如果真 (递归深度 = 0 或 递归深度 = 空) 递归深度 = 1 如果真结束 ' 输出当前递归深度 调试输出 (“当前递归深度:”, 递归深度) ' 继续递归调用 模拟栈溢出_递归 (递归深度 + 1) 

⚠️运行结果:当递归深度约为1000时,程序会崩溃,提示“栈溢出”。

2.3.2 栈溢出风险的规避方法
  1. 设置合理的递归深度限制:在递归调用前检查递归深度,当超过限制时停止调用;
  2. 使用迭代法替代递归:将递归问题转化为循环问题(如计算阶乘、斐波那契数列都可以用迭代法实现);
  3. 优化栈帧大小:减少局部变量的数量和大小,避免使用静态数组和大量文本型变量。

2.4 迭代法替代递归的示例

2.4.1 计算阶乘的迭代法
.版本 2 .子程序 计算阶乘_迭代, 整数型, 公开, 迭代计算阶乘,参数n≥0 .参数 n, 整数型, , 要计算阶乘的整数 .局部变量 结果, 整数型 .局部变量 i, 整数型 ' 初始化结果 结果 = 1 ' 循环计算阶乘 计次循环首 (n, i) 结果 = 结果 × i 计次循环首结束 ' 处理n=0的情况 如果真 (n = 0) 结果 = 1 如果真结束 返回 (结果) 
2.4.2 计算斐波那契数列的迭代法
.版本 2 .子程序 计算斐波那契_迭代, 长整数型, 公开, 迭代计算斐波那契数列,参数n≥0 .参数 n, 整数型, , 斐波那契数列的第n项 .局部变量 结果, 长整数型 .局部变量 i, 整数型 .局部变量 前一项, 长整数型 .局部变量 前两项, 长整数型 ' 处理n=0和n=1的情况 .如果 (n = 0) 结果 = 0 .否则 .如果 (n = 1) 结果 = 1 .否则 ' 初始化前两项 前两项 = 0 前一项 = 1 ' 循环计算第n项 i = 2 判断循环首 (i ≤ n) 结果 = 前一项 + 前两项 ' 更新前两项 前两项 = 前一项 前一项 = 结果 i = i + 1 判断循环尾 () .如果结束 .如果结束 返回 (结果) 

三、参数传递的高级形式

3.1 值传递与引用传递的区别

3.1.1 值传递

值传递是易语言参数传递的默认方式,它会复制实参的值到形参中,形参的修改不会影响实参的值。

.版本 2 .子程序 交换两个整数_值传递, , 公开, 尝试交换两个整数(值传递,无法实现交换) .参数 a, 整数型, , 要交换的第一个整数 .参数 b, 整数型, , 要交换的第二个整数 .局部变量 临时变量, 整数型 ' 尝试交换a和b的值 临时变量 = a a = b b = 临时变量 ' 输出形参的值(此时a和b的值已交换) 调试输出 (“形参a的值:”, a) 调试输出 (“形参b的值:”, b) 
.版本 2 .子程序 _测试值传递按钮_被单击 .局部变量 x, 整数型 .局部变量 y, 整数型 ' 初始化x和y的值 x = 10 y = 20 ' 输出实参的初始值 调试输出 (“实参x的初始值:”, x) 调试输出 (“实参y的初始值:”, y) ' 调用值传递的交换子程序 交换两个整数_值传递 (x, y) ' 输出实参的最终值(此时x和y的值未交换) 调试输出 (“实参x的最终值:”, x) 调试输出 (“实参y的最终值:”, y) 

运行结果:形参a和b的值已交换,但实参x和y的值未交换。


3.1.2 引用传递

引用传递需要在参数声明时使用参考关键字(英文为ByRef),它会将实参的内存地址传递到形参中,形参的修改会直接影响实参的值。

.版本 2 .子程序 交换两个整数_引用传递, , 公开, 交换两个整数(引用传递,能够实现交换) .参数 a, 整数型, 参考, 要交换的第一个整数(引用传递) .参数 b, 整数型, 参考, 要交换的第二个整数(引用传递) .局部变量 临时变量, 整数型 ' 交换a和b的值(直接修改实参的内存地址) 临时变量 = a a = b b = 临时变量 ' 输出形参的值(此时a和b的值已交换) 调试输出 (“形参a的值:”, a) 调试输出 (“形参b的值:”, b) 
.版本 2 .子程序 _测试引用传递按钮_被单击 .局部变量 x, 整数型 .局部变量 y, 整数型 ' 初始化x和y的值 x = 10 y = 20 ' 输出实参的初始值 调试输出 (“实参x的初始值:”, x) 调试输出 (“实参y的初始值:”, y) ' 调用引用传递的交换子程序 交换两个整数_引用传递 (x, y) ' 输出实参的最终值(此时x和y的值已交换) 调试输出 (“实参x的最终值:”, y) ' 输出20 调试输出 (“实参y的最终值:”, x) ' 输出10 

运行结果:实参x和y的值已交换,形参的值也已交换。


3.2 数组参数的类型声明与使用

在易语言中,数组作为参数传递时,默认也是值传递,但由于数组是一个指针类型的变量(存储数组的首地址),所以形参对数组元素的修改会影响实参对数组元素的访问。

3.2.1 一维数组参数的类型声明与使用
.版本 2 .子程序 计算数组元素的和_一维, 整数型, 公开, 计算一维数组所有元素的和 .参数 目标数组, 整数型, 数组, , 要计算和的一维数组 .局部变量 和, 整数型 .局部变量 索引, 整数型 ' 遍历数组并计算和 计次循环首 (取数组成员数 (目标数组), 索引) 和 = 和 + 目标数组 [索引] 计次循环首结束 返回 (和) 
.版本 2 .子程序 _测试一维数组参数按钮_被单击 .局部变量 测试数组, 整数型, 数组 .局部变量 数组元素的和, 整数型 ' 初始化测试数组 重定义数组 (测试数组, 真, 5) 测试数组 [1] = 1 测试数组 [2] = 2 测试数组 [3] = 3 测试数组 [4] = 4 测试数组 [5] = 5 ' 调用计算和的子程序 数组元素的和 = 计算数组元素的和_一维 (测试数组) ' 输出结果 调试输出 (“一维数组所有元素的和:”, 数组元素的和) 

运行结果:一维数组所有元素的和:15,符合预期。


3.2.2 二维数组参数的类型声明与使用
.版本 2 .子程序 计算二维数组元素的和, 整数型, 公开, 计算二维数组所有元素的和 .参数 目标二维数组, 整数型, 数组, , 要计算和的二维数组 .局部变量 和, 整数型 .局部变量 行索引, 整数型 .局部变量 列索引, 整数型 ' 遍历二维数组并计算和 行索引 = 1 判断循环首 (行索引 ≤ 取数组成员数 (目标二维数组)) 列索引 = 1 判断循环首 (列索引 ≤ 取数组成员数 (目标二维数组 [行索引])) 和 = 和 + 目标二维数组 [行索引] [列索引] 列索引 = 列索引 + 1 判断循环尾 () 行索引 = 行索引 + 1 判断循环尾 () 返回 (和) 
.版本 2 .子程序 _测试二维数组参数按钮_被单击 .局部变量 测试二维数组, 整数型, 数组 .局部变量 二维数组元素的和, 整数型 ' 初始化测试二维数组 重定义数组 (测试二维数组, 真, 2, 3) 测试二维数组 [1] [1] = 1 测试二维数组 [1] [2] = 2 测试二维数组 [1] [3] = 3 测试二维数组 [2] [1] = 4 测试二维数组 [2] [2] = 5 测试二维数组 [2] [3] = 6 ' 调用计算和的子程序 二维数组元素的和 = 计算二维数组元素的和 (测试二维数组) ' 输出结果 调试输出 (“二维数组所有元素的和:”, 二维数组元素的和) 

运行结果:二维数组所有元素的和:21,符合预期。


3.3 自定义数据类型(UDT)参数的类型声明与使用

自定义数据类型(UDT)作为参数传递时,默认也是值传递(会复制整个UDT变量的内存内容),如果UDT变量的成员非常多或包含大量数组,会导致参数传递效率低下。此时可以使用引用传递来提高效率。

3.3.1 UDT参数的值传递
.版本 2 .数据类型 员工信息类型 .成员 员工编号, 文本型 .成员 员工姓名, 文本型 .成员 员工年龄, 整数型 .成员 员工部门, 文本型 .成员 员工薪资, 双精度小数型 .子程序 修改员工薪资_值传递, , 公开, 尝试修改员工薪资(值传递,无法实现修改) .参数 员工信息, 员工信息类型, , 要修改薪资的员工信息(值传递) .参数 调整金额, 双精度小数型, , 薪资调整金额(正数为增加,负数为减少) ' 尝试修改员工薪资 员工信息.员工薪资 = 员工信息.员工薪资 + 调整金额 ' 输出形参的员工薪资 调试输出 (“形参的员工薪资:”, 员工信息.员工薪资) 
.版本 2 .子程序 _测试UDT值传递按钮_被单击 .局部变量 测试员工信息, 员工信息类型 ' 初始化测试员工信息 测试员工信息.员工编号 = “YG202X001” 测试员工信息.员工姓名 = “张三” 测试员工信息.员工年龄 = 25 测试员工信息.员工部门 = “技术部” 测试员工信息.员工薪资 = 5000.0 ' 输出实参的初始员工薪资 调试输出 (“实参的初始员工薪资:”, 测试员工信息.员工薪资) ' 调用修改薪资的子程序(值传递) 修改员工薪资_值传递 (测试员工信息, 500.0) ' 输出实参的最终员工薪资(此时薪资未修改) 调试输出 (“实参的最终员工薪资:”, 测试员工信息.员工薪资) 

运行结果:形参的员工薪资已修改为5500.0,但实参的最终员工薪资仍为5000.0。


3.3.2 UDT参数的引用传递
.版本 2 .数据类型 员工信息类型 .成员 员工编号, 文本型 .成员 员工姓名, 文本型 .成员 员工年龄, 整数型 .成员 员工部门, 文本型 .成员 员工薪资, 双精度小数型 .子程序 修改员工薪资_引用传递, , 公开, 修改员工薪资(引用传递,能够实现修改) .参数 员工信息, 员工信息类型, 参考, 要修改薪资的员工信息(引用传递) .参数 调整金额, 双精度小数型, , 薪资调整金额(正数为增加,负数为减少) ' 修改员工薪资(直接修改实参的内存地址) 员工信息.员工薪资 = 员工信息.员工薪资 + 调整金额 ' 输出形参的员工薪资 调试输出 (“形参的员工薪资:”, 员工信息.员工薪资) 
.版本 2 .子程序 _测试UDT引用传递按钮_被单击 .局部变量 测试员工信息, 员工信息类型 ' 初始化测试员工信息 测试员工信息.员工编号 = “YG202X001” 测试员工信息.员工姓名 = “张三” 测试员工信息.员工年龄 = 25 测试员工信息.员工部门 = “技术部” 测试员工信息.员工薪资 = 5000.0 ' 输出实参的初始员工薪资 调试输出 (“实参的初始员工薪资:”, 测试员工信息.员工薪资) ' 调用修改薪资的子程序(引用传递) 修改员工薪资_引用传递 (测试员工信息, 500.0) ' 输出实参的最终员工薪资(此时薪资已修改) 调试输出 (“实参的最终员工薪资:”, 测试员工信息.员工薪资) 

运行结果:实参和形参的员工薪资都已修改为5500.0。


四、回调函数的原理与易语言实现

4.1 回调函数的基本概念

回调函数是指被另一个函数(调用者)作为参数传递,并在适当的时候由调用者执行的函数,常用于实现程序模块间的解耦(如排序算法的比较函数、事件处理函数、数据处理的钩子函数等)。


4.2 易语言中回调函数的实现方法

易语言中没有直接的“函数指针”类型,但可以通过指针类型的变量(如整数型指针、字节集指针)或用户自定义事件来实现回调函数的功能。

4.2.1 使用用户自定义事件实现回调函数(事件驱动型回调)
.版本 2 .支持库 eGrid ' 程序集数据段定义的用户自定义事件 .程序集事件 数据处理完成事件_回调, , 公开, 数据处理完成后触发的回调事件 .参数 处理结果, 整数型, , 数据处理的结果 .程序集变量 数据处理完成事件句柄, 整数型, , 用于存储用户自定义事件的句柄 .子程序 初始化数据处理完成事件, , 公开, 初始化用户自定义事件 .局部变量 事件ID, 整数型 ' 注册用户自定义事件 事件ID = 注册用户自定义事件 (“数据处理完成事件_回调”) 数据处理完成事件句柄 = 事件ID ' 输出事件ID 调试输出 (“数据处理完成事件_回调的事件ID:”, 事件ID) 
.版本 2 .支持库 eGrid .子程序 数据处理_调用者, , 公开, 模拟数据处理的调用者,处理完成后触发回调事件 .参数 数据, 整数型, , 要处理的数据 .局部变量 处理结果, 整数型 ' 模拟数据处理过程(耗时1秒) 延迟 (1000) ' 计算处理结果 处理结果 = 数据 × 2 ' 触发回调事件 ' 第一个参数:控件句柄(如果是全局事件,可以传0) ' 第二个参数:事件ID ' 第三个参数及以后:事件的参数 发送用户消息 (0, 数据处理完成事件句柄, 0, 处理结果) 
.版本 2 .支持库 eGrid .子程序 _数据处理完成事件_回调, , 公开, 数据处理完成事件_回调的处理函数 .参数 处理结果, 整数型, , 数据处理的结果 ' 输出处理结果 调试输出 (“数据处理完成!处理结果:”, 处理结果) ' 显示提示信息 信息框 (“数据处理完成!处理结果:” + 到文本 (处理结果), 0, “提示”) 
.版本 2 .支持库 eGrid .子程序 _测试事件驱动型回调按钮_被单击 .局部变量 测试数据, 整数型 ' 初始化用户自定义事件 初始化数据处理完成事件 () ' 初始化测试数据 测试数据 = 10 ' 调用数据处理的调用者 数据处理_调用者 (测试数据) 

运行结果:延迟1秒后,输出数据处理完成的结果为20,并显示提示信息。


4.2.2 使用指针类型的变量实现回调函数(指针驱动型回调)

这种方法需要使用易语言的“指针操作”命令,较为复杂,但性能较高,常用于处理大量数据的场景。

.版本 2 .支持库 spec .支持库 EThread ' 程序集数据段定义的回调函数指针类型 .程序集类型 数据处理回调函数指针, 子程序, 公开, 数据处理回调函数的指针类型 .参数 处理结果, 整数型, , 数据处理的结果 .子程序 数据处理_指针驱动型, , 公开, 模拟数据处理的调用者,使用指针驱动型回调 .参数 数据, 整数型, , 要处理的数据 .参数 回调函数指针, 整数型, , 回调函数的指针 .局部变量 处理结果, 整数型 .局部变量 回调函数, 数据处理回调函数指针, 静态 ' 模拟数据处理过程(耗时0.5秒) 延迟 (500) ' 计算处理结果 处理结果 = 数据 × 3 ' 调用回调函数 ' 将回调函数指针转换为子程序变量 指针到子程序 (回调函数, 回调函数指针) ' 调用回调函数 回调函数 (处理结果) 
.版本 2 .支持库 spec .支持库 EThread .子程序 数据处理完成_指针驱动型回调, , 公开, 指针驱动型回调的处理函数 .参数 处理结果, 整数型, , 数据处理的结果 ' 输出处理结果 调试输出 (“指针驱动型数据处理完成!处理结果:”, 处理结果) ' 显示提示信息 信息框 (“指针驱动型数据处理完成!处理结果:” + 到文本 (处理结果), 0, “提示”) 
.版本 2 .支持库 spec .支持库 EThread .子程序 _测试指针驱动型回调按钮_被单击 .局部变量 测试数据, 整数型 .局部变量 回调函数地址, 整数型 ' 初始化测试数据 测试数据 = 20 ' 获取回调函数的地址 回调函数地址 = 取子程序地址 (&数据处理完成_指针驱动型回调) ' 调用数据处理的调用者 数据处理_指针驱动型 (测试数据, 回调函数地址) 

运行结果:延迟0.5秒后,输出数据处理完成的结果为60,并显示提示信息。


五、多线程环境下的子程序调用

5.1 多线程的基本概念与易语言实现

多线程是指在同一时间内,一个程序可以同时执行多个任务(线程),提高程序的运行效率。在易语言中,可以使用“多线程支持库”来实现多线程的功能。

5.1.1 多线程的简单示例
.版本 2 .支持库 EThread .子程序 线程任务_1, , 公开, 线程任务1(输出1-10) .局部变量 i, 整数型 ' 输出1-10 计次循环首 (10, i) 调试输出 (“线程任务1输出:”, i) 延迟 (200) ' 模拟任务的耗时 计次循环首结束 
.版本 2 .支持库 EThread .子程序 线程任务_2, , 公开, 线程任务2(输出11-20) .局部变量 i, 整数型 ' 输出11-20 i = 11 判断循环首 (i ≤ 20) 调试输出 (“线程任务2输出:”, i) 延迟 (300) ' 模拟任务的耗时 i = i + 1 判断循环尾 () 
.版本 2 .支持库 EThread .子程序 _测试多线程按钮_被单击 .局部变量 线程ID1, 整数型 .局部变量 线程ID2, 整数型 ' 创建线程任务1 线程ID1 = 启动线程 (&线程任务_1, , ) ' 创建线程任务2 线程ID2 = 启动线程 (&线程任务_2, , ) ' 输出线程ID 调试输出 (“线程任务1的ID:”, 线程ID1) 调试输出 (“线程任务2的ID:”, 线程ID2) 

运行结果:线程任务1和线程任务2会交替输出1-20,体现了多线程的并发执行特性。


5.2 多线程的线程同步基础(临界区的使用)

在多线程环境下,多个线程可能会同时访问共享资源(如全局变量、程序集变量、文件等),导致数据不一致的问题(即“线程安全问题”)。为了解决这个问题,我们需要使用线程同步机制,如临界区、互斥锁、信号量等。

5.2.1 线程安全问题的示例
.版本 2 .支持库 EThread ' 程序集数据段定义的共享资源 .程序集变量 共享变量, 整数型, , 共享变量,初始值为0 .子程序 线程任务_3, , 公开, 线程任务3(对共享变量进行+1操作) .局部变量 i, 整数型 .局部变量 临时变量, 整数型 ' 对共享变量进行10000次+1操作 计次循环首 (10000, i) ' 读取共享变量的值 临时变量 = 共享变量 ' 模拟任务的耗时 延迟 (1) ' 对临时变量进行+1操作 临时变量 = 临时变量 + 1 ' 将临时变量的值写回共享变量 共享变量 = 临时变量 计次循环首结束 
.版本 2 .支持库 EThread .子程序 线程任务_4, , 公开, 线程任务4(对共享变量进行+1操作) .局部变量 i, 整数型 .局部变量 临时变量, 整数型 ' 对共享变量进行10000次+1操作 计次循环首 (10000, i) ' 读取共享变量的值 临时变量 = 共享变量 ' 模拟任务的耗时 延迟 (1) ' 对临时变量进行+1操作 临时变量 = 临时变量 + 1 ' 将临时变量的值写回共享变量 共享变量 = 临时变量 计次循环首结束 
.版本 2 .支持库 EThread .子程序 _测试线程安全问题按钮_被单击 .局部变量 线程ID1, 整数型 .局部变量 线程ID2, 整数型 ' 初始化共享变量 共享变量 = 0 ' 创建线程任务1 线程ID1 = 启动线程 (&线程任务_3, , ) ' 创建线程任务2 线程ID2 = 启动线程 (&线程任务_4, , ) ' 等待两个线程任务完成 ' 简化处理,等待5秒 延迟 (5000) ' 输出共享变量的最终值 调试输出 (“共享变量的最终值:”, 共享变量) ' 预期值为20000,但实际值约为10000左右 

运行结果:共享变量的最终值约为10000左右,远小于预期值20000,说明存在线程安全问题。


5.2.2 使用临界区解决线程安全问题

临界区是一种简单的线程同步机制,它允许同一时间内只有一个线程进入临界区,访问共享资源。

.版本 2 .支持库 EThread ' 程序集数据段定义的共享资源和临界区变量 .程序集变量 共享变量_临界区, 整数型, , 共享变量,初始值为0 .程序集变量 临界区对象, 整数型, , 临界区对象的句柄 .子程序 初始化临界区, , 公开, 初始化临界区对象 ' 创建临界区对象 临界区对象 = 创建临界区 () 
.版本 2 .支持库 EThread .子程序 线程任务_5, , 公开, 线程任务5(对共享变量进行+1操作,使用临界区) .局部变量 i, 整数型 .局部变量 临时变量, 整数型 ' 对共享变量进行10000次+1操作 计次循环首 (10000, i) ' 进入临界区 进入临界区 (临界区对象) ' 读取共享变量的值 临时变量 = 共享变量_临界区 ' 模拟任务的耗时 延迟 (1) ' 对临时变量进行+1操作 临时变量 = 临时变量 + 1 ' 将临时变量的值写回共享变量 共享变量_临界区 = 临时变量 ' 离开临界区 离开临界区 (临界区对象) 计次循环首结束 
.版本 2 .支持库 EThread .子程序 线程任务_6, , 公开, 线程任务6(对共享变量进行+1操作,使用临界区) .局部变量 i, 整数型 .局部变量 临时变量, 整数型 ' 对共享变量进行10000次+1操作 计次循环首 (10000, i) ' 进入临界区 进入临界区 (临界区对象) ' 读取共享变量的值 临时变量 = 共享变量_临界区 ' 模拟任务的耗时 延迟 (1) ' 对临时变量进行+1操作 临时变量 = 临时变量 + 1 ' 将临时变量的值写回共享变量 共享变量_临界区 = 临时变量 ' 离开临界区 离开临界区 (临界区对象) 计次循环首结束 
.版本 2 .支持库 EThread .子程序 _测试临界区按钮_被单击 .局部变量 线程ID1, 整数型 .局部变量 线程ID2, 整数型 ' 初始化临界区对象 初始化临界区 () ' 初始化共享变量 共享变量_临界区 = 0 ' 创建线程任务1 线程ID1 = 启动线程 (&线程任务_5, , ) ' 创建线程任务2 线程ID2 = 启动线程 (&线程任务_6, , ) ' 等待两个线程任务完成 ' 简化处理,等待25秒 延迟 (25000) ' 输出共享变量的最终值 调试输出 (“共享变量的最终值(使用临界区):”, 共享变量_临界区) ' 预期值为20000,实际值为20000 

运行结果:共享变量的最终值为20000,符合预期,说明临界区成功解决了线程安全问题。


六、真实案例:员工薪资计算系统的模块解耦与递归应用

6.1 系统功能需求

员工薪资计算系统的功能需求包括:

  1. 员工信息管理(添加、删除、修改、查询员工信息);
  2. 薪资计算(基础薪资、绩效薪资、加班薪资、社保公积金扣除、个人所得税计算);
  3. 薪资查询(按部门查询、按员工编号查询、按薪资范围查询);
  4. 薪资报表生成(导出Excel文件、打印报表);
  5. 数据批量处理(导入员工信息JSON文件);
  6. 递归计算(计算部门总薪资)。

6.2 系统核心代码实现(重点展示模块解耦与递归应用)

6.2.1 部门总薪资的递归计算(嵌套UDT的应用)
.版本 2 .数据类型 部门信息类型 .成员 部门编号, 文本型 .成员 部门名称, 文本型 .成员 直接下属部门数组, 部门信息类型, 数组, , 嵌套的UDT数组,用于存储直接下属部门的信息 .成员 部门员工信息数组, 员工信息类型, 数组, , 部门员工信息数组 .子程序 计算部门总薪资_递归, 双精度小数型, 公开, 递归计算部门总薪资(包括直接下属部门和部门员工的薪资) .参数 目标部门信息, 部门信息类型, 参考, 要计算总薪资的目标部门信息(引用传递,提高效率) .局部变量 总薪资, 双精度小数型 .局部变量 下属部门索引, 整数型 .局部变量 部门员工索引, 整数型 ' 计算目标部门员工的总薪资 计次循环首 (取数组成员数 (目标部门信息.部门员工信息数组), 部门员工索引) 总薪资 = 总薪资 + 计算员工总薪资 (&计算基础薪资, &计算绩效薪资, &计算加班薪资, &计算社保公积金扣除, &计算个人所得税, 目标部门信息.部门员工信息数组 [部门员工索引]) 计次循环首结束 ' 递归计算直接下属部门的总薪资 计次循环首 (取数组成员数 (目标部门信息.直接下属部门数组), 下属部门索引) 总薪资 = 总薪资 + 计算部门总薪资_递归 (目标部门信息.直接下属部门数组 [下属部门索引]) 计次循环首结束 ' 返回部门总薪资 返回 (总薪资) 

6.2.2 员工总薪资的计算(使用回调函数实现模块解耦)

将基础薪资、绩效薪资、加班薪资、社保公积金扣除、个人所得税计算的子程序作为回调函数传递到计算员工总薪资的子程序中,实现薪资计算方式的灵活替换。

.版本 2 .支持库 spec .支持库 EThread ' 程序集数据段定义的回调函数指针类型 .程序集类型 基础薪资计算回调函数指针, 双精度小数型, 公开, 基础薪资计算回调函数的指针类型 .参数 员工信息, 员工信息类型, 参考, 员工信息(引用传递) .程序集类型 绩效薪资计算回调函数指针, 双精度小数型, 公开, 绩效薪资计算回调函数的指针类型 .参数 员工信息, 员工信息类型, 参考, 员工信息(引用传递) .程序集类型 加班薪资计算回调函数指针, 双精度小数型, 公开, 加班薪资计算回调函数的指针类型 .参数 员工信息, 员工信息类型, 参考, 员工信息(引用传递) .程序集类型 社保公积金扣除计算回调函数指针, 双精度小数型, 公开, 社保公积金扣除计算回调函数的指针类型 .参数 员工信息, 员工信息类型, 参考, 员工信息(引用传递) .程序集类型 个人所得税计算回调函数指针, 双精度小数型, 公开, 个人所得税计算回调函数的指针类型 .参数 应纳税所得额, 双精度小数型, , 应纳税所得额 .子程序 计算基础薪资, 双精度小数型, 公开, 计算基础薪资(示例:固定基础薪资) .参数 员工信息, 员工信息类型, 参考, 员工信息(引用传递) .局部变量 基础薪资, 双精度小数型 ' 示例:根据员工部门和职位计算基础薪资 .如果 (员工信息.员工部门 = “技术部”) .如果 (员工信息.员工职位 = “高级工程师”) 基础薪资 = 15000.0 .否则 .如果 (员工信息.员工职位 = “中级工程师”) 基础薪资 = 10000.0 .否则 .如果 (员工信息.员工职位 = “初级工程师”) 基础薪资 = 6000.0 .如果结束 .如果结束 .如果结束 .否则 .如果 (员工信息.员工部门 = “销售部”) 基础薪资 = 5000.0 ' 销售部基础薪资较低,主要靠绩效 .否则 基础薪资 = 4000.0 ' 其他部门基础薪资统一为4000 .如果结束 .如果结束 返回 (基础薪资) 
.版本 2 .支持库 spec .支持库 EThread .子程序 计算员工总薪资, 双精度小数型, 公开, 计算员工总薪资(使用回调函数实现模块解耦) .参数 基础薪资计算回调, 整数型, , 基础薪资计算回调函数的指针 .参数 绩效薪资计算回调, 整数型, , 绩效薪资计算回调函数的指针 .参数 加班薪资计算回调, 整数型, , 加班薪资计算回调函数的指针 .参数 社保公积金扣除计算回调, 整数型, , 社保公积金扣除计算回调函数的指针 .参数 个人所得税计算回调, 整数型, , 个人所得税计算回调函数的指针 .参数 员工信息, 员工信息类型, 参考, 员工信息(引用传递) .局部变量 基础薪资, 双精度小数型 .局部变量 绩效薪资, 双精度小数型 .局部变量 加班薪资, 双精度小数型 .局部变量 社保公积金扣除, 双精度小数型 .局部变量 应纳税所得额, 双精度小数型 .局部变量 个人所得税, 双精度小数型 .局部变量 总薪资, 双精度小数型 .局部变量 基础薪资计算函数, 基础薪资计算回调函数指针, 静态 .局部变量 绩效薪资计算函数, 绩效薪资计算回调函数指针, 静态 .局部变量 加班薪资计算函数, 加班薪资计算回调函数指针, 静态 .局部变量 社保公积金扣除计算函数, 社保公积金扣除计算回调函数指针, 静态 .局部变量 个人所得税计算函数, 个人所得税计算回调函数指针, 静态 ' 将回调函数指针转换为子程序变量 指针到子程序 (基础薪资计算函数, 基础薪资计算回调) 指针到子程序 (绩效薪资计算函数, 绩效薪资计算回调) 指针到子程序 (加班薪资计算函数, 加班薪资计算回调) 指针到子程序 (社保公积金扣除计算函数, 社保公积金扣除计算回调) 指针到子程序 (个人所得税计算函数, 个人所得税计算回调) ' 计算各部分薪资 基础薪资 = 基础薪资计算函数 (员工信息) 绩效薪资 = 绩效薪资计算函数 (员工信息) 加班薪资 = 加班薪资计算函数 (员工信息) 社保公积金扣除 = 社保公积金扣除计算函数 (员工信息) ' 计算应纳税所得额 应纳税所得额 = 基础薪资 + 绩效薪资 + 加班薪资 - 社保公积金扣除 - 5000.0 ' 5000.0为起征点 ' 计算个人所得税(应纳税所得额≤0时,个人所得税为0) .如果 (应纳税所得额 ≤ 0) 个人所得税 = 0.0 .否则 个人所得税 = 个人所得税计算函数 (应纳税所得额) .如果结束 ' 计算总薪资 总薪资 = 基础薪资 + 绩效薪资 + 加班薪资 - 社保公积金扣除 - 个人所得税 ' 输出各部分薪资 调试输出 (“员工编号:”, 员工信息.员工编号) 调试输出 (“基础薪资:”, 基础薪资) 调试输出 (“绩效薪资:”, 绩效薪资) 调试输出 (“加班薪资:”, 加班薪资) 调试输出 (“社保公积金扣除:”, 社保公积金扣除) 调试输出 (“应纳税所得额:”, 应纳税所得额) 调试输出 (“个人所得税:”, 个人所得税) 调试输出 (“总薪资:”, 总薪资) ' 返回总薪资 返回 (总薪资) 

七、总结

💡本章总结:本章主要介绍了易语言子程序的高级应用,包括递归调用机制与实现(经典案例、栈溢出风险规避)、参数传递的高级形式(值传递与引用传递的区别、数组参数、UDT参数)、回调函数的原理与易语言实现(事件驱动型、指针驱动型)、多线程环境下的子程序调用(线程同步基础),以及真实案例员工薪资计算系统的模块解耦与递归应用。
⚠️注意事项:在使用递归时,必须设置合理的终止条件,避免栈溢出;在使用引用传递时,要注意对实参的修改会直接影响原始值;在使用指针类型的变量时,要确保指针操作的正确性,避免内存访问错误;在使用多线程时,要注意线程安全问题,使用适当的线程同步机制。
学习成果:通过本章的学习,读者已经掌握了易语言子程序的高级应用,可以开发出功能更强大、结构更清晰、性能更高的易语言程序,并为后续学习面向对象编程(初步)和数据库操作奠定基础。

Read more

uv终极技巧:一招精准指定Python版本,告别版本混乱!

还在为不同项目间Python版本冲突而烦恼?掌握uv的版本指定技巧,让每个项目都运行在“量身定制”的解释器环境中! 摘要 本文将深入解析在使用uv进行Python项目管理时,如何在不同场景下精准指定Python版本。从项目初始化、现有项目版本切换到全局版本管理,你将掌握一套完整的Python版本控制方案,彻底解决“我的代码需要Python 3.9,但系统默认是3.11”这类经典问题。 🎯 为什么需要指定Python版本? 在真实开发中,指定Python版本至关重要: * 依赖兼容性:某些包仅支持特定Python版本 * 团队统一:确保所有开发者使用相同版本 * 生产一致性:避免开发与生产环境版本不一致导致的Bug * 多版本测试:验证代码在不同Python版本下的表现 🚀 三大场景实战指南 场景一:创建新项目时指定版本(最常用) 在项目初始化阶段指定Python版本是最佳实践: # 方式1:使用 --python 参数直接指定 uv init --python 3.9# 这将创建一个使用Python 3.9的新项目# 方式2:指定精确版本 uv in

By Ne0inhk
【C++】 —— 笔试刷题day_15

【C++】 —— 笔试刷题day_15

刷题day_15,继续加油!!! 一、平方数 题目解析 题目给出一个数,让我们找到离它最近的一个平方数,然后输出即可。 算法思路 这道题总体来说还是非常简单的。 这里先来看一种思路,就是从1开始找,找到小于x的最大的平方数l和大于x的最小的平方数r;然后判断r-x和x-l中哪一个最小即可。 但是,主播主播你的这种思路确实能解决问题,但还是太麻烦了,有没有更加简单易上手的方法; 有的兄弟有的,我们不妨来看看这种思路: 我们知道sqrt函数可以对一个数进行开根号预算,它会返回一个double类型是数据; 那我们对这个返回值强转一下,转成整型不就拿到l,再对其加一就拿到了r。 这样我们这个数是在区间[l*l , r*r]内的,我们判断r*r - x和x - l*l哪一个最小即可。 代码实现 #include<iostream>#include<cmath>usingnamespace std;

By Ne0inhk

Visual C++ Redistributable终极指南:一键解决所有运行时组件问题

Visual C++ Redistributable终极指南:一键解决所有运行时组件问题 【免费下载链接】vcredistAIO Repack for latest Microsoft Visual C++ Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist VisualCppRedist AIO项目是解决Microsoft Visual C++运行时组件问题的完整解决方案,涵盖从2005到2022所有主要版本,彻底解决dll文件缺失、软件安装失败等常见问题。这个强大的工具集能够帮助用户快速诊断和修复各类VC++运行时错误,让您的系统环境更加稳定可靠。 🚀 项目核心功能概览 全面覆盖所有版本 VisualCppRedist AIO整合了Microsoft Visual C++ Redistributable运行时的所有重要版本: * 经典版本:2005、2008、2010 * 现代版本:2012、2013、2015-2022 * 扩展组件:Visual

By Ne0inhk

安卓系统层开发之C++与JNI核心技术

轻量化视频生成与Android原生集成:从模型到应用的完整实践 在移动设备上实时生成高质量视频,曾是仅限高端服务器和专业工作站的任务。然而,随着轻量化AI模型的崛起,这一能力正迅速向消费级硬件下沉。Wan2.2-T2V-5B 就是一个典型代表——它以50亿参数的“精巧身材”,实现了在普通安卓设备上秒级生成连贯动态视频的能力。这背后不仅是模型架构的创新,更依赖于高效的系统层设计,尤其是C++与Java之间的无缝协作。 要让这样一个复杂的视频生成引擎在安卓平台上稳定运行,关键在于如何桥接高性能计算与移动应用生态。这就引出了一个核心命题:如何通过JNI(Java Native Interface)将底层C++逻辑安全、高效地暴露给上层Java/Kotlin代码,同时避免常见的性能陷阱和内存泄漏问题。 Wan2.2-T2V-5B 采用的是典型的扩散式生成架构,但其真正亮点在于为移动端量身定制的轻量化策略。整个流程始于一段文本提示,经过文本编码器转化为语义向量后,时间感知模块开始介入,确保帧与帧之间的动作过渡自然流畅。空间解码器负责输出480P分辨率的画面,而运动推理引擎则预测物体的连续轨

By Ne0inhk