博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
垃圾代码评析——关于《C程序设计伴侣》9.4——链表(二)
阅读量:7208 次
发布时间:2019-06-29

本文共 3802 字,大约阅读时间需要 12 分钟。

前文链接:

【重构】

  首先,什么是链表?无论《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 
2. #include
3. 4. //----------通用函数---------------------//5. void *my_malloc( size_t );6. //--------------------------------------//7. 8. 9. //----------“数据”类型----------------//10. typedef 11. struct12. {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 node26. {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(队列)。

 

转载地址:http://izlum.baihongyu.com/

你可能感兴趣的文章
Css 常用属性
查看>>
GRIDVIEW多行多列合并单元格(合并列)
查看>>
sharepoint2010问卷调查(3)-实现问卷的开始和结束时间(采用自定义字段类型)...
查看>>
java final
查看>>
【吐槽】VS2012的安装项目只能用InstallShield Limited Edition
查看>>
win7重装系统时,使用PE工具箱进入系统看到的“C盘变成0.2G,D盘变成48G左右”这是什么回事?...
查看>>
JQuery URL的GET参数值获取方法
查看>>
关于Char* ,CString ,WCHAR*之间的转换问题
查看>>
第十二天--Property List和NSUserDefaults
查看>>
JS Bin Tips and Bits • About
查看>>
Sharepoint学习笔记—习题系列--70-576习题解析 -(Q40-Q44)
查看>>
nodejs发展
查看>>
Fragment过度动画分析一
查看>>
UBI文件系统简介
查看>>
《现代操作系统》精读与思考笔记 第一章 引论
查看>>
System.out.print实现原理猜解
查看>>
每日英语:The Invasion of the Online Tutors
查看>>
codepage IMLangCodePages
查看>>
Leetcode: Valid Parentheses
查看>>
JavaScript Structure
查看>>