
标题: [数值计算] [已解决]批处理()嵌套的变量延时与endlocal作用域问题 [打印本页]
作者: amwfjhh 时间: 2014-11-11 10:28 标题: [已解决]批处理()嵌套的变量延时与endlocal作用域问题
本帖最后由 amwfjhh 于 2014-11-12 09:40 编辑
以下代码源自于plp626发贴子:http://bbs.bathome.net/thread-15721-1-1.html
我试着将其排版一下时发现一些涉及变量延时及endlocal作用域的问题,代码如下:- @echo off
-
- set localon=setlocal enabledelayedexpansion
- set localoff=endlocal
-
- REM 获得100000次执行 @echo off 任务的耗时,存放在ct1变量内
- set t1=%time%
- for /l %%a in (1 1 100000)do @echo off
- set t2=%time%
- REM pause
- echo t1 : %t1% t2 : %t2%
- call :_etime t1 t2 tc1
- set /a tc1*=10
- echo 10万次echo off耗时 %tc1% 毫秒
- REM pause
-
- REM 初始工作(该处可省略),比如定义一些变量等,以便后面高效执行你的代码
- rem 。。。。
-
- REM 获得500次执行“你的代码”任务的耗时,存放在ct2变量内
- set t1=%time%
- for /l %%a in (1 1 500)do (
- set str=0123_ABCDXYZabcdxyz
- rem 版本一代码(或者版本二代码,但相应的str值请更换)
- echo do something else...>nul 2>nul
- )
- set t2=%time%
- echo t1 : %t1% t2 : %t2%
- REM pause
- call :_etime t1 t2 tc2
- set /a tc2*=10
- echo 你的代码耗时 %tc2% 毫秒
- REM PAUSE
-
-
- REM 计算执行一次“你的代码”与执行一次“@echo off”的耗时比
- set/a rate=200*tc2/tc1
- echo 一次任务与一次“@echo off命令”耗时比=%rate%
- pause
- goto :EOF
-
- (
- :_etime <begin> <end> <ret> //求时差
- echo %1 %2 %3
- REM pause>nul
- %localon%
- Set /a c=(!%2:~,2!-!%1:~,2!)*360000+(1!%2:~3,2!-1!%1:~3,2!)*6000+1!%2:~-5,2!!%2:~-2!-1!%1:~-5,2!!%1:~-2!
- echo c : %c%
- set /a c+=-8640000*(c^>^>31)
- echo c : %c%
- %localoff%&set %3=%c%
- echo c : %c%
- goto :eof
- )
复制代码
具体疑问为:
1)将%localoff%(即endlocal,下同)后面的&set %3=%c%放至下一行,则%3取不到任何值,从帮助上得之,endlocal之后变量会还原为setlocal之前的值,此时为空可以理解,但为什么跟在同一行的%localoff%之后,它还能取到本已“无效”的值呢?
2)关于:_etime函数前的那个括号,现在这样书写可以得到正常值,但如果把:_etime所在行与上面的(行换下位置的话,则会提示此时不应该有*360000+之类的错误提示,如果用^将()转义,则又得不到正确的计算值,请问这又是为何,()的特殊应有到底有何禁忌?为什么我其它批处理里面有相类似的调用都能正常执行,到这里就会碰到变量延时的问题呢?还是说当()遇到set语句中再有()时,才会出现此类报错?
先谢谢各位前辈指教,如有参考内容,也请在回贴中指明地址,定当认真拜读,谢谢。
作者: apang 时间: 2014-11-11 13:19
回答第一个问题:- setlocal enabledelayedexpansioon
- set "a=1"
- endlocal & echo,%a%
复制代码
第3行属于复合语句,预处理时endlocal & echo,%a% 是作为一个整体同时处理的,在此之前a已赋值为1,所以显示1
这个与复制代码
道理一样,这一句也是复合语句,在此之前a尚未赋值(为空),所以显示空
作者: amwfjhh 时间: 2014-11-11 13:47
回复 2# apang
谢谢apang的解答,加深了我对“语句块”的理解,它们应该跟()作用是一样的,语句块内命令共享同一生命周期,想到这那这句应该可以被(),然后断行来替找,如下,经实验结果正常。- @echo off
-
- set localon=setlocal enabledelayedexpansion
- set localoff=endlocal
-
- REM 获得100000次执行 @echo off 任务的耗时,存放在ct1变量内
- set t1=%time%
- for /l %%a in (1 1 100000)do @echo off
- set t2=%time%
- REM pause
- echo t1 : %t1% t2 : %t2%
- call :_etime t1 t2 tc1
- set /a tc1*=10
- echo 10万次echo off耗时 %tc1% 毫秒
- REM pause
-
- REM 初始工作(该处可省略),比如定义一些变量等,以便后面高效执行你的代码
- rem 。。。。
-
- REM 获得500次执行“你的代码”任务的耗时,存放在ct2变量内
- set t1=%time%
- for /l %%a in (1 1 500)do (
- set str=0123_ABCDXYZabcdxyz
- rem 版本一代码(或者版本二代码,但相应的str值请更换)
- echo do something else...>nul 2>nul
- )
- set t2=%time%
- echo t1 : %t1% t2 : %t2%
- REM pause
- call :_etime t1 t2 tc2
- set /a tc2*=10
- echo 你的代码耗时 %tc2% 毫秒
- REM PAUSE
-
-
- REM 计算执行一次“你的代码”与执行一次“@echo off”的耗时比
- set/a rate=200*tc2/tc1
- echo 一次任务与一次“@echo off命令”耗时比=%rate%
- pause
- goto :EOF
-
- (
- :_etime <begin> <end> <ret> //求时差
- echo %1 %2 %3
- REM pause>nul
- %localon%
- Set /a c=(!%2:~,2!-!%1:~,2!)*360000+(1!%2:~3,2!-1!%1:~3,2!)*6000+1!%2:~-5,2!!%2:~-2!-1!%1:~-5,2!!%1:~-2!
- echo c : %c%
- set /a c+=-8640000*(c^>^>31)
- echo c : %c%
- (
- %localoff%
- set %3=%c%
- )
- echo c : %c%
- goto :eof
- )
复制代码
作者: amwfjhh 时间: 2014-11-11 15:09
本帖最后由 amwfjhh 于 2014-11-11 15:29 编辑
用""或者^转义之后,在local之间能获得变量值,但就是拿不出来……
- :_etime <begin> <end> <ret> //求时差
- (
- echo %1 %2 %3
- REM pause>nul
- %localon%
- Set /a "c=(!%2:~,2!-!%1:~,2!)*360000+(1!%2:~3,2!-1!%1:~3,2!)*6000+1!%2:~-5,2!!%2:~-2!-1!%1:~-5,2!!%1:~-2!"
- echo c : !c!
- set /a "c+=-8640000*(c>>31)"
- echo c : !c!
- %localoff%&set %3=%c%
- call echo %3 : %%%3%%
-
- goto :eof
- )
复制代码
不管是set %3=%c%还是set %3=!c!都得不到正确值……
作者: amwfjhh 时间: 2014-11-12 09:29
本帖最后由 amwfjhh 于 2014-11-12 09:31 编辑
终于弄明白了。函数内set无法取得其值其本质还是在于变量延时与语句块之间的相互关系。
以下是原文:- :etime <begin> <end> <ret> //求时差
- setlocal enabledelayedexpansion
- Set/a "c=(!%2:~,2!-!%1:~,2!)*360000+(1!%2:~3,2!-1!%1:~3,2!)*6000+1!%2:~-5,2!!%2:~-2!-1!%1:~-5,2!!%1:~-2!,c+=-8640000*(c>>31)"
- endlocal&set %3=%c%&goto:eof
复制代码
在原文中,函数体未用括号括起来,也即函数体之间每行语句是独立的,按先后顺序执行,因此set语句下行已被正确赋值,而set %3=%c%与endlocal组成语句块,处于同一执行周期,可用%c%取其更新后的值,setlocal enabledelayedexpansion仅对set 里面的!%@:~,2!之类开启变量延时扩展,如果此处仅为纯数字引用,则无需开启。
而在加了括号后的语句中:- :_etime <begin> <end> <ret> //求时差
- (
- echo %1 %2 %3
- REM pause>nul
- %localon%
- set /a "c=(!%2:~,2!-!%1:~,2!)*360000+(1!%2:~3,2!-1!%1:~3,2!)*6000+1!%2:~-5,2!!%2:~-2!-1!%1:~-5,2!!%1:~-2!,c+=-8640000*(c>>31)"
- echo c : !c!
- %localoff%&set %3=%c%
-
- call echo %3 : %%%3%%
-
- goto :eof
- )
复制代码
由于函数体被括号引用起来,导致整个函数体组成了一个语句块,对于脚本宿主来说,它们据有相同执行周期,这样导致的结果就是,setlocal所在行与set行,endlocal行看似为三条独立语句,实则仍为一个语句块,在同一周期被执行,set /a的结果不会被其下行的%c%所接收,而由于在语句块内开启了变量延时,!c!所取的值即为实时取值,此时的实时,就严格按照语句先后顺序来执行,endlocal之后,休想再用它来得到!c!值,因此就看到如结果所显示的,在endlocal之前的echo c : !c!可以得值,但此值不可被返回给call所指定的接收变量,解决办法有两个:一是开启整个脚本的变量延时,去除函数体内的临时开关;二是去掉函数体两边的括号,将函数体内的语句由语句块变成独立的语句集合。
作者: amwfjhh 时间: 2014-11-12 09:39
了解了这些细微差别,就可以像写C程序一样来写批处理了,先注册函数名(可忽略),将函数体写在主程序之后,主程序执行完毕直接 GOTO :EOF退出批处理,这样可读性就要好得多,行如下方:- @echo off
- setlocal enabledelayedexpansion
-
- set funCalc=call :_etime
-
- REM 获得100000次执行 @echo off 任务的耗时,存放在ct1变量内
- set t1=%time%
- for /l %%a in (1 1 100000)do (echo off)
- set t2=%time%
- REM pause
- echo t1 : %t1% t2 : %t2%
- %funCalc% t1 t2 tc1
- echo tc1 : %tc1%
- set /a tc1*=10
- echo 10万次echo off耗时 %tc1% 毫秒
- REM pause
-
- REM 初始工作(该处可省略),比如定义一些变量等,以便后面高效执行你的代码
- rem 。。。。
-
- REM 获得500次执行“你的代码”任务的耗时,存放在ct2变量内
- set t1=%time%
- for /l %%a in (1 1 500)do (
- set str=0123_ABCDXYZabcdxyz
- rem 版本一代码(或者版本二代码,但相应的str值请更换)
- echo do something else...>nul 2>nul
- )
- set t2=%time%
- echo t1 : %t1% t2 : %t2%
- REM pause
- %funCalc% t1 t2 tc2
- echo tc2 : %tc2%
- set /a tc2*=10
- echo 你的代码耗时 %tc2% 毫秒
- REM PAUSE
-
-
- REM 计算执行一次“你的代码”与执行一次“@echo off”的耗时比
- echo.&echo.
- echo tc1 : %tc1% tc2 : %tc2%
- set/a rate=200*tc2/tc1
- echo 一次任务与一次“@echo off命令”耗时比=%rate%
- pause
- goto :EOF
-
- :_etime <begin> <end> <ret> //求时差
- (
- echo %1 %2 %3
- REM pause>nul
- set /a "c=(!%2:~,2!-!%1:~,2!)*360000+(1!%2:~3,2!-1!%1:~3,2!)*6000+1!%2:~-5,2!!%2:~-2!-1!%1:~-5,2!!%1:~-2!,c+=-8640000*(c>>31)"
- set %3=!c!
-
- echo %3 : !%3!
-
- goto :eof
- )
复制代码
欢迎光临 批处理之家 (http://bathome.net./) |
Powered by Discuz! 7.2 |