易语言返回数组的5种实用方案:从编译报错到工程选型
2026/9/13 16:25:42 网站建设 项目流程

你有过这种经历吗?子程序里数据全部算完了,最后一行想写返回 (数组),结果一编译直接甩你一句:错误(10044): 不能将“整数型 数组”数据转换到“整数型”数据。我早期写易语言的时候,在论坛搜这个问题,翻了几十页帖子,几乎全是新人在问同一件事。

数组返回在易语言里确实是个绕不开的老大难。子程序返回类型下拉框里压根就没有“整数型数组”这个选项,于是大家只能各显神通:有人用全局数组,有人返回数组第一个元素的地址,有人直接把数组成员拼成文本再拆开。方法五花八门,但真到了正式项目里,很多写法都有隐患。

这篇文章我就把易语言里“返回数组”这件事从头到尾讲透。先解释为什么编译器不让你这么写,再给出五种实际可用的方案,包括最推荐的参考数组参数法、硬核的指针法、以及全局变量、自定义数据类型、字节集序列化三个变通路线,最后结合我做过的实际项目聊聊怎么选型。无论你是刚接触易语言的新手,还是已经在写大漠脚本、webbrowser2操作、数据处理这类工具的老手,这篇文章都值得你先收藏再往下看。

1. 先从报错说起:易语言为什么不让你“返回(数组)”

1.1 返回值机制与数组数据结构的矛盾

要理解这个问题,得先搞清楚易语言子程序返回值的工作原理。易语言的子程序在执行完毕时,会把返回值放到调用方预先分配好的一个“返回空间”里。这个返回空间的大小和类型,在编译器看来是固定的、已经是确定的——它更像食堂打饭窗口递出来的一个托盘,托盘多大、能装几份菜,做饭前就定死了。

而数组是一种复杂数据类型。它的长度不固定,成员数可以随时增减,内部还得维护“当前成员数”“分配空间”这类元信息。数组需要的是一整块可以动态伸缩的连续内存,更像一卡车货,不是一个托盘能装下的。所以易语言在语言层面做了个最简单粗暴的决定:返回类型不允许声明为数组,任何想把整个数组塞进返回值的行为,编译直接拒绝。

这也就解释了为什么返回 (数组 [1])能编译通过却不是你想要的——它只是返回了数组的第一个元素,一个普通的整数,当然能塞进返回空间里。

1.2 新手最容易踩的三种错误写法

我在论坛和群里见过太多类似代码,归纳一下有三种典型错误:

  1. 直接返回数组名返回 (局部数组),编译器报错,这是最常见的。
  2. 返回数组元素返回 (局部数组 [1]),编译能过,但调用方只拿到一个值,数组的其余成员全丢了。
  3. 把数组转成文本再返回:比如把整数数组成员拼成"1,2,3,4"返回,调用方再分割文本。这种方法能用,但有两个问题:一是数据类型被降级了,如果数组里存的是字节集、自定义数据类型,拼接还原极其痛苦;二是性能差,大数组反复做文本拼接和分割,效率感人。

这三种写法的共同根源,是试图在“返回值”这条路上一条道走到黑。但易语言早就提供了另一条更合理的路,只是很多教程没讲透——那就是用参数把数据“带出来”。

2. 最推荐的正路:用“参考数组”参数把结果带出来

2.1 标准代码模板:统计区间内质数

“参考数组”是易语言参数定义里一个非常关键的功能。在子程序参数里定义一个数组类型的参数,勾上“参考”,这个参数就不再是传入值的副本,而是直接指向调用方那个数组变量的内存地址。你在子程序里对数组做的所有操作,都会实时反映到外面的变量上。

我直接给一个完整例子。需求是:传入一个数值区间,返回这个区间内所有的质数。

.版本 2 .支持库 spec .子程序 取区间质数, 整数型 .参数 起始值, 整数型 .参数 结束值, 整数型 .参数 结果数组, 整数型, 参考 数组 .局部变量 i, 整数型 .局部变量 j, 整数型 .局部变量 是否为质数, 逻辑型 .局部变量 计算上限, 整数型 ' 先清空结果数组,防止上次的残留数据混进来 清除数组 (结果数组) 如果 (结束值 < 起始值) 返回 (0) 如果结束 计次循环首 (结束值 - 起始值 + 1, i) 是否为质数 = 真 计算上限 = 求平方根 (起始值 + i - 1) 如果 (计算上限 ≥ 2) 变量循环首 (2, 计算上限, 1, j) 如果 ((起始值 + i - 1) % j = 0) 是否为质数 = 假 跳出循环 如果结束 变量循环尾 () 如果结束 如果 (是否为质数) 加入成员 (结果数组, 起始值 + i - 1) 如果结束 计次循环尾 () 返回 (取数组成员数 (结果数组))

