编程总结
在刷题之前需要反复练习的编程技巧,尤其是手写各类数据结构实现,它们好比就是全真教的上乘武功
单链表中的每个结点不仅包含值,还包含链接到下一个结点的引用字段。通过这种方式,单链表将所有结点按顺序组织起来
手写完成后,我们针对性的进行练习,练习题如下:
1. 添加结点
2. 删除结点
3. 设计链表
-------------------------------------分割线---------------------------------------
4. 实战手法分析
手法1:删除结点函数的书写,其中
if (index == 0) { // 删除第0个点
tmp->next = tmp->next->next;
tmp->next = NULL;
tmp->next->val = 0;
free(tmp->next);
return ;
}
这一块并没有判断 tmp->next->next是否存在,所以导致程序出现异常 NULL->next
手法2:Free结点函数的书写,其中
/* head -> node1 -> node2 -> node3 */
void myLinkedListFree(MyLinkedList *obj)
{
MyLinkedList *tmp = obj;
MyLinkedList *freeNode;
while (tmp) {
freeNode = tmp;
tmp = tmp->next;
freeNode->next = NULL;
freeNode->val = 0;
free(freeNode);
}
free(obj); // 释放了两次指针
printf("7\n");
}
执行时出现了下面的红框的log :
起初没有细看这里面的log,出现了double free. 而是有个技巧,通过加 printf,找到出错的代码行,再进一步定位分析,也是一种方法.
------------------------------------分割线----------------------------------
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
// 我有一个struct MyLinkedList_t, 它的别名是MyLinkedList.
typedef struct List {
int val;
struct List *next; // 此处不能写 MyLinkedList,此时还不能识别该别名.
} List;
// 在此处初始化数据结构
List *ListCreate() {
// 在链表的第一个结点之前会额外增设一个结点,头结点
List *head = (List *)malloc(sizeof(List));
if (head == NULL) {
return NULL;
}
head->next = NULL;
head->val = 0; // 头结点的val用来指示链表长度
return head;
}
// 获取链表中第index个节点的索引值。如果索引无效,则返回-1
int ListGet(List *obj, int index)
{
int cnt = 0;
List *tmp = obj->next; // 手法: 因为我们是以首元结点开始index为0,所以要obj->next以首元开始, 而不是 List *tmp = obj;
while (tmp) {
if (index == cnt) {
return tmp->val;
}
cnt++;
tmp = tmp->next;
}
return -1;
}
// 首元结点:链表中第一个元素所在的结点,头插法
/* 在链表的第一个元素之前添加值为val的节点。插入后,新节点将成为链表的第一个节点 */
/* obj -> node1 -> node2 */
/* obj -> newNode -> node1 -> node2 */
void AddNodeAtHead(List *obj, int val)
{
List *newNode = (List *)malloc(sizeof(List));
if (newNode == NULL) {
return ;
}
obj->val++; // 记录链表长度
newNode->val = val;
newNode->next = obj->next;
obj->next = newNode;
}
// 将值为val的节点附加到链表的最后一个元素 尾插法
/* head -> node1 -> node2 */
/* head -> node1 -> node2 -> tail */
void AddNodeAtTail(List *obj, int val)
{
List *tail = (List *)malloc(sizeof(List));
List *tmp = obj;
if (tail == NULL) {
return;
}
obj->val++; // 记录链表长度
tail->val = val;
tail->next = NULL;
while (tmp->next) {
tmp = tmp->next;
}
tmp->next = tail;
}
/* 在链表的索引节点之前添加一个值为val的节点。如果索引等于链表的长度,
则节点将附加到链表的末尾。如果索引大于长度,则不会插入节点 */
/* obj -> node1 -> node2 */
/* obj -> node1 -> newNode -> node2 */
void AddNodeAtIndex(List *obj, int index, int val)
{
int cnt = 0;
if (index == obj->val) {
AddNodeAtTail(obj, val);
return ;
}
else if (index > obj->val) {
return ;
}
List *tmp = obj;
List *newNode = (List *)malloc(sizeof(List));
newNode->val = val;
while (tmp) {
if (cnt == index) {
break;
}
tmp = tmp->next;
cnt++;
}
obj->val++; // 记录链表长度
newNode->next = tmp->next;
tmp->next = newNode;
return ;
}
/* 如果索引有效,请删除链表中的第index个节点 */
/* head -> node1 -> node2 -> node3 */
/* head -> node1 -> node3 */
void DeleteAtIndex(List *obj, int index)
{
List *node = NULL;
List *tmp = obj;
int cnt = 0;
while (tmp) {
if (cnt == index) {
break;
}
cnt++;
tmp = tmp->next;
}
if (tmp->next == NULL) { // 删除的是最后的NULL,直接返回,手法
return;
}
obj->val--;
node = tmp->next;
tmp->next = node->next;
free(node);
}
/* head -> node1 -> node2 -> node3 */
void ListFree(List *obj)
{
List *tmp = obj;
List *freeNode;
while (tmp) { // 释放头结点->首元结点内的
freeNode = tmp;
tmp = tmp->next;
freeNode->next = NULL;
freeNode->val = 0;
free(freeNode);
}
}
int main(void)
{
List *obj = ListCreate();
int val = 1;
int index = 0;
AddNodeAtHead(obj, 7);
AddNodeAtHead(obj, 2);
AddNodeAtHead(obj, 1);
AddNodeAtIndex(obj, 3, 0);
DeleteAtIndex(obj, 2);
AddNodeAtHead(obj, 6);
AddNodeAtTail(obj, 4);
val = ListGet(obj, 4);
AddNodeAtHead(obj, 4);
AddNodeAtIndex(obj, 5, 0);
AddNodeAtHead(obj, 6);
ListFree(obj);
}