返回列表 发帖

仿批处理命令call参数列表 调用 多参数的变量型函数

最近和zm讨论中说到这个问题,之前的代码丢了,现在从自己盘里找了这个div子过程(例子比较典型)改造之:
@echo off
call:div 31 2003 1000 ans
echo 31/2003 的前1000位小数为:
echo %ans%
pause&goto:eof
:div <dividend> <divisor> <pre> <ret> //code by plp
Setlocal Enabledelayedexpansion&set/a b=%2,R=%1%%b*10&set "dc="
For /l %%z In (1 1 %3)Do set/a d=R/b,R=R%%b*10&set dc=!dc!!d!
endlocal&set %4=%dc%COPY
这是典型的call调用,div子过程共4个参数,
第一个参数为被除数,第二个为除数,第三个为小数的位数(精度),第四个为返回的变量名
调用后子过程没有改变父环境变量值,因为有setlocal 和endlocal 。。。

讨论的问题是,如果用变量型函数像call这样“调用”多参数,代码如何写?
@echo off
:: 定义_div 变量型函数
rem 代码。。。。。?
%_div% 31 2003 1000 ans
echo 31/2003 的前1000位小数为:
echo %ans%COPY
@echo off
:: 定义_div 变量型函数 ;返回两个整数商的小数部分
:: <dividend> <divisor> <pre> <ret> //code by plp
Set "_div=setlocal enabledelayedexpansion&set n=&set argv=&for %%a in (1 2)do if defined argv ((for %%b in (^!argv^!)do set/a n+=1&set #^!n^!=%%b)&set/a b=^!#2^!,R=^!#1^!%%b*10&set dc=&(For /l %%z In (1 1 ^!#3^!)Do set/a d=R/b,R=R%%b*10&set dc=^!dc^!^!d^!)&for /f "tokens=1-2" %%A in (^"^^!#4^^! ^^!dc^^!^^")do endlocal&set %%A=%%B)else set argv="
%_div% 31 2003 1000 ans
echo 31/2003 的前1000位小数为:
echo %ans%
if "%~0"=="%~f0" set/p=
goto:eof
___END___
思路大致是
setlocal&set argv=
for %%a in (1 2)do if defined argv (
   for 获取argv的变量值用#“数组”存放,#1为第一个参数入口,#2为第二个参数入口。。。
  你的变量型函数在这里展开。。。。
  endlocal&退出并复制结果给返回变量名
) else set argv=COPY


对于变量型函数多参数的调用,有简单直接的思路(但要写代码者自己维护变量空间)
大家可参看此贴一楼的测试代码
http://www.bathome.net/viewthread.php?tid=11799

新手对变量型函数不太了解的,可参看变量型函数发源贴:
http://www.bathome.net/thread-5861-1-1.html
1

评分人数

    • CrLf: 原来可以这样,很巧妙!PB + 10 技术 + 2

绝了!

TOP

看的我云里雾里

TOP

回复 3# qq2501


    %_div% 31 2003 1000 ans 就是用第一次 for 循环将其后跟随的 31 2003 1000 ans 赋值给变量 :,再在第二次循环中引用,通过次序的颠倒,巧妙地在执行函数体之前,取得位于函数体之后的值。
    函数中不是有一句 for %%a in (1 1)do if defined : ( ... )else set :=  吗?这里的第一个次循环就是用来执行 else 进行取值的,赋值成功后,第二次循环才是执行函数内容

TOP

本帖最后由 CrLf 于 2011-12-21 07:49 编辑

发一个将普通“格式化”为变量型函数的工具,格式化后的函数,只有用变量延迟直接或二次扩展才能动态地读取变量,而且原有空行、以 : 开头的行均被忽略。
但是思路比较粗糙,对 % 未进行成对判断,对开启变量延迟后含有 ! 的命令未做充分的二次转义处理(难度太大,等于要解析语法),并且不支持扩展参数,参数外的双引号必须被去除(其实也不是必须,但是条件太苛刻)。