调用方这样写就行:

.子程序 测试取质数 .局部变量 质数集合, 整数型, , "0" .局部变量 数量, 整数型 数量 = 取区间质数 (2, 100, 质数集合) 调试输出 (“一共找到”, 数量, “个质数”) 调试输出 (质数集合)

注意第三行.参数 结果数组, 整数型, 参考 数组,中间的“参考”就是传址的意思。子程序内部对结果数组执行清除数组加入成员,实际上操作的就是外面质数集合这块内存。子程序结束后,外面的质数集合已经自动变成结果了。

2.2 参考数组方案的三条使用纪律

方案本身很简单,但我在实际项目里总结了几条纪律,踩过坑才明白。

第一条,子程序开头一定要先清空结果数组。因为调用方可能拿一个已经装过数据的数组来接收结果,如果不清理,数组合并起来会算出一堆错误数据。我上面代码里的清除数组 (结果数组)就是干这个用的。如果子程序可能要返回固定长度的数组,也可以配合重定义数组 (结果数组, 假, 长度)来初始化。

第二条,返回一个整型成员数,能极大提升调用方的便利性。参考数组本身没有“这次到底返回了几个成员”的显式信息,调用方如果不取数组成员数,就得硬编码长度,非常容易越界。所以我的习惯是子程序统一返回成员数,用不上的人忽略返回值也不会影响功能。

第三条,参考数组参数尽量放在参数列表的最后。这虽然不是硬性要求,但代码可读性会好很多。尤其是参数多的时候,调用方一眼看过去,最先看到的是输入条件,最后看到的是输出容器,逻辑非常顺。

这个方案的优点是安全、直观、性能也好,九成场景都够用了。缺点也有:调用看着不像“获取返回值”,必须得先声明一个数组变量再传进去;而且它只能输出“数组”这一种结果,如果你同时想返回一个整数状态和一个数组,就得加参数或者换其他方案。

3. 硬核做法:指针返回与内存操作的原理和坑

3.1 取变量数据地址的正确用法

如果你的数组需要交给外部DLL、模块或者别的语言处理,那参考参数可能就不够用了,这时候得用指针方案。核心命令是取变量数据地址 (),它能拿到一个变量在内存中的首地址,返回一个整数。

给你看一段我用过的代码。场景是从外部接口拿一批数据,整理成数组后,把数组在内存中的位置返回出去,由调用方读取:

.版本 2 .支持库 spec .程序集 程序集1 .程序集变量 结果缓存数组, 整数型, , "0" .子程序 取斐波那契数列地址, 整数型 .参数 项数, 整数型 .局部变量 i, 整数型 清除数组 (结果缓存数组) 重定义数组 (结果缓存数组, 假, 项数) 如果 (项数 ≥ 1) 结果缓存数组 [1] = 0 如果结束 如果 (项数 ≥ 2) 结果缓存数组 [2] = 1 如果结束 变量循环首 (3, 项数, 1, i) 结果缓存数组 [i] = 结果缓存数组 [i - 1] + 结果缓存数组 [i - 2] 变量循环尾 () 返回 (取变量数据地址 (结果缓存数组 [1]))

这里面有个关键点:存放结果的数组必须是程序集变量或者全局变量,不能是子程序内部的局部数组。因为局部数组在子程序执行完的那一刻,内存就被回收了,你返回的地址立刻变成一个悬空指针。外面拿到这个地址去读数据,轻则读到垃圾数据,重则直接让程序崩溃。这个坑我栽过不止一次,调试器里看内存是好的,一编译出去运行就随机崩溃,排查了很久才意识到是变量生命周期的问题。

3.2 指针返回后如何还原数组

拿到地址之后怎么还原成数组?最通用的思路是:地址是数组首元素的内存位置,整数类型占4个字节,所以第N个元素就在地址 + (N - 1) * 4这个位置。你可以用支持库里的内存读取命令或模块命令去逐个取出来,也可以用“指针到字节集”先按长度取出一整块字节,再用取字节集数据按顺序恢复。

我当时是配合精易模块写的,取回来的逻辑大致是:

