• 垃圾代码评析——关于《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(队列)。

  • 相关阅读:
    prototype常用函数
    SQL Left Join,用于在网站中显示相关联的内容
    .net中ajax实现检测用户名的完整测子
    绑定数据源后,Repeater在创建时触发的事件,以及如果在cs文件中调用某个字段的值
    sql2005 转换到 sql2000
    FCKeditor 2.6.3在ASP.NET中的配置方法
    怎样和老板谈绩效考核(3)
    If Tomorrow Never Comes
    为何会被拒(1)
    为何会被拒(2)
  • 原文地址:https://www.cnblogs.com/pmer/p/2783672.html
Copyright © 2020-2023  润新知