在 Common Lisp 中,列印整數一般用函式format
。例如,上面的程式碼會往標準輸出中列印出233這個數字:
(format t "~D" 233)
除此之外,format
還可以控制列印內容的寬度、填充字元、是否列印正負號等方面。例如,要控制列印的內容至少佔據6列的話,可以用如下程式碼
(format t "~6D" 233)
如果不使用字串形式的 DSL,而是以關鍵字引數的方式來實現一個能夠達到同樣效果的函式format-decimal
,程式碼可能如下:
(defun format-decimal (n
&key
mincol)
"列印整數 N 到標準輸出。
MINCOL 如果不為 NIL,則表示所列印的內容至少要佔據的列數。"
;; 通過取餘的方式得到 N 的每一位並逐個入棧,之後出棧的順序就是從左到右列印的順序了。
(let ((digits '()))
(cond ((zerop n)
(push 0 digits))
(t
(do ((n n (truncate n 10)))
((zerop n))
(push (rem n 10) digits))))
;; 列印出填充用的空格。
(when (and (integerp mincol) (> mincol (length digits)))
(dotimes (i (- mincol (length digits)))
(declare (ignorable i))
(princ #\Space)))
(dolist (digit digits)
(princ (code-char (+ digit (char-code #\0)))))))
(format-decimal 233 :mincol 6)
如果要求用數字0而不是空格來填充左側的列,用format
的寫法如下:
(format t "~6,'0D" 233)
format-decimal
想要做到同樣的事情,可以這麼寫:
(defun format-decimal (n
&key
mincol
(padchar #\Space))
"列印整數 N 到標準輸出。
MINCOL 如果不為 NIL,則表示所列印的內容至少要佔據的列數。
PADCHAR 表示式為了填充多餘的列時所用的字元。"
(check-type mincol (or integer null))
(check-type padchar character)
;; 通過取餘的方式得到 N 的每一位並逐個入棧,之後出棧的順序就是從左到右列印的順序了。
(let ((digits '()))
(cond ((zerop n)
(push 0 digits))
(t
(do ((n n (truncate n 10)))
((zerop n))
(push (rem n 10) digits))))
;; 列印出填充用的空格。
(when (and (integerp mincol) (> mincol (length digits)))
(dotimes (i (- mincol (length digits)))
(declare (ignorable i))
(princ padchar)))
(dolist (digit digits)
(princ (code-char (+ digit (char-code #\0)))))))
(format-decimal 233 :mincol 6 :padchar #\0)
-D
預設是不會列印非負整數的符號的,可以用修飾符@
來修改這個行為。例如,(format t "~6,'0@D" 233)
會列印出00+233
。稍微修改一下就可以在format-decimal
中實現同樣的功能
(defun format-decimal (n
&key
mincol
(padchar #\Space)
signed)
"列印整數 N 到標準輸出。
MINCOL 如果不為 NIL,則表示所列印的內容至少要佔據的列數。
PADCHAR 表示式為了填充多餘的列時所用的字元。"
(check-type mincol (or integer null))
(check-type padchar character)
(flet ((to-digits (n)
;; 通過取餘的方式得到 N 的每一位並逐個入棧,之後出棧的順序就是從左到右列印的順序了。
(let ((digits '()))
(cond ((zerop n)
(push #\0 digits))
(t
(do ((n n (truncate n 10)))
((zerop n))
(push (code-char (+ (rem n 10) (char-code #\0))) digits))))
digits)))
;; 通過取餘的方式得到 N 的每一位並逐個入棧,之後出棧的順序就是從左到右列印的順序了。
(let ((digits (to-digits (abs n))))
(when (or signed (< n 0))
(push (if (< n 0) #\- #\+) digits))
;; 列印出填充用的空格。
(when (and (integerp mincol) (> mincol (length digits)))
(dotimes (i (- mincol (length digits)))
(declare (ignorable i))
(princ padchar)))
(dolist (digit digits)
(princ digit)))))
(format-decimal 233 :mincol 6 :padchar #\0 :signed t)
除了@
之外,:
也是一個~D
的修飾符,它可以讓format
每隔3個數字就列印出一個逗號,方便閱讀比較長的數字。例如,下列程式碼會列印出00+23,333
:
(format t "~9,'0@:D" 23333)
為此,給format-decimal
新增一個關鍵字引數comma-separated
來控制這一行為。
(defun format-decimal (n
&key
comma-separated
mincol
(padchar #\Space)
signed)
"列印整數 N 到標準輸出。
COMMA-SEPARATED 如果為 T,則每列印3個字元就列印一個逗號。
MINCOL 如果不為 NIL,則表示所列印的內容至少要佔據的列數。
PADCHAR 表示填充多餘的列時所用的字元。
SIGNED 控制是否顯示非負整數的加號。"
(check-type comma-separated boolean)
(check-type mincol (or integer null))
(check-type padchar character)
(check-type signed boolean)
(flet ((to-digits (n)
;; 通過取餘的方式得到 N 的每一位並逐個入棧,之後出棧的順序就是從左到右列印的順序了。
(let ((digits '()))
(cond ((zerop n)
(push #\0 digits))
(t
(do ((count 0 (1+ count))
(n n (truncate n 10)))
((zerop n))
(when (and comma-separated (> count 0) (zerop (rem count 3)))
(push #\, digits))
(push (code-char (+ (rem n 10) (char-code #\0))) digits))))
digits)))
;; 通過取餘的方式得到 N 的每一位並逐個入棧,之後出棧的順序就是從左到右列印的順序了。
(let ((digits (to-digits (abs n))))
(when (or signed (< n 0))
(push (if (< n 0) #\- #\+) digits))
;; 列印出填充用的空格。
(when (and (integerp mincol) (> mincol (length digits)))
(dotimes (i (- mincol (length digits)))
(declare (ignorable i))
(princ padchar)))
(dolist (digit digits)
(princ digit)))))
(format-decimal -23333 :comma-separated t :mincol 9 :padchar #\0 :signed t)
事實上,列印分隔符的步長,以及作為分隔符的逗號都是可以定製的。例如,可以改為每隔4個數字列印一個連字元
(format t "~9,'0,'-,4@:D" 23333)
對於format-decimal
來說這個修改現在很簡單了
(defun format-decimal (n
&key
(commachar #\,)
(comma-interval 3)
comma-separated
mincol
(padchar #\Space)
signed)
"列印整數 N 到標準輸出。
COMMACHAR 表示當需要列印分隔符時的分隔符。
COMMA-INTERVAL 表示當需要列印分隔符時需要間隔的步長。
COMMA-SEPARATED 如果為 T,則每列印3個字元就列印一個逗號。
MINCOL 如果不為 NIL,則表示所列印的內容至少要佔據的列數。
PADCHAR 表示填充多餘的列時所用的字元。
SIGNED 控制是否顯示非負整數的加號。"
(check-type commachar character)
(check-type comma-interval integer)
(check-type comma-separated boolean)
(check-type mincol (or integer null))
(check-type padchar character)
(check-type signed boolean)
(flet ((to-digits (n)
;; 通過取餘的方式得到 N 的每一位並逐個入棧,之後出棧的順序就是從左到右列印的順序了。
(let ((digits '()))
(cond ((zerop n)
(push #\0 digits))
(t
(do ((count 0 (1+ count))
(n n (truncate n 10)))
((zerop n))
(when (and comma-separated (> count 0) (zerop (rem count comma-interval)))
(push commachar digits))
(push (code-char (+ (rem n 10) (char-code #\0))) digits))))
digits)))
;; 通過取餘的方式得到 N 的每一位並逐個入棧,之後出棧的順序就是從左到右列印的順序了。
(let ((digits (to-digits (abs n))))
(when (or signed (< n 0))
(push (if (< n 0) #\- #\+) digits))
;; 列印出填充用的空格。
(when (and (integerp mincol) (> mincol (length digits)))
(dotimes (i (- mincol (length digits)))
(declare (ignorable i))
(princ padchar)))
(dolist (digit digits)
(princ digit)))))
(format-decimal -23333 :commachar #\- :comma-interval 4 :comma-separated t :mincol 9 :padchar #\0 :signed t)
全文完。