Pointers on C——12 Using Structures and Pointers.4

来源:互联网 发布:一个人失忆薛凯琪 知乎 编辑:程序博客网 时间:2024/05/10 13:49

Optimizing the Insert Function

优化插入函数

It appears that inserting a node at the beginning of the list must be a special case. After all, the pointer that must be adjusted to insert the first node is the root pointer. For every other node, the pointer to be adjusted is the link field of the previous node.These seemingly different operations are really the same.

看上去,把个节点插入到链表的起始位置必须作为种特殊情况进行处理毕竟,我们此时插入新节点需要修改的指针是根指针对于任何他节点,对指针进行修改时实际修改的是前节点的link 字段。这两看上去不同的操作实际上是一样


The key to eliminating the special case is to realize that every node in the list has a pointer somewhere pointing to it. For the first node, it is the root pointer, and for every other node it is the link field of the preceding node. The important point is that there is a pointer somewhere pointing to each node. Whether the pointer is or is not contained in a node is irrelevant.

消除特殊情况的键在于:我们必认识到,链表中的每个节点都有个指向它的指针对于1 个节点,这个指针是根指针;对于他节点,这个指针个节点的l 烛。重点在于每个节点都个指针指向它。至于该指针是不是位于个节点的内部则无关紧要


Letʹs look at the list once more to clarify this point. Here is the first node and its corresponding pointer.

让我们再次观察这个链表,弄清这个概念。这是第1 个节和指向它的指针


If the new value is inserted before the first node, then this pointer must be changed.

如果新值插入到第1 个节点之前,这个指针就必须进行修改

Here is the second node and its pointer.

下面是第2 个节点和指向它的指针


If the new value is inserted before the second node, then this pointer must be changed.Note that weʹre concerned only with the pointer; the node that contains it is irrelevant.

The same pattern holds for every node in the list.

如果新值需要插入到第2 个节点之前,那么这个指针必须进行修改注意我们只考虑指向这个节点的指针,至于哪个节点包含这个指针则无关紧要对于链表中的其他节点,都可以应用这个模式


Now letʹs take a look at the modified function as it begins to execute. Here are its variables as they appear just after the first assignment statement.

现在让我们看下修改后的函数〈当它开始执行时〉。下面显示了第1 条赋值语句之后各个变量的情况


We have a pointer to the current node and a pointer to the link that points to the current node. We donʹt need anything else! If the value in the current node is larger than the new value, the rootp pointer tells us which link field must be changed to link the new node into the list. If insertions elsewhere in the list can be expressed the same way, the special case disappears. The key is the pointer/node relationship we saw earlier.

我们拥有一个指向当前节点的指针,以及个"指向当前节点的link 字段的"指针。除此之外,我们就不需要别的了!如果当前节点的值大于新值,那么rootp 指针就会告诉我们哪个link 字段必须进行修改,以便让新节点链接到链表中如果在链表其他位置的插入也可以用同样的方式进行表示,就不存在前面提到的特殊情况了其关键在于我们前面看到的指针/节点关系


When moving to the next node, save a pointer to the link that points to the next node instead of keeping a pointer to the previous node. It is easy to diagram what is desired.

当移动到下个节点时,我们保存个"指向下个节点的Ii nk 字段的"指针,而不是保存一个指向前节点的指针我们很容易画出张描述这种情况的图


Notice here that rootp is not pointing to the node; it points to the link field within the node. This fact is the key to simplifying the insert function, but it depends upon our being able to obtain the address of the link field of the current node. This operation is easy in C. The expression &current->link does the trick. Program 12.3 is the final version of our insertion function. The rootp parameter is now called linkp, because it points to many different links now, not just the root. We donʹt need previous any more, because our link pointer takes care of locating the link that needs to be modified. The special case at the end of the function is gone because we always have a pointer to the link field that needs to be changed—we modify the root variable in exactly the same way as the link field of a node. Finally, register declarations have been added to the pointer variables to improve the efficiency of the resulting code.

注意,这里rooφ 并不指向节点本身,而是指向节点内部的link 字段。这是简化插入函数的关键所在,但我们必须能够取得当前节点的link 字段的地址。在C 中,这种操作是非常容易的。表式&current-> link 就可以达到这个目的。程序12.3 是我们的插入函数的最终版本rootp 参数现在称为linkp,因为它现在指向的是不同的恤字段,而不仅仅是根指针我们不再需要previous 指针,因为我们的恤指针可以负责寻找需要修改的link 字段前面那个函数最后部分用于处理特殊情况的代码也不见了,因为我们始终拥有个指向需要修改的link 字段的指针一一我们用种和修改节点的link 字段完全一样的方式修改root 变量。最后,我们在函数的指针变量中增加了register 声明,用于提高结果代码的效率


The while loop in this final version is trickier because of the embedded assignment to current. Here is an equivalent, though slightly longer loop.

我们在最终版本中的while 循环中增加了一个窍门,它嵌入了对current 的赋值。下面是一个功能相同,但长度稍长的循环。


/*

** Look for the right place.

*/

current = *linkp;

while( current != NULL && current->value < value ){

linkp = &current->link;

current = *linkp;

}

/*

** Insert into an ordered, singly linked list. The arguments are

** a pointer to the first node in the list, and the value to

** insert.

*/

