第五章: 高级的字串处理

MudOS v21c2

中阶 LPC

 Descartes of Borg
 November 1993

第五章: 高级的字串处理

5.1 字串是什麽

基础 LPC  课本教你字串是简单资料型态. LPC 一般来说也这样处理字串. 不过, 在底下的 driver 程式是以 C  写成的, 它没有字串资料型态. driver  实际上视字串为复杂资料型态, 由字元的阵列所组成 ---- 一种简单的 C  资料型态. LPC 在另一方面来说, 并不认识字元资料型态 (可能有一两种 driver 认得字元资料型态, 但是一般上来说不认得) . 其结果是, 你可以对字串作一些类似阵列的处理, 而其他的 LPC  资料型态则否.

你第一个该学与字串有关的外部函式是 strlen().  这个外部函式传回一个 LPC 字串中, 以字元为单位的长度. 就从这个外部函式的行为来说, 你可以看到 driver  视字串由更小的元素所组成, 并以此处理之. 在本章之中, 你将学到如何以更基础的字元和子字串层次处理字串.


5.2 字串是字元阵列

你可以对阵列作的事, 几乎都可以用於字串, 除了在字元基础上指定其值以外. 最基本的是, 你实际上可以在字元前後加上 ''  (单引号) 将它当作字元常数. 所以 'a'  和 "a"  在 LPC  中是完全不一样的东西. 'a' 表示是一个字元, 不能用於指定叙述或其他的运算式中 (比较两值的式子除外).  另一方面, "a" 是由单一字元所组成的字串. 你可以加减其他的字串, 并指定它为变数值.

对字串变数来说, 你可以存取单独的字元跟字元常数作比较. 其语法与阵列相同. 换句话说, 以下叙述:

    if(str[2] == 'a')
是一个有效的 LPC  叙述, 将 str  的第二个字元与 'a'  字元作比较. 你必须非常小心, 你不会把阵列元素与字元相比较, 也不会把字串的字元与字串相比较.

LPC 也让你使用范围运算子 (range operator) ..  一起存取多个字元:

    if(str[0..1] == "ab")
换句话讲, 你可以看 str  字串中第 0  到 1  个字元是什麽. 如同阵列, 你必须小心使用索引或范围运算子, 才不会试着参考比最後一个索引还大的索引数. 这样会导致错误.

现在你可以看到字串和阵列之间的几处相似点:

  • 1)  两者你都可以藉由索引存取个别的元素.
    •  a)  字串个别的元素是字元.
    •  b)  阵列个别的元素符合阵列的资料型态.
 
  • 2)  你可以运算一个范围之内的值.
    •  a)  例: "abcdef"[1..3]  是 "bcd"  字串
    •  b)  例: ({ 1, 2, 3, 4, 5 })[1..3] 整数阵列 ({ 2, 3, 4 })
当然, 你应该记住基本上的相异点: 字串不是由更基本的 LPC  资料型态所组成. 换句话说, 你没办法将值指定给字串中单独的字元.


5.3 sscanf()  外部函式

不使用 sscanf(),  你在 LPC  中就无法更有效处理字串. 没有它, 你就只能处理传给命令函式之命令叙述的整个字串. 换句话讲, 你没办法处理一个像 "give sword to leo" 的命令, 因为你没有方法分析 "sword to leo" 的成分. 像这种使用多个参数的命令, 它们使用 sscanf() 外部函式让命令更接近英文.

大部分的人都觉得 sscanf() 的说明文件相当难懂. 这个函式并不算是非常符合说明文件中的格式. 如同前述, 这函式用於读取字串, 并分析出有用的成分. 技术上来说, 它读取一个字串, 并分析成一个或一个以上的各种型态之变数. 举个
例子:

int give(string str) {
    string what, whom;

    if(!str) return notify_fail("Give what to whom?\n");
    if(sscanf(str, "%s to %s", what, whom) != 2)
      return notify_fail("Give what to whom?\n");
    ... 其馀的 give 程式码 ...
}

