• 数据结构实战开发教程(四)线性表的链式存储结构、单链表的具体实现、单链表的遍历与优化、典型问题分析(Bugfix)


    二十一、线性表的链式存储结构

    1、线性表的链式存储结构

    顺序存储结构线性表的最大问题是:插入和删除需要移动大量的元素!如何解决?

            

    2、链式存储的定义

    为了表示每个数据元素与其直接后继元素之间的逻辑关系;

    数据元素除了存储本身的信息外,还需要存储其直接后继的信息。

            

    3、链式存储逻辑结构

    • 基于链式存储结构的线性表中,每个结点都包含数据域指针域
      • 数据域:存储数据元素本身
      • 指针域:存储相邻结点的地址

            

    4、专业术语的统一

    • 顺序表
      • 基于顺序存储结构的线性表
    • 链表
      • 基于链式存储机构的线性表
        • 单链表:每个结点只包含直接后继的地址信息
        • 循环链表:单链表中的最后一个结点的直接后继第一个结点
        • 双向链表:单链表中的结点包含直接前驱后继的地址信息

    5、链表中的基本概念

    • 头结点
      • 链表中的辅助结点,包含指向第一个数据元素的指针
    • 数据结点
      • 链表中代表数据元素的结点,表现形式为:(数据元素,地址)
    • 尾结点
      • 链表中的最后一个数据结点,包含的地址信息为空

    6、单链表中的结点定义

            

    7、单链表中的内部结构

            

            头结点在单链表中的意义是:辅助数据元素的定位,方便插入和删除操作;因此,头结点不存储实际的数据元素

    8、在目标位置处插入数据元素

            1. 从头结点开始,通过current指针定位到目标位置

            2. 从堆空间申请新的Node结点

            3. 执行操作:

            

    9、在目标位置处删除数据元素             

            1. 从头结点开始,通过current指针定位到目标位置

            2. 使用toDel指针指向需要删除的结点

            3. 执行操作:

            

            

    10、编程实验:图解插入与删除

    11、小结

    • 链表中的数据元素在物理内存中无相邻关系
    • 链表中的结点都包含数据域指针域
    • 头结点用于辅助数据元素的定位,方便插入和删除操作
    • 插入和删除操作需要保证链表的完整性

    二十二、单链表的具体实现

    1、课程目标

    完成链式存储结构线性表的实现

    2、LinkList设计要点

    • 类模板,通过头结点访问后继结点
    • 定义内部结点类型Node,用于描述数据域和指针域
    • 实现线性表的关键操作(增,删,查,等)

    3、LinkList的定义

            

    4、编程实验:链表的实现

    LinkList.h

    5、问题

    头结点是否存在隐患?实现代码是否需要优化?

    6、头结点的隐患

    7、代码优化

    insert , remove , get , set 等操作都涉及元素定位。

    8、编程实验:链表的优化

    LinkList.h

    1. #ifndef LINKLIST_H_
    2. #define LINKLIST_H_
    3. /*******************************************************************************
    4. * Include _Files *
    5. *******************************************************************************/
    6. #include "List.h"
    7. #include "Exception.h"
    8. /*******************************************************************************
    9. * Type Definition *
    10. *******************************************************************************/
    11. namespace DTLib
    12. {
    13. template < typename T >
    14. class LinkList : public List<T>
    15. {
    16. protected:
    17. struct Node : public Object // 结点类
    18. {
    19. Node* next; // 指针域
    20. T value; // 数据域
    21. };
    22. mutable struct : public Object // mutable 突破const的限制而设置,永远处于可变的状态,即使在一个const函数中
    23. {
    24. Node* next; // 指向0元素
    25. char reserved[sizeof(T)]; // 为了与Node内存对齐,以便后续使用强制类型转换,继承于Object同理。
    26. } m_header; // 创建头结点 不直接用 Node m_header,因为T有可能是类,在类运行构造函数时,可能会抛出异常
    27. int m_length; // 当前线性表的长度
    28. int m_step; // 步进次数 遍历元素的时候使用 move(); end(); next(); current()
    29. Node* m_current; // 游标 遍历元素的时候使用 move(); end(); next(); current()
    30. /**********************************************************************
    31. * Function: position()
    32. * Description: 移动i次,返回第i-1个结点的地址
    33. * Input: i 数组类地址
    34. * m_header 头结点地址
    35. * Output:
    36. * Return: Node 返回第i-1个结点的地址
    37. * Others: O(n)
    38. * Modify Date Version Author Modification
    39. * ------------------------------------------------------------
    40. * 2022/03/18 1.0 Create
    41. **********************************************************************/
    42. Node* position(int i) const
    43. {
    44. Node* ret = reinterpret_cast<Node*>(&m_header); // 头结点地址 - 强制类型转换
    45. for(int p=0; p<i; p++) // 移动i次
    46. {
    47. ret = ret->next;
    48. }
    49. return ret;
    50. }
    51. /**********************************************************************
    52. * Function: create()
    53. * Description: 创建新结点(堆空间)
    54. * Input:
    55. * Output:
    56. * Return: Node 返回新结点的地址
    57. * Others:
    58. * Modify Date Version Author Modification
    59. * ------------------------------------------------------------
    60. * 2022/03/18 1.0 Create
    61. **********************************************************************/
    62. virtual Node* create()
    63. {
    64. return new Node();
    65. }
    66. /**********************************************************************
    67. * Function: destroy()
    68. * Description: 销毁结点(堆空间)
    69. * Input: pn 待销毁的结点
    70. * Output:
    71. * Return:
    72. * Others:
    73. * Modify Date Version Author Modification
    74. * ------------------------------------------------------------
    75. * 2022/03/18 1.0 Create
    76. **********************************************************************/
    77. virtual void destroy(Node* pn) // 释放结点
    78. {
    79. delete pn;
    80. }
    81. public:
    82. /**********************************************************************
    83. * Function: LinkList()
    84. * Description: 构造函数 - 初始化参数
    85. * Input:
    86. * Output:
    87. * Return:
    88. * Others:
    89. * Modify Date Version Author Modification
    90. * ------------------------------------------------------------
    91. * 2022/03/18 1.0 Create
    92. **********************************************************************/
    93. LinkList()
    94. {
    95. m_header.next = NULL;
    96. m_length = 0;
    97. m_step = 1;
    98. m_current = NULL;
    99. }
    100. /**********************************************************************
    101. * Function: insert()
    102. * Description: 插入新结点(函数重载)
    103. * Input: i 在位置i插入新结点(默认插在最后)
    104. * e 带插入结点的数据(value)
    105. * Output:
    106. * Return: bool 判断插入结点位置是否合法
    107. * Others: 异常 申请内存是否成功
    108. * Modify Date Version Author Modification
    109. * ------------------------------------------------------------
    110. * 2022/03/18 1.0 Create
    111. **********************************************************************/
    112. bool insert(const T& e)
    113. {
    114. return insert(m_length, e); // 新结点插在最后
    115. }
    116. bool insert(int i, const T& e)
    117. {
    118. bool ret = ((0 <= i) && (i <= m_length)); // 位置i合法性校验
    119. if( ret )
    120. {
    121. Node* node = create(); // 创建新结点 - 申请内存
    122. if( node != NULL )
    123. {
    124. Node* current = position(i); // 获取新结点前一个结点的地址
    125. node->value = e; // 新节点的数据域(value)赋值
    126. node->next = current->next; // 新节点的指针域(next) 指向下一个结点
    127. current->next = node; // 上一个结点的指针域(next) 指向新结点
    128. m_length++; // 当前线性表的长度 +1
    129. }
    130. else
    131. {
    132. THROW_EXCEPTION(NoEnoughMemoryException, "No memory to insert new element ...");
    133. }
    134. }
    135. return ret;
    136. }
    137. /**********************************************************************
    138. * Function: remove()
    139. * Description: 删除结点
    140. * Input: i 在位置i删除结点
    141. * Output:
    142. * Return: bool 判断删除结点位置是否合法
    143. * Others: O(n)
    144. * Modify Date Version Author Modification
    145. * ------------------------------------------------------------
    146. * 2022/03/18 1.0 Create
    147. **********************************************************************/
    148. bool remove(int i)
    149. {
    150. bool ret = ((0 <= i) && (i < m_length)); // 位置i合法性校验
    151. if( ret )
    152. {
    153. Node* current = position(i); // 获取待删除结点前一个结点的地址
    154. Node* toDel = current->next; // 临时保存 待删除结点的地址
    155. if( m_current == toDel ) // 判断游标是否指向 待删除结点的地址
    156. {
    157. m_current = toDel->next; // 指向 待删除结点的下一个结点的地址
    158. }
    159. current->next = toDel->next; // 待删除结点的下一个结点的地址,赋值给待删除结点的前一个结点的next
    160. m_length--; // 当前线性表的长度 -1 为了防止元素为类时,析构函数抛出异常,无法保证链表的安全性
    161. destroy(toDel); // 释放该结点所占得内存
    162. }
    163. return ret;
    164. }
    165. /**********************************************************************
    166. * Function: set()
    167. * Description: 修改第i个结点的值
    168. * Input: i 待修改结点的位置
    169. * Output:
    170. * Return: bool 判断修改结点位置是否合法
    171. * Others: O(n)
    172. * Modify Date Version Author Modification
    173. * ------------------------------------------------------------
    174. * 2022/03/18 1.0 Create
    175. **********************************************************************/
    176. bool set(int i, const T& e)
    177. {
    178. bool ret = ((0 <= i) && (i < m_length)); // 位置i合法性校验
    179. if( ret )
    180. {
    181. position(i)->next->value = e; // 待修改结点的前一个结点->next == 待修改的结点的地址
    182. }
    183. return ret;
    184. }
    185. /**********************************************************************
    186. * Function: get()
    187. * Description: 获取第i个结点的值(函数重载)
    188. * Input: i 待获取结点的位置
    189. * Output:
    190. * Return: bool 判断获取结点位置是否合法
    191. * Others: O(n)
    192. * Modify Date Version Author Modification
    193. * ------------------------------------------------------------
    194. * 2022/03/18 1.0 Create
    195. **********************************************************************/
    196. virtual T get(int i) const
    197. {
    198. T ret;
    199. if( get(i, ret) )
    200. {
    201. return ret;
    202. }
    203. else
    204. {
    205. THROW_EXCEPTION(IndexOutOfBoundsException, "Invalid parameter i to get element ...");
    206. }
    207. return ret;
    208. }
    209. bool get(int i, T& e) const
    210. {
    211. bool ret = ((0 <= i) && (i < m_length)); // 位置i合法性校验
    212. if( ret )
    213. {
    214. e = position(i)->next->value; // 待获取结点的前一个结点->next == 待获取的结点的地址
    215. }
    216. return ret;
    217. }
    218. /**********************************************************************
    219. * Function: find()
    220. * Description: 查找指定元素的位置
    221. * Input: e 待查找结点的值(value)
    222. * Output:
    223. * Return: int 返回结点的位置
    224. * Others: O(n)
    225. * Modify Date Version Author Modification
    226. * ------------------------------------------------------------
    227. * 2022/03/18 1.0 Create
    228. **********************************************************************/
    229. int find(const T& e) const
    230. {
    231. int ret = -1;
    232. int i = 0;
    233. Node* node = m_header.next; // 获取 0 元素的地址
    234. while( node )
    235. {
    236. if( node->value == e ) // 判断值是否相等
    237. {
    238. ret = i; // 获取位置
    239. break;
    240. }
    241. else
    242. {
    243. node = node->next; // 移动到下一个结点
    244. i++; // 位置 +1
    245. }
    246. }
    247. return ret;
    248. }
    249. /**********************************************************************
    250. * Function: length()
    251. * Description: 当前线性表的长度
    252. * Input:
    253. * Output:
    254. * Return: int 当前线性表的长度
    255. * Others: O(1)
    256. * Modify Date Version Author Modification
    257. * ------------------------------------------------------------
    258. * 2022/03/18 1.0 Create
    259. **********************************************************************/
    260. int length() const
    261. {
    262. return m_length;
    263. }
    264. /**********************************************************************
    265. * Function: clear()
    266. * Description: 清空线性表
    267. * Input:
    268. * Output:
    269. * Return:
    270. * Others: O(n)
    271. * Modify Date Version Author Modification
    272. * ------------------------------------------------------------
    273. * 2022/03/18 1.0 Create
    274. **********************************************************************/
    275. void clear()
    276. {
    277. while( m_header.next ) // 0元素的地址是否为 NULL
    278. {
    279. Node* toDel = m_header.next; // 暂存0结点的地址
    280. m_header.next = toDel->next; // 新0号元素地址赋值给头结点
    281. m_length--; // 当前线性表的长度 -1 为了防止元素为类时,析构函数抛出异常,无法保证链表的安全性
    282. destroy(toDel); // 销毁结点
    283. }
    284. m_current = NULL;
    285. }
    286. /*
    287. 遍历所有元素 推荐使用方法
    288. 移动到0号元素 判断是否到尾部 移动到下一个结点
    289. for (list.move(0); !list.end();list.next())
    290. {
    291. cout << list.current() << endl;
    292. }
    293. */
    294. /**********************************************************************
    295. * Function: move()
    296. * Description: 将游标定位到目标位置(i)
    297. * Input: i 移动到第i个元素
    298. * step 步进默认为 1
    299. * Output:
    300. * Return: bool 判断i位置是否合法
    301. * Others:
    302. * Modify Date Version Author Modification
    303. * ------------------------------------------------------------
    304. * 2022/03/18 1.0 Create
    305. **********************************************************************/
    306. virtual bool move(int i, int step = 1)
    307. {
    308. bool ret = (0 <= i) && (i < m_length) && (step > 0);
    309. if( ret )
    310. {
    311. m_current = position(i)->next; // 目标位置的地址
    312. m_step = step;
    313. }
    314. return ret;
    315. }
    316. /**********************************************************************
    317. * Function: end()
    318. * Description: 游标是否到达尾部
    319. * Input:
    320. * Output:
    321. * Return: bool 游标是否到达尾部
    322. * Others:
    323. * Modify Date Version Author Modification
    324. * ------------------------------------------------------------
    325. * 2022/03/18 1.0 Create
    326. **********************************************************************/
    327. virtual bool end()
    328. {
    329. return (m_current == NULL);
    330. }
    331. /**********************************************************************
    332. * Function: current()
    333. * Description: 获取游标所指向的数据元素
    334. * Input:
    335. * Output:
    336. * Return: T 游标所指向的数据元素
    337. * Others: 异常 游标已到尾部,无法获取到值
    338. * Modify Date Version Author Modification
    339. * ------------------------------------------------------------
    340. * 2022/03/18 1.0 Create
    341. **********************************************************************/
    342. virtual T current()
    343. {
    344. if( !end() ) // 游标是否到达尾部
    345. {
    346. return m_current->value;
    347. }
    348. else
    349. {
    350. THROW_EXCEPTION(InvalidOperationException, "No value at current position ...");
    351. }
    352. }
    353. /**********************************************************************
    354. * Function: next()
    355. * Description: 移动游标 m_step次
    356. * Input:
    357. * Output:
    358. * Return: bool 是否成功移动游标 m_step次
    359. * Others:
    360. * Modify Date Version Author Modification
    361. * ------------------------------------------------------------
    362. * 2022/03/18 1.0 Create
    363. **********************************************************************/
    364. virtual bool next()
    365. {
    366. int i = 0;
    367. while( (i < m_step) && !end() ) // 移动次数是否为m_step && 游标是否到达尾部
    368. {
    369. m_current = m_current->next; // 移动一次游标
    370. i++;
    371. }
    372. return (i == m_step);
    373. }
    374. /**********************************************************************
    375. * Function: ~LinkList()
    376. * Description: 析构函数 - 清空线性表
    377. * Input:
    378. * Output:
    379. * Return:
    380. * Others: O(n)
    381. * Modify Date Version Author Modification
    382. * ------------------------------------------------------------
    383. * 2022/03/18 1.0 Create
    384. **********************************************************************/
    385. ~LinkList()
    386. {
    387. clear();
    388. }
    389. };
    390. }
    391. #endif // LINKLIST_H_

    9、小结

    • 通过类模板实现链表,包含头结点成员长度成员
    • 定义结点类型,并通过堆中的结点对象构成链式存储
    • 为了避免构造错误的隐患,头结点类型需要重定义(内存布局必须一致,都要继承object
    • 代码优化是编码完成后必不可少的环节

    二十三、顺序表和单链表的对比分析

    1、问题

    如何判断某个数据元素是否存在于线性表中?

    2、遗失的操作 - find

    • 可以为线性表(List)增加一个查找操作
    • int find(const T& e) const;
      • 参数:
        • 待查找的数据元素
      • 返回值:
        • >= 0:数据元素在线性表中第一次出现的位置
        • -1:数据元素不存在

    3、数据元素查找示例

            

    4、编程实验:查找操作

            List.h

    ① 实际开发时,建议每个自定义类都继承object父类,避免编译错误。

    ② find函数需要比对两个元素是否相同,当比对元素为类时,需要重载==和!=操作符。

    5、时间复杂度对比分析

            

    6、有趣的问题

    顺序表的整体时间复杂度比单链表要低,

    那么单链表还有使用价值吗?

    7、效率的深度分析

    • 实际工程开发中,时间复杂度只是效率的一个参考指标
      • 对于内置基础类型,顺序表和单链表的效率不相上下
      • 对于自定义类类型顺序表在效率上低于单链表
    • 插入和删除
      • 顺序表:涉及大量数据对象的复制操作
      • 单链表:只涉及指针操作,效率与数据对象无关
    • 数据访问
      • 顺序表:随机访问,可直接定位数据对象
      • 单链表:顺序访问,必须从头访问数据对象,无法直接定位

    8、工程开发中的选择

    • 顺序表
      • 数据元素的类型相对简单,不涉及深拷贝
      • 数据元素相对稳定,访问操作远多于插入和删除操作
    • 单链表
      • 数据元素的类型相对复杂,复制操作相对耗时
      • 数据元素不稳定,需要经常插入和删除,访问操作较少

    9、小结

    • 线性表中元素的查找依赖于相等比较操作符( == )
    • 顺序表适用于访问需求量较大的场合(随机访问)
    • 单链表适用于数据元素频繁插入删除的场合(顺序访问)
    • 当数据类型相对简单时,顺序表和单链表的效率不相上下

    二十四、单链表的遍历与优化

    1、问题

    如何遍历单链表中的每一个数据元素?

    2、当前单链表的遍历方法

            

    • O(n)    O(n^2)
    • 遗憾的事实
      • 不能以线性的时间复杂度完成单链表的遍历
    • 新的需求
      • 为单链表提供新的方法,在线性时间内完成遍历

    3、设计思路(游标)

    • 在单链表的内部定义一个游标(Node* m_current)
    • 遍历开始前将游标指向位置为0的数据元素
    • 获取游标指向的数据元素
    • 通过结点中的next 指针移动游标

    4、设计思路(游标)

    提供一组遍历相关的函数,以线性的时间复杂度遍历链表。

            

    5、遍历函数原型设计

    • bool move(int i, int step = 1);  目标位置  每次移动几个结点
    • bool end();
    • T current();
    • bool next();

    6、编程实验:单链表的遍历

    LinkList.h

    7、单链表内部的一次封装

    8、编程实验:内部的封装

    LinkList.h

    9、问题

    封装create 和destroy 函数的意义是什么?

    封装结点的申请和删除操作更有利于增强扩展性。

    10、小结

    • 单链表的遍历需要在线性时间内完成
    • 在单链表内部定义游标变量,通过游标变量提高效率
    • 遍历相关的成员函数是相互依赖,相互配合的关系
    • 封装结点的申请和删除操作更有利于增强扩展性

    二十五、静态单链表的实现

    1、讨论中。。。

            

    2、单链表的一个缺陷

    • 触发条件
      • 长时间使用单链表对象频繁增加和删除数据元素
    • 可能的结果
      • 堆空间产生大量的内存碎片,导致系统运行缓慢

    3、新的线性表

    • 设计思路:
      • 在“单链表”的内部增加一片预留的空间,所有的Node对象都在这片空间中动态创建和动态销毁。

            

    4、静态单链表的继承层次结构

    5、静态单链表的实现思路

    • 通过模板定义静态单链表类(StaticLinkList)
    • 在类中定义固定大小的空间(unsigned char [])
    • 重写create和destroy函数,改变内存的分配和归还方式
    • 在Node类中重载operator new,用于在指定内存上创建对象

    6、编程实验:静态单链表的实现

    StaticLinkList.h

    1. #ifndef STATICLINKLIST_H_
    2. #define STATICLINKLIST_H_
    3. /*******************************************************************************
    4. * Include _Files *
    5. *******************************************************************************/
    6. #include "LinkList.h"
    7. /*******************************************************************************
    8. * Type Definition *
    9. *******************************************************************************/
    10. namespace DTLib
    11. {
    12. template< typename T, int N >
    13. class StaticLinkList : public LinkList<T>
    14. {
    15. protected:
    16. typedef typename LinkList<T>::Node Node; // 使用typename的原因:编译器无法辨识标识符究竟是什么(是一个类型还是一个成员名称(静态数据成员或者静态函数))
    17. struct SNode : public Node // 结点的定义
    18. {
    19. void* operator new(unsigned int size, void* loc) // 重载new操作符
    20. {
    21. (void)size; // 消除 size没有使用的警告
    22. return loc;
    23. }
    24. /*
    25. placement new 是重载operator new 的一个标准、全局的版本,它不能够被自定义的版本代替
    26. placement new的执行忽略了size_t参数,只返还第二个参数。其结果是允许用户把一个对象放到一个特定的地方,达到调用构造函数的效果。
    27. 括 号里的参数ptr是一个指针,它指向一个内存缓冲器,placement new将在这个缓冲器上分配一个对象。Placement new的返回值是这 个被构造对象的地址(比如括号中的传递参数)。
    28. placement new主要适用于:在对时间要求非常高的应用程序中,因为这些程序分配的时间是确定 的;长时间运行而不被打断的程序;以及执行一个垃圾收集器 (garbage collector)。
    29. */
    30. };
    31. unsigned char m_space[sizeof(SNode) * N]; // N个空间单元
    32. int m_used[N]; // 标记空间单元使用情况
    33. /**********************************************************************
    34. * Function: create()
    35. * Description: 在指定内存中创建新结点
    36. * Input:
    37. * Output:
    38. * Return: Node 返回新结点的地址
    39. * Others:
    40. * Modify Date Version Author Modification
    41. * ------------------------------------------------------------
    42. * 2022/03/18 1.0 Create
    43. **********************************************************************/
    44. Node* create()
    45. {
    46. SNode* ret = NULL;
    47. for(int i=0; i<N; i++) // 遍历各个空间
    48. {
    49. if( !m_used[i] ) // 如果没有被使用
    50. {
    51. ret = reinterpret_cast<SNode*>(m_space) + i; // 获取到对应的内存单元
    52. ret = new(ret)SNode() ; // 调用SNode类的构造函数
    53. m_used[i] = 1; // 标记当前的内存单元已使用
    54. break;
    55. }
    56. }
    57. return ret;
    58. }
    59. /**********************************************************************
    60. * Function: destroy()
    61. * Description: 在指定内存中删除结点
    62. * Input: pn 欲删除结点的地址
    63. * Output:
    64. * Return:
    65. * Others:
    66. * Modify Date Version Author Modification
    67. * ------------------------------------------------------------
    68. * 2022/03/18 1.0 Create
    69. **********************************************************************/
    70. void destroy(Node* pn)
    71. {
    72. SNode* space = reinterpret_cast<SNode*>(m_space);
    73. SNode* psn = dynamic_cast<SNode*>(pn);
    74. for(int i=0; i<N; i++)
    75. {
    76. if( psn == (space + i) )
    77. {
    78. m_used[i] = 0; // 该空间单元使用情况为 可用
    79. psn->~SNode(); // 调用析构函数,这里需要使用的是子类的析构函数,故需要对pn进行一个强制类型转换成子类对象
    80. break;
    81. }
    82. }
    83. }
    84. public:
    85. // 构造函数
    86. StaticLinkList()
    87. {
    88. for(int i=0; i<N; i++)
    89. {
    90. m_used[i] = 0;
    91. }
    92. }
    93. // 获取空间单元个数
    94. int capacity()
    95. {
    96. return N;
    97. }
    98. // 析构函数
    99. ~StaticLinkList()
    100. {
    101. this->m_header.next = NULL; // 头结点标记为 空
    102. }
    103. };
    104. }
    105. #endif // STATICLINKLIST_H_

    7、Q&A

    LinkList中封装create和destroy函数的意义是什么?

    为静态单链表( StaticLinkList )的实现做准备。

    StaticLinkList 与 LinkList的不同仅在于链表结点内存分配上的不同

    因此,将仅有的不同封装于父类和子类的虚函数中

    8、小结

    • 顺序表与单链表相结合后衍生出静态单链表
    • 静态单链表是LinkList的子类,拥有单链表的所有操作
    • 静态单链表在预留的空间中创建结点对象
    • 静态单链表适合于频繁增删数据元素的场合(最大元素个数固定

    二十六、典型问题分析(Bugfix)

    1、ISSUE 1

    • 创建异常对象时的空指针问题

    • 需要判断_message是否为空(strdup默认入参不为空)

    2、ISSUE2

    • LinkList 中的数据元素删除

    • List.remove在释放类内存时,析构函数抛出异常后,链表依然长度为3。
      • 为了防止元素为类时,析构函数抛出异常,无法保证链表的安全性。
        • 在释放内存之前,即调用析构函数之前,调整链表长度。

    3、ISSUE 3

    • LinkList 中遍历操作与删除操作的混合使用

    • 当删除某个结点之前,需判断游标是否也指向该结点。若是,则将游标指向下一个结点。

    4、ISSUE 4

    • StaticLinkList中数据元素删除时的效率问题

    5、ISSUE 5

    • StaticLinkList是否需要提供析构函数?

    • 问题描述:StaticLinkList继承于LinkList,在未定义析构函数时,会依次调用 子类析构函数、父类析构函数,在父类析构函数中会调用clear函数,

    clear函数中调用了destroy函数,destroy函数调用了delete函数释放内存,而StaticLinkList使用的不是堆空间,所以会导致问题。

    • 解决方案:在StaticLinkList创建析构函数直接调用父类的clear函数,此时由于构造函数和析构函数,不支持多态,所以会调用子类中的destroy函数。

    6、ISSUE 6

    • DTLib是否有必要增加多维数组类?
      • 多维数组的本质:数组的数组!

    7、实践经验

    是软件就有bug,因此需要不停的迭代升级,解决问题。库是一种特殊的软件产品,也会存在各种bug,也需要迭代升级,解决问题。

  • 相关阅读:
    Blazor技术开发了一个访客管理系统
    java:枚举类
    【无标题】
    css点击文字(非按钮) 能自动改变颜色。
    通过融合UGV的地图信息和IMU的惯性测量数据,实现对车辆精确位置和运动状态的估计和跟踪研究(Matlab代码实现)
    Linux 下 chmod 777 修改权限
    使用 PyTorch Edge Delegate 加速移动设备端的 PyTorch 模型
    eBPF学习笔记(一)—— eBPF介绍&内核编译
    通信原理_2 确定信号分析
    [Linux入门]---Linux项目自动化构建工具-make/Makefile
  • 原文地址:https://blog.csdn.net/qq_38426928/article/details/125464856