一、结构体基础
【知识回顾】自定义数据结构的本质
//结构体中有结构体变量、结构体指针
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
struct student
{
char name[64];
int age;
char *p;
};
struct teacher
{
char name[64];
int age;
char *p;
struct student s1; //老师结构体中有学生结构体变量
struct student *pstu;
struct teacher *ptea;
};
//struct teacher //这种定义数据类型的方法是错误的,因为编译器不知道
//{ //这种数据类型所占内存的大小
// struct teacher tea;
//};
int main()
{
struct teacher t1;
struct student s2;
t1.age = 40;
t1.s1.age = 24;
//我们想通过pstu去操作pstu所指向的内存空间,但是这个内存空间不存在。
//但是没有内存哪有指针!
//t1.pstu->age = 20; //没有内存哪有指针? //正确的使用方法,见下
t1.pstu = &s2; //先让pstu指向学生变量s2
t1.pstu->age = 20; //在进行赋值
strcpy(t1.pstu->name,"guojiawei");
printf("%d,%s\n",t1.pstu->age,t1.pstu->name);
getchar();
}
二、链表
链表的学习目录
链表图
//【综合示例】链表的创建、打印、插入、删除、销毁、逆置
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
typedef struct Node
{
int data;
struct Node *next;
}SLIST;
//函数的声明
int create_SList(SLIST** Head) ;
int SList_print(SLIST* pHead);
int SList_Insert(SLIST* pHead,int x,int y);
int SList_Delete(SLIST* pHead,int y);
int SList_Destroy(SLIST* pHead);
int SList_Reverse(SLIST* pHead); //链表的逆置
int main()
{
SLIST *pHead = NULL;
if(create_SList(&pHead) != 0)
return -1;
printf("打印链表:");
SList_print(pHead);
//在值为x的结点后面插入值为y的结点
int x ;
int y ;
printf("输入要查找结点为 x 的值 ");
scanf("%d",&x);
printf("输入要插入的值 y = ");
scanf("%d",&y);
if(SList_Insert(pHead,x,y) != 0)
{
return -1;
}
printf("在值为x的结点后面插入值为y的结点后,打印链表:\n");
SList_print(pHead);
//删除结点为y的链表结点
int rv;
printf("输入要删除的值 y = ");
scanf("%d",&y);
rv = SList_Delete(pHead,y);
if(rv == 0)
{
printf("找到值为y的点,删除的值为y的结点后,打印链表:\n");
SList_print(pHead);
}
else
{
printf("没找到值为y的点,直接打印链表:\n");
SList_print(pHead);
}
//链表的逆置
SList_Reverse(pHead);
printf("链表逆置后,打印链表:");
SList_print(pHead);
//销毁链表
SList_Destroy(pHead);
getchar();
}
//创建链表
int create_SList(SLIST** Head) //用二级指针作形参,传入的实参是一级指针的地址
{
int ret = 0;
int data = 0;
//1.建立头结点并初始化
SLIST *pHead = (SLIST*)malloc(sizeof(SLIST));
if(pHead==NULL)
{
ret = -1;
printf("func create_SList() err:%d",ret);
return ret;
}
pHead->data = 0;
pHead->next = NULL;
//2.循环创建结点,结点数据域中的数值从键盘输入,以输入值为-1结束输入
SLIST *pCur = NULL; //当前指针:辅助指针
pCur = pHead; //准备环境,让pCur指向pHead
printf("请输入数据:(-1:输入完毕)\n");
scanf("%d",&data);
while(data !=-1)
{
SLIST *pNew = (SLIST*)malloc(sizeof(SLIST));
if(pNew == NULL)
{
ret = -2;
SList_Destroy(pHead); //很重要:能防止内存泄漏
printf("func:create_SList() err malloc:%d",ret);
return ret;
}
pNew->data = data;
//①新结点入链表
pCur->next = pNew;
pNew->next = NULL;
//②当前结点指针pCur下移(新结点变成当前结点)
pCur = pNew;
printf("请输入数据:(-1:输入完毕)\n");
scanf("%d",&data);
}
*Head = pHead; //二级指针作输出的模型
return ret;
}
//打印链表
int SList_print(SLIST* pHead)
{
if(pHead == NULL)
return -1;
SLIST* pCur = NULL;
pCur = pHead->next; //准备环境
while(pCur)
{
printf("%7d",pCur->data);
pCur = pCur->next;
}
printf("\n");
return 0;
}
//在x结点后插入值为y的结点;如果x结点不在,就把y结点插入到表尾
int SList_Insert(SLIST* pHead,int x,int y)
{
int ret = 0;
if(pHead == NULL) //如果无头结点,则error
{
ret = -1;
printf("func:SList_Insert error %d",ret);
return ret;
}
//----------------------------------------------------------------
//若有头,则在该链表中查找值为x的结点
//环境准备
SLIST* pPre = pHead;
SLIST* pCur = pHead->next;
SLIST* pTmp = NULL;
while(pCur!=NULL)
{
if(pCur->data == x) //如果找到了x
{
break;
}
//如果没找到x,重置pPre、pCur的位置(把它俩的位置后移一个位置),继续下一次while循环
pPre = pCur;
pCur = pCur->next;
}
//while循环完成后,有下面两种情况:一种是查找到x的结点;一种是没查找到x结点
//case1:若没查找到x结点(则此时pPre指向链表的最后一个结点; pCur指向最后一个结点的下一个节点即空结点)
if(pCur == NULL)
{
pCur = (SLIST*)malloc(sizeof(SLIST));
if(pCur == NULL)
{
ret = -2;
printf("func:SList_Insert error: malloc %d",ret);
SList_Destroy(pHead);
return ret;
}
pCur->data = y;
pPre->next = pCur;
pCur->next = NULL;
}
else //如果没有找到值为x的结点:此时pCur指向值为x的结点,pPre指向值为x结点的前驱
{
SLIST* pNew = (SLIST*)malloc(sizeof(SLIST));
if(pNew == NULL)
{
ret = -2;
printf("func:SList_Insert error: malloc %d",ret);
SList_Destroy(pHead);
return ret;
}
pNew->data = y;
pNew->next = pCur;
pPre->next = pNew;
}
return ret;
}
//删除值为y的结点
int SList_Delete(SLIST* pHead,int y)
{
//环境准备
SLIST *pPre = pHead; //保存当前结点pCur的前驱
SLIST *pCur = pHead->next;
//查找值为y的结点
while(pCur != NULL)
{
if(pCur->data == y)
{
break;
}
pPre = pCur;
pCur = pCur->next;
}
//while循环后,有两种情况:查找到值为y的结点;没查找到值为y的结点
//1.如果没有找到值为y的结点
if(pCur == NULL)
{
return -1;
}
else //2.如果找到值为y的结点:此时pCur指向值为y的结点,pPre指向值为y的结点的前驱
{
//删除y结点
pPre->next = pCur->next;
free(pCur);
pCur = NULL;
return 0;
}
}
//销毁链表
int SList_Destroy(SLIST* pHead)
{
SLIST *pTmp = NULL; //当前结点的后继
SLIST *pCur = pHead; //当前结点
if(pHead == NULL) //如果是空链表
return -1;
while(pCur)
{
pTmp = pCur->next;
free(pCur);
pCur = pTmp;
}
return 0;
}
int SList_Reverse(SLIST* pHead) //链表的逆置,注意:使当前指针pCur指向第二个结点
{
if(NULL==pHead) //如果传入的头结点为空,则error
{
return -1; //异常返回
}
//如果不够两个业务结点,则不用逆置链表,直接返回 //即:只有头结点没有业务结点||只有一个业务结点
if( (NULL==pHead->next)||(NULL == pHead->next->next) )
return 0; //不用逆置,直接返回
//--------------------------------------------------------------
//如果至少有两个业务结点,则需要逆置链表
//环境准备
SLIST *pCur = pHead->next->next; //当前结点直接从第二个业务结点开始
SLIST *pPre = pHead->next; //用于保存当前结点的前驱,每次都把pPre与pCur进行交换
SLIST *pTmp = NULL; //辅助指针(用来提前保存指向当前结点的后继的指针,缓存指针位置)
while(pCur != NULL)
{
//逆置前,先保存(“缓存”)当前结点的后继(防止断链)
pTmp = pCur->next;
//逆置过程:使当前结点pCur指向它的前驱结点pPre
pCur->next = pPre;
//逆置后,让pPre、pCur后移一个位置(重置准备环境)
pPre = pCur;
pCur = pTmp;
}
//while完成后,还剩下头结点和第一个结点的指针域没有处理,下面进行处理
pHead->next->next = NULL; //【重点】设置原本的第一个业务结点的指针域为NULL
pHead->next = pPre; //把头结点指向原链表的尾结点
return 0;
//总结:用到3个结构体变量,pPre、pCur、pTmp
//其中:pPre、pCur用于每次逆置; pTmp用于在逆置前保存pCur的后继(防止断链)
//逆置完成后pPre指向原链表的最后一个结点
//【重要】逆置全部完成后,不要忘记两件事:
//1.把pHead->next = pPre;(把头结点的指针域指向最后一个结点)
//2.把新链表的尾结点(即:原链表的第一个结点,为pHead->next)设置为NULL,即pHead->next->next = NULL;
}
/*
链表操作的技巧:
1.明确 pHead、pPre、pCur、pNext、pTail、pTmp的位置
1.初始化上面的几个变量:即环境准备
while(...) //一般情况下是 while(pCur != NULL)
{
2.处理上面的几个变量
3.重置上面几个变量的位置:即重置环境
}
4.退出while时,要“明确一共分为几个情况”以及在每种
情况下,上面几个变量的位置。
5.进行最后的操作
*/
链表逆置的解析图