最近复习C语言,个人觉得最好的复习方式就是用C做个小项目,既能复习又能学以致用不至于太枯燥。缺点是不看书本的复习容易忽略C语言中一些细小的问题。做的这个小项目的目的主要是复习结构体和指针的使用,并没有使用C的文件编程,因此并不能把输入的信息保存到本地。复习到文件知识点后将会重新完善此小项目。
整个项目我托管在github方便后续管理地址为:
点击打开链接
任务要求:从键盘输入包括学号、姓名、性别、出生年月日、籍贯、所在院系、专业奖惩信息保存在磁盘文件具有单项查询或多项查询功能具有插入修改和删除信息功能具有输出文件数据信息功能。
任务分解:主要是对链表进行操作,①创建链表的同时输入学生信息。②对链表进行添加和插入操作。③对链表进行删除操作。④输出链表信息的操作。⑤对链表进行查询操作。
一、定义数据结构
定义数据student结构体类型,C语言中没有类的概念,在定义数据类型时结构体就显得非常方便,其中学生信息为数据域保存下个节点的地址数据成员为指针域结构体成员和代码的分析我以注释的方式写在代码后面。
struct student{
char stNumber[12]; //定义字符数组用来存储学生学号
char name[10]; //用来存储学生姓名
char sex[6]; //用来存储学生性别
char birthDate[10]; //用来出生日期
char nativePlace[50]; //存储籍贯
char department[20]; //存储部门
char achOrPun[50]; //存储奖惩信息
struct student *next; //指针域存储下一个节点的地址
};
typedef struct student STNODE; //使用typedef为现有类型创建同义字,定义易于记忆和使用的类型名
复习中遇到一些问题:本来打算用整型来保存学生学号和出生日期,在调试中发现学号和出生日期的位数太长,整型不足以用来存储,所以决定还是用字符数组用来存储,这样统一用字符也方便后续操作。
二、创建链表
创建链表,具体代码解释以注释方式附在代码后面。
STNODE *stCreat(){
STNODE *head,*tail,*pnew; //head为头节点,tail相当于一个中介用来把pnew与链表相连,pnew用来保存新开辟的节点。
char exitFlag; //用来判断是否退出创建链表
head=(STNODE *)malloc(sizeof(STNODE));//开辟STNODE类型内存空间并把地址赋给头节点
if(head==NULL){ //判断是否赋值成功
printf("创建失败!\n");
return NULL;
}
head->next=NULL;
tail=head;
while(1){
do{
printf("是否退出Y Or N:"); //这个用来是否继续创建新的节点继续保存信息
scanf(" %c",&exitFlag);
}while(exitFlag !='Y' && exitFlag !='N'&&exitFlag!='y'&&exitFlag!='n');//判断大小写YN是否输入正确
if(exitFlag=='Y'||exitFlag=='y')
break;
pnew=(STNODE *)malloc(sizeof(STNODE));//创建新节点
if(pnew==NULL) //判断创建是否成功
printf("创建失败!\n");
printf("学号:"); //输入学生信息
scanf("%s",pnew->stNumber);
printf("姓名:");
scanf("%s",pnew->name);
printf("性别:");
scanf("%s",pnew->sex);
printf("出生日期:");
scanf("%s",pnew->birthDate);
printf("籍贯:");
scanf("%s",pnew->nativePlace);
printf("院系:");
scanf("%s",pnew->department);
printf("奖惩信息:");
scanf("%s",pnew->achOrPun);
pnew->next=NULL; //尾节点指针域为空
tail->next=pnew; //与前一节点相连接
tail=tail->next; //tail指向最新的节点
exitFlag=NULL;
}
return head; //返回头节点地址
}
复习中遇到的问题:刚开始在上述代码链表与新节点连接那个地方理不明白,后来通过画格子解决了。如果不好理解,可以向我一样用格子来表示一个个节点,用箭头串起来。
三、对链表进行添加和插入操作,代码解释以注释方式附在代码后面
void append(STNODE *head)
{
STNODE *temp,*pnew=NULL;
pnew=(STNODE *)malloc(sizeof(STNODE));//创建新节点
if(pnew==NULL) //判断创建是否成功
printf("创建失败!\n");
printf("学号:"); //输入学生信息
scanf("%s",pnew->stNumber);
printf("姓名:");
scanf("%s",pnew->name);
printf("性别:");
scanf("%s",pnew->sex);
printf("出生日期:");
scanf("%s",pnew->birthDate);
printf("籍贯:");
scanf("%s",pnew->nativePlace);
printf("院系:");
scanf("%s",pnew->department);
printf("奖惩信息:");
scanf("%s",pnew->achOrPun);
for(temp=head;temp->next!=NULL;temp=temp->next);//temp跟踪到head尾节点
temp->next=pnew; //尾节点指向新节点
pnew->next=NULL;
}
这段代码就是在链表尾部添加一个新的节点,如果创建链表那段代码搞懂,这个应该容易理解。
对链表的插入操作,这段代码比上述代码多了一个与插入节点的后面链表相连接。
void insert(STNODE *head,int i)
{
STNODE *temp;
STNODE *pnew;
int j;
temp=head;
pnew=(STNODE *)malloc(sizeof(STNODE));//创建新节点
if(pnew==NULL) //判断创建是否成功
printf("创建失败!\n");
printf("学号:"); //输入学生信息
scanf("%s",pnew->stNumber);
printf("姓名:");
scanf("%s",pnew->name);
printf("性别:");
scanf("%s",pnew->sex);
printf("出生日期:");
scanf("%s",pnew->birthDate);
printf("籍贯:");
scanf("%s",pnew->nativePlace);
printf("院系:");
scanf("%s",pnew->department);
printf("奖惩信息:");
scanf("%s",pnew->achOrPun);
for(j=1;j<i&&temp!=NULL;j++)
temp=temp->next;
if(temp==NULL){
printf("节点不存在!\n");
return ;
}
pnew->next=temp->next;
temp->next=pnew;
}
复习中遇到的问题:还是最后新节点与链表相连接的问题,解决方法我还是通过画格子方法理解。
四、对链表进行删除操作
先上代码,代码解析同样以注释方式附在代码后面。
void stDelete(STNODE *head,int i)
{
int j;
STNODE *temp1,*temp2;
temp1=head;
for(j=1;j<i&&temp1!=NULL;j++){ //利用for循环使temp指向要删去节点
temp1=temp1->next;
}
if(temp1==NULL){
printf("删除的节点不存在\n");
return;
}
temp2=temp1->next;
temp1->next=temp2->next;
free(temp2); //释放删去的结点的内存
}
复习中遇到问题:此段代码需要注意的就是要利用free函数释放掉删掉的链表节点内存,不然内存会被占用浪费掉。
五、输出链表信息
五、输出链表信息
此功能分为两个函数,一个是输出单个节点信息,一个是利用for循环调用输出单个节点的函数输出整个链表信息,好处就是在后面系统查询学生信息的时候可以直接调用输出单个节点的信息,而不用输出整个链表信息。这段代码并没有什么难度。。。。。就不加以说明了。
/**********************************************
输出单个节点信息
**********************************************/
void displayUnit(STNODE *unit){
if(unit==NULL){
printf("信息为空!");
return;
}else{
printf("学号:%s\n",unit->stNumber);
printf("姓名:%s\n",unit->name);
printf("性别:%s\n",unit->sex);
printf("出生日期:%s\n",unit->birthDate);
printf("籍贯:%s\n",unit->nativePlace);
printf("院系:%s\n",unit->department);
printf("奖惩信息:%s\n",unit->achOrPun);
printf("---------------------------\n");
}
}
/**********************************************
输出链表信息
**********************************************/
void displayList(STNODE *head){
STNODE *temp;
temp=head;
for(temp=head->next;temp!=NULL;temp=temp->next){
displayUnit(temp);
}
}
六、编辑学生信息
本段代码主要利用switch函数进行对已保存的学生信息进行修改,好处是只需要输入需要修改的学生信息项目,不用对整个学生信息进行编辑。
void stListEdit(STNODE *head,int i){
STNODE *temp;
int j;
char swFlag;
char *pNum=NULL;
temp=head;
for(j=1;j<=i;j++){
temp=temp->next;
}
displayUnit(temp); //输出当前学生信息
do{ //选择要修改学生信息
printf("选择要修改的信息\n");
printf("a.学号 b.姓名 c.性别 d.出生日期 f.籍贯 g.院系 h.奖惩信息 i.退出: ");
scanf(" %c",&swFlag);
while(swFlag<'a'||swFlag>'i'){ //判断选择是否有效
printf("输入有错,请重新选择:");
scanf(" %c",&swFlag);
}
switch(swFlag){
case 'a':printf("学号:");
scanf("%s",temp->stNumber);break;
case 'b':printf("姓名:");
scanf("%s",temp->name);break;
case 'c':printf("性别:");
scanf("%s",temp->sex);break;
case 'd':printf("出生日期:");
scanf("%s",temp->birthDate);break;
case 'f':printf("籍贯:");
scanf("%s",temp->department);break;
case 'g':printf("院系:");
scanf("%s",temp->department);break;
case 'h':printf("奖惩信息:");
scanf("%s",temp->achOrPun);break;
case 'i':break;
}
}while(swFlag!='i');
}
复习中遇到的问题:在行字符判断的时候注意scan()函数输入格式,例如scanf("%c",&..),和scanf(" %c",&..),输入格式%c前面加一个空格可以避免空字符的输入。
七、查询操作
本段代码主要使用strcmp函数进行字符串比较,然后返回查询到的节点位置
/************************************************************
按字符查询
************************************************************/
int stSearch(STNODE *head,char *character)
{
STNODE *temp;
int location=0;
for(temp=head;strcmp(temp->stNumber,character)!=0;temp=temp->next){
location++;
}
if(location==0){
printf("没找到!\n");
return 0;
}else{
displayUnit(temp);
return location;
}
}
此段代码也不难,貌似没啥解释的。
整个任务功能基本已经分解完毕,因为所有的数据都以字符串形式保存,字符查询函数可以查询所有的学生信息项目。
这格项目是在vs2010环境下做的,main函数我就不贴出来了,整个项目代码我放在GitHub:
点击打开链接