.子程序 从地址还原整数数组 .参数 数组地址, 整数型 .参数 成员数量, 整数型 .参数 输出数组, 整数型, 参考 数组 .局部变量 数据块, 字节集 .局部变量 i, 整数型 清除数组 (输出数组) 数据块 = 指针到字节集 (数组地址, 成员数量 × 4) 计次循环首 (成员数量, i) 加入成员 (输出数组, 取字节集数据 (数据块, #整数型, (i - 1) × 4 + 1)) 计次循环尾 ()

如果你是直接在易语言内部自己调用自己,其实没必要绕这一圈,参考参数法就够了。指针方案的价值主要体现在两个地方:一是数据要跨DLL、跨模块传递;二是你想把数组暂存在内存里,后续用汇编、C++或者其他工具直接操作这块缓冲区。有这类需求再上指针,否则就是给自己找麻烦。

3.3 悬空指针风险与边界条件

指针方案最大的风险就是内存生命周期。刚才说的是局部数组的问题,还有一个更隐蔽的坑:程序集变量本身是共享的,你返回地址之后,如果子程序再次被调用,结果缓存数组会被重定义,旧地址指向的内容就可能被覆盖。调用方如果没能及时把数据复制出去,拿到的就是下一次调用的结果。

我给你的实操建议是:

  • 指针返回后立刻复制数据,别保存地址过夜;
  • 如果函数可能要并发调用,程序集变量方案就别用了,得用线程局部存储;
  • 32位易语言里整数型固定4字节,算偏移时别用取字节集长度之类的长度去套;
  • 地址如果是0或者异常小,直接返回空数组并记录日志,别硬着头皮读内存。

4. 三种实用变通:全局变量、自定义数据类型、字节集序列化

4.1 程序集变量中转:最快但要注意污染

这可能是很多老易语言程序员的默认做法。子程序里把结果写到一个程序集变量数组,返回值改成整数型表示成员数,调用方再直接访问那个程序集变量。

.版本 2 .程序集 窗口程序集_启动窗口 .程序集变量 公共结果数组, 整数型, , "0" .子程序 计算平方序列, 整数型 .参数 个数, 整数型 .局部变量 i, 整数型 清除数组 (公共结果数组) 计次循环首 (个数, i) 加入成员 (公共结果数组, i × i) 计次循环尾 () 返回 (取数组成员数 (公共结果数组))

调用方只要在调用后读取公共结果数组就行。优点是代码量最少、最容易理解;缺点是“后门”太大——任何子程序都能随手改动这个数组,排查数据被谁污染的时候,得全局搜索所有引用点。而且程序集变量是全局共享的,一旦子程序被重复调用,之前的数据立刻就没了。所以这个方案我只在单线程小工具里用,正式项目里宁可多写几行参考参数,也不图这个省事。

4.2 自定义数据类型“打包”返回

如果你要返回的不只是数组,而是“一个整数状态 + 一个文本 + 一个整数数组”这种组合,自定义数据类型是个好路子。

先定义一个类型,里面塞一个动态数组成员:

.数据类型 整数数组包 .成员 数据, 整数型, , "0" .成员 有效数量, 整数型

然后子程序返回这个类型:

.子程序 生成数组包, 整数数组包 .局部变量 包装, 整数数组包 .局部变量 i, 整数型 计次循环首 (5, i) 加入成员 (包装.数据, i × 10) 计次循环尾 () 包装.有效数量 = 5 返回 (包装)

这个方案的核心思路是:数组本身不能直接作为返回类型,但自定义数据类型可以。你把数组藏到类型成员里,返回整个类型变量,数组自然就跟着一起被带出来了。实践下来这个写法在易语言里是能正常工作的,而且代码结构清晰,特别适合返回值本身包含大量字段的场景。

需要注意一点:数组成员在数据类型里要声明为动态数组,也就是维度填"0"。这样加入成员才能自由扩张成员数。另外返回整个类型变量相当于做了一次结构体拷贝,如果数组特别大,性能会比参考数组差一些。

4.3 字节集序列化:适合持久化和跨进程传递

最后一个思路是序列化。既然数组不能直接返回,那就把它转成一种能返回的类型。字节集就是一个很好的载体。

.子程序 整数数组转字节集, 字节集 .参数 源数组, 整数型, 数组 .局部变量 结果, 字节集 .局部变量 i, 整数型 计次循环首 (取数组成员数 (源数组), i) 结果 = 结果 + 到字节集 (源数组 [i]) 计次循环尾 () 返回 (结果)

转成字节集之后,它可以写到文件、存入数据库、通过网络发给别的进程,甚至扔给加密狗接口做缓冲区处理。对面拿到字节集,再按同样的规则解析回数组。

还原的时候要注意:字节集本身不记录“这个数组有几个成员”。所以如果你要跨进程传递,最好约定一个简单协议,比如前4个字节存成员数,后面每4个字节存一个整数。否则接收方拿到一串字节,根本分不清边界在哪。这里补充说明一下,易语言的取字节集数据 (字节集, #整数型, 索引)是按“从第几个字节开始取”来算的,不是按数组下标,新手很容易搞混。

这个方案性能是三四个方案里最差的,因为涉及大量字节拼接和拷贝,但它获得的灵活度是其他方案给不了的,特别是跨程序、跨设备传数据的时候,几乎是唯一选择。

5. 真实项目里的选型建议:结合大漠、浏览器控件等场景

5.1 不同应用场景对应的最佳方案

代码看完了,最后聊点实际项目里的选择。我平时接的易语言需求,大半和大漠插件、webbrowser2支持库、数据采集相关,这些场景里“返回数组”几乎是天天要用的。比如大漠找多点颜色,返回的是“坐标串”,大多数人先分割文本成文本数组。如果要把这个数组再交给另一个子程序处理,我一般直接定义成参考数组参数传下去。大漠脚本里真正需要指针传数组的很少,除非你在写插件或者对接外部DLL。

webbrowser2操作网页元素时,经常要把网页里所有符合条件的链接、图片地址收集到数组里。这种场景我会优先选自定义数据类型方案或参考数组方案,因为元素信息不只是“一堆字符串”,可能还带着标题、链接、序号等多字段,挨个用全局数组去存能把你乱死,定义一个网页元素信息类型,里面挂个文本数组,返回一个类型变量,干净利落。

至于加密狗、外部硬件接口这类场景,它们往往要求你传入一个固定大小的缓冲区地址或者返回一段原始数据块,这时候字节集序列化方案最合适。先把要提交的数据序列化成字节集,再通过相关命令填入缓冲区;读取响应时,把原始数据块转成字节集再解析。这个思路在处理各种“不明觉厉”的底层接口时非常好使。

为了方便你选,我整理了一张表:

方案实现难度性能最适用场景最大的坑
参考数组参数大部分同进程内传数据忘记清空旧数据
指针返回地址最高跨DLL/模块/底层交互悬空指针、数据被覆盖
全局/程序集变量极低单线程快速原型多线程污染、可排查性差
自定义数据类型多字段复杂结构返回结构体拷贝的开销
字节集序列化持久化、跨进程传数据需要约定成员数协议

5.2 与C语言数组返回方式的对比

学会了易语言的搞法,再看C语言反而有种豁然开朗的感觉。C语言里函数也不能“返回一个数组”,它只能返回指向数组首元素的指针,和易语言的指针方案在原理上完全一致。区别在于,C语言程序员得自己malloc一块内存,返回指针后还要记得free,生命周期管理全靠自觉;而易语言的程序集变量方案相当于帮你用全局内存兜底了,省事,但也容易埋雷。

C语言里数组作为函数参数时会隐式退化为指针,所以子函数对数组的修改天然会作用到外部。这点和易语言的“参考数组”参数几乎一模一样,理解了C的传参退化和数组指针,再看易语言参考数组就一点都不神秘了。

5.3 我的一些经验和小技巧

最后分享几个我在实际项目中沉淀下来的习惯,不一定对所有人都适用,但确实帮我避免了很多返工。

给参考数组参数命名时,我会统一用结果数组返回数据这类前缀明确的命名,一眼就能看出是输出参数。配合返回成员数的约定,整个项目里所有数组输出的子程序风格都是统一的,新接手的同事看代码会非常舒服。

多线程环境下,我坚决不用全局数组方案。曾经有一个网络数据采集工具,开八个线程去抓数据,每个线程都往一个全局数组里写结果,最后抓回来的一千多条记录里混着各种重复和错位,排查了大半天。后来改成每个线程传入独立的参考数组,问题立刻消失。线程安全这种东西,能靠“不共享变量”解决,就别指望运气。

如果数组比较大,比如上万成员,别用文本拼接方案。我见过有人把一万多个坐标拼成字符串,再在另一个子程序里分割文本,整整卡了十几秒。用参考数组或直接操作内存,性能差了一两个数量级。

遇到返回空数组的情况,记得在调用方做判断。比如我这个子程序循环里一个质数都没找到,返回0,外部如果不判断就直接取成员数,很容易误以为自己数据没传进来。防呆设计多做一步,后面少焦虑十分钟。

做易语言开发这些年,绕过的坑确实不少。数组返回这个问题,本质上是“返回值这个通道本身太窄,而数组又太大”之间的矛盾。理解了这个本质,你会发现网络上的各种“黑科技”无非就是换个通道把数据运出去。最推荐的还是参考数组参数,安全、高效、结构清晰,足够应付绝大多数项目;真正需要指针、需要跨进程的时候,再把字节集和自定义数据类型拿出来,按场景选就不会错。

最后再给你一个小技巧:写子程序之前,先想清楚这个数据“从哪来、到哪去、生命周期归谁管”。把这几件事理顺了,用不用数组返回、用哪种方案返回,其实都是顺理成章的事。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询