1. 从“删除成员”说起:为什么这个小功能会让新手卡壳
用易语言写程序,数组是绕不开的数据结构,而“删除数组成员”又是数组操作里最容易出幺蛾子的一个功能。不少刚接触易语言的朋友,第一次写批量删除就把下标搞乱套了——明明删的是第三个,结果第五个被误删;或者用计次循环删成员,越删越错,最后直接数组越界崩溃。这篇教程就把删除成员这件事彻底拆开,讲清楚删除指定成员的底层逻辑、两种具体写法(单删和批量删)、以及藏在细节里的那些坑。
我默认你能看懂易语言的基本语法,知道数组是什么,会新建一个窗口程序并把数组变量加进去。如果你连这些基础还不太熟,建议先随手建一个编辑框和一个按钮,在按钮事件里写代码,一行行跟着敲,这篇全讲实操,跑了代码看效果才能记住。
2. 先搞懂“删除成员”背后的两条底层逻辑
2.1 数组成员被删除之后,会发生什么
很多新手以为删除数组成员就是把那个格子擦掉,后面都往前挪。这个理解大方向对,但有个关键细节:易语言的“删除成员”是物理删除,不是仅仅标记成空。
举个例子,你有个数组arr = {1, 2, 3, 4, 5},执行了删除成员 (arr, 3, 1)之后,数组变成{1, 2, 4, 5},原来下标为4和5的值往前补位,数组的成员数由5变成4。这个“前移”就是关键:删除你指定的那一个成员之后,所有位于它后面的成员下标都会减1。
如果你在循环里删除成员,下标一变,就极容易漏删或越界。这就像排队打饭,叫走一个人,后面所有人都往前走一步。你原本记好的“我下一个要找第5个人”,结果他已经是第4个了。
2.2 下标重排带来的一切麻烦,都源于同一件事
下标重排是数组删除操作的底层规律,所有“删除指定成员”的问题,本质上都是下标追踪问题。
比如这个常见场景:你想把数组里所有偶数删掉。你用计次循环(正向)去遍历数组,每遇到一个偶数就删除,看上去很顺理成章。但实际跑起来你会发现,有些偶数根本没被检查到,甚至删完之后数组里还留着偶数。原因就在于:你删了一个成员后,后面的成员整体前移,而循环变量还在原来的位置继续加1,等于跳过了紧挨过来的那个成员。
处理这类问题有两种经典思路:反向循环和整体筛选重建。二者我后面都会详细写,这里先记住一个核心原则:只要在循环里删成员,必须考虑下标重排带来的偏移。
3. 删除数组成员的三种核心方式与选型思路
3.1 方法一:直接指定下标——删除成员 (数组变量, 下标, 1)
这是最基础、最直接的删除方式,也是易语言内置的“删除成员”命令。语法长这样:
删除成员 (数组变量, 下标, 删除数量)第一个参数是你要操作的数组变量,第二个参数是起始下标,第三个参数是从这个下标开始连续删几个成员。如果只想删一个,第三个参数写1就行。
用这段代码来体验:
.版本 2 .程序集 窗口程序集_启动窗口 .子程序 _按钮1_被单击 .局部变量 数组, 整数型, , "10" .局部变量 i, 整数型 计次循环首 (10, i) 数组 [i] = i 计次循环尾 删除成员 (数组, 4, 1) ' 此时的数组变成:1, 2, 3, 5, 6, 7, 8, 9, 10 ' 数组成员数变成9看起来毫无难度。参数一目了然:数组、从第几个开始删、删几个。但实际项目中我见过一堆人在这里栽跟头,不是因为命令不会用,而是因为不知道下标是否存在就删。万一数组长度只有3,你写了个“删除成员 (数组, 4, 1)”,程序直接给你报一个“数组引用超出范围”的错,严重时直接闪退。
所以在删除前,务必判断数组的成员数(取数组成员数)是否大于等于待删除的下标。判断逻辑后面我会统一写。
3.2 方法二:赋值空数组重建——适合批量筛选的“笨但稳”方案
如果你需要批量删除大量成员,比如删除所有小于0的值,或者删除所有包含指定关键词的行,直接直接逐条删除会非常麻烦,下标偏移问题也容易出错。这时候换个思路:重建数组。
思路是新建一个临时的空数组,遍历原数组,把不需要删除的成员按顺序填进去,最后用临时数组覆盖原数组。这个方案的代码量略多,但逻辑铁稳,不会出现下标越界或漏删。
示例代码:
.版本 2 .程序集 窗口程序集_启动窗口 .子程序 _按钮1_被单击 .局部变量 原数组, 整数型, , "0" .局部变量 新数组, 整数型, , "0" .局部变量 i, 整数型 .局部变量 数组长度, 整数型 原数组 = {1, -2, 3, -4, 5, -6, 7, 8} 数组长度 = 取数组成员数 (原数组) 计次循环首 (数组长度, i) 如果真 (原数组 [i] > 0) 加入成员 (新数组, 原数组 [i]) 如果真结束 计次循环尾 重定义数组 (原数组, 假, 取数组成员数 (新数组)) 计次循环首 (取数组成员数 (新数组), i) 原数组 [i] = 新数组 [i] 计次循环尾注意里面有个细节:重定义数组那一步是必要的,否则原数组的成员数还是旧的,直接赋值会越界。此外,如果新数组本来定义就是0长度,加入成员会自动扩充,这点很方便。
3.3 方法三:传入“数值匹配”的自动删除——用数组_删除成员命令
易语言本身的命令库里没有直接的“按内容删除”命令,但精易模块、超级模块等常见扩展模块里提供了一些封装好的数组操作函数,例如数组_删除成员、数组_删除重复、数组_取成员数等。这些模块如果你项目里有现成引用,能用则用,省事不少;但如果只是写个小工具或学习阶段,不建议为了删个数组专门引入一个模块,增加体积不说,还容易对模块产生依赖。等基础功打牢了再按需引用模块,这是比较稳的学习路径。
引入模块后的删除方式类似:
数组_删除成员 (数组, 下标)有些模块支持传入多次出现的内容然后符号标记删除,具体看模块自带说明。不过无论用什么封装,底层逻辑还是刚才说的那套:删除后下标前移,批量删除仍要考虑遍历方式。
4. 实操环节:从单个指定删除到批量删除的完整实现
4.1 搭一个可视化测试环境
学习这类数组操作,我强烈建议别在脑袋里硬想,直接搭一个测试窗口,用编辑框把每一步的数组变化打出来。理由很简单:你能亲眼看到删除前和删除后的对比,下标偏移的规律一下子就清晰了。
测试界面长这样:
- 一个编辑框(多行,只读,用来显示结果)
- 一个按钮 “单删测试”
- 一个按钮 “批量删除负数”
- 一个按钮 “删除指定内容成员”
先给编辑框属性里把“是否只读”设为真,多行文本设为真,滚动条设为纵向。这样输出的结果不会被手误改到,看长结果也舒服。
4.2 单删指定成员:判断、删除、输出一条龙
完整代码可以这样写:
.版本 2 .程序集 窗口程序集_启动窗口 .子程序 _按钮_单删测试_被单击 .局部变量 数组, 整数型, , "0" .局部变量 长度, 整数型 .局部变量 删除位置, 整数型 .局部变量 结果文本, 文本型 .局部变量 i, 整数型 数组 = {10, 20, 30, 40, 50} 删除位置 = 2 长度 = 取数组成员数 (数组) 如果真 (删除位置 > 长度) 编辑框1.内容 = “删除失败:下标超出范围” 返回 如果真结束 删除成员 (数组, 删除位置, 1) 结果文本 = “删除后数组为:” 计次循环首 (取数组成员数 (数组), i) 结果文本 = 结果文本 + 到文本 (数组 [i]) + “ ” 计次循环尾 编辑框1.内容 = 结果文本 ' 输出:删除后数组为:10 30 40 50这里面有三个习惯值得强调:
- 删除位置先做合法性判断——“数组引用超出范围”这种错误99%是没做前置判断导致的。凡是操作源自我外部传入的下标,一律先跟数组长度比一下。
- 删除前可以把原数组内容打印出来,这样对比更直观。
- 删除之后用取数组成员数重新动态计算长度,不要继续用之前存的长度变量,否则循环输出时会越界。
4.3 批量删除指定内容:正向循环、反向循环、筛选重建三种写法的对比
现在代码场景升级:数组内容为{1, -2, 3, -4, 5, -6, 7, -8},要求把所有负数删掉。
4.3.1 先给反例:正向循环为什么不行
先说清楚正向循环的翻车过程,很多朋友卡就卡在“自己写了半天找不到哪里错了”。
.版本 2 .子程序 _按钮_正向错误示例_被单击 .局部变量 数组, 整数型, , "0" .局部变量 i, 整数型 数组 = {1, -2, 3, -4, 5, -6, 7, -8} 计次循环首 (取数组成员数 (数组), i) 如果真 (数组 [i] < 0) 删除成员 (数组, i, 1) 如果真结束 计次循环尾这段代码执行完,结果会让你懵:数组变成{1, 3, 5, -6, 7},负数竟然没删干净。
原因走一遍:
- i=1,值1不是负数,不删
- i=2,值是-2,删掉,数组变成 {1, 3, -4, 5, -6, 7, -8}
- i=3,此刻下标3的值是3(原先下标4的-4已经前移到了下标3),但循环继续走 i=4,所以下标3的-4根本没被检查到
- 于是 -4 被漏掉
也就是说,正向循环配合删除操作,在连续出现待删内容时,后面那个就会被跳过去。
4.3.2 解法一:反向循环,从尾部往头部删
这是最常用也最推荐的批量删除方式。反向循环之所以不会被下标偏移坑到,是因为你删掉一个成员后,受影响的是这个下标后面的所有元素,而反向循环下一次要访问的下标比当前更小,前面没动过,路径安全。
代码:
.版本 2 .子程序 _按钮_反向删除_被单击 .局部变量 数组, 整数型, , "0" .局部变量 i, 整数型 数组 = {1, -2, 3, -4, 5, -6, 7, -8} 变量循环首 (取数组成员数 (数组), 1, -1, i) 如果真 (数组 [i] < 0) 删除成员 (数组, i, 1) 如果真结束 变量循环尾 ' 结果:{1, 3, 5, 7}注意这里用的是变量循环首,三个参数依次是初值(原最大下标)、终值(1)、步长(-1)。倒着删的过程中,下标只减不增,所有待处理的元素都在当前下标之前,前移不干扰后面的事,逻辑安全。
4.3.3 解法二:筛选重建,遍历时只留需要的
重建数组的思路前面已经讲过,优点是逻辑直观、不会越界、不管数组多大都稳;缺点是额外占用一份内存、要多写几行代码。对小规模数组来说,这份内存开销可以忽略不计。
实际项目中,如果删除逻辑比较复杂(例如同时满足多个条件才删除),我更喜欢筛选重建。你只需要把“保留”条件写清楚,就不用费劲反向去想了。
.版本 2 .子程序 _按钮_筛选中删除_被单击 .局部变量 原数组, 整数型, , "0" .局部变量 新数组, 整数型, , "0" .局部变量 i, 整数型 原数组 = {1, -2, 3, -4, 5, -6, 7, -8} 计次循环首 (取数组成员数 (原数组), i) 如果真 (原数组 [i] > 0) 加入成员 (新数组, 原数组 [i]) 如果真结束 计次循环尾 原数组 = 新数组 ' 结果:{1, 3, 5, 7}最后那行原数组 = 新数组在易语言里是允许的,数组变量可以直接整体赋值。这比之前先重定义再逐成员复制省事得多。
4.3.4 解法三:关键词判断后删除文本型数组
有时候你要删除的不是数值,而是文本型数组里包含某个关键词的成员。比如从一段日志行的集合里删掉所有包含“DEBUG”的行。数值数组和文本数组在删除逻辑上完全一致,只是判断条件从< 0变成了寻找文本 (数组 [i], “要删除的关键词”, 1, 假) != -1。
示例:
.版本 2 .子程序 _按钮_删除包含关键词_被单击 .局部变量 文本数组, 文本型, , "0" .局部变量 i, 整数型 文本数组 = {“正常日志”, “DEBUG-启动”, “运行结果正常”, “DEBUG-结束”} 变量循环首 (取数组成员数 (文本数组), 1, -1, i) 如果真 (寻找文本 (文本数组 [i], “DEBUG”, 1, 假) ≠ -1) 删除成员 (文本数组, i, 1) 如果真结束 变量循环尾 ' 结果:{“正常日志”, “运行结果正常”}这个写法在文本处理场景特别实用。比如采集回来的邮件列表、日志列表、账号列表,想按关键词筛掉一部分,直接套这个模板改条件即可。
5. 项目级案例:从一个完整应用里看删除的取舍
光讲语法容易看过就忘。我拿自己处理过的某个模拟项目举例:某跨平台系统的本地配置读取模块里,需要在启动时读取一组服务器地址,过滤掉失效地址和重复地址,再参与后续连接检测。这里就同时用到了“删除指定成员”和“批量删除”。
配置文件里的原始地址数组是这样的:
192.168.1.10:8080 192.168.1.10:8080 10.0.0.5:9000 (空行) 192.168.1.22:8000需求是:
- 删除空行成员
- 删除重复成员,只保留第一次出现的那个
- 删除指定前缀的地址(例如局域网内某网段全部排除)
这里三步对应的做法分别是:
第一步:删除空行
变量循环首 (取数组成员数 (地址数组), 1, -1, i) 如果真 (地址数组 [i] = “”) 删除成员 (地址数组, i, 1) 如果真结束 变量循环尾第二步:删除重复成员
“删除重复”本质上是“删除指定内容的成员”的进阶版,判断条件变成:如果当前成员在前面已经出现过,则删除。因为是遍历到后面才判断前面是否出现过,所以用正向计次循环配合一个“判断是否已经出现过”的子程序,或者倒过来用反向循环也完全可行。
一段简洁写法(反向循环+子程序判断):
.版本 2 .子程序 _按钮_去重_被单击 .局部变量 地址数组, 文本型, , "0" .局部变量 i, 整数型 .局部变量 j, 整数型 变量循环首 (取数组成员数 (地址数组), 2, -1, i) 变量循环首 (i - 1, 1, -1, j) 如果真 (地址数组 [i] = 地址数组 [j]) 删除成员 (地址数组, i, 1) 跳出循环 如果真结束 变量循环尾 变量循环尾注意内层循环一旦删了当前成员,就直接跳出循环,避免再和更前面的成员做对比,因为下标已经变了,继续对比会出错。这一步是很多人栽过没想明白的地方。
第三步:删除指定前缀地址
比如要把所有10.0.0.开头的地址排除:
变量循环首 (取数组成员数 (地址数组), 1, -1, i) 如果真 (寻找文本 (地址数组 [i], “10.0.0.”, 1, 假) = 1) 删除成员 (地址数组, i, 1) 如果真结束 变量循环尾这里逻辑是寻找文本返回值等于1,说明关键词出现在文本最开头。匹配前缀比单纯包含更严格,适合按网段排除的场景。
三步走完,再去重、再删除,最后得到的干净地址列表继续参与后续端口探测。这个案例虽然小,但已经把“删除指定成员”“批量解除重复”“按关键词过滤”全串起来了。你能自己复现一遍,对删除操作的理解会扎实很多。
6. 常见问题与排查技巧:我从踩坑现场总结的避坑清单
6.1 数组下标越界,程序直接报错
这是删除数组成员的高频错误,基本都出在没做下标检查。删除前先判断删除位置是否处于有效范围内:
如果真 (删除位置 < 1 或 删除位置 > 取数组成员数 (数组)) 返回 如果真结束如果删除位置是变量,多确认几遍这个变量在传入之前有没有可能被算成0或负数。易语言数组下标从1开始,写0就崩,这和某些从0开始的编程语言不一样,别惯性思维。
6.2 循环删除时漏删成员
这是批量删除最经典的问题,原因前面已经分析过:正向循环+删除导致下标前移,跳过了紧随其后的成员。排查方法很简单,每删一次就把数组内容打印一次,观察下标变化。
解决方案只有两条路:反向循环,或者筛选重建。没有第三条所谓“取巧”的路,学到这里就死记这两条。
6.3 删除之后使用旧的数组长度变量
删除操作会改变数组成员数,如果删除后你继续用之前保存的数组长度做循环或赋值,轻则忽略最后一个成员,重则越界。正确做法是:任何时候需要长度,直接用取数组成员数 (数组)现场取。
我见过一个奇葩但常见的bug:删除后用一个变量保存长度,后来往数组里追加了新成员,长度变量没更新,循环输出时漏掉了最后几个。负载虽小,排查起来却费时。
6.4 删除文本型数组时找不到“内容”
文本型成员中间可能有不可见字符、空格、换行符,导致你用寻找文本判断时返回-1。排查办法:把成员内容用字节集形式打印出来,看看有没有空格、换行、制表符干扰。
建议删除前先用删首尾空 (数组 [i])做一次清洗,把成员内容规范化,再判断关键词,这样漏判率大大降低。
6.5 重定义数组后忘记填第三参数
易语言里重定义数组 (数组, 假, 新长度)第二个参数表示是否保留原有内容,第三参数是重定义后的最大下标。新手有时候只填两个参数,编译不过去,或者运行时数组莫名其妙变空。
这个不算复杂,但确实很常见。建议删除操作尽量走“整体赋值数组变量=新数组”这条更省心的路,少直接和重定义打交道。
6.6 批量删除“指定所有成员都删除”时结果变成空数组
比如你写了一个循环,把数组里所有成员都删掉了。此时取数组成员数返回0,数组变量变成一个空数组。这不是错误,是正常状态。但如果后续代码直接用数组 [1],就会越界。
判断数组是否为空,用取数组成员数=0来判断。空数组的遍历要单独做好保护,加一个如果真 (取数组成员数 (数组) = 0) 提前返回,避免后面无效操作。
7. 从删除数组成员到程序思维的进阶提示
这个小功能看着不起眼,但把“删除”吃透之后,你会发现自己在处理循环和集合数据时的思路会宽很多。很多进阶操作,比如数组去重、数组排序、多条件筛选、分页显示,底层都会反复用到删除或重建的逻辑。
如果还想继续深入,我建议你做这几个练习:
- 把数组里所有连续的重复成员只保留一个(注意是连续重复,不是全部去重)
- 删除数组中所有包含指定多个关键词之一的成员
- 将删除后的数组重新排序并保持下标连续
- 自定义一个数组类型的“栈”,用删除成员实现“出栈”操作
我自己的感受是,前三个练习能帮你彻底摆脱“数组越界恐惧症”,最后一个练习会让你理解数据结构的基本封装思路。做两周这类练习之后,处理复杂数据时你对下标的敏感度会明显提升,写着写着就能预判哪些地方会越界、哪些地方顺序不对了。可能这就是所谓的手感吧。
最后再多提一句:如果你用的是带扩展模块的易语言环境,模块工具看着方便,但排查问题时反而多一层黑盒。基础数组操作我建议先自己手写几遍再上模块,心里有底,出问题也敢断定是模块的锅还是自己的锅。