sscanf()  外部函式需要叁个以上的参数. 第一个参数是你想分析的字串. 第二个参数称为控制字串. 控制字串是一个模型, 表示原来所写的字串格式为何, 它该如何分析. 其馀的参数是变数, 你会由控制字串指定值给它们.

控制字串由叁种不同的元素组成:

  • 1)  常数
  • 2)  被分析的变数参数
  • 3)  要丢弃的变数
在 sscanf() 之中你变数参数的数目必须与控制字串中第二种元素的数目相等. 在上述的例子中, 控制字串是 "%s to %s",  是叁个元素的控制字串, 由一个常数部分 (" to ") 和两个被分析的变数参数 ("%s") 组成. 在此没有要丢弃的变数.

控制字串基本上指出函式应该在 str  字串中寻找 " to ".  在此常数之前不管是什麽东西, 会以字串型态放在第一个变数参数中. 同理, 常数後面的任何东西, 会放在第二个.

变数元素以 %  符号跟着一个解释码表示. 如果变数元素要丢弃, % 符号之後跟着 *  号, 再跟着解释变数的码. 常见的变数元素解释码是 s  表示字串, 和 d 表示整数. 另外, 你的 mudlib 可能支援其他的转换码, 像是 f  表示浮点数. 所以在上述的两个例子中, 控制字串中的 %s 指出原来字串中, 不管什麽东西出现在对应的位置上, 就会以字串被分析成新的变数.

来一个简单的练习. 你要怎麽把字串 "145"  转成一个整数 ?

答案:

int x;
sscanf("145", "%d", x);
sscanf()  执行之後, x 会等於整数 145.

无论何时, 你使用控制字串分析一个字串, 函式会寻找原来字串中第一次出现第一个常数的地方. 举个例, 如果你的字串是 "magic attack 100",  并撰写了以下的程式码:

int improve(string str) {
    string skill;
    int x;

    if(sscanf(str, "%s %d", skill, x) != 2) return 0;
    ...
}

你会发现你得到 sscanf() 错误的传回值 (稍後再多讨论传回值) . 控制字串 "%s %d",  是由被分析的两个变数和一个常数组成的. 常数是 " ". 所以函式寻找原字串中第一次出现 " "  的地方, 把 " "  之前的任何东西放入 skill, 并试着把 " "  之後的任何东西放入 x. 这样一来, 把 "magic attack 100" 分成 "magic" 和 "attack 100" 两个部分. 但是函式没办法把 "attack 100" 变成一个整数, 所以它传回 1, 表示有一个变数值成功分析出来 ("magic" 转 skill).

也许你已经从上面的例子中猜到, 但是 sscanf() 外部函式传回一个整数, 是从原字串成功分析出来的变数值个数. 这里有些传回值的例子让你看看:
 

sscanf("swo  rd descartes", "%s to %s", str1, str2) 传回: 0
sscanf("swo  rd descartes", "%s %s", str1, str2) 传回: 2
sscanf("200 gold to descartes", "%d %s to %s", x, str1, str2) 传回: 3
sscanf("200 gold to descartes", "%d %*s to %s", x, str1) 传回: 2
 
x 是一个整数, 而 str1 和 str2 是字串.


5.4 总结

LPC 字串可以视为字元的阵列, 但是你要牢记的是, LPC 并没有字元资料型态 (绝大多数, 但不是所有的 driver 皆是).  既然字元不是一种真正的 LPC  资料型态, 你就无法像其他资料型态一样, 处理一个 LPC  字串中单独的字元. 注意, 虽然字串和阵列之间的相似关系可以让你比较容易了解字串的范围运算子和索引的概念, 两者仍有不同之处.

虽然除了 sscanf() 之外, 高级的字串处理仍牵涉到其他的外部函式, 它们却不常需要用到. 你应该阅读你 mud  中这些外部函式的 man  或 help 档案: explode() 、implode() 、replace_string()、sprintf().  这些都是非常有价值的工具, 尤其是你想在 mudlib 层次上撰写程式码之时.

Copyright (c) George Reese 1993


译者: Spock of the Final Frontier 98.Jul.26.

回到上一页