垃圾代码评析——关于《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(队列)。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架