字符串
字符串
在 emacs 里字符串是有序的字符数组,和 c 语言的字符串数组不同,emacs 的字符串可以容纳任何字符,包括 \0 :
(setq foo "abc\000abc") ; => "abc^@abc"
关于字符串有很多高级的属性,例如字符串的表示有单字节和多字节类型,字符串可以有文本属性等等
字符
首先构成字符串的字符其实就是一个整数
一个字符 'A' 就是一个整数 65 但是目前字符串中的字符被限制在 0-524287 之间
字符的读入语法是在字符前加上一个问号,比如 ?A 代表字符 'A'
?A ; => 65 ?a ; => 97
- 对于 标点 来说,也可以用同样的语法,但是最好在前面加上转义字符 \ ,因为有些标点会有岐义,比如 ?\(
- \ 必须用 ?\\ 表示
控制字符,退格、制表符,换行符,垂直制表符,换页符,空格,回车,删除和 escape 表示为 ?\a, ?\b, ?\t, ?\n, ?\v, ?\f, ?\s, ?\r, ?\d, 和 ?\e
?\a => 7 ; control-g, `C-g' ?\b => 8 ; backspace, <BS>, `C-h' ?\t => 9 ; tab, <TAB>, `C-i' ?\n => 10 ; newline, `C-j' ?\v => 11 ; vertical tab, `C-k' ?\f => 12 ; formfeed character, `C-l' ?\r => 13 ; carriage return, <RET>, `C-m' ?\e => 27 ; escape character, <ESC>, `C-[' ?\s => 32 ; space character, <SPC> ?\\ => 92 ; backslash character, `\' ?\d => 127 ; delete character, <DEL>
没有特殊意义的字符,加上转义字符 \ 是没有副作用的,比如 ?\+ 和 ?+ 是完全一样的 所以标点还是都用转义字符来表示吧
控制字符 可以有多种表示方式,比如 C-i,这些都是对的,它们都对应数字 9:
?\^I ?\^i ?\C-I ?\C-i
meta 字符 是用 修饰键 (通常就是 Alt 键)输入的字符。之所以称为修饰键,是因为这样输入的字符就是在其修饰字符的第 27 位由 0 变成 1 而成,也就是如下操作:
(logior (lsh 1 27) ?A) ; => 134217793 ?\M-A ; => 134217793
可以用 \M- 代表 meta 键,加上修饰的字符就是新生成的字符。比如:?\M-A, ?\M-\C-b. 后面这个也可以写成 ?\C-\M-b。
如果还记得前面说过字符串里的字符不能超过 524287 的话,这就可以看出字符串是不能放下一个 meta 字符的,所以按键序列在这时只能用 vector 来储存
其它的修饰键也是类似的,emacs 用 :
- 第 25 位来表示 shift 键
- 第 24 对应 hyper
- 第 23 对应 super
- 第 22 对应 alt
测试
- 字符串测试使用 stringp ,没有 charp,因为字符就是整数
- string-or-null-p 当对象是一个字符或 nil 时返回 t
char-or-string-p 测试是否是字符串或者字符类型
比较头疼的是 emacs 没有测试字符串是否为空的函数
这是我用的这个测试函数,使用前要测试字符串是否为 nil:
(defun string-emptyp (str) (not (string< "" str)))
构造
- 产生一个字符串可以用 make-string 。这样生成的字符串包含的字符都是一样的
要生成不同的字符串可以用 string 函数
(make-string 5 ?x) ; => "xxxxx" (string ?a ?b ?c) ; => "abc"
在已有的字符串生成新的字符串的方法有 substring , concat
- substring 的后两个参数是起点和终点的位置,如果终点越界或者终点比起点小都会产生一个错误。这个在使用 substring 时要特别小心
(substring "0123456789" 3) ; => "3456789" (substring "0123456789" 3 5) ; => "34" (substring "0123456789" -3 -1) ; => "78"
concat 函数相对简单,就是把几个字符串连接起来
比较
char-equal 可以比较两个字符是否相等。与整数比较不同,这个函数还考虑了大小写
- 如果 case-fold-search 变量是 t 时,这个函数的字符比较是忽略大小写的
编程时要小心,因为通常 case-fold-search 都是 t,这样如果要考虑字符的大小写时就不能用 char-equal 函数了
- 字符串比较使用 string= ,string-equal 是一个别名
- string< 是按 字典序 比较两个字符串,string-less 是它的别名
空字符串小于所有字符串,除了空字符串,前面 string-emptyp 就是用这个特性 当然直接用 length 检测字符串长度应该也可以,还可以省去检测字符串是否为空
- 没有 string> 函数
转换
- 字符转换成字符串可以用 char-to-string 函数
- 字符串转换成字符可以用 string-to-char
- 当然只是返回字符串的第一个字符
- 数字和字符串之间的转换:
- string-to-number 可以设置字符串的进制,可以从 2 到 16
- number-to-string 只能转换成 10 进制的数字
如果要输出八进制或者十六进制,可以用 format 函数:
(string-to-number "256") ; => 256 (number-to-string 256) ; => "256" (format "%#o" 256) ; => "0400" (format "%#x" 256) ; => "0x100"
如果要输出成二进制,好像没有现成的函数了。calculator 库倒是可以,这是我写的函数:
(defun number-to-bin-string (number) (require 'calculator) (let ((calculator-output-radix 'bin) (calculator-radix-grouping-mode nil)) (calculator-number-to-string number))) (number-to-bin-string 256) ; => "100000000"
- concat 可以把一个 字符构成 的 列表或者向量 转换成 字符串
- vconcat 可以把一个 字符串 转换成一个 向量
- append 可以把一个 字符串 转换成一个 列表
(concat '(?a ?b ?c ?d ?e)) ; => "abcde" (concat [?a ?b ?c ?d ?e]) ; => "abcde" (vconcat "abdef") ; => [97 98 100 101 102] (append "abcdef" nil) ; => (97 98 99 100 101 102)
- 大小写转换使用的是 downcase 和 upcase 两个函数
- 这两个函数的参数既可以字符串,也可以是字符
- capitalize 可以使字符串中单词的 第一个字符大写 ,其它字符小写
- upcase-initials 只使第一个单词的第一个字符大写,其它字符小写
- 这两个函数的参数如果是一个字符,那么只让这个字符大写
(downcase "The cat in the hat") ; => "the cat in the hat" (downcase ?X) ; => 120 (upcase "The cat in the hat") ; => "THE CAT IN THE HAT" (upcase ?x) ; => 88 (capitalize "The CAT in tHe hat") ; => "The Cat In The Hat" (upcase-initials "The CAT in the hAt") ; => "The CAT In The HAt"
格式化
format 类似于 C 语言里的 printf 可以实现对象的字符串化,数字的格式化和 printf 的参数差不多
- 值得一提的是 %S 这个格式化形式,它可以把 对象的输出形式 转换成 字符串 ,这在调试时是很有用的
查找和替换
字符串查找的核心函数是 string-match 。这个函数可以从 指定的位置 对字符串进行 正则表达式匹配 ,如果匹配成功,则返回 匹配的起点 ,如:
(string-match "34" "01234567890123456789") ; => 3 (string-match "34" "01234567890123456789" 10) ; => 13
注意 string-match 的参数是一个 regexp
emacs 好象没有内建的查找子串的函数。如果想把 string-match 作为一个查找子串的函数,可以先用 regexp-quote 函数先处理一下子串。比如:
(string-match "2*" "232*3=696") ; => 0 (string-match (regexp-quote "2*") "232*3=696") ; => 2
事实上,string-match 不只是查找字符串,它更重要的功能是捕捉匹配的字符串 如果你对正则表达式不了解,可能需要先找一本书,先了解一下什么是正则表达式
string-match 在查找的同时,还会 记录 下每个要 捕捉 的 字符串的位置 。这个位置可以在匹配后用 match-data 、 match-beginning 和 match-end 等函数来获得。先看一下例子:
(progn (string-match "3\\(4\\)" "01234567890123456789") (match-data)) ; => (3 5 4 5)
正则表达式捕捉的字符串按 括号的顺序 对应一个 序号
- 整个模式对应序号 0
- 第一个括号对应序号 1
- 第二个括号对应序号 2
。。。以此类推
所以 3\(4\) 这个正则表达式中有序号 0 和 1,最后 match-data 返回的一系列数字对应的分别是要捕捉字符串的起点和终点位置 也就是说子串 "34" 起点从位置 3 开始,到位置 5 结束 而捕捉的字符串 "4" 的起点是从 4 开始,到 5 结束 这些位置可以用 match-beginning 和 match-end 函数用对应的序号得到
注意:
- 起点位置:捕捉字符串的 第一个字符的位置
- 终点位置:不是捕捉的字符串最后一个字符的位置,而是 下一个字符的位置
这个性质对于循环是很方便的。比如要查找上面这个字符串中所有 34 出现的位置:
(let ((start 0)) (while (string-match "34" "01234567890123456789" start) (princ (format "find at %d\n" (match-beginning 0))) (setq start (match-end 0)))) ;; find at 3 ;; find at 13 ;; nil
替换使用的函数是 replace-match 。这个函数既可以用于 字符串 的替换,也可以用于 缓冲区的文本 替换
对于字符串的替换,replace-match 只是按 给定的序号 把字符串中的 那一部分用提供的字符串 替换了而已:
(let ((str "01234567890123456789")) (string-match "34" str) (princ (replace-match "x" nil nil str 0)) (princ "\n") (princ str)) ;; 012x567890123456789 ;; 01234567890123456789"01234567890123456789"
可以看出 replace-match 返回的字符串是替换后的新字符串,原字符串被没有改变
如果怎样把上面这个字符串中所有的 34 都替换掉? 如果想就使用同一个字符串来存储,可能对于固定的字符串,这个还容易一些 如果不是的话,就要花一些脑筋了,因为替换之后,新的字符串下一个搜索起点的位置就不能用 (match-end 0) 给出来的位置了,而是要扣除替换的字符串和被替换的字符串长度的差值
- 对 字符串的替换 有一个函数 replace-regexp-in-string :把每次匹配部分之前的子串收集起来,最后再把所有字符串连接起来
单字符的替换 有 subst-char-in-string 函数
但是 emacs 没有类似 perl函数或者程序 tr 那样进行字符替换的函数,只能自己建表进行循环操作了