垃圾代码评析——关于《C程序设计伴侣》9.4——链表(二)

前文链接:http://www.cnblogs.com/pmer/archive/2012/11/21/2781703.html

【重构】

  首先,什么是链表?无论《C程序设计》还是《C程序设计伴侣》都没有给出清晰准确的定义。
  不知道什么叫链表,当然不可能正确地写出关于链表的代码,《C程序设计伴侣》中出现为链表安装了一条不伦不类的“义尾”(tail)的怪现象也就不足为怪了。
  在这里我给出我对链表的定义:链表就是一个指针,这个指针,要么值为NULL,要么指向其中含有另一个链表的数据。
  当然,链表有很多种,这里的定义只是最简单的一种——单向链表。
  这个定义明显模仿了n!的定义方法,是一种递归式的定义。这种递归式定义的东西用递归的方法很容易实现。
  其次,链表结点的描述。

typedef 
   struct
   {
      char name[20];
      float score;
   }
data_t;

typedef 
   struct node
   {
      data_t item ;
      struct node *next ;
   }
node_t;

 

  这样就建立了一个抽象的结点类型。这样处理的好处是成功地把数据从结点中在形式上剥离开了,这对代码的结构及可维护性都非常很重要。因为我们知道,在一个糟糕的数据结构上是绝对无法建立良好的代码结构的,而代码的结构糟糕则一定会导致可维护性的下降。
  选择了链表就是选择了链表的优点而不是选择了链表的劣势,这就和你买辆汽车是用来开的而不是用来推的道理一样。和同样属于线性结构的数组相比,链表的优势是很容易在前面加入结点结点,但在尾部加入结点则要麻烦得多。数组则相反,即使在有足够的预留空间的情况下,不经过一番折腾也很难在数组的开始加入一个数据,但是很容易在数组的尾部加入一个数据(只要事先预留了位置)。
  既然链表很容易在头部加入结点,通常情况下建立链表也应该选择这种方式——不断地向其中添加数据就行了。代码如下:

1. #include <stdlib.h>
2. #include <stdio.h>
3. 
4. //----------通用函数---------------------//
5. void *my_malloc( size_t );
6. //--------------------------------------//
7. 
8. 
9. //----------“数据”类型----------------//
10. typedef 
11.    struct
12.    {
13.       char name[20];
14.       float score;
15.    }
16. data_t;
17. //-----------关于“数据”的函数-----------//
18. int  input  ( data_t * ) ; 
19. void output ( data_t * ) ; 
20. //---------------------------------------//
21. 
22. 
23. //---------“结点”类型------------------//
24. typedef 
25.    struct node
26.    {
27.       data_t item ;
28.       struct node *next ;
29.    }
30. node_t;
31. //--------“链表”操作函数---------------//
32. void create( node_t ** );
33. void insert( node_t ** ,  node_t * );
34. void print ( node_t * );
35. //--------------------------------------//
36. 
37. 
38. int main( void )
39. {
40.    node_t *head = NULL ; //空链表
41.    
42.    puts("建立链表");
43.    create( &head ); 
44.    //测试 
45.    puts("输出链表");
46.    print( head );
47. 
48.    return 0;
49. }
50. 
51. //-------通用函数定义-------------------//
52. void *my_malloc( size_t size )
53. {
54.    void *p = malloc( size );
55.    if( p == NULL )
56.    {
57.       puts("内存不足");
58.       exit(1); 
59.    }   
60.    return p;
61. }
62. //--------------------------------------//
63. 
64. //---------“数据”操作函数定义---------//
65. int input ( data_t *p_data )
66. {
67.    puts("请输入姓名、成绩:");
68.    if( scanf( "%20s%f" , p_data->name , &p_data->score ) != 2 ) //20很重要 
69.    {
70.       while( (getchar()) != '\n' ) //清空输入缓存 
71.          ;
72.       return 0 ;   
73.    }
74.    return !0 ;
75. }
76. 
77. void output ( data_t *p_data )
78. {
79.    printf ("姓名:%s 成绩:%.2f\n" , p_data->name , p_data->score  ) ;
80. }
81. //--------------------------------------//
82. 
83. 
84. //----------“链表”操作函数定义--------//
85. 
86. void print( node_t *p_node )
87. {
88.    if( p_node != NULL )
89.    {
90.       output ( &p_node->item ); 
91.       print( p_node->next );
92.    }
93. }
94. 
95. void insert( node_t ** p_next ,  node_t * p_node)
96. {
97.    p_node -> next = * p_next ;
98.    * p_next = p_node ;
99. }
100. 
101. void create( node_t **p_next )
102. {
103.    data_t data;
104. 
105.    if( input ( & data ) != 0 )
106.    {
107.       node_t *p_node = my_malloc( sizeof (*p_node) );
108.       p_node->item = data ;
109.       insert( p_next , p_node );//插入结点
110.       create( p_next );//继续 
111.    }
112. }
113. //--------------------------------------//

 

  这种写法,由于把“数据”视为结点的一个抽象成员,成功地把“数据”部分的操作与关于链表的操作像油和水一样地分离开来。在开发过程中可以把它们作为两个相对独立的模块开发(即所谓“高内聚低耦合”)。而且即使以后对“数据”部分有所修改,只要接口没有发生改变,链表部分完全不受任何影响。

  或许有人仍然希望新加入的结点都接到链表的尾部,由于这里的create()函数是一次性从零开始建立链表,所以实现这点也并不困难甚至可以说是非常简单,只需要把create()函数定义中的  

create( p_next );//继续

改成

create( &(*p_next)->next );   //create( p_next );//继续

 

就可以了。
  也就是说,对于一次性建立的链表,压根不需要有tail这种东东。

  但在实际开发中,并不是总能碰到问题要求一次性建立链表这种“好事”,如果问题要求动态地(非一次性地)从链表尾部加入结点、并且要求从前面删除结点呢?这时才需要在head的基础上再加一个记录尾部结点的tail。但即使这样也不要“首尾两端”,而应该“首尾共济”,大大方方地

typedef 
 struct 
 {
    node_t *head;
    node_t *tail;
 } 
queue_t  ;

 

好了。
只不过这种东东并不叫链表(Linked list),而叫Queue(队列)。

 

posted @ 2012-11-22 23:53  garbageMan  阅读(1448)  评论(2编辑  收藏  举报