mud_sanc 板


LINE

作者 [email protected] (打混的蟑螂史巴克), 看板 Mud 标题 [蟑螂贺失恋] 中阶 LPC - 第五章 - 高级的字串处理 时间 中山医学院BBS站 (Sun Jul 26 17:52:22 1998) ─────────────────────────────────────── 中阶 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. 第六章: 中级继承 (inheritance) 6.1 基础继承 在基础 LPC 课本中, 你学到 mudlib 如何藉由继承维持 mud 物件之间的一致 性. 继承让 mud 管理人撰写所有的 mudlib 物件, 或某一种的 mudlib 物件都 必须拥有的基本函式, 让你可以专心创作使物件独树一格的函式. 当你建造一个 房间、武器、怪物时, 你使用一套早已替你写好的的函式, 并将它们让你的物件 继承之. 以此方法, 所有 mud 中的物件可以依靠别的物件表现某种方式的行为. 举个例, 玩家物件实际上依靠所有房间物件其中称为 query_long() 的一个函式 以得知房间的叙述. 继承让你不用担心 query_long() 长得如何. 当然, 这份课本会试着超越继承的基本知识, 让程式撰写人更了解 LPC 程式设 计中, 继承如何运作. 目前还不需要深入高级区域程式码撰写人/初级 mudlib 程式撰写人要知道的细节. 本章会试着详细解释, 你继承一个物件时所发生的事. 6.2 复制 (cloning) 与继承 当一个档案第一次以一个物件被参考 (相对於读取档案的内容) , 游戏试着将档 案载入记忆体, 并创造一个物件. 如果该物件成功载入记忆体, 它就成为主本 (master copy) . 物件的主本可被复制, 但是不用作实际上的游戏物件. 主本用 於支援游戏中任何的复制物件. 主本是 mud LPC 程式撰写争辩的源头之一, 也就是要复制它还是继承它. 对房 间来说就没有问题, 因为在游戏中每个房间物件应该只有一份. 所以你一般使用 继承来创造房间. 很多 mud 管理人, 包括我自己在内, 鼓励创作人复制标准的 怪物物件, 并从房间物件中设定之, 而不是让怪物分为单独的档案, 并继承标准 怪物物件. 如同我前述的部分, 每次一个档案被参考, 用於创造一个物件时, 一份主本就会 被载入记忆体. 像是你做以下的事: void reset() { object ob; ob = new("/std/monster"); /* clone_object("/std/monster") some places */ ob->set_name("foo monster"); ... 其余的怪物设定程式码, 之後再将怪物搬入房间中 ... } driver 会寻找是否有一个称为 "/std/monster" 的主物件. 如果没有, 它就创 造一个. 如果存在, 或已被创造出来, driver 就创造一个称为 "/std/monster#<编号>" 的复制物件. 如果此时是第一次参考 "/std/monster" , 结果会创造两个物件: 主物件和复制物件. 另一方面, 让我们假设你在一个继承 "/std/monster" 的特殊怪物档案中的 create() 里面, 已经做好所有的设定. 不从你房间复制标准怪物物件, 而你复 制你自己的怪物档案. 如果标准怪物尚未载入, 因为你的怪物继承它, 所以载入 之. 另外, 你档案的一个主本也被载入记忆体. 最後, 创造出一份你怪物的复制, 并搬入你的房间. 总共游戏中增加了三个物件. 注意, 你无法轻易地使用主本做 到这些. 举例来说, 如果你想做: "/wizards/descartes/my_monster"->move(this_object()); 而非 new("/wizards/descartes/my_monster")->move(this_object()); 你会无法修改 "my_monster.c" 并更新它, 因为更新 (update) 指令摧毁一个物 件现存的主版本. 在某些 mudlib 中, 它也载入新版本到记忆体中. 想像一下, 玩家在战斗中杀得如火如荼的时候, 因为你更新档案让怪物消失无踪 ! 此时他 们的脸色可不好看. 所以当你只是计划要复制时, 复制是一个有用的工具. 如果你对怪物并没有做什 麽特殊的事, 又不能藉由几个外界呼叫 (call other) 做到, 那你可以避免载入 许多无用的主物件而节省了你 mud 的资源. 不过, 如果你计画要对一个物件增 加一些功能 (撰写你自己的函式) 或是如果你有一个单独的设定多次重复使用 (你有一队完全一样的半兽人守卫, 所以你撰写一个特别的半兽人档案并复制之), 继承就相当有用. 6.3 更深入继承 当 A 物件和 B 物件继承 C 物件, 三个物件全都有自己的一套资料, 而由 C 物件共享一套函式定义. 另外, A 和 B 在它们个别的程式码中会有自己的函式 定义. 因为本章余下的部分都需要范例说明, 我们使用以下的程式码. 在此别因 为一些看起来没有意义的程式码而困扰. C 物件 private string name, cap_name, short, long; private int setup; void set_name(string str); nomask string query_name(); private int query_setup(); static void unsetup(); void set_short(string str); string query_short(); void set_long(string str); string query_long(); void set_name(string str) { if(!query_setup()) { name = str; setup = 1; } nomask string query_name() { return name; } private query_setup() { return setup; } static void unsetup() { setup = 0; } string query_cap_name() { return (name ? capitalize(name) : ""); } } void set_short(string str) { short = str; } string query_short() { return short; } void set_long(string str) { long = str; } string query_long() { return str; } void create() { seteuid(getuid()); } B 物件 inherit "/std/objectc"; private int wc; void set_wc(int wc); int query_wc(); int wieldweapon(string str); void create() { ::create(); } void init() { if(environment(this_object()) == this_player()) add_action("wieldweapon", "wield"); } void set_wc(int x) { wc = x; } int query_wc() { return wc; } int wieldweapon(string str) { ... code for wielding the weapon ... } A 物件 inherit "/std/objectc"; int ghost; void create() { ::create(); } void change_name(string str) { if(!((int)this_object()->is_player())) unsetup(); set_name(str); } string query_cap_name() { if(ghost) return "A ghost"; else return ::query_cap_name(); } 你可以看到, C 物件被 A 物件和 B 物件继承. C 物件代表的是一个相当简化 的基本物件, 而 B 也是相当简化的武器, A 是简化的活物件. 虽然我们有三个 物件使用这些函式, 每一个函式在记忆体中只维持一份. 当然, 从 C 物件而来 的变数在记忆体中有三份, 而 A 物件和 B 物件各有一份变数在记忆体中. 每 一个物件有自己的资料. 6.4 函式和变数标签 (label) 注意, 以上的许多函式是以本文和基础课本中还未介绍过的标签处理之, 这些标 签就是 static (静态) 、private (私有)、nomask (不可遮盖) . 这些标签定 义一个物件的资料和函式拥有特殊的特权. 你至今所使用的函式, 其预设的标签 是 public (公共). 只有某些 driver 预设如此, 有的 driver 并不支援标签. 一个公共变数是物件宣告它之後, 其继承树之下的所有物件皆可使用之. 在 C 物件中的公共物件可以被 A 物件与 B 物件存取之. 同样, 公共函式在物件宣 告它以後, 可以被继承树之下的所有物件呼叫之. 相对於公共的是私有. 一个私有变数或函式只能由宣告它的物件内部参考之. 如 果 A 物件或 B 物件试着参考 C 物件中的任何私有变数, 就会导致错误, 因 为这些变数它们根本看不到, 或说因为它们有私有标签, 无法被继承物件使用. 不过, 函式提供一个变数所没有的独特挑战. LPC 外部物件有能力藉由外界呼叫 (call other) 呼叫其他物件中的函式. 而私有标签无法防止外界呼叫. 要防止外界呼叫, 函式要使用静态标签. 一个静态函式只能由完整的物件内部或 driver 呼叫之. 我所谓的完整物件就是 A 物件可以呼叫它所继承 C 物件中 的函式. 静态标签只防止外部的外界呼叫. 另外, this_object()->foo() 就算 有静态标签, 也视为内部呼叫. 既然变数无法由外部参考, 它们就不需要一个同效的标签. 某几行程式里, 有人 决定要捣蛋, 并对变数使用静态标签以造成完全不同的意义. 更令人发狂的是, 这标签在 C 程式语言里头一点意义也没有. 一个静态变数无法经由 save_object() 外部函式储存, 也无法由 restore_object() 还原. 自己试试. 一般来说, 在一个公共函式中有一个私有变数是个很好的练习, 使用 query_*() 函式读取继承变数的值, 并使用 set_*()、add_*() 和其他此类的函式改变这些 值. 在撰写区域程式码时, 这实际上并不需要担心太多. 实际上的情形是, 撰写 区域程式码并不需要本章所谈的任何东西. 不过, 要成为真正优秀的区域程式码 撰写人, 你要有能力阅读 mudlib 程式码. 而 mudlib 程式码到处都是这些标签. 所以你应该练习这些标签, 直到你可以阅读程式码, 并了解它为什麽要以这种方 式撰写, 还有它对继承这些程式码的物件有何意义. 最後一个标签是不可遮盖, 因为继承的特性允许你重写早已定义的函式, 而不可 遮盖的标签防止此情形发生. 举例来说, 你可以看到上述的 A 物件重写 query_cap_name() 函式. 重写一个函式称为僭越 (override) 该函式. 最常见 的函式僭越就像这样, 当我们的物件 (A 物件) 因为特殊的条件情况, 需要在特 定情形下处理函式呼叫. 在 C 物件中, 因为了 A 物件可能是鬼魂而放入测试 的程式码, 是一件很蠢的事. 所以, 我们在 A 物件中僭越 query_cap_name(), 测试该物件是否为鬼魂. 如果是, 我们改变其他物件询问其名字时所发生的事. 如果不是鬼魂, 我们想回到普通的物件行为. 所以我们使用范围解析运算子 (scope resolution operator, ::) 呼叫继承版本的 query_cap_name() 函式, 并传回它的值. 一个不可遮盖函式无法经由继承或投影 (shadow) 僭越之. 投影是一种反向继承, 将在高级 LPC 课本中详细介绍. 在上述的范例中, A 物件和 B 物件 (实际上, 其他任何物件也不行) 无法僭越 query_name(). 因为我们想让 query_name() 作为物件唯一的监识函式, 我们不想让别人透过投影或继承欺骗我们. 所以此函 式有不可遮盖标签. 6.5 总结 透过继承, 一个程式撰写人可以使用定义在其他物件中的函式, 以避免产生一堆 相似而重复的物件, 并提高 mudlib 物件与物件行为的一致性. LPC 继承允许物 件拥有极大的特权, 定义它们的资料如何被外部物件和继承它们的物件存取之. 资料的安全性由 nomask、private、static 这些标签维持之. 另外, 一个程式码撰写人能藉由僭越, 改变非防护函式的功能. 甚至在僭越一个 函式的过程中, 一个物件可以透过范围解析运算子存取原来的函式. Copyright (c) George Reese 1993 译者: Spock of the Final Frontier 98.Jul.28. -- Boldly go where no mudder has gone before... Spock (roach admin) 蟑螂管理员 homepage: http://bbs.csmc.edu.tw/spock/ From The Final Frontier 140.128.136.12 4000 ※ 来源:‧中山医学院BBS -- 絮情小站 bbs.csmc.edu.tw‧[FROM: www.csmc.edu.tw] --



※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 218.170.228.153







like.gif 您可能会有兴趣的文章
icon.png[问题/行为] 猫晚上进房间会不会有憋尿问题
icon.pngRe: [闲聊] 选了错误的女孩成为魔法少女 XDDDDDDDDDD
icon.png[正妹] 瑞典 一张
icon.png[心得] EMS高领长版毛衣.墨小楼MC1002
icon.png[分享] 丹龙隔热纸GE55+33+22
icon.png[问题] 清洗洗衣机
icon.png[寻物] 窗台下的空间
icon.png[闲聊] 双极の女神1 木魔爵
icon.png[售车] 新竹 1997 march 1297cc 白色 四门
icon.png[讨论] 能从照片感受到摄影者心情吗
icon.png[狂贺] 贺贺贺贺 贺!岛村卯月!总选举NO.1
icon.png[难过] 羡慕白皮肤的女生
icon.png阅读文章
icon.png[黑特]
icon.png[问题] SBK S1安装於安全帽位置
icon.png[分享] 旧woo100绝版开箱!!
icon.pngRe: [无言] 关於小包卫生纸
icon.png[开箱] E5-2683V3 RX480Strix 快睿C1 简单测试
icon.png[心得] 苍の海贼龙 地狱 执行者16PT
icon.png[售车] 1999年Virage iO 1.8EXi
icon.png[心得] 挑战33 LV10 狮子座pt solo
icon.png[闲聊] 手把手教你不被桶之新手主购教学
icon.png[分享] Civic Type R 量产版官方照无预警流出
icon.png[售车] Golf 4 2.0 银色 自排
icon.png[出售] Graco提篮汽座(有底座)2000元诚可议
icon.png[问题] 请问补牙材质掉了还能再补吗?(台中半年内
icon.png[问题] 44th 单曲 生写竟然都给重复的啊啊!
icon.png[心得] 华南红卡/icash 核卡
icon.png[问题] 拔牙矫正这样正常吗
icon.png[赠送] 老莫高业 初业 102年版
icon.png[情报] 三大行动支付 本季掀战火
icon.png[宝宝] 博客来Amos水蜡笔5/1特价五折
icon.pngRe: [心得] 新鲜人一些面试分享
icon.png[心得] 苍の海贼龙 地狱 麒麟25PT
icon.pngRe: [闲聊] (君の名は。雷慎入) 君名二创漫画翻译
icon.pngRe: [闲聊] OGN中场影片:失踪人口局 (英文字幕)
icon.png[问题] 台湾大哥大4G讯号差
icon.png[出售] [全国]全新千寻侘草LED灯, 水草

请输入看板名称,例如:BabyMother站内搜寻

TOP