您的位置:首页 > 其它

Common Lisp学习之十:输出格式化控制与高级循环控制

2013-12-29 22:56 232 查看
1 format
1.1 Format函数
其通用调用方式为:(format stream control-string format-args)
第一个参数用于输出目的地,它可以是T NIL 一个流或一个带有填充指针的字符串。T是stdout的简称;NIL将使format返回一个字符串。其他情况均返回NIL。
第二个参数为控制字符串,用来指示如何进行显示,包含众多的格式化指令。第二节将重点介绍这些指令的作用。
第三个参数为需要被输出的格式化参数

1.2 Format指令
所有指令都以波浪线开始并终止于单个字符,例如~A。字符可以是大写或小写。

某些指令带有前置参数,其紧跟在~后,用来控制对指令的进一步细分。多个前置参数用','分隔。这些前置参数可以是十进制数字,也可以是字符。

前置参数对应的值有两种获取方式:前置参数v导致format使用一个格式化参数并将其值用作前置参数,前置参数#将被求值为格式化参数的个数。如果要指定一个前置参数,但不指定其前面的那个,那么必须为每个未指定的参数加上一个逗号。例如~F的两个前置参数N,M分别用于指定整数和小数部分占位。可以省略掉N,但,必须保留。

基本上参数都可以使用:或@修饰符,它们可以对指令进行细微的调整。

1.2.1 通用格式化

~% 用于输出一个换行符,接受前置参数指定换行个数
~& 只有当位于行为非行首时才输出换行符,也支持输出多个换行
~~ 用来输出~,可以控制输出个数

通用格式化指令为~A,它支持任何类型的格式化参数。类似的~S,也支持任何类型的参数,但用于输出可被read读回的形式。对于这两个指令,使用":"修饰,可将NIL输出成()

1.2.2 字符与整数指令

~C 输出单个字符,无前置参数 ":"修饰可将不可打印字符按名字输出,"@"修饰可按字面值输出
~D 以十进制输出整数,":"修饰可间隔三位输出",","@"修饰可打印正负号,两个修饰符可同时使用;支持两个前置参数,第一个指定输出的最小宽度,第二个指定占位符的类型,默认为空格。在":"修饰时支持另两个前置参数,第3个指定数位组的分隔符,第4个指定每组中数位的数量。
~X 以十六进制输出,其他参数与D相同
~O 以八进制输出
~B 以二进制输出
~NR 以N进制输出

1.2.3 浮点指令

~F 以十进制输出小数,第二个前置参数指定小数部分的输出位数
~E 使用科学计数法来输出,第二个前置参数指定小数部分的输出位数
~$ 用于输出货币单位

1.2.4 英语指令

~R 把数字输出为英文表示的基数,":"修饰表示为序数,"@"修饰表示为罗马数字

~P 如果格式参数不为1,则输出一个参数,可以用来制造复数;":"修饰支持重用格式参数并表示为基数。如(format nil "I have ~R file~:P" 5) => "I have five files"。和"@"同时使用,将输出y或ies。

~( ~) 用于控制输出文本中的大小写,标记之间的部分转化为小写。“@”修饰支持首字母大写,":"修饰支持包有首字符大写,二者同时使用将所有字符变为大写。

1.2.5 条件格式化

~[ ~A ~; ~A~:; ~] 在~[和~]之间的选项可以根据格式参数提供的[0 n-1]位置索引进行选择输出。选项间以~;进行分隔。如果最后一个选项以~:;结束,表示其作为默认子句提供。

使用":"修饰,则变成二先一,在格式参数为假时处理第1个选项,其他处理第2个选项。
使用"@"修饰,

1.2.6 迭代
~{ ~} f支持对参数提供的列表进行迭代处理,直到数据结束。例如(format t "~{~d ~}" (list 1 2 3)) =>"1 2 3 "。为了更好的处理结尾元素,~^指令可在列表无元素时令迭代立即终止。只输出参数,但不执行参数后面指定的格式化。

1.2.7 参数跳转
~* 跳过一个参数,不进行输出处理,":"修饰则可以向前跳,使得一个参数可以使用多次。
可以为~*指定一个前置参数,指定跳的个数。

3 Loop高级用法
Loop提供了用来编写迭代构造的专用语言。其基本结构是一个子句集合,其中每个子句都以一个循环关键字开始。每个子句被Loop宏解析方式取决于具体的关键字。

3.1 For/as关键字
For子句用于在下级子句中对各种对象进行迭代:数字范围、列表、向量、HASH表、包中符号。循环可以包含多个for子句,每个子句都可以命名自己的循环变量。此时任何一个子句达到结束条件循环都会终止。
(loop
for item in list
for i from 1 to 10
do (something))


3.1.1 计数循环
算术计数循环由for后跟下列介词短语中的1-3个构成: [init] [end] [step]
起始短语指定子句的变量初始值,由介词from/downfrom/upfrom之一后接一个初值数字形式构成。
终止短语指定循环的终止点,由介词to/upto/below/downto/above之一后接终值的形式所构成。
步长短语由介词by form构成,form必须求值为正数。

对于递增时,初值默认为0,递减时必须手工指定初值。另外LOOP宏运行在编译期,必须完全基于介词而不形式的值一决定步进的方向。

如果只是希望循环重复特定次数,使用repeat num

3.1.2 循环集合和包

对list进行迭代的形式
(loop for var in list-form by func=cdr ..)


by用来指定向列表向下移动的函数,in可以用on代替,此时迭代在构成列表的点对单元上步进。

对字符串和向量迭代的形式
(loop for var across "str" .. )


对HASH进行迭代的形式
(loop for var being the hash-keys/hash-values in ht ..);;为了同时可以访问k,v在尾部支持using子句。
(loop for var being the hash-keys in ht using (hash-value v) ...)
(loop for var being the hash-values in ht using (hash-key k) ...)


3.1.3 自定义步进
(loop for var = init-form [then step-form] ..)


3.1.4 解构变量
(loop for (a b) in '((1 2) (3 4) (5 6))
do (something with a b))


如果你想忽略一个解构列表中的值,可以用NIL代替相应变量的名字。

3.2 局部变量
通常循环需要的变量会在for子句中隐式声明,如果需要额外的变量,可以使用with子句来声明。
(with var [=value-form]) var会在首次迭代前初始化,并在迭代结束后删除。如果有多个with子句,则按顺序求值,且后面的变量可以使用前面声明的局部变量。

3.3 值汇聚
值汇聚为循环过程中涉及值汇聚的用法提供了简洁的表示法。其形式为:
verb form [ into var ]
每次迭代,汇聚子句对form求值并将其按verb指定的方式保存到into指定的变量中。如果没有into子句,则汇聚出一个默认值作为整个循环表达式返回的值。
可用的动词有collect/append/nconc/count/sum/maximize/minimize,以及其对应的进行时的同义词:collecting/appending/...。
collect子句会构造一个列表,列表中包含排列的所有form的值。append和nconc也汇聚一个列表,但其汇聚的值本身也必须是列表。
其余的动词都用来汇聚数值:count统计form为真的次数;sum收集所有form的值的和;max和min则收集最值。

3.4 无条件执行和条件执行
可以使用do来在循环中执行任意的语句,do后可以接多个语句,这些形式将会全部求值。do子句结束于一个循环的闭合括号或另一个关键字。
(loop for i from 1 to 10 do (forms))


由于do子句可以执行包含任意Lisp形式,因此执行条件控制的构造。
(loop for i from 1 to 10 do (codition-forms))


codition-forms的形式为:(condition test-form action-form)
condition 可以是IF/WHEN/UNLESS等,test-form 可以是任何正规Lisp形式,action-form可以是值汇聚子句、无条件执行子句、另一个条件子句。多个循环子句可以通过AND连成单个条件。

为了便于取值,使用变量it来指代由test-form返回的值。如(loop for key in lst when (gethash key ht) collect it)

return可以从loop中跳出,其后接单个Lisp形式,此形式的值将立即作为整个循环的值返回。
(loop for i from 1 to 10 do (print i) return 100) 将使循环只执行一次。
作为对比,流程控制符,return和return-from可以从任意块中返回。其作用区间比loop大。

对于命名Loop,return-from也可以只从loop中跳出。(loop named outer ...)

下面的例子给出了loop的一些使用方法。
(loop for i from 1 to 100
if (evenp i)
minimize i into min-even and
maximize i into max-even and
unless (zerop (mod i 4))
sum i into even-not-fours-total
end
and sum i into even-total
else
minimize i into min-odd and
maximize i into max-odd and
when (zerop (mod i 5))
sum i into fives-total
end
and sum i into odd-total
do (update-analysis min-even
max-even
min-odd
max-odd
even-total
odd-total
fives-total
even-not-fours-total))


3.5 设置与拆除
Loop提供 了initially和finally用于引入那些原本会运行在循环主体以外的代码。
所有initially形式会被组合成一个序言,在所有局部循环变量初始化以后并在循环体开始之前运行一次。而finally形式则简单地组合成一个尾声,在循环体的最后一次迭代结束以后运行。序言和尾声可以引用局部循环变量。

即使循环执行了0次,序言也会执行;但尾声在以下情况下时不执行:执行了return子句;return/return-from或其他流程操作符被调用;循环被always、never、thereis子句终止。

3.6 终止测试
在Loop中关键字while until always never thereis用来终止循环测试。其形式为key-word test-form。五种子句都会在每次执行迭代时对test-form求值,然后判断是否进行终止。

while和until代表温和的终止。当它们终止循环时,控制会跳过循环的其他部分,传到尾声部分。

always、never和thereis则终止循环,同时跳过尾声部分的执行。它们还提供了自己的返回值,因此不能和汇聚类子句配合使用,除非汇聚子句带有into子句。always和never返回布尔值。

alway和never用于检测test-form始终为真或为假。而thereis检测曾经真过。如果真过,则返回此值并结束循环。否则返回NIL。
(loop for char across "abc123" thereis (digit-char-p char)) ==> 1
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: