Linux下使用C语言编写学生管理系统(完)
写的较多,除了整体代码之外,用到什么也会记录下来(看完感觉有用的话给俺评论一下有用,有大佬看的话可以给点建议)
一.设计目的:复习C语言,并且在Linux中使用vim编写学生管理系统,学习使用Makefile将多个文件链接起来。
二.各个模块功能以及实现
1.定义结构体以及结构体的初始化
结构体以及记录过了。学生有以下几个基本信息:姓名、年龄、学号、性别。
重点1:内存问题
tips:结构体的成员在内存中的布局是有内存对齐的。内存对齐是为了优化内存访问的效率和硬件对齐要求而进行的一种处理方式。
请看VCR:
typedef struct{
int age;
char name[50];
int id;
char sex[10];
}student;
当这样定义时,我用sizeof()打印出来的内存大小是72字节
typedef struct{
int age;
int id;
char name[50];
char sex[10];
}student;
当这样定义时,我用sizeof()打印出来的内存大小是68个字节
这两个结构体定义内容是相同的,都是定义了
int age;int id;char name[50]; char sex[10];
但是他们所占用的大小就是不一样。想深入去了解一下的同学可以自己去搜一下关于内存对齐方面的知识。
所以我们在定义结构体的时候,切记把相同类型的变量定义在一块,这样做可以减少对内存的占用,也就是说可以减少浪费空间。
重点2:结构体初始化值问题
就用我们这个学生管理系统举例吧,你在写完结构体的初始化之后,哪怕在代码写完之后,结构体里面的所有值也是通过运行程序的时候在对每一个值进行修改的。但是这些值最开始你是不知道的,也就是野值,这些值是有可能会导致程序出现问题的,我大一写这个代码的时候也没有进行初始化,虽然代码运行起来没有出问题吧,但是现在看起来这样并不好
请看VCR:
#include <stdio.h>
#define MAX 20
typedef struct{
int age;
int id;
char name[50];
char sex[10];
}student;
int main()
{
student A[MAX]; //定义一个结构体数组
for(int i=0;i<MAX;i++)
{
printf("A[%d].age:%d A[%d].id:%d\n", i, A[i].age, i, A[i].id); //用.操作符去访问结构体成员
}
return 0;
}
记住多用#define宏定义哦
结果:
从输出结果可以看出,这个结构体数组内存中的每一个值都是随机的,这样的野值会不会出现问题呢,我想可能是会的,并且就是对于新手来说,可能认为定义了一个结构体变量之后,这个结构体变量就自动初始化赋值为0了,我大一的时候就是这样想的,也可能是我当时学的不到位吧。
这个问题如何解决呢?
函数 void *memset(void *s,int c,size_t n);
这个函数后面具体说,先看代码:
#include <stdio.h>
#include <string.h>
#define MAX 20
typedef struct{
int age;
int id;
char name[50];
char sex[10];
}student;
int main()
{
student A[MAX]; //定义一个结构体数组
memset(A,0,sizeof(student)*MAX);
for(int i=0;i<MAX;i++)
{
printf("A[%d].age:%d A[%d].id:%d\n", i, A[i].age, i, A[i].id); //用.操作符去访问结构体成员
}
return 0;
}
这个代码和上面的比起来多了一个头文件string和函数 memset(A,0,sizeof(student)*MAX)
结果:
现在可以看到每一个值都被初始化为0了
只多了这一行代码,让自己的程序运行起来更加安全,所以兄弟们不要嫌麻烦。
现在说函数:
void *memset(void *s,int c,size_t n);
通过刚刚上面的例子以及可以看出来了,函数memset()一共三个参数,第一个是地址,第二个是值,第三个是大小;
void *s------表示所需要初始化的地址,地址是要用&来进行取址操作的,但是我们是操作数组,所以直接数组名称即可,数组名称放在这里面就是代表数组的首地址开始(不了解的可以在去学习一下数组)
int c---------从代码里面就可以看出来了,表示初始化的值,代码中是0,意思就是这些内存中的值变成0呗
size_t------也可以看出来,我先用一个sizeof(student)去读取出student这一个结构体的长度,然后乘20(也就是MAX)来求出结构体数组A[MAX]所占的内存空间
sizeof()应该都学过怎么用吧,就是读取括号里面的字节大小,这个东西灵活用起来也很厉害
2.main主函数
看VCR:
#include <stdio.h>
#include <string.h>
#include "struct.h" //结构体初始化
#include "menu.h" //打印首页面
#include "add_student.h" //增加学生
#include "look_student.h" //查看所有学生
#include "find_student.h" //查找学生
#include "change_student.h"//修改学生信息
#include "delete_student.h"//删除学生信息
int size=0; //当前学生数量
// 使用嵌套结构体会更好一点,这个弄完后面跟进
int main()
{
int flag=1,choose=0;flag2=1;
student ST[STUDENT_MAX]; //定义一个最大为20人的结构体数组
memset(ST,0,sizeof(student)*STUDENT_MAX); //将结构体数组所有的值清零
while(flag) //循环标志位
{
Meun_Display(); //打印首页面
while(flag2) //循环标志位
{
printf("输入编号:");
if(scanf("%d", &choose)==1) flag2=0; //标志位置0
else{
printf("输入错误,请重新输入!");
int c;
while ((c = getchar()) != '\n' && c != EOF); //清除缓存区
}
}
switch(choose)
{
case(1):Add_student(ST);break; //添加学生
case(2):Look_student(ST);break; //查看所有学生
case(3):Find_student(ST);break; //查找学生
case(4):Change_student(ST);break; //修改学生信息
case(5):Delete_student(ST);break; //删除学生信息
case(6):flag=0;break; //标志位置0,退出循环
default:printf("输入有误,请重新输入!");break;
}
return 0;
}
}
头文件多先不用管,我是因为学习Makefile所以才写在多个文件下的,如果只是单纯的用的话可以把他们放在同一个文件里面就行。
开始解说:
重点1:头文件#include <stdio.h> 和 #include "menu.h"的区别
看代码中可以看出大概有两种头文件类型:
- 一种是 #include <stdio.h>
- 一种是 #include “menu.h”
区别:
1.< > 尖括号:这种方式用于包含系统头文件。编译器会在系统默认的头文件路径中搜索该文件。一般来说,系统头文件位于操作系统或编译器的标准库中
2." " 双引号:这种方式用于包含用户自定义的头文件。编译器会先在当前源文件所在目录下搜索该文件,如果找不到,再在系统默认的头文件路径中搜索。用户可以将自定义的头文件放在与源文件相同的目录下,或者在编译时通过指定额外的头文件路径来使编译器能够找到它。
总结:< >就是在操作系统的标准库里面寻找 ------“xxx"就是在源文件所在文件夹下面找或者指定的地方寻找----------自己定义的头文件一般都使用"xxx”
重点2:scanf的问题
while(flag2) //循环标志位
{
printf("输入编号:");
if(scanf("%d", &choose)==1) flag2=0; //标志位置0
else{
printf("输入错误,请重新输入!");
int c;
while ((c = getchar()) != '\n' && c != EOF); //清除缓存区
}
}
这一部分为啥要这样写呢,如果是这样写
Meun_Display(); //打印首页面
printf("输入编号:");
scanf("%d", &choose)
switch(choose)
{
}
没写的地方和上面一样,自己理解一下
会出现一个问题哈,因为现在scanf()这个函数写入的是int类型的变量,如果输入一个a,b,c,d这种字符型或者不是整型的变量,程序会卡死,会一直循环操作不了。按什么都没用!!!
看VCR:
这里我输入一个s,然后程序就会卡死,一直循环执行打印首页面。我试过只要是没有进行清除缓存区操作不管是 while for() do{}while(); goto; 只要是循环就会卡死。
函数:
函数:int scanf(const char * restrict format,…);
看一下键盘输入的过程:
scanf有缓存区,当输入非法字符(要求的类型与输入的类型不符合),scanf会直接跳过,该输入及不会被接受也不会被清除,被存放在scanf的缓存区,当下次调用scanf函数时,会直接从缓存区读取非法字符,造成死循环。
解决办法:很明确,清除缓存区,让下一次输入正常输入即可。
1.函数fflush(stdin);
int a;
fflush(stdin); //缓存区清除函数
scanf("%d", &a);
这样可以解决,在每次输入前将缓存区清除一下就好
但是Linux种gcc没有该函数,就是这个函数用不可了,所以我没用这个‘,想具体了解的去查这个函数。
2.函数getchar()
int getchar():从控制台(键盘)读一个字符
**getchar()**读取字符,不管是空格还是什么,读取到’\n’停止
所以我们用这个函数将缓存区中的所有字符读取出来,相当于清空缓存区了;
注意:当getchar()在读取结束或出现错误时会返回EOF
int c;
while ((c = getchar()) != '\n' && c != EOF) { } // 清空输入缓冲区
肯定不能读取一个字符就停下来,所以使用while来进行一直读取,从代码中可以到,读取到’\n’并且等于EOF(即读取结束)的时候结束循环,就可以做到清空缓存区了。
在主函数main()中看不到结构体的初始化以及符号常量的定义,我把这些放在别的头文件中了