合并行.bat :
@echo off
echo %*
set "g=&"
set "code=setlocal enabledelayedexpansion&set n=&set :=&for %%%%a in (1 1) do if defined : ((for %%%%b in (!:!) do set/a n+=1&set #!n!=%%%%b"
for %%z in (%*) do (
for /f "usebackeol=:tokens=*" %%b in ("%%~z") do (
set str=%%b
setlocal enabledelayedexpansion
if !str:~^,1! neq ^) (set "str=(!str!") else set g=
if !code:~-1! neq ^( (set "code=!code!)") else set g=
set code=!code!!g!!str!
for /f "delims=" %%c in ("!code!") do endlocal&set code=%%c
)
call :Test %%z
)
exit
:Test
set out=&setlocal enabledelayedexpansion
set "code=!code!)) else set :="
set b=!code!
for /l %%a in (1 1 9) do set code=!code:%%%%a=$#%%a$!
for /l %%a in (0 1 8000) do (
set /a "n-=^!^!n"
if !code:~%%a^,4!==$ (set "out=!out!^!"&set code=!code:~3!) else set out=!out!!code:~%%a,1!
if !code:~%%a!.==. echo 合并后:!out!&echo !out!>1.cmd&endlocal&call 转义.bat 1.cmd "_%~n1"&pause&exit /b
)COPY
转义.bat :
@echo off&setlocal enabledelayedexpansion&set hh=^
for %%a in ("!hh!") do (
endlocal
set out=
set tmp=
for /f "usebackdelims=" %%b in (%1) do set hs=%%b
setlocal enabledelayedexpansion
for /f "eol=;delims=" %%b in ("!hs:"^=%%~a"!") do (
endlocal&set str=%%b&set /a n=!n
setlocal enabledelayedexpansion
if !n!==0 set "str=!str:^=^^!"
if !n%%b neq n%%b (
for /l %%c in (8101 -100 1) do if "!str:~%%c!"=="" set len=%%c
for /l %%c in (0 1 !len!) do if !str:~%%c^,1!==^^! (
if !n!==0 (set "tmp=!tmp!^^^^^^^!") else set "tmp=!tmp!^^^!"
) else set "tmp=!tmp!!str:~%%c,1!"
set str=!tmp!
)
if !n!==0 for %%c in (^( ^) ^& ^| ^< ^>) do set "str=!str:%%c=^%%c!"
for /f "eol=:delims=" %%c in ("!out!!str!") do endlocal&set out=%%c
)
)
setlocal enabledelayedexpansion
echo 转义后:!out!&echo set "%~2=!out!">>2.cmd
pauseCOPY
样本:
:strlen <stringVarName> [retvar]
:: 思路: 二分回溯联合查表法
:: 说明: 所求字符串大小范围 0K ~ 8K;
::    stringVarName ---- 存放字符串的变量名
::    retvar      ---- 接收字符长度的变量名
:: 原帖:http://www.dostips.com/forum/viewtopic.php?f=3&t=1429
setlocal enabledelayedexpansion
set $=%1#
set N=&for %%z in (4096 2048 1024 512 256 128 64 32 16)do if !$:~%%z!. NEQ . set/aN+=%%z&set $=!$:~%%z!
set $=!$!fedcba9876543210&set/aN+=0x!$:~16,1!
for /f "delims=" %%a in ("%2=!n!")do endlocal&set %%aCOPY
拖动样本到  合并.bat  上的处理结果:
set "_%%:=setlocal enabledelayedexpansion&set n=&set :=&for %%a in (1 1)do if defined : ((for %%b in (^!:^!)do set/a n+=1&set #^!n^!=%%b)&(setlocal enabledelayedexpansion)&(set $=^!#1^!#)&(set N=&for %%z in (4096 2048 1024 512 256 128 64 32 16)do if ^!$:~%%z^!. NEQ . set/aN+=%%z&set $=^!$:~%%z^!)&(set $=^!$^!fedcba9876543210&set/aN+=0x^!$:~16,1^!)&&(for /f "delims=" %%a in ("^^^!#2^^^!=^^^!n^^^!")do endlocal&set %%a))else set :="COPY
1

评分人数

    • plp626: 不错,细节问题再优化技术 + 1

TOP

DIY 一个定义全局函数的模板,部分靠先前的代码转换,部分靠手工(搞死人了...),变量型函数的全局声明和调用:
@echo off
call :dim strlen checkdate count
setlocal enabledelayedexpansion
%_strlen% basfdglksdfjlsdkj n
echo "test测试Hello World" 中共有 %n% 个字符
echo;
echo 检查日期是否正确
%_checkdate% 2011 2 29
echo 2011229日的检查结果如上
echo;
%_count% a ahahahahahahhhahahhhaha n1
%_count% h ahahahahahahhhahahhhaha n2
echo ahahahahahahhhahahhhaha 中有 %n1% 个 a、 %n2% 个 h
pause&exit
:dim
for %%: in (%*)do (
if /i %%:==strlen set "_%%:=setlocal enabledelayedexpansion&set n=&set :=&for %%a in (1 1)do if defined : ((for %%b in (^!:^!)do set/a n+=1&set #^!n^!=%%b)&setlocal enabledelayedexpansion&set $=^!#1^!#&(set N=&for %%z in (4096 2048 1024 512 256 128 64 32 16)do if ^!$:~%%z^!. NEQ . set/aN+=%%z&set $=^!$:~%%z^!)&set $=^!$^!fedcba9876543210&set/aN+=0x^!$:~16,1^!&&for /f "delims=" %%a in ("^^!#2^^!=^^!n^^!")do endlocal&set %%a)else set :="
if /i %%:==checkdate set "_%%:=setlocal enabledelayedexpansion&set n=&set :=&for %%a in (1 1)do if defined : ((for %%b in (^!:^!)do set/a n+=1&set #^!n^!=%%b)&setlocal disabledelayedexpansion&set/a"y=#1,m=#2,d=#3,test=^^!^(y%%4^|^^!^(y%%100^)*^^!^^!^(y%%400^)^)*^^!^(m^^^^2^)+^(m+m/8^)%%2-2*^^!^(m^^^^2^)+30,0/^(test/d*^^!^(m/13^)^)"2>nul&&echo Right||echo Wrong)else set :="
if /i %%:==count set "_%%:=setlocal enabledelayedexpansion&set n=&set :=&for %%a in (1 1)do if defined : ((for %%b in (^!:^!)do set/a n+=1&set #^!n^!=%%b)&setlocal enabledelayedexpansion&set key=:^!#1^!&set str=^!#2^!&set ret=^!#3^!&(for %%a in ("^^!_hh^^!")do for /f "delims=" %%b in ("^^!key^^!")do for /f "delims=" %%c in ("[^^!str%%b=%%~a]^^!")do set /a n+=1)&for /f "delims=" %%a in ("^^!ret^^!=^^!n^^!")do endlocal&set %%a)else set :="&set _hh=^
)COPY

TOP

本帖最后由 netbenton 于 2011-12-21 10:06 编辑

晕倒,我发错地方了,当灌水了。
研究了一下,真的不错

TOP

本帖最后由 plp626 于 2011-12-21 10:05 编辑

回复 7# netbenton

我这是拾人牙慧;
话说很少见netben兄发帖哈,
---------
忽然觉得此帖合并到兄的那个发源贴后有利于坛友讨论,和阅读。
申请合并。

TOP

不错呀~~
这样可以增加了通用性和可读性


::水话::
没有那么多时间玩BAT了,看到强铁,不得不顶~~
::水话::

TOP

2011年国际Batch混乱代码大赛第一名。

TOP

回复 3# qq2501


回复 10# Demon


原文跨度太大,直接从普通函数成型为改进的变量型函数了,缺乏过渡确实不太容易理解,我还是“翻译”一下吧,希望有助于大家的理解。

当原文中声明了 _div 这个变量型函数之后,运行以下代码:
%_div% 31 2003 1000 ansCOPY
的实际效果等同于执行了以下代码:
setlocal enabledelayedexpansion
set n=
set argv=
for %%a in (1 2) do (
    if defined argv (
        rem 因为此时 argv 为空,所以先执行的是 else,即 set argv= 31 2003 1000 ans
        for %%b in (!argv!)do set/a n+=1&set #!n!=%%b
        rem 第 2 步,将获取的参数分别设为 #1、#2 一直到 #N
        rem 在例子中,共有四个参数:#1=31,#2=2003,#3=1000,#4=ans
        set/a b=!#2!,R=!#1!%%b*10&set dc=
        For /l %%z In (1 1 !#3!)Do set/a d=R/b,R=R%%b*10&set dc=!dc!!d!
        rem 第 3 步,调用函数主体,针对参数进行操作
        for /f "tokens=1-2" %%A in ("!#4! !dc!")do endlocal&set %%A=%%B
        rem 第 4 步,结束上一个 setlocal 并利用 for 保留变量
    )else (
        set argv= 31 2003 1000 ans
        rem 第 1 步:将参数赋值
    )
)
rem 最终结果存储在 ans 变量中。COPY
核心思想就是利用 for+if 先执行后面的代码(进行赋值),再回过头来执行前面的代码(具体操作)。
这样解释不知还有难理解的地方吗?

TOP

天马行空,眼花缭乱。我看看看!
寂寞是黑白的,但黑白不是寂寞,是永恒。BAT 需要的不是可能,而是智慧。

TOP

偶也这么用过,嘻嘻。但个人对批处理函数不怎么感兴趣(如果bat稍微复杂,我往往用其他脚本)。

依照C语言,这种形式叫“宏函数”比较合适。

TOP

这个从后面获得参数的方法确实巧妙,高!

TOP

这方法很巧妙!

TOP

返回列表