一.链表部分
1.链表的建立
(1)首先先认识一下链表的结构
链表由三个部分组成:头部,中间各个结点部分,尾部。
一个结点由两部分组成:数据和指针,指针应该指向下一个结点。
当然,头部和尾部的数据部分不适用,头部指针指向头结点,尾部指针为NULL。
下面为建立一个拥有名字和得分的链表(链表长度可以自定义):
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <string.h>
struct Information
{
char name[10];
int score;
};
struct node
{
struct Information Information;
struct node *next;
};
int main()
{
struct node *head,*p,*q,*t;
int n,a,i;
char c[10];
head=NULL;
scanf("%d",&n);
for(i=0;i<n;i++)
{
scanf("%d %s",&a,c);
p=(struct node *)malloc(sizeof(struct node));
memcpy(p->Information.name,c,sizeof(c));
p->Information.score=a;
p->next=NULL;
if(head==NULL)
{
head=p;
}
else
{
q->next=p;
}
q=p;
}
t=head;
while(t!=NULL)
{
printf("Cser's name:%-10s\t",t->Information.name);//×ó¶ÔÆë
printf("His score:%d\n",t->Information.score);
t=t->next;
}
}
2.链表中的查找(包含插入和删除)
思考:当我们需要对链表中的数据进行删除或者向链表中插入一个结点时,我们需要找到什么?
查找功能是我认为在这些功能里最重要的那个,我们以后想要得到的插入和删除
,都是建立在链表的查找之上如果你想删除或者插入但是你却连位置都没有,这
就很尴尬了,所以链表查找我觉得挺重要的,也是挺难理解的,因为只要理解这
个后面的插入和删除功能。拿删除来举例,我们应该将需要删除的结点“架空”,让他前一个结点指向他后一
个,这样就可以实现删除,当然,插入同理,所以总结:链表中的查找,就是要找到前一个结点。
核心:找到目标的前驱节点
方法:一个负责查找的指针,一个负责记录前驱节点的指针
分析途中查找前驱节点的过程:假设我们要找的目标结点为从左往右第三个,那么就让指针p去查找目标,
让指针q放在指针p之前,用来找目标结点的前驱节点。最终函数应该返回的是指针q(无论是要删除还是插
入,核心都是找到前驱节点,之后再进行处理)。
以下是对已经建立好的链表,进行插入(按照分数的高低):
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <string.h>
struct Information
{
char name[10];
int score;
};
struct node
{
struct Information Information;
struct node *next;
};
int main()
{
struct node *head,*p,*q,*t;
int n,a,i;
char c[10],xin[10];
head=NULL;
scanf("%d",&n);
for(i=0;i<n;i++)
{
scanf("%d %s",&a,c);
p=(struct node *)malloc(sizeof(struct node));
memcpy(p->Information.name,c,sizeof(c));
p->Information.score=a;
p->next=NULL;
if(head==NULL)
{
head=p;
}
else
{
q->next=p;
}
q=p;
}
t=head;
while(t!=NULL)
{
printf("Cser's name:%s\t",t->Information.name);
printf("His score:%d\t\n",t->Information.score);
t=t->next;
}
printf("以上为插入新结点之前的链表................\n");
printf("插入一个新结点..........\n");
//插入的过程
scanf("%d %s",&a,xin);
t=head;
while(t!=NULL)
{
if(t->next->Information.score>a || t->next==NULL)
{
p=(struct node *)malloc(sizeof(struct node));
p->next=NULL;
p->Information.score=a;
memcpy(p->Information.name,xin,sizeof(xin));
p->next=t->next;
t->next=p;
break;
}
t=t->next;
}
printf("插入新结点之后的链表........\n");
t=head;
while(t!=NULL)
{
printf("Cser's name:%-10s\t",t->Information.name);
printf("His score:%d\n",t->Information.score);
t=t->next;
}
}
以下是对已经建立好的链表,进行删除(按照输入需删除的得分):
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <string.h>
//删除有问题,无法进入到删除函数。。。。。。。
struct Information
{
char name[10];
int score;
};
struct node
{
struct Information Information;
struct node *next;
};
void cuts(struct node *head );
int main()
{
struct node *head,*p,*q,*t;
int n,a,i;
char c[10],cut;
head=NULL;
scanf("%d",&n);
fflush(stdin);
for(i=0;i<n;i++)
{
scanf("%d %s",&a,c);
p=(struct node *)malloc(sizeof(struct node));
memcpy(p->Information.name,c,sizeof(c));
p->Information.score=a;
p->next=NULL;
if(head==NULL)
{
head=p;
}
else
{
q->next=p;
}
q=p;
}
t=head;
while(t!=NULL)
{
printf("Cser's name:%-10s\t",t->Information.name);
printf("His score:%d\n",t->Information.score);
t=t->next;
}
printf("删除一个结点的数据:\n");
cuts(head);
}
void cuts(struct node *head)
{
struct node *q=NULL,*t,*p;
int b;
q=head;
p=q->next;
printf("请输入你要删除的分数:\n");
scanf("%d",&b);
while(p!=NULL)
{
if(p->Information.score==b)
{
q->next=p->next;
free(p);
p=q->next;
}
else
{
q=q->next;
p=p->next;
}
}
printf("检查删除后的数据;\n");
t=head;
while(t!=NULL)
{
printf("Cser's name:%-10s\t",t->Information.name);
printf("His score:%d\n",t->Information.score);
t=t->next;
}
}
3.链表的销毁
(1)链表销毁的基本思路
上次说了链表的构建,现在呢链表的销毁,他要是销毁,必须释放所有成员的内存
空间,现在一个问题摆在面前,到底我们是从前往后删,还是从后往前删?
如果从后往前删,我们先要用指针指向最后一个结构体的前驱结点的next的值,修改
前驱结点的next的值为空,然后用指针释放最后一个结构体的空间,但是这里最后一
个结构体的前驱结点的next还得你写程序判断,相对从前往后删来说比较麻烦,所以
我们用的是从前往后删。
(2) 图示销毁的过程
先让p指向,头结点后面那个第一个有效节点。 (红色箭头的p)
然后把头结点的next值修改成,现在第一个有效节点的next成员的值(蓝色的箭头)
然后再释放p,这样不会使链表中途断开,一直删完。当删到最后一个时,head->next变为NULL,销毁正式结束。
(3)代码
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <string.h>
//有问题,无法判断是否销毁链表。。。。。
struct Information
{
char name[10];
int score;
};
struct node
{
struct Information Information;
struct node *next;
};
void destory(struct node *head);
int main()
{
struct node *head,*p,*q,*t;
int n,a,i;
char c[10],answer[4];
head=NULL;
scanf("%d",&n);
for(i=0;i<n;i++)
{
scanf("%d %s",&a,c);
p=(struct node *)malloc(sizeof(struct node));
memcpy(p->Information.name,c,sizeof(c));
p->Information.score=a;
p->next=NULL;
if(head==NULL)
{
head=p;
}
else
{
q->next=p;
}
q=p;
}
t=head;
while(t!=NULL)
{
printf("Cser's name:%s\t",t->Information.name);
printf("His level:%d\n",t->Information.score);
t=t->next;
}
printf("您是否要销毁数据?(Y/N)\n");
scanf("%s",answer);
if(answer=='Y')
{
printf("数据销毁完毕\n验证数据是否销毁:\n");
destory(head);
if(head->next==NULL)
{
printf("数据销毁成功\n");
}
}
else if(answer=='N')
{
printf("收到,不销毁数据\n");
}
}
void destory(struct node *head)
{
struct node*f;
while(head->next)
{
f=head->next;
head->next=f->next;
free(f);
}
}
问题:如何判断是否成功销毁链表
二.printf和scanf函数的小知识点
(1)printf函数
(2)scanf函数
3.printf和scanf的返回值
printf:输出的字符数
scanf:读入的项目数
三.学到的新函数
1.malloc函数(理解原型)
(1)malloc是动态内存分配函数,用于申请一块连续的指定大小的内存区域,以void*类型返回分配的内存
的区域地址
(2)malloc函数原型:extern void * malloc(unsigned int num_bytes);
意为分配长度为num_bytes字节的内存块
(3)malloc函数返回的实际是一个无类型指针,必须在其前面加上类型强制转换
2.calloc函数
函数名: calloc
功 能: 在内存的动态存储区中分配n个长度为size的连续空间,函数返回一个指向分配起始地址的指针;如果分配不成功,返回NULL。
跟malloc的区别:
calloc在动态分配完内存后,自动初始化该内存空间为零,而malloc不初始化,里边数据是随机的垃圾数据。
用 法: void *calloc(unsigned n,unsigned size);
3.snprintf函数
头文件:
#include<stdio.h>
函数原型:
int snprintf(char *str, size_t size, const char *format, …);
函数参数:
str:目标字符串;size:拷贝字节数(Bytes); format:源字符串; …格式
函数功能:
最多从源字符串format中拷贝size字节的内容(含字符串结尾标志’\0’)到目标字符串
The functions snprintf() write at most size bytes (including the terminating null byte (’\0’)) to str.
返回值:
成功返回源串的长度(strlen, 不含’\0’)
失败返回负值
例子及理解:
由于最多拷贝size个字节到目标字符串,那么通常目标字符串就设置成size大小就不会有越界问题
下面将目标字符串的长度设置为size大小,分别实验源串长度小于,等于,大于size的情况
四.文件与预编译
1.#define <名字> <值>
(1)名字必须是一个单词,值可以是各种东西
(2)在C语言的编译器开始编译之前,编译预处理会把程序中的名字换成值
(3)如果一个宏的值中有其他目的宏的名字,也会被替换的
(4)如果一个宏的值超过一行,最后一行之间的行末需要加 \
例子:
#define p printf("12345");\
printf("6789");
则打印 12345
6789
2.带参数的宏的规则
(1)一切都要带括号,整个值要带括号,参数出现的每个地方都要带括号
例子:#define p printf("12345");\ printf("6789");
3.头文件
把函数原型放到一个头文件(以.h结尾)中,在需要调用这个函数的源代码文件(.c文件)中#include 这个头
文件,就能让编译器在编译的时候就知道函数的愿你选哪个
4.声明和定义
(1)声明是不产生代码的东西
(2)定义是产生代码的东西
5.标准头文件结构
#ifndef _LIST_HEAD_ //必须是大写
#define _LIST_HEAD_
#include ”node.h“
.
.
.
.
#endif // _LIST_HEAD_