Skip to content

2.10 编辑器2.0:main一行不改

回到编辑器。2.2节说过,编辑器就是一个线性表加一个光标位置:敲字符是在光标处Insert,退格是Delete光标前一个,左右移动只改光标,显示是从0到Length−1逐个Get。这个分析和字符怎么存没有关系——不管是顺序表还是链表,编辑器要做的四件事对应的都是同样的四个操作。

2.5节把编辑器搭在顺序表上时,特意守了一条纪律:main只调用Init、Length、Get、Insert、Delete,不碰text.datatext.length。现在链表的六个操作名字、参数、返回值和顺序表完全一样,那么把类型名换掉,main应该能原样跑起来。试一下。

显示函数只用Length和Get,参数类型改成LinkList *

c
void Show(LinkList *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');
}

main是2.7节编辑器1.1的那份,只把SeqList text;改成LinkList text;

c
int main(void) {
    LinkList text;
    int cursor = 0;
    char cmd, ch;

    if (!Init(&text)) return 1;
    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);
    }
    Destroy(&text);
    return 0;
}

编译运行,输入2.5节那段会话,输出一模一样:

text
i c       c|
i a       ca|
i t       cat|
l         ca|t
i x       cax|t
d         ca|t
r         cat|
q

这就是编辑器2.0。整个应用层的改动是两处类型名,逻辑一个字没动,而底下的存储结构已经从一段连续的数组换成了散落在内存各处的结点。

这个结果不是巧合,是2.2节到现在一路铺垫的目的。抽象数据类型把"能做什么"和"怎么做到"分开:编辑器只依赖前者,所以后者随便换。反过来想,如果2.5节的main图省事直接写了text.data[cursor] = ch,现在就得重写整个main——链表根本没有data数组。

顺便一提,这也是为什么实际工程里通常把线性表的实现放在单独的文件里(比如seqlist.clinklist.c),编辑器只包含一个头文件;换实现就是换一个文件,应用代码不需要重新编译以外的任何改动。本书为了阅读方便把代码写在一起,思路是一样的。

编辑器2.0能跑,但换链表的初衷是解决2.6节的问题:在行首打字慢。换了之后到底快没快?下一节量一量。

到这里你有了:跑在链表上的编辑器2.0,main只改了类型名。 验证了:只通过操作访问数据,换存储结构时应用层不动。 下一步:量一量它到底快没快。