14.0.0 结构体与枚举
14.1.0 指针与函数
14.1.1 指针与函数的两种使用
指针作为函数的参数
效果:在函数的内部可以通过这个参数指针去修改实参变量的值
当函数的返回值有多个的时候,可以使用指针作为参数。
指针作为参数是地址传递。
如果函数的参数是一个指针,函数希望传递给调用者一个信息,函数内部只会去取指针指向的变量的值而不会去修改它,那么这个时候,这个参数指针可以用使用const来修饰。
指针作为函数的返回值
指针还可以作为函数的返回值。但是,不能返回局部变量的地址,换句话说:返回的指针指向的变量一定要保证在函数结束之后,那个空间没有被回收。
如果就是要返回一个指针,那么就要保证这个指针指向的空间在函数结束以后仍然存在。这个时候,我们可以将数据创建在堆区,然后返回堆区的地址。
但一定要记得,使用完了以后要使用free释放堆区的空间。
例子:
int* test()
{
int* arr = calloc(3,sizeof(int));
*arr = 10;
*(arr+1) = 20;
*(arr+2) = 30;
return arr;
}
注意:
函数是可以返回局部变量的值的,在main函数中可以声明一个比变量来接收这个函数返回的值的,但是不能返回局部变量的地址,因为在main函数接收了地址以后,原先函数内的变量就被回收,其值也会发生变化,依循原来的地址已经无法得到原来的值了。
案例:
写一个函数,传入1个1到7的星期数,返回对应的英文星期名。
因为这个函数要返回字符串,建议使用指针作为返回值。
返回值是字符指针,字符指针指向的值是存储在常量区的,函数结束不会被回收,所以这里不需要声明堆区空间。
char* getEnglishWeek(int day)
{
char* weekday = NULL;
switch(day)
{
case 1:
weekday = “Monday”;
break;
case 2:
weekday = “Tuesday;
break;
case 3:
weekday = “Wednesday”;
以下省略……
case 7:
weekday = “Sunday”;
break;
}
return weekday;
}
14.1.2 指向函数的指针
可以理解,声明了一个函数,系统可能是需要将其存储在内存中的。而函数存储的位置是在内存的代码段。既然函数要存储在内存中,那么肯定要占用1块空间,既然是空间那么肯定是有地址的。
于是,我们就可以声明一个指针存储这个函数的地址,让这个指针指向这个函数。
于是,调用函数可以有两种方式:
直接使用函数名调用
使用指向函数的指针间接调用
使用方法:
1个指针函数的指针,并不是任意的函数都可以指向。
而是有限定的,要求指向的函数的返回值类型和参数描述必须要与指针的描述一致。
语法:
返回值类型(*指针名)([参数列表]);
void (*pFunction)();
表示声明了一个指向函数的指针,名字叫做pFunction,这个指针只能指向没有返回值,并且没有参数的函数。
int (*pFun)(int num1,int num2);
表示声明了一个指向函数的指针,名字叫做pFun,这个指针只能指向返回值为int类型、并且有两个整型参数的函数。
指向函数的指针的初始化:
取到符合指针条件的函数的地址
函数的名字就代表函数的地址
将地址赋值给指针变量
直接将符合条件的函数的名称赋值给这个指针
注意:函数名代表函数的地址,不要将函数名加上小括号以后再赋值给函数指针,那样取到的是函数的返回值。
例子:假设test()是一个函数,可以这么写
void (*pFunc)() = test;
如果使用指针间接的调用指向的函数呢?
假设test()是一个函数,可以这么写:
pFunc();
或者
(*pFunc)();
如果是有返回值和参数的函数int test(int num1,int num2),可以这么写:
int num = pFunc(20,30);
小技巧:
如果要定义一个指针指向一个函数,可以直接拷贝这个函数头,删除函数名,将其替换为(*指针名)即可。
14.2.0 结构体
14.2.1 结构体概述
我们知道不同的数据类型的变量是用来保存不同类型的数据的。
我现在知道的变量类型其实并不能满足所有数据的存储要求,例如:我们要求声明一个变量,来保存一个人的信息。
一个人的信息包含了很多不同的数据的集合,我们是无法使用现在已经学过的某一种数据类型来定义的。而是需要多个数据类型合起来才能实现。
此时也不能使用数组,因为数组是同一种数据类型变量的多个集合。而一个人的信息是多种不同数据类型的集合。
所以,我们希望能有一种变量,可以集合多种不同的数据类型。
C语言系统则提供了一种功能,可以自定义数据的类型,指定这种数据类型的变量由哪些小变量合成。
这种功能,便叫做结构体。
语法格式:
struct 新类型名称
{
//要创建的新类型由哪些变量联合而成
数据类型1 小变量名称1;
数据类型2 小变量名称2;
数据类型3 小变量名称3;
};
注意:
我们使用结构体仅仅是创建了1个新的类型而已,并没有声明变量。
结构体的作用是在指定新的数据类型是由哪些小变量组合而成的。
例子:
struct Student
{
char *name;
int age;
int score;
float height;
};
那么如何声明结构体类型的变量呢?
语法格式:
struct 新类型名称 变量名;
例如:
struct Student stu;
代表声明了1个struct Student类型的变量,变量名字叫做stu。
这个时候,stu才是一个变量,才会在内存中申请空间。
这个变量中,是由这个新的结构体类型规定的小变量组合而成的。
注意:这个时候stu结构体变量的类型是struct Student类型的,而不是Student。
14.2.2 结构体变量的初始化
结构体变量的初始化的意义:为结构体变量中的小变量赋值。
初始化的语法:
结构体变量名称.成员名 = 数据;
这里的成员就是在声明结构体时定义的组成结构体的小变量。
例子:以上一节我们举例的变量为例
Stu.name = “Jack”;
Stu.score = 88;
Stu.age = 17;
Stu.height = 189.5;
到这里,总结一下什么时候要定义结构体。
当我们要保存一个数据,但是发现这个数据是一个大数据,这个数据是由多个小数据联合起来的,那么这个时候,先使用结构体自定义这个数据类型由哪些小变量合成的,然后再根据这个结构体类型声明变量来保存数据。
14.2.3 使用结构体的注意
- 一定要先使用结构体定义新的类型,然后才可以根据这个类型声明这个类型的变量
- 结构体变量是一个变量,所以可以批量声明
- 例子:struct Student xiaoMing,Jack,meimei,lilei;
- 定义结构体名称的时候,首字母应该大写
- 之前学习的是先声明结构体类型,再根据这个类型声明变量,其实这两步可以合为一步。
例子:
struct Student
{
char *name;
int score;
int age;
float height;
} jack,meimei,lilei;
这就是声明结构体类型的同时声明结构体类型的变量。
- 匿名结构体:结构体类型在声明的时候可以不起名字。这个时候只能在声明结构体的同时就创建变量,且不能单独声明这个结构体类型的变量。
14.2.4 结构体变量的默认值和初始化其他方法
结构体变量的初始化还有其他方法:
在声明结构体变量的同时,就为结构体变量的成员初始化。
例子:
struct Student xiaoHua = {“小花”,17,89};
在声明结构体变量的同时,按顺序初始化部分成员
struct Student liLei = {“李雷”};
指定成员的初始化
struct Student liLei = {.name = “李雷”,.age = 17,.score = 100};
结构体变量的成员的默认值:
声明一个结构体变量,如果没有为其成员赋值,成员是有值的,其值是垃圾值。
但如果在声明结构体变量的同时,只要初始化一个成员,其他的成员就会被自动初始化为0。
14.2.5 结构体类型的作用域
如果结构体类型是定义在函数的内部,那么这个结构体类型只能在这个函数中使用。
如果我们希望这个结构体类型可以用在所有函数中,那么就把这个结构体类型定义在函数的外面,定义在最顶上。
一般情况下,结构体类型都是定义在函数外面的,以让所有函数可以使用。
14.2.6 结构体变量之间的相互赋值
- 相同结构体类型的变量之间绝对是可以相互赋值的。
- 结构体变量之间的赋值原理是将源结构体变量中的每一个成员的值拷贝一份,赋值给目标结构体变量中对应的成员。
- 结构体变量之间赋值是值传递
14.2.7 结构体数组
假设有一个结构体类型,保存学生的信息:
struct Student
{
char* name;
int age;
int score;
};
假设我们有五个学生的信息需要输入,
struct Student stu1 = {“Jim”,18,100};
struct Student stu2 = {“Mary”,19,90};
struct Student stu3 = {“Ace”,18,88};
struct Student stu4 = {“Jack”,20,80};
struct Student stu5 = {“Bill”,22,20};
我们可以声明5个结构体变量,然后分别输入他们的信息,分别保存,这样虽然可以,但是管理起来比较麻烦。
于是,我们引入了结构体数组:
语法结构:struct 结构体类型名称 数组名称[数组长度];
例子:struct Student students[5];
表示我们声明了1个长度为5的结构体数组,数组名为students,类型为struct Student。
例如:我们可以这么做
students[0] = stu1;
students[1] = stu2;
students[2] = stu3;
students[3] = stu4;
students[4] = stu5;
遍历的话可以使用循环结构:
for(int i = 0; i < 5; i++)
{
printf(“姓名:%s 年龄:%d 成绩:%d\n”,
students[i].name,
students[i].age,
students[i].score);
}
注意:
在赋值的时候不可以这样赋值:
students[0] = {“Jim”,18,100};
因为这种时候系统会将这种赋值方式和数组赋值的方式混在一起,所以无法使用这种形式赋值,如果非要用这种形式,应该如下操作:
students[0] = (struct Student){“Jim”,18,100};
结构体数组的初始化:
先声明结构体数组,然后用下标一个一个元素的赋值
在声明结构体数组的同时,给所有元素初始化
struct Student students[] =
{
{“Jim”,18,100},
{“Mary”,19,90},
{“Ace”,18,88},
{“Jack”,20,80},
{“Bill”,22,20}
};
结构体数组的长度计算:
也可以使用sizeof计算出数组占用的总字节数,再用这个总字节数除以每个元素占用的字节数。
例子:sizeof(students) / sizeof(struct Student);
14.2.8 结构体指针
举例说明:
struct Student
{
char* name;
int age;
int score;
};
struct Student xiaoMing = {“小明”,18,100};
xiaoMing首先是一个变量,类型是struct Student类型的。
既然xiaoMing是一个变量,那么这个变量肯定有地址,既然有地址,那么就可以声明1个指针指向这个结构体变量。
结构体指针的声明:
格式:
struct 结构体类型名称* 指针名;
例如:struct Student* pStu;
声明了一个pStu指针变量,这个指针变量的类型是struct Student,这个指针就只能指向struct Student类型的变量。 初始化: 首先,取出结构体变量的地址 使用取地址运算符&,就可以取出结构体变量的地址 然后,将地址赋值给指针变量 例子:struct Student xiaoMing = {“小明”,18,100}; struct Student pStu = &xiaoMing;
如何使用指向结构体变量的指针来间接访问这个结构体变量呢?
(结构体指针名).成员名 (pStu).age = 19;
结构体指针名->成员名
pStu->age = 19;
14.2.9 结构体嵌套
有那么一种情况,如果我们要记录一个人的情况,利用前面的知识我们已经知道可以使用结构体类型来定义一个变量保存多种数据,但是我们发现当用来保存出生年月日的时候,我们只使用一个成员来保存年月日三个信息似乎并不合适,于是在这里我们引入了结构体嵌套的功能。
例如:
struct Date
{
int year;
int month;
int day;
};
struct Person
{
char* name;
int age;
float money;
struct Date birthday;
};
这种结构便是结构体嵌套。
那么如何初始化呢?
struct Person xiaoMing = {“小明”,18,1234.50,{1999,12,20}};
输出:
printf(“姓名:%s 年龄:%d 财产:%.2f 出生日期:%d年%d月%d日\n”,xiaoMing.name,xiaoMing.age,xiaoMing.money,
xiaoMing.birthday.year,
xiaoMing.birthday.month,
xiaoMing.birthday.day);
什么时候能用到结构体嵌套?
当我们在为结构体定义成员的时候,发现某个成员也是一个复杂的数据,需要几个小变量合起来描述,那么这个时候就可以再定义一个结构体类型来表示它。
14.2.10 结构体与函数
结构体作为函数的参数
结构体是我们自定义的一种数据类型,所以当然可以作为函数的参数来使用
结构体作为参数的传递是值传递
如果希望函数的内部可以修改实参结构体变量的值,那么就需要使用指针
例子:
struct Student
{
char* name;
int age;
int score;
};
void test(struct Student stu)
{
if(stu.score >= 60)
{
printf(“恭喜%s,你及格了!\n”,stu.name);
}
else if(stu.score < 60)
{
printf(“对不起%s,你不及格!\n”,stu.name);
}
}
int main()
{
struct Student s1 = {“小明”,18,89};
test(s1);
return 0;
}
结构体作为函数的返回值
结构体类型完全可以作为函数的返回值
在返回的时候,直接将这个结构体变量的值返回即可
如果你要返回结构体变量的地址,那么就要把这个结构体变量创建在堆区
例子:
struct Student
{
char* name;
int age;
int score;
};
struct Student test()
{
struct Student s1 = {“Helen”,18,100};
return s1;
}
int main()
{
struct Student helen = test();
return 0;
}
14.3.0 枚举
14.3.1 枚举的概述
某些变量的取值是限定的,只能是指定的几个值当中的一个,除此以外其他的不行。
C语言默认没有提供可以限定取值类型的变量,那么我们就只能自定义具备限定取值的类型。
而枚举就具有这样的功能。
作用:支持程序员新创建1种数据类型,这个数据类型的变量的取值被限定。
如何使用枚举创建1个新的数据类型来达到限定变量的取值呢?
语法格式:
enum 新类型名称
{
限定取值1,限定取值2,限定取值3,……
}
例如:
enum Direction
{
East,
South,
West,
North
};
表示新创建了一个数据类型,这个数据类型的名字叫做enum Direction,可以声明这个类型的变量,这个变量中就只能存储这其中指定的任意一个。
声明枚举类型的变量:
语法格式:
enum 枚举类型名称 变量名;
例如:
enum Direction dir;
表示声明了1个变量,变量名字叫做dir,变量的类型是enum Direction
这个变量的特点:只能存储这个枚举类型限定的取值之一
枚举变量的初始化:
只能为这个枚举变量赋枚举类型限定的取值之一。
例如:
enum Direction dir = East;
不可以赋值其他除East、West、South和North以外的内容。
什么时候要使用枚举?
如果有一个变量的取值,只能是取指定几个中的一个,别的不可以,那么这个时候就可以使用枚举。
枚举的作用域:
如果将枚举类型定义在函数的内部,那么这个类型就只能在这个函数内部使用,如果希望将这个枚举的类型给所有的函数使用,就需要将这个枚举类型定义在函数外部,也就是顶部。
每一个枚举值/枚举项都有一个对应的整型的数。
默认从0开始,依次递增,
无论是什么类型的枚举变量,都是占用4个字节,
枚举变量中真正存储的是枚举值所对应的整型的数,
所以我们可以使用%d来输出枚举变量的值。
我们也可以直接为枚举变量赋值一个整型的数据,虽然我们可以这么做,但是并不推荐这么做,因为赋值整数的话,代码的可读性变得很差,用枚举值则可读性会变得很高。
默认情况下,每一个枚举值对应的整型的数从0开始,依次递增,但是我们也可以手动的设定枚举值对应的整型的数。
例如:
enum Direction
{
East = 10,
South = 20,
West = 30,
North = 40
};
14.3.2 枚举的使用规范
- 枚举类型的名称命名规范:首字母大写,即每个单词的首字母大写
- 枚举值的命名规范:枚举值的名称都以枚举类型来开头
14.3.3 typedef
typedef即type define 类型定义。
作用:为一个已经存在的数据类型取一个别名。如果我们想要使用这个类型,直接使用这个别名就可以了。
语法格式:
typedef 已经存在的数据类型 别名;
例如:
typedef int zhenShu;
这表示为int数据类型取了一个别名,叫做zhenShu,如果我们要使用int类型,可以直接使用zhenShu。这个时候zhenShu完全等价于int。
这样我们也可以有字符串类型了:
typedef char* string;
什么时候为已经存在的数据类型取一个别名呢?
当数据类型很长的时候,就可以为这个数据类型取一个短一点的别名,这样使用起来比较方便。
使用typedef为结构体类型取别名:
先声明结构体类型,然后再使用typedef为这个结构体类型取一个短别名
struct Student
{
char* name;
int age;
int score;
};
typedef struct Student Student;
声明结构体类型的同时,就使用typedef来为结构体类型取一个短别名
typedef struct Student
{
char* name;
int age;
int score;
} Student;
声明匿名结构体的同时,就使用typedef来为结构体类型取一个短别名
typedef struct
{
char* name;
int age;
int score;
} Student;
使用typedef为枚举类型取一个短别名:
先声明枚举类型,然后再使用typedef来为枚举类型取一个短别名
enum Direction
{
DirectionEast,
DirectionSouth,
DirectionWest,
DirectionNorth
};
typedef enum Direction Dir;
声明枚举类型的同时,就使用typedef来为枚举类型取一个短别名
typedef enum Direction
{
DirectionEast,
DirectionSouth,
DirectionWest,
DirectionNorth
} Dir;
在使用上面这种方式的时候,枚举的名称就可以不写了
typedef enum
{
DirectionEast,
DirectionSouth,
DirectionWest,
DirectionNorth
} Dir;
14.4.0 案例——学生查询系统
14.4.1 分析和各段代码
要处理学生的信息,一个学生的信息是由以下内容组成而成的:
学号
姓名
年龄
性别
成绩
从这可以看出,我们应该定义1个结构体类型来保存1个学生的数据
#define NUM 45
typedef struct
{
int ID;
char* name;
int age;
Gender gender;
int score;
} Student;
另外,性别也最好使用枚举来限制信息种类,所以我们应该再定义一个枚举来限制性别。
typedef enum
{
GenderMale,
GenderFemale
} Gender;
默认的学生信息要如何处理?
可以使用1个长度为10个的结构体数组来存储默认的学生信息。而这个数组是要用多个函数来访问的,所以可以定义为全局数组变量。
Student students[NUM] =
{
{1,"Alan",18,GenderMale,100},
{2,"Andy",19,GenderMale,80},
{3,"Jack",17,GenderMale,90},
{4,"Bill",52,GenderMale,2},
{5,"Rose",18,GenderFemale,90},
{6,"Helen",17,GenderFemale,100},
{7,"Sandy",18,GenderFemale,88},
{8,"Cherry",16,GenderFemale,70},
{9,"Dannel",20,GenderMale,60},
{10,"Will",19,GenderMale,90},
};
业务流程:
显示操作菜单,并接收用户的选择
判断用户的选择,根据用户的选择来做不同的事情
然后再返回初始菜单
这应该是一个循环的过程
显示操作主菜单可以写成一个独立的函数,返回值便是用户的选择。
int showMainMenu()
{
printf("*******************************************************\n");
printf("* S学生管理系统S *\n");
printf("* 1.查询学生 *\n");
printf("* 2.新增学生 *\n");
printf("* 3.删除学生 *\n");
printf("* 4.修改学生 *\n");
printf("* 5.退出系统 *\n");
printf("*******************************************************\n");
printf("请输入菜单编号:");
int userSelect = 0;
scanf("%d",&userSelect);
return userSelect;
}
在main函数中接收这个显示菜单的函数,并保存其返回值userSelect。
然后可以用switch-case结构来判断菜单的选择做出相应的操作。
从菜单中可以得知,相应的操作会有五个选项:
查询
增加
删除
修改
退出
于是除了第5个以外,可以写四个不同的函数来执行这些操作。
int main(int argc, const char * argv[])
{
while (1)
{
int userSelect = showMainMenu();
switch (userSelect)
{
case 1:
query();
break;
case 2:
add();
break;
case 3:
delete();
break;
case 4:
modify();
break;
case 5:
printf("感谢您使用本系统!\n");
return 0;
}
}
return 0;
第一个,查询。我们会发现,下面还有子菜单,于是我们再增加一个函数来显示子菜单,并返回用户的选择结果。
int showQueryMenu()
{
printf("*******************************************************\n");
printf("* 1.查询所有学生 *\n");
printf("* 2.根据学号查询 *\n");
printf("* 3.根据姓名查询 *\n");
printf("* 4.根据年龄查询 *\n");
printf("* 5.根据性别查询 *\n");
printf("* 6.根据成绩查询 *\n");
printf("*******************************************************\n");
printf("请选择你要的查询方式:输入0返回上级菜单 ");
int userSelect = 0;
scanf("%d",&userSelect);
return userSelect;
}
同样的,选择的结果接收后也用switch-case来判断并做出相应操作。
void query()
{
int userSelect = showQueryMenu();
switch (userSelect)
{
case 0:
return;
case 1:
queryAll();
break;
case 2:
queryID();
break;
case 3:
queryName();
break;
case 4:
queryAge();
break;
case 5:
queryGender();
break;
case 6:
queryScore();
break;
}
}
这时候的操作是6个选项:
查询全部学生
根据编号查询
根据姓名查询
根据年龄查询
根据性别查询
根据成绩查询
这里又要再写6个函数来实现这六个功能:
先是查询所有学生信息:
void queryAll()
{
printf("学号\t\t姓名\t\t\t年龄\t\t性别\t\t成绩\n");
for (int i = 0; i < realLength; i++)
{
printf("%d\t\t%s\t\t%d\t\t%s\t\t%d \n",
students[i].ID,
students[i].name,
students[i].age,
students[i].gender == GenderMale ? "男" : "女",
students[i].score);
}
}
根据学号查询:
void queryID()
{
int ID = 0;
printf("请输入要查询的学生的学号:输入0返回上级菜单 ");
scanf("%d",&ID);
if (ID == 0)
{
return;
}
printf("学号\t\t姓名\t\t\t年龄\t\t性别\t\t成绩\n");
printf("%d\t\t%s\t\t%d\t\t%s\t\t%d \n",
students[ID-1].ID,
students[ID-1].name,
students[ID-1].age,
students[ID-1].gender == GenderMale ? "男" : "女",
students[ID-1].score);
}
根据姓名查询:
void queryName()
{
char name[15];
rewind(stdin);
printf("请输入要查询的学生的名字:输入0返回上级菜单 ");
fgets(name, 15, stdin);
size_t len = strlen(name);
if (name[len - 1] == '\n')
{
name[len - 1] = '\0';
}
if (*name == '0')
{
return;
}
printf("学号\t\t姓名\t\t\t年龄\t\t性别\t\t成绩\n");
for (int i = 0; i <realLength; i++)
{
if (*name == *students[i].name)
{
printf("%d\t\t%s\t\t%d\t\t%s\t\t%d \n",
students[i].ID,
students[i].name,
students[i].age,
students[i].gender == GenderMale ? "男" : "女",
students[i].score);
}
}
}
根据年龄查询:
void queryAge()
{
int minAge = 0;
int maxAge = 0;
printf("请输入要查询的学生的最小年龄:输入0返回上级菜单 ");
scanf("%d",&minAge);
if (minAge == 0)
{
return;
}
printf("请输入要查询的学生的最大年龄:");
scanf("%d",&maxAge);
printf("学号\t\t姓名\t\t\t年龄\t\t性别\t\t成绩\n");
for (int i = 0; i < realLength; i++)
{
if (students[i].age >= minAge && students[i].age <= maxAge)
{
printf("%d\t\t%s\t\t%d\t\t%s\t\t%d \n",
students[i].ID,
students[i].name,
students[i].age,
students[i].gender == GenderMale ? "男" : "女",
students[i].score);
}
}
}
根据性别查询:
void queryGender()
{
int gender = 0;
printf("请输入要查询的学生的性别:0->男 1->女 输入-1返回上一级菜单\n");
scanf("%d",&gender);
if (gender == -1)
{
return;
}
printf("学号\t\t姓名\t\t\t年龄\t\t性别\t\t成绩\n");
for (int i = 0; i < realLength; i++)
{
if (students[i].gender == gender)
{
printf("%d\t\t%s\t\t%d\t\t%s\t\t%d \n",
students[i].ID,
students[i].name,
students[i].age,
students[i].gender == GenderMale ? "男" : "女",
students[i].score);
}
}
}
最后是根据成绩查询:
void queryScore()
{
int min = 0;
int max = 0;
printf(“请输入最小成绩:输入-1返回上级菜单 ");
scanf("%d",&min);
if (min == -1)
{
return;
}
printf("请输入最大成绩:");
scanf("%d",&max);
printf("学号\t\t姓名\t\t\t年龄\t\t性别\t\t成绩\n");
for (int i = 0; i < realLength; i++)
{
if (students[i].score >= min && students[i].score <= max)
{
printf("%d\t\t%s\t\t%d\t\t%s\t\t%d \n",
students[i].ID,
students[i].name,
students[i].age,
students[i].gender == GenderMale ? "男" : "女",
students[i].score);
}
}
}
接下来是新增学生,新增学生是需要将输入的学生信息保存到数组中去,也就是要把这个学生的信息构建成1个结构体变量,然后再把这个结构体变量存储到数组之中。
因为需要增加存到原有的结构体数组中,所以更改了宏NUM的基数为45,另外因为在查询时会变成输出45个学生的数据,我们并不希望这么做,于是新建一个全局变量realLength作为初始的学生人数。
int realLength = 10;
另外还要注意这段代码中,开始存储新增学生的姓名的变量是一只局部的字符数组变量,在函数运行结束后会被回收,所以无法传递到main函数中,于是我们就新建一个堆区存储的空间来生成一个字符串指针,然后将字符数组的内容拷贝给这个字符串指针,这样就可以传递学生的姓名了。
void add()
{
int ID = 0;
printf("请输入要新增的学生的学号:输入0返回上级菜单 ");
scanf("%d",&ID);
if (ID == 0)
{
return;
}
printf("请输入新增的学生的姓名:");
char name1[15];
rewind(stdin);
fgets(name1, 15, stdin);
size_t len = strlen(name1);
if (name1[len - 1] == '\n')
{
name1[len - 1] = '\0';
}
char *name = calloc(len+1, sizeof(char));
strcpy(name, name1);
printf("请输入新增的学生的年龄:");
int age = 0;
scanf("%d",&age);
printf("请输入新增的学生的性别:0->男 1->女 ");
int gender = 0;
scanf("%d",&gender);
printf("请输入新增学生的成绩:");
int score = 0;
scanf("%d",&score);
Student stu = {ID,name,age,gender,score};
students[realLength] = stu;
realLength++;
}
完成了新增学生,接着是删除学生。先输入要删除的学生的学号,在数组中找到学号为想要删除的那个学生,确定这个人在第几个下标,然后将后面的元素一个一个往上替换。
void delete()
{
loop:
printf("请输入要删除的学生的学号:输入0返回上级菜单 ");
int ID = 0;
scanf("%d",&ID);
int deleteIndex = -1;
for (int i = 0; i < realLength; i++)
{
if (students[i].ID == ID)
{
deleteIndex = i;
break;
}
}
if (ID == 0)
{
return;
}
if (deleteIndex == -1)
{
printf("没找到这个学号的学生!请重试!\n");
goto loop;
}
for (int i = deleteIndex + 1; i <realLength; i++)
{
students[i-1] = students[i];
}
realLength--;
printf("操作成功!\n");
}
最后是修改学生,修改学生的函数和新增比较类似:
void modify()
{
printf("请输入要修改的学生的学号:输入0返回上级菜单 ");
int ID = 0;
scanf("%d",&ID);
if (ID == 0)
{
return;
}
printf("请输入新的姓名:");
char name1[15];
rewind(stdin);
fgets(name1, 15, stdin);
size_t len = strlen(name1);
if (name1[len - 1] == '\n')
{
name1[len - 1] = '\0';
}
char *name = calloc(len+1, sizeof(char));
strcpy(name, name1);
printf("请输入新的年龄:");
int age = 0;
scanf("%d",&age);
printf("请输入新的性别:0->男 1->女 ");
int gender = 0;
scanf("%d",&gender);
printf("请输入新的成绩:");
int score = 0;
scanf("%d",&score);
Student stu = {ID,name,age,gender,score};
students[ID-1] = stu;
printf("修改成功!\n");
}
14.4.2 完整代码
本学生查询系统的完整代码如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define NUM 45
typedef enum
{
GenderMale,
GenderFemale
} Gender;
typedef struct
{
int ID;
char* name;
int age;
Gender gender;
int score;
} Student;
Student students[NUM] =
{
{1,"Alan",18,GenderMale,100},
{2,"Andy",19,GenderMale,80},
{3,"Jack",17,GenderMale,90},
{4,"Bill",52,GenderMale,2},
{5,"Rose",18,GenderFemale,90},
{6,"Helen",17,GenderFemale,100},
{7,"Sandy",18,GenderFemale,88},
{8,"Cherry",16,GenderFemale,70},
{9,"Dannel",20,GenderMale,60},
{10,"Will",19,GenderMale,90},
};
int realLength = 10;
int showMainMenu()
{
printf("*******************************************************\n");
printf("* S学生管理系统S *\n");
printf("* 1.查询学生 *\n");
printf("* 2.新增学生 *\n");
printf("* 3.删除学生 *\n");
printf("* 4.修改学生 *\n");
printf("* 5.退出系统 *\n");
printf("*******************************************************\n");
printf("请输入菜单编号:");
int userSelect = 0;
scanf("%d",&userSelect);
return userSelect;
}
int showQueryMenu()
{
printf("*******************************************************\n");
printf("* 1.查询所有学生 *\n");
printf("* 2.根据学号查询 *\n");
printf("* 3.根据姓名查询 *\n");
printf("* 4.根据年龄查询 *\n");
printf("* 5.根据性别查询 *\n");
printf("* 6.根据成绩查询 *\n");
printf("*******************************************************\n");
printf("请选择你要的查询方式:输入0返回上级菜单 ");
int userSelect = 0;
scanf("%d",&userSelect);
return userSelect;
}
void queryAll()
{
printf("学号\t\t姓名\t\t\t年龄\t\t性别\t\t成绩\n");
for (int i = 0; i < realLength; i++)
{
printf("%d\t\t%s\t\t%d\t\t%s\t\t%d \n",
students[i].ID,
students[i].name,
students[i].age,
students[i].gender == GenderMale ? "男" : "女",
students[i].score);
}
}
void queryID()
{
int ID = 0;
printf("请输入要查询的学生的学号:输入0返回上级菜单 ");
scanf("%d",&ID);
if (ID == 0)
{
return;
}
printf("学号\t\t姓名\t\t\t年龄\t\t性别\t\t成绩\n");
printf("%d\t\t%s\t\t%d\t\t%s\t\t%d \n",
students[ID-1].ID,
students[ID-1].name,
students[ID-1].age,
students[ID-1].gender == GenderMale ? "男" : "女",
students[ID-1].score);
}
void queryName()
{
char name[15];
rewind(stdin);
printf("请输入要查询的学生的名字:输入0返回上级菜单 ");
fgets(name, 15, stdin);
size_t len = strlen(name);
if (name[len - 1] == '\n')
{
name[len - 1] = '\0';
}
if (*name == '0')
{
return;
}
printf("学号\t\t姓名\t\t\t年龄\t\t性别\t\t成绩\n");
for (int i = 0; i <realLength; i++)
{
if (*name == *students[i].name)
{
printf("%d\t\t%s\t\t%d\t\t%s\t\t%d \n",
students[i].ID,
students[i].name,
students[i].age,
students[i].gender == GenderMale ? "男" : "女",
students[i].score);
}
}
}
void queryAge()
{
int minAge = 0;
int maxAge = 0;
printf("请输入要查询的学生的最小年龄:输入0返回上级菜单 ");
scanf("%d",&minAge);
if (minAge == 0)
{
return;
}
printf("请输入要查询的学生的最大年龄:");
scanf("%d",&maxAge);
printf("学号\t\t姓名\t\t\t年龄\t\t性别\t\t成绩\n");
for (int i = 0; i < realLength; i++)
{
if (students[i].age >= minAge && students[i].age <= maxAge)
{
printf("%d\t\t%s\t\t%d\t\t%s\t\t%d \n",
students[i].ID,
students[i].name,
students[i].age,
students[i].gender == GenderMale ? "男" : "女",
students[i].score);
}
}
}
void queryGender()
{
int gender = 0;
printf("请输入要查询的学生的性别:0->男 1->女 输入-1返回上一级菜单\n");
scanf("%d",&gender);
if (gender == -1)
{
return;
}
printf("学号\t\t姓名\t\t\t年龄\t\t性别\t\t成绩\n");
for (int i = 0; i < realLength; i++)
{
if (students[i].gender == gender)
{
printf("%d\t\t%s\t\t%d\t\t%s\t\t%d \n",
students[i].ID,
students[i].name,
students[i].age,
students[i].gender == GenderMale ? "男" : "女",
students[i].score);
}
}
}
void queryScore()
{
int min = 0;
int max = 0;
printf("请输入最小成绩:输入-1返回上级菜单 ");
scanf("%d",&min);
if (min == -1)
{
return;
}
printf("请输入最大成绩:");
scanf("%d",&max);
printf("学号\t\t姓名\t\t\t年龄\t\t性别\t\t成绩\n");
for (int i = 0; i < realLength; i++)
{
if (students[i].score >= min && students[i].score <= max)
{
printf("%d\t\t%s\t\t%d\t\t%s\t\t%d \n",
students[i].ID,
students[i].name,
students[i].age,
students[i].gender == GenderMale ? "男" : "女",
students[i].score);
}
}
}
void query()
{
int userSelect = showQueryMenu();
switch (userSelect)
{
case 0:
return;
case 1:
queryAll();
break;
case 2:
queryID();
break;
case 3:
queryName();
break;
case 4:
queryAge();
break;
case 5:
queryGender();
break;
case 6:
queryScore();
break;
}
}
void add()
{
int ID = 0;
printf("请输入要新增的学生的学号:输入0返回上级菜单 ");
scanf("%d",&ID);
if (ID == 0)
{
return;
}
printf("请输入新增的学生的姓名:");
char name1[15];
rewind(stdin);
fgets(name1, 15, stdin);
size_t len = strlen(name1);
if (name1[len - 1] == '\n')
{
name1[len - 1] = '\0';
}
char *name = calloc(len+1, sizeof(char));
strcpy(name, name1);
printf("请输入新增的学生的年龄:");
int age = 0;
scanf("%d",&age);
printf("请输入新增的学生的性别:0->男 1->女 ");
int gender = 0;
scanf("%d",&gender);
printf("请输入新增学生的成绩:");
int score = 0;
scanf("%d",&score);
Student stu = {ID,name,age,gender,score};
students[realLength] = stu;
realLength++;
}
void delete()
{
loop:
printf("请输入要删除的学生的学号:输入0返回上级菜单 ");
int ID = 0;
scanf("%d",&ID);
int deleteIndex = -1;
for (int i = 0; i < realLength; i++)
{
if (students[i].ID == ID)
{
deleteIndex = i;
break;
}
}
if (ID == 0)
{
return;
}
if (deleteIndex == -1)
{
printf("没找到这个学号的学生!请重试!\n");
goto loop;
}
for (int i = deleteIndex + 1; i <realLength; i++)
{
students[i-1] = students[i];
}
realLength--;
printf("操作成功!\n");
}
void modify()
{
printf("请输入要修改的学生的学号:输入0返回上级菜单 ");
int ID = 0;
scanf("%d",&ID);
if (ID == 0)
{
return;
}
printf("请输入新的姓名:");
char name1[15];
rewind(stdin);
fgets(name1, 15, stdin);
size_t len = strlen(name1);
if (name1[len - 1] == '\n')
{
name1[len - 1] = '\0';
}
char *name = calloc(len+1, sizeof(char));
strcpy(name, name1);
printf("请输入新的年龄:");
int age = 0;
scanf("%d",&age);
printf("请输入新的性别:0->男 1->女 ");
int gender = 0;
scanf("%d",&gender);
printf("请输入新的成绩:");
int score = 0;
scanf("%d",&score);
Student stu = {ID,name,age,gender,score};
students[ID-1] = stu;
printf("修改成功!\n");
}
int main(int argc, const char * argv[])
{
while (1)
{
int userSelect = showMainMenu();
switch (userSelect)
{
case 1:
query();
break;
case 2:
add();
break;
case 3:
delete();
break;
case 4:
modify();
break;
case 5:
printf("感谢您使用本系统!\n");
return 0;
}
}
return 0;
}
完整电子书,请点击购买: