Skip to content

2.5 编辑器1.0:跑起来

线性表实现完了,现在把编辑器搭在它上面。按2.2节的分析,编辑器就是一个线性表加一个光标位置。光标用一个整数cursor表示"下一个敲入的字符放在第几个位置",取值从0到Length(text):0是行首,等于长度是行尾。

真实的编辑器要逐键读取键盘,涉及终端控制,与本章无关。这里用简单的指令代替按键,每条指令一个字母:

指令含义对应操作
i x在光标处插入字符xInsert
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.datatext.length。编辑器不知道、也不需要知道字符是怎么存的。这不只是代码风格,它是抽象数据类型思想在代码上的落脚点,也是2.4把L从全局变量改成参数的目的之一——2.8节把存储结构换成链表时,这段main会原样保留,你可以自己对比。

到这里你有了:能运行的编辑器1.0。 用到的操作:Init、Length、Get、Insert、Delete,main没有直接访问数组。 下一步:量一量它的效率,找出两个问题。