#include <stdlib.h>

#include <stdio.h>

#include "sll_node.h"

#define FALSE 0

#define TRUE 1

int

sll_insert( register Node **linkp, int new_value )

{

register Node *current;

register Node *new;

/*

** Look for the right place by walking down the list

** until we reach a node whose value is greater than

** or equal to the new value.

*/

while( ( current = *linkp ) != NULL &&

current->value < new_value )

linkp = &current->link;

/*

** Allocate a new node and store the new value into it.

** In this event, we return FALSE.

*/

new = (Node *)malloc( sizeof( Node ) );

if( new == NULL )

return FALSE;

new->value = new_value;

/*

** Insert the new node into the list, and return TRUE.

*/

new->link = current;

*linkp = new;

return TRUE;

}

Program 12.3 Insert into an ordered, singly linked list: final version insert3.c


To begin, current is set to point to the first node in the list. The while test checks whether weʹve reached the end of the list. If not, it then checks whether we are at the proper place for the insertion. If not, the body of the loop executes, which sets linkp to point to the link field in the current node, and advances current to the next node.

开始, current 被设置为指向链表的第1 个节点while 循环测试我们是否到达了链表的尾部如果没有,它接着检查我们是否到达了正确的插入位置如果不是,循环体继续执行,并把linkp设置为指向当前节点的link 字段,并使current 指向下个节点


The fact that the last statement in the loop body is identical to the statement just prior to the loop leads to the ʺsimplificationʺ of embedding the assignment to current within the while expression. The result is a more complex but more compact loop,because we have eliminated the redundant assignment to current.

循环的最后条语句和循环之前的那条语句相同,这就促使我们对它进行"简化气方法是把current 的赋值嵌入到while 表达式中。其结果是个稍为复杂但更加紧凑的循环,因为我们消除了current 的冗余赋值


Eliminating the special case made this function simpler. There are two factors that make this improvement possible. The first factor is our ability to interpret the problem correctly. Unless you can identify the commonality in seemingly different operations,you will be stuck writing extra code to handle special cases. Often this knowledge is acquired only after you have worked with the data structure for a while and understand it more clearly. The second factor is that the C language provides the right tools for you to exploit the commonality.

消除特殊情况使这个函数更为简单这个改进之所以可行是由于两方面的因素第1 个因素是我们正确解释问题的能力除非你可以在看上去不同的操作中总结出共性,不然你只能编写额外的代码来处理特殊情况通常,这种知识只有在你学习了一阵数据结构并对其有进一步的理解之后才能获得第2 个因素是C 语言提供了正确的工具帮助你归纳问题的共性


The improved function depends on Cʹs ability to obtain the address of existing objects. Like many C features, this ability is both powerful and dangerous. In Modula and Pascal, for example, there isnʹt an ʺaddress ofʺ operator, so the only pointers that exist are those produced by dynamic memory allocation. It is not possible to obtain a pointer to an ordinary variable or even to a field of a dynamically allocated structure.Pointer arithmetic is not allowed, and there isnʹt any means for casting a pointer from one type to another. These restrictions are advantageous in that they prevent the programmer from making mistakes such as subscripting off the end of an array and generating pointers of one type that in fact point to objects of some other type.

这个改进的函数依于C 能够取得现存对象的地址这一能力和许多C 语言特性一样,这个能力既成力巨大,又暗伏凶险例如,在Modula 和Pascal 中并不存在"取地址"操作符,所以指针唯一的来源就是动态内存分自己。我们没有办法获得一个指向普通变量的指针或甚至是指向一个动态分自己的结构的字段的指针对指针不允许进行算术运,也没有办法把一种类型的指针通过强制类型转换为另一种类型的指针这些限制的优点在于它们可以防止诸如"越界引用数纽元素"或"产生一种类型的指针但实际上指向另一种类型的对象" 这类错误


There are far fewer restrictions on pointers in C, which is why we were able to improve the insertion function. On the other hand, C programmers must be more careful when using pointers to avoid mistakes. The Pascal philosophy to pointers is sort of like saying, ʺYou might hurt yourself with a hammer, so we wonʹt give you one.ʺ The C philosophy is, ʺHere is a hammer. In fact, here are several kinds of hammers. Good luck.ʺ With this power, C programmers can get into more trouble than Pascal programmers, but good C programmers can produce smaller, more efficient, and more maintainable code than their Pascal or Modula counterparts. This is one of the reasons why C is so popular in industry, and why experienced C programmers are in such demand.

C 的指针限制要少得多,这也是我们能改进插入函数的原因所在另一方面, C 程序员在使用指针时必须加倍小心 以避免产生错误Pascal 语言的指针哲学有点类似下面这样的说法:"使用锤子可能会伤着你自己 所以我们不给你锤子。" C 语言的指针哲学则是: "给你锤子,实际上你可以使用好几种锤子权,你好运!"有了这个能力之后, C 程序员较之Pascal 程序员更容易陷入麻烦,但优秀的C 程序员可以比他们的Pascal 和Modula 同行产生体和、史小、效率更高、可维护性更佳的代码这也是C 语言在业界为何如此流行以及经验丰富的C 程序员为何如此受青昧的原因之一


上一章 Pointers on C——12 Using Structures and Pointers.3

原创粉丝点击