[新手上路]批处理新手入门导读[视频教程]批处理基础视频教程[视频教程]VBS基础视频教程[批处理精品]批处理版照片整理器
[批处理精品]纯批处理备份&还原驱动[批处理精品]CMD命令50条不能说的秘密[在线下载]第三方命令行工具[在线帮助]VBScript / JScript 在线参考
返回列表 发帖
本帖最后由 qzwqzw 于 2012-11-14 17:55 编辑

setlocal的层级与call的关系是第一次听说
受教了

exit/b只会退出当前函数的call环境

“调用外部函数”要看用什么方式调用了
据我所知call lib :funciton的方式是无法让%~f0获取父脚本名的

标签长度的问题以前咱们讨论过,你大概忘记了
http://www.bathome.net/redirect. ... 5976&pid=104939

说起注释
我觉的这是一个标准化的重要内容
对于注释的形式、内容都应该有一定的规范
比如所有的函数库都必须通过注释注明
修订版本、更新日期、可运行环境 、遵循的接口标准
所有的函数都必须注明函数的用途、用法、已发现的限制等
天的白色影子

TOP

本帖最后由 CrLf 于 2012-11-14 16:42 编辑

回复 13# plp626


    我的习惯是中途不改写,到结束脚本时再统一改写,多变量转存也许可临时定义含有 for 嵌套的变量型函数,有构思,回家试。另,昨天没理解老兄说的一去不返,今天才有点明白,莫非exit/b会同时退出所有子函数?还有,现在想来,调用外部函数时,%~f0是否还能获取父脚本名?手机无法测试,求验证
————————————
我构想中对函数依赖的声明是作为固定格式的注释写给使用者参考的,并不需要实际执行
————————————
函数内部的标签若要标准化将面临标签长度的限制,在8个字符长度的局限下如何同时保证可识别性和唯一性?

TOP

回复 20# qzwqzw


   
另外由于递归带来的另一问题是setlocal的层级限制
所以如果是支持递归的函数
建议不使用setlocal

每次 call 都是独享 32 张表空间的,所以不必担心递归时会超过 setlocal 上限,以前测试时写的一个 内存杀手.bat:
  1. @echo off
  2. %1 start /b cmd /v /c %0 :&exit/b
  3. ::启动一个新的 cmd 打开变量延迟而避免使用 setlocal,并关闭自身
  4. for /f "delims==" %%a in ('set') do set %%a=
  5. set f=for /l %%a in (9 -1)do
  6. %f:9=7% %f:a=b% %f:a=c% %f:a=d% set @%%a%%b%%c%%d=@@@@@@@@@@@@@@@@@@@@@%%a%%b%%c%%d
  7. for /l %%a in (8000 1 8191) do set @%%a=@@@@@@@@@@@@@@@@@@@@@%%a
  8. rem 设置一大堆的变量,总量为 8192*32 字节
  9. :test
  10. set /a f+=1
  11. (set /a f+=31
  12. for /l %%a in (%f% 1 !f!) do (
  13. echo %%a
  14. setlocal
  15. rem 不断 setlocal 开辟新的变量表
  16. ))
  17. pause
  18. call :test
  19. ::每次 call 都拥有独立的内存空间,所以又可以执行 32 个 setlocal...
复制代码

TOP

本帖最后由 qzwqzw 于 2012-11-14 17:45 编辑
是否说明我们也许要另外构造一些专门针对类数组的函数?

这个可以有,但未必是必须的,因为大多数场合下,我们只会对这个类数组进行简单的操作

不太明白老兄对于递归的意图和顾虑

这个你成功的说服了我,确实在函数内部定义引用宿主脚本的环境变量不是必须的
这个我是受了过去代码的思维定势了
%~f0的用法我以前见到过,只不过没有留下印象
现在我对它的印象是比较深刻了

exit /b 我一直有比较大的成见,因为效率太低了
call 脚本中遇到exit/b 退出就一去不返了

我认为exit /b可以用在抛出错误的特定场合
这种场合下“退出就一去不返”是需要的特性
而大多数的脚本错误抛出不会太多次
所以效率是不需要着重考虑的问题

