外观
2.5 编辑器1.0:跑起来
线性表实现完了,现在把编辑器搭在它上面。按2.2节的分析,编辑器就是一个线性表加一个光标位置。光标用一个整数cursor表示"下一个敲入的字符放在第几个位置",取值从0到Length(text):0是行首,等于长度是行尾。
真实的编辑器要逐键读取键盘,涉及终端控制,与本章无关。这里用简单的指令代替按键,每条指令一个字母:
| 指令 | 含义 | 对应操作 |
|---|---|---|
i x | 在光标处插入字符x | Insert |
d | 退格,删掉光标前一个字符 | Delete |
l | 光标左移 | 只改cursor |
r | 光标右移 | 只改cursor |
q | 退出 |
每处理一条指令,就把当前这一行连同光标打印出来。显示单独写成一个函数,它只用Length和Get就能把整行显示出来:
c
void Show(SeqList *L, int cursor) {
int n = Length(L);
for (int i = 0; i < n; i++) {
if (i == cursor) putchar('|');
putchar(Get(L, i));
}
if (cursor == n) putchar('|');
putchar('\n');
}主程序是一个读指令、执行、显示的循环:
c
int main(void) {
SeqList text;
int cursor = 0;
char cmd, ch;
Init(&text);
while (scanf(" %c", &cmd) == 1 && cmd != 'q') {
switch (cmd) {
case 'i':
scanf(" %c", &ch);
if (ch == '_') ch = ' '; /* 用_代表空格 */
if (Insert(&text, cursor, ch)) cursor++;
else printf("插入失败\n");
break;
case 'd':
if (cursor > 0) { Delete(&text, cursor - 1); cursor--; }
break;
case 'l':
if (cursor > 0) cursor--;
break;
case 'r':
if (cursor < Length(&text)) cursor++;
break;
}
Show(&text, cursor);
}
return 0;
}两个细节:scanf(" %c")格式串开头的空格是为了跳过上一条指令留下的换行,漏掉它程序会把换行当成指令;scanf读%c时会跳过空格,所以约定用_输入空格。
一段示例会话(左边是输入,右边是程序的输出):
text
i c c|
i a ca|
i t cat|
l ca|t
i x cax|t
d ca|t
r cat|
q请注意这段main的写法:它只调用了Init、Length、Get、Insert、Delete五个操作,从头到尾没有直接读写text.data和text.length。编辑器不知道、也不需要知道字符是怎么存的。这不只是代码风格,它是抽象数据类型思想在代码上的落脚点,也是2.4把L从全局变量改成参数的目的之一——2.8节把存储结构换成链表时,这段main会原样保留,你可以自己对比。
到这里你有了:能运行的编辑器1.0。 用到的操作:Init、Length、Get、Insert、Delete,main没有直接访问数组。 下一步:量一量它的效率,找出两个问题。