返回列表 发帖

[数值计算] [已解决]批处理()嵌套的变量延时与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
)COPY
具体疑问为:
1)将%localoff%(即endlocal,下同)后面的&set %3=%c%放至下一行,则%3取不到任何值,从帮助上得之,endlocal之后变量会还原为setlocal之前的值,此时为空可以理解,但为什么跟在同一行的%localoff%之后,它还能取到本已“无效”的值呢?
2)关于:_etime函数前的那个括号,现在这样书写可以得到正常值,但如果把:_etime所在行与上面的(行换下位置的话,则会提示此时不应该有*360000+之类的错误提示,如果用^将()转义,则又得不到正确的计算值,请问这又是为何,()的特殊应有到底有何禁忌?为什么我其它批处理里面有相类似的调用都能正常执行,到这里就会碰到变量延时的问题呢?还是说当()遇到set语句中再有()时,才会出现此类报错?


先谢谢各位前辈指教,如有参考内容,也请在回贴中指明地址,定当认真拜读,谢谢。

回答第一个问题:
setlocal enabledelayedexpansioon
set "a=1"
endlocal & echo,%a%COPY
第3行属于复合语句,预处理时endlocal & echo,%a% 是作为一个整体同时处理的,在此之前a已赋值为1,所以显示1
这个与
set "a=1" & echo,%a%COPY
道理一样,这一句也是复合语句,在此之前a尚未赋值(为空),所以显示空

TOP

回复 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
)COPY

TOP

本帖最后由 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
)COPY
不管是set %3=%c%还是set %3=!c!都得不到正确值……

TOP

本帖最后由 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:eofCOPY
在原文中,函数体未用括号括起来,也即函数体之间每行语句是独立的,按先后顺序执行,因此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
)COPY
由于函数体被括号引用起来,导致整个函数体组成了一个语句块,对于脚本宿主来说,它们据有相同执行周期,这样导致的结果就是,setlocal所在行与set行,endlocal行看似为三条独立语句,实则仍为一个语句块,在同一周期被执行,set /a的结果不会被其下行的%c%所接收,而由于在语句块内开启了变量延时,!c!所取的值即为实时取值,此时的实时,就严格按照语句先后顺序来执行,endlocal之后,休想再用它来得到!c!值,因此就看到如结果所显示的,在endlocal之前的echo c : !c!可以得值,但此值不可被返回给call所指定的接收变量,解决办法有两个:一是开启整个脚本的变量延时,去除函数体内的临时开关;二是去掉函数体两边的括号,将函数体内的语句由语句块变成独立的语句集合。

TOP

了解了这些细微差别,就可以像写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
)COPY

TOP

返回列表