goto :eof 和 exit/b 相比,我觉得无论是效率还是功能都完全处于劣势,个人认为,最好的方案还是使用外置函数,一来不需要花费逐行寻找标签的时间,二来运行到文件末尾时自然会结束函数。

goto :eof仍然应该是大多数函数结束自身的代码
一者是编程者的思维惯性
二者是与外置函数需要重新定位文件相比,它的效率未必低多少
况且goto :eof有着与goto :end不同的处理方式
效率未必会差到哪里

有时候你不得不需要在函数内部中途改写外部变量

这种场合我还没有遇到过
一般来说如果在函数内部改写了外部变量
那么至少在函数结束前这个改写都是有效的
只需要在函数结束前用endlocal&set
那么这个改写就持久化了
除非遇到函数内部以类exit的形式非正常结束
这个改写才是无效的
但如果将类exit的形式定义为只有发生错误才能使用
那么不改写反而符合正常的逻辑

干脆用高级语言的栈空间思想吧, 临时变量函数内部的变量都用栈来维护

栈的结构决定了存取时的复杂性
将这种复杂性普遍应用于整个库上是得不偿失的

14楼变量批量复制的实现不错

递归是迟早要考虑的,一大堆的脚本,互相调用,掉得多了,迟早会遇到有间接调用自己的时候

递归是少部分函数需要考虑的事情
另外凡是支持递归的函数都应该自己设置递归终结
我还看不出递归和声明函数调用依赖之间的关系

另外由于递归带来的另一问题是setlocal的层级限制
所以如果是支持递归的函数
建议不使用setlocal
或者在内部另使用一组标签来进行递归
这个与函数内部if...goto...形式的条件循环类似

由此带来另外一个问题
函数内部的标签命名是否需要标准化
以避免出现多个类似:loop/:menu/:end重复标签的情况发生
天的白色影子

TOP

回复 18# plp626


    只需要声明下级函数就够了,不管更细的分支。如果这个声明的格式足够统一,那么完整的拓扑结构其实可以另外写个独立的脚本来分析

TOP

a 调 b c;
b 调 d e;
c 调 b d a;

函数依赖关系不是树状的, 你会不自觉地陷入间接调用的情况,你要强制不能递归还得额外做些检测工作, 仅不能递归,我觉得就大大削弱库的功能,

一个的格式声明。。。,那么每个函数都会产生额外的解析语句,这一点你是否考虑过

TOP

回复 16# plp626


    只需要按一定的格式声明只能从哪些父函数调用、函数中需要调用到哪些子函数(不考虑间接调用)、引用时是否需要和特定函数按照前后顺序排布就可以了,否则不是很容易在不知情的时候因为勿删具有依赖关系的函数而致错吗?

TOP

本帖最后由 plp626 于 2012-11-14 01:00 编辑

回复 15# CrLf


    递归是迟早要考虑的,一大堆的脚本,互相调用,掉得多了,迟早会遇到有间接调用自己的时候。。

声明函数调用依赖这会使得问题很复杂, 你会充当人肉解释器的感觉。。。

----------------------
关于全局变量:
函数外部变量的修改在库函数实现中 需求强劲;

TOP

回复 13# plp626


    如果要对父函数递归,那么自然有可能出现某个函数调用子函数后再调用父函数甚至父函数的父函数的特殊情况,这样岂不是要像堆栈一样累加保存每一层的函数名?否则如何保证函数互相调用的过程中总是能知道谁是父函数?
    虽说有时确实需要递归,但函数之间的关联太紧密总是违背编程惯例的。题外话,如果非要调用其他函数,那我觉得必须声明与哪些函数具有依赖性。
------------------------------------------------
    学习 htm dom 的过程中我深深地感受到,非必要情况下函数中对全局变量进行修改实在不利于脚本的维护,所以还是 setlocal 和 endlocal 较好,或者用约定的特殊前缀来定义函数内的变量,父脚本避免定义此类变量,仅在函数中使用,退出前还要将其清空以保证变量表是干净的
------------------------------------------------
    goto :eof 和 exit/b 相比,我觉得无论是效率还是功能都完全处于劣势,个人认为,最好的方案还是使用外置函数,一来不需要花费逐行寻找标签的时间,二来运行到文件末尾时自然会结束函数。

