线性结构的基本介绍到了这里就暂时告一段落。这篇文章主要是用来总结线性结构的一些特点跟应用的。线性结构是数据结构中的最基础的结构,以下的数据结构都是基于这个结构的。尤其是链表,树跟图的结构都可以由此衍生出来。
链表的遍历模式
通过之前的学习,我们知道我们当时写moveCursorBackward时,是通过遍历链表来找到cursor之前的一个。代码中使用while循环来进行遍历:
void EditorBuffer::moveCursorBackward() {
Cell *cp = start; //定义一个指针,指向链表的开始
if (cursor != start) {
while (cp->link != cursor) {//当其指向的link不是当前cursor指向的单元格时
cp = cp->link; //更新cp的值
}
cursor = cp; //最终找到cursor指向的元素之前的位置,复制给当前的cursor
}
}
但是我们平时却更加喜欢使用For循环来进行遍历操作,因为这样我们就可以在循环中添加许多的操作。于是我们尝试着对while循环的内容进行改写:
for (cp = start; cp->link != cursor; cp = cp->link) {
/* Empty */
}
这样的表示就更加贴近我们平时的编程范式,指针从首指针开始,当cp指向的link指针不是指向当前的游标,于是cp自动往下走。在此例子中,for循环的内部不需要做任何的操作,当for里面的判断完成,cp就自增到了我们想要的位置。当然,编程范式要求我们最好还是在这些不需要做任何操作的循环中写上注释。
因为for循环在处理链接列表时非常有用,所以认识用于操纵列表结构的标准循环模式是非常重要的。 在C ++程序中,遍历链表的模式在概念上类似于用于遍历数组元素的模式。 举个例子,对有效大小为n的数组中的每个元素执行遍历操作的模式如下所示:
for (int i = 0; i < n; i++) {
//. . . code using a[i] . . .
}
对于链表来说,其标准的遍历范式就是:
for (Cell *cp = list; cp != NULL; cp = cp->link) {
//. . . code using cp . . .
}
链式buffer的基本操作实现
趁热打铁,我们先利用for循环实现之前的showContents(),输出链表的每个元素:
void EditorBuffer::showContents() {
for (Cell *cp = start->link; cp != NULL; cp = cp->link) {
cout << ' ' << cp->ch;
}
cout << endl;
for (Cell *cp = start; cp != cursor; cp = cp->link) {
cout << " ";
}
cout << '^' << endl;
}
上述的两个for循环注意看清cp刚刚开始的指向。 我们之前分析的链表都是从一开始就有具体的数据的,并没有说如何从0构造一个链表。接下来看看一个链表是怎么构成的。链表的构成也即是buffer的构造函数:
EditorBuffer::EditorBuffer() {
start = cursor = new Cell; //创建一个新的节点,并且一开始头指针跟游标都指向它
start->link = NULL;//其指针字段为空,也就是这个单元是空单元
}
然后是buffer的析构函数,对于buffer的析构函数,它的作用就是释放所有分配给该类的资源,包括链表中的所有单元格。用我们刚刚学的for循环,大致可以这样写:
for (Cell *cp = start; cp != NULL; cp = cp->link) {
delete cp;
}
然而这样却有个小问题,问题在于,我们顺序遍历的时候,我们是先删除节点,再往下遍历的,当我们删除节点的时候,同时也就删除了指向下一个节点的指针,除非我们可以声明一个变量时刻在删除之前保留那个指向下一个节点的指针,否则删除过程将找不到下一个节点。所以我们也可以这样写:
EditorBuffer::~EditorBuffer() {
Cell *cp = start;
while (cp != NULL) {
Cell *next = cp->link;
delete cp;
cp = next;
}
}
当我们采用析构函数创建了空节点之后,就可以采用插入字符的方法添加链表了。具体的我就不再整理了。之前的文章都有。
双向链表
我们在讨论 moveCursorBackward() 方法中,讨论过为什么链表中的指针后移动比起前移要复杂的多,那是因为节点中只存储了指向下一个节点的指针,并没有存放指向上一个节点的指针。如果我们在节点中添加多一个字段prev,使其指向上一个节点,那么逆序遍历(或者上前移动一个位置)也可以很快执行。但是相对的,类的空间就增加了。这种做法又称为以空间换时间。具有上述结构的链表,我们称之为双向链表。 比如具有下列结构的buffer:
其双向链表表现为:
双向链表的实现非常简单,跟之前的链表原理是一样的,这里就不再赘述,等有时间再总结相应的代码。