从一个错误的嵌套结构体开始入手
struct Test
{
int x;
int y;
struct Test test;
};
会陷入无限的递归出现段错误
struct Test
{
int x;
int y;
struct Test *test;//变成指针之后就不会报错
};
变成指针以后就不会循环了,因为这个语句的意思只是声明
了一个指针,而不是无限循环。这就是链表的结构。
单链表
把结构体改变为链表的结构
//链表book
struct Book2
{
char title[128];
char author[40];
struct Book2 *next;
};
在单链表中插入元素
先连后断,保证能找到后一个节点。
#include<stdio.h>
#include<stdlib.h>
//链表book
struct Book2
{
char title[128];
char author[40];
struct Book2 *next;
};
void getInput(struct Book *book)
{
printf("input book name:\n");
scanf("%s",book->title);
printf("input writer:\n");
scanf("%s",book->author);
}
void addBook(strcut Book **library)//这里要进行两层的解引用,它是一个指向Book2结构的指针的指针 因为要修改指针的值,所以要传入指针的地址
{
struct Book2 *book,*temp;
book=(struct Book2*)malloc(sizeof(struct Book2));
if (book==NULL)
{
printf("fail!\n");
exit(1);
}
getInput(book);
if(*library !=NULL)
{
temp=*library;
*library=book;
book->next=temp;//把book.next指向上一次的地址
}
else{//*library 是空的,即直接把head指向这个新生成的节点
*library=book;
book->next=NULL;
}
}
void printLibrary(struct Book *library)
{
struct Book *book;
int count=1;
book=library;
while (book!=NULL)
{
printf("Book%d:",count);
printf("name:%s",book->title);
book=book->next;
count++;
}
}
void releaseLibrary(struct Book **library)
{
struct Book *temp;
while (*library!=NULL)
{
//这里需要增加一个临时变量,使得释放能正常运行
temp=*library;
*library=(*library)->next;
free(temp);
}
}
int main()
{
struct Book2 *library=NULL;
addBook(&library);//这里要给library的地址,而不是library,因为要修改的并不是library,而是其前一个节点
//打印成员
printLibrary(library);
releaseLibrary(library);
return 0;
}
形参里解引用两次的解释:
首先简单来说假如你写一个函数执行完使得p=3并传回主函数,你怎么写?
一种是直接赋值/例子说明,语法简略: int func(int p){ p=3,return p }
这样做的时候你不得不在main函数里面写上 p= func§;因为函数里面的局部变量不会改变main里面的值,所以你需要“返回+赋值”两道工序。
指针的作用就是解决这道工序,传入地址再更改可以改值!
如果改为 void func(int *p){ *p=3 }即更改完毕,main里面写 func(&p); 就好。
该题同理,头插法用常人的思路只用一个" * "能不能写?能写!
以下为我的代码/这里没有语法问题(这个函数需要你能看明白):
struct Book *add_book(struct Book *library)
{
struct Book *book=NULL,*temp;
book = (struct Book *)malloc(sizeof(struct Book));
if(book==NULL) exit(1);
printf("请输入书名:\n");
scanf("%s",book->title);
printf("请输入作者:\n");
scanf("%s",book->author);
if(library!=NULL)
{
temp = library;
library = book;
book->next = temp;
}
else
{
library = book;
book->next = NULL;
}
printf("插入成功\n");
return library;
}
通常看这些代码没那么多*脑袋就不晕了,这里你要明白,这个形参里的指针library跟例子里面的常量p是一样的,无论library怎么改都不会进入main函数,所以必须要有“返回+赋值”。
于是主程序必须写成这样:{… library=add_book(library); …}。放心,该头插法可以正常运行。
现在我们跟示例一样将p变成p,函数变为void add_book(struct Book **library),将里面所有的library直接变为library, 这样函数没有返回值,main函数里面的语句变为:add_book(&library);
用我这种思路的好处是:1.你不必纠结于“”的具体意义就能写出的函数。2.你可以主动“降维”,写单*的函数实现相同的功能,有效降低大脑负荷。
尾插法
void addBook(strcut Book **library)//这里要进行两层的解引用,它是一个指向Book2结构的指针的指针 因为要修改指针的值,所以要传入指针的地址
{
struct Book2 *book,*temp;
book=(struct Book2*)malloc(sizeof(struct Book2));
if (book==NULL)
{
printf("fail!\n");
exit(1);
}
getInput(book);
if(*library !=NULL)
{
temp=*library;//这个和头插一样
//定位单链表的尾部位置
while(temp->next !=NULL)
{
temp=temp->next;
}
//找到最后一个节点之后插入数据
temp->next=book;
book->next=NULL;
}
else{//*library 是空的,即直接把head指向这个新生成的节点
*library=book;
book->next=NULL;
}
}
高效尾插法
void addBook(strcut Book **library)//这里要进行两层的解引用,它是一个指向Book2结构的指针的指针 因为要修改指针的值,所以要传入指针的地址
{
static struct Book *tail;//用来记录单链表尾部的位置,需要变成静态变量 不需要每次都进行遍历
struct Book2 *book,*temp;
book=(struct Book2*)malloc(sizeof(struct Book2));
if (book==NULL)
{
printf("fail!\n");
exit(1);
}
getInput(book);
if(*library !=NULL)
{
tail->next=book;
book->next=NULL;
}
else{//*library 是空的,即直接把head指向这个新生成的节点
*library=book;
book->next=NULL;
}
//最后把book设置为新的tail
tail=book;
}
搜索节点
struct Book *searchBook(struct Book *library,char *target)
{
struct Book *book;
book=library;
while (book!=NULL)
{
if(!strcmp(book->title,target)||!strcmp(book->author,target))//strcmp 若俩字符串相等则返回0 否则返回1
{
break;//找到就跳出
}
book=book->next;
}
return book;
}
实战训练
单链表的插入相比数组,并不用把数组插入元素后面的元素都往后移动。
例:
让用户输入若干整数,然后进行插入:
struct Node
{
int value;
struct Node *next;
};
void insertNode(struct Node **head,int value)//value 是插进去具体的值
{
struct Node *prev;
struct Node *curr;
struct Node *new;
curr=*head;
pre=NULL;
while(curr!=NULL && curr->value<value)//来查找定位
{
prev=curr;
curr=curr->next;
}
//申请一个内存空间来存放新的节点插入找到的位置之前
new=(struct Node *)malloc(sizeof(struct Node));
if (new==NULL)
{
exit(1);
}
new->value=value;
new->next=curr;
if (prev==NULL)//自身是一个空的单链表
{
*head=new;
}
else{
prev->next=new;
}
}
void printNode(struct Node *head)
{
struct Node *curr;
curr=head;
while (curr!=NULL)
{
printf("%d\n",curr->value);
curr=curr->next;
}
putchar('\n');
}
int main()
{
struct Node *head=NULL;
int input;
while (1)
{
printf("input a num:");
if(input==-1)
{
break;
}
insertNode(&head,input);
printNode()
}
return 0;
}
例2:
链表的删除
struct Node
{
int value;
struct Node *next;
};
void insertNode(struct Node **head,int value)//value 是插进去具体的值
{
struct Node *prev;
struct Node *curr;
struct Node *new;
curr=*head;
pre=NULL;
while(curr!=NULL && curr->value<value)//来查找定位
{
prev=curr;
curr=curr->next;
}
//申请一个内存空间来存放新的节点插入找到的位置之前
new=(struct Node *)malloc(sizeof(struct Node));
if (new==NULL)
{
exit(1);
}
new->value=value;
new->next=curr;
if (prev==NULL)//自身是一个空的单链表
{
*head=new;
}
else{
prev->next=new;
}
}
void printNode(struct Node *head)
{
struct Node *curr;
curr=head;
while (curr!=NULL)
{
printf("%d\n",curr->value);
curr=curr->next;
}
putchar('\n');
}
void deleteNode(struct Node **head,int input)
{
struct Node *prev;
struct Node *curr;
curr=*head;
prev=NULL;
while(curr!=NULL && curr->value!=value)
{
pre=curr;
curr=curr->next;
}
if (curr==NULL)
{
printf("not found!");
return ;
}
else{
if (prev==NULL)
{
*head=curr->next;
}
else
{
pre->next=cur->next;
cur->next=NULL;
}
}
}
int main()
{
struct Node *head=NULL;
int input;
while (1)
{
printf("input a num:");
if(input==-1)
{
break;
}
deleteNode(&head,input);
printNode()
}
return 0;
}