TOP

关于批量复制,分享一些技巧。。
  1. :pset [ [/a] <varname=value>;] ...
  2. setlocal enabledelayedexpansion&set "arg= %*"
  3. endlocal&set %arg:;=&set %&goto:eof
复制代码
http://www.bathome.net/redirect.php?goto=findpost&ptid=4725&pid=76283&fromuid=353

TOP

本帖最后由 plp626 于 2012-11-14 01:21 编辑

回复 12# CrLf

我确实忽视了%~f0获取脚步自身路径;
但这里提供一个不太常见的情形:递归,(这也是后续总要面临的棘手问题)
%0, %~f0,总之, 遇到%var%这样预处理获取的值递归调用中,我暂且认为这是批处理的一个缺陷,
我用的方法就是保存变量然后setlocal; 读取的时候用!var!
--------------------------------------------------
对于多值返回,有时候效率会需要考虑,
函数开启setlocal,要想改写父环境变量,唯一的途径就是在endlocal时后才能改写,
带来的问题就是,你无法在开启setlocal的函数内部去改写外部环境变量的值(只能读,不能写),像C语言的指针传出参数用法没戏了;
有时候你不得不需要在函数内部中途改写外部变量,只好endlocal去改写,那么函数内部产生的“局部” 环境变量 也跟着释放了, 这不是我们想要的,
为此,我再三思考 在这个特殊需求的函数中,放弃开启setlocal

我是这个观点偷懒用setlocal, 你就要为效率付出代价。。

我有个大胆的想法,干脆用高级语言的栈空间思想吧, 临时变量函数内部的变量都用栈来维护。。。
------------------------------------
  1. setlocal
  2. set test1=value1
  3. set test2=value2
  4. set test3=value3
  5. endlocal & set ret=%test1% %test2% %test3%
  6. for %%e in (%ret%) do echo %%e
复制代码
确实是个不错的方法,除去考虑效率的问题,在可读性和可维护性上都很好, 下一步就是需要约定一个 特殊变量 前缀的问题,用于维护这类返回值。。
----------------------------------------------

call 脚本中遇到exit/b 退出就一去不返了
exit/b 仅仅用于cmd下的脚本调试,此时已经不是考虑效率的问题了, 子过程返回得用goto:eof

TOP

本帖最后由 CrLf 于 2012-11-13 18:25 编辑

回复 11# qzwqzw


    十分赞同 qzw 对于多返回参数使用指针指向类数组的观点,一直没想到用在这里,确实是个好办法!不过这是否说明我们也许要另外构造一些专门针对类数组的函数?比如清空数组、数组排序、提取与条件匹配的数组等等?
---------------------------------------------------
    不太明白老兄对于递归的意图和顾虑,在子函数中无论何时都可以通过 %0 获取当前函数名、用 %~f0 获取父脚本完整路径(plp看这里~嘿嘿),莫非兄台担忧的是无法在函数的子函数中回调父函数?
---------------------------------------------------
   其实对于 exit /b 我一直有比较大的成见,因为效率太低了,但无奈的是只有他能在函数中任意改变 errorlevel...执保留意见

TOP

本帖最后由 qzwqzw 于 2012-11-13 10:30 编辑

“使用子函数的标签名作为返回值变量名”所带来的变量遍历效率问题
与程序中实际调用的子函数的个数有线性关系
在该个数未达到一定的数量级时对效率的影响应该比较有限
当然如何应用标签名以及如何处理标签名中的:是个需要考虑的问题

“固定变量名作为传递运算结果的“中介”,赋值多了一次”
如果是海量级的递归调用确实会有比较显著的影响
此种情况下应该允许该函数使用参数作为函数返回值变量名
此种情况可以看作是函数使用了一个变量指针作为实参
不将它看作返回值变量
因此不会影响整体的函数调用接口的约定

“对于返回多个结果”
可以参考一般高级语言的几种处理方式
一是将所有结果以伪数组的方式存入单一环境变量
  1. setlocal
  2. set test1=value1
  3. set test2=value2
  4. set test3=value3
  5. endlocal & set ret=%test1% %test2% %test3%
  6. for %%e in (%ret%) do echo %%e
复制代码
二是将所有结果的指针存入单一环境变量
  1. setlocal
  2. set test1=value1
  3. set test2=value2
  4. set test3=value3
  5. endlocal & (
  6. set "ret=test1 test2 test3"
  7. set test1=%test1%
  8. set test2=%test2%
  9. set test3=%test3%
  10. )
  11. setlocal EnableDelayedExpansion
  12. for %%e in (%ret%) do echo %%e=!%%e!
复制代码
“指示程序自身路径的变量”
这个可以不用%0%
可以使用其它约定的名称
这种用法也是我发帖时临时构造的
没有考虑到解析错误的问题

“子函数在什么情况非得递归父脚本?”
这分为两个方面
1-%0是用来递归调用子函数自身而非父脚本
这在子函数内部用到shift处理参数数组时可能会用到
当然这种情况一般在子函数内部处理
未必属于接口约定的范畴

2-递归父脚本的情况还是会有的
“递归”严格的来说应该是callback(回调)
比如我在上面提到的错误抛出问题
这是一个典型的需要回调父脚本的情况
因为同一个函数中的同一类错误
在不同的脚本环境中可能需要不同方式的处理
所以把错误转交给父脚本处理是一般性的选择
而此时的%0一般会被定位成子函数的标签名
而为了保证错误抛出后不再会被return
也无法使用类似call :main的形式
所以需要一个指示主函数或者父脚本路径的变量

对于返回的错误代码
还是建议尽量以exit /b number形式
利用系统内置的errorlevel吧
天的白色影子

TOP

本帖最后由 plp626 于 2012-11-12 22:34 编辑

函数返回值
用固定的环境变量名来做子函数返回值变量名
优点是:  便于管理,思路清晰,可借鉴 C语言 函数栈帧思路,解决后续很多问题。。
缺点是:  讲这个固定变量名作为传递运算结果的“中介”,赋值多了一次,相对来说效率低了一点

第一个实参名作为返回变量名
优点是: 传递结果只进行一次赋值操作,一步到位,效率高
缺点是: 过于灵活,使得问题复杂化,比如,如果每次调用时第一个参数名给的变量名都不同,可能产生很多临时变量,后续问题一大堆,还没想清楚,待大家讨论


setlocal 的讨论
先看看大家对于返回多个结果这个问题的解决办法,再说。。

程序自身路径:
这是个棘手的问题,如果多个函数调用,就得用setlocal来解决, 所以还是看大家对setlocal的讨论

TOP

本帖最后由 CrLf 于 2012-11-12 18:43 编辑

回复 8# qzwqzw


赞同“用固定的环境变量来做子函数返回值”,不太赞同“使用子函数的标签名作为返回值变量名”,cmd 中的变量读取效率如此蛋疼,还是能减少尽量少吧,而且应该使用在变量表中次序靠后的变量名,甚至用 %cmdcmdline% 都可以
-----------------------------------------------------------
如果“返回变量名为第一个实参名”
那么子函数在处理不定个数的参数数组时就会碰到麻烦

有点鱼与熊掌的味道,这看法我很赞同,但有时返回值不止一个就很头疼了
-----------------------------------------------------------
子函数内的环境变量是否除了返回值变量全部setlocal

窃以为这是在子函数需要产生临时变量时必须的,对全局变量的操作通过其他方法另行转存(除非变量中含换行符,否则应该能处理所有情况)
-----------------------------------------------------------
子函数内发生错误是否统一抛出处理?错误抛出后是在子程序中终止退出还是在主程序中?

另,建议返回值也以环境变量形式存在,即类似 bat_err 之类的变量名(最好还能短一点?比如 _err ?观察比较各种字符,似乎 _ 在表中名次比较靠后)
-----------------------------------------------------------
子函数内是否需要一个指示程序自身路径的变量%0%,以解决用%0递归调用自身时的问题

不赞同,首先以数字开头的变量名就是大忌,必须要用变量延迟才能避免错误解析,其次我实在想不到一个子函数在什么情况非得递归父脚本?

TOP

返回列表