C语言中构造类型一共有4种,它们分别是:
数组(array)、结构体(struct)、枚举类型(enum)、共用体(union)。
结构体
结构体的语法
1、结构体的基本用法
结构体(Struct)从本质上讲是一种自定义的数据类型,只不过这种数据类型比较复杂,和前面讲过的数组有点像,差别就在数组里的元素必须是同一个类型,而结构体里的成员可以是不同类型的。
在实际开发中,我们可以将一组类型不同的、但是用来描述同一件事物的变量放到一个结构体中。
例如,学生有姓名、学号、年龄、班级、成绩等属性,学了结构体后,我们可以自定义一种新数据类型就是学生,这种类型里包含这些属性信息。
结构体的定义形式为:
struct 结构体名{
结构体所包含的变量或数组或另一个结构体;
....
};
结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员(Member):
student 为结构体名,它包含了 5 个成员,分别是 name、num、age、class、score。
结构体成员的定义方式与变量和数组的定义方式相同,只是不能初始化。
注意:大括号后面的分号;不能少,这是一条完整的语句。
像 int、float、char 等是由C语言本身提供的数据类型,不能再进行分拆,我们称之为基本数据类型;
结构体可以包含多个基本类型,也可以包含其他的结构体,所以我们称它为复杂数据类型或构造数据类型。
结构体变量
既然结构体是一种数据类型,那么就可以用它来定义变量:
都由 5 个成员组成。注意关键字struct不能少。
你也可以在定义结构体的同时定义结构体变量:
将变量放在结构体定义的最后即可。
如果只需要 stu1、stu2 两个变量,后面不需要再使用结构体,那么在定义时也可以不给出结构体名:
#include <stdio.h>
//推荐把结构体的定义放在程序文件的最上面,不建义放在函数内
struct student{
char * name;
int stuNo;
int age;
char class; //一个字符表示,如'a','b'班
float score;
}stu3,stu4; //可以声明多个结构体
struct student stu1; //声明了一个全局变量,类型是:学生结构体,学生1,也可以函数内部声明局部变量
int main(){
struct student stu2;
return 0;
}
成员的获取和赋值
结构体使用点号.获取单个成员。
获取结构体成员的一般格式为:
结构体变量名.成员名;
通过这种方式可以获取成员的值,也可以给成员赋值:
#include <stdio.h>
int main(){
struct{
char *name; //姓名
int num; //学号
int age; //年龄
char class; //班级
float score; //成绩
} stu1;
//给结构体成员赋值
stu1.name = "Tom";
stu1.num = 12;
stu1.age = 18;
stu1.class= 'A';
stu1.score = 136.5;
//读取结构体成员的值
printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.1f!\n",
stu1.name, stu1.num, stu1.age, stu1.class, stu1.score);
return 0;
}
除了可以对成员进行逐一赋值,也可以在定义时整体赋值,例如:
struct{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在小组
float score; //成绩
} stu1, stu2 = { "Tom", 12, 18, 'A', 136.5 };
不过整体赋值仅限于定义结构体变量的时候,在使用过程中只能对成员逐一赋值,这和数组的赋值非常类似。
需要注意的是,结构体是一种自定义的数据类型,是创建变量的模板,不占用内存空间;
结构体变量才包含了实实在在的数据,需要内存空间来存储。
结构体数组
所谓结构体数组,是指数组中的每个元素都是一个结构体。
在实际应用中,C语言结构体数组常被用来表示一个拥有相同数据结构的群体,比如一个班的学生。
在C语言中,定义结构体数组和定义结构体变量的方式类似:
struct stu{
char *name; //姓名
int num; //学号
int age; //年龄
char class; //班级
float score; //成绩
}team[5];
结构体数组在定义的同时也可以初始化,例如:
struct stu{
char *name; //姓名
int num; //学号
int age; //年龄
char class; //班级
float score; //成绩
}team[5] = {
{"Li ping", 5, 18, 'C', 145.0},
{"Zhang ping", 4, 19, 'A', 130.5},
{"He fang", 1, 18, 'A', 148.5},
{"Cheng ling", 2, 17, 'F', 139.0},
{"Wang ming", 3, 17, 'B', 144.5}
};
当对数组中全部元素赋值时,也可不给出数组长度,例如:
struct stu{
char *name; //姓名
int num; //学号
int age; //年龄
char class; //班级
float score; //成绩
}team[] = {
{"Li ping", 5, 18, 'C', 145.0},
{"Zhang ping", 4, 19, 'A', 130.5},
{"He fang", 1, 18, 'A', 148.5},
{"Cheng ling", 2, 17, 'F', 139.0},
{"Wang ming", 3, 17, 'B', 144.5}
};
【示例】计算全班学生的总成绩、平均成绩和以及 140 分以下的人数。
#include <stdio.h>
struct{
char *name; //姓名
int num; //学号
int age; //年龄
char class; //班级
float score; //成绩
}team[] = {
{"Li ping", 5, 18, 'C', 145.0},
{"Zhang ping", 4, 19, 'A', 130.5},
{"He fang", 1, 18, 'A', 148.5},
{"Cheng ling", 2, 17, 'F', 139.0},
{"Wang ming", 3, 17, 'B', 144.5}
};
int main(){
int num_140 = 0;
float sum = 0;
for(int i=0; i<5; i++){
sum += team[i].score;
if(team[i].score < 140) num_140++;
}
printf("sum=%.2f\naverage=%.2f\nnum_140=%d\n", sum, sum/5, num_140);
return 0;
}
结构体指针
当一个指针变量指向结构体时,我们就称它为结构体指针。
C语言结构体指针的定义形式一般为:
struct 结构体名 *变量名;
下面是一个定义结构体指针的实例:
//结构体
struct stu{
char *name; //姓名
int num; //学号
int age; //年龄
char class; //班级
float score; //成绩
} stu1 = { "Tom", 12, 18, 'A', 136.5 };
//结构体指针
struct stu *pstu = &stu1;
也可以在定义结构体的同时定义结构体指针:
struct stu{
char *name; //姓名
int num; //学号
int age; //年龄
char class; //班级
float score; //成绩
} stu1 = { "Tom", 12, 18, 'A', 136.5 }, *pstu = &stu1;
注意,结构体变量名和数组名不同,数组名在表达式中会被转换为数组指针,而结构体变量名不会,无论在任何表达式中它表示的都是整个集合本身,要想取得结构体变量的地址,必须在前面加&
通过指针获取结构体成员
通过结构体指针可以获取结构体成员,一般形式为:
(*pointer).memberName
或者:
pointer->memberName
第一种写法中,.的优先级高于*,(pointer)两边的括号不能少。
如果去掉括号写作pointer.memberName,那么就等效于*(pointer.memberName),这样意义就完全不对了。
第二种写法中,->是一个新的运算符,习惯称它为“箭头”,我个人喜欢叫“goes to”,有了它,可以通过结构体指针直接取得结构体成员;这也是->在C语言中的唯一用途。
上面的两种写法是等效的,我们通常采用后面的写法,这样更加直观。
【示例】结构体指针的使用。
#include <stdio.h>
int main(){
struct{
char *name; //姓名
int num; //学号
int age; //年龄
char class; //班级
float score; //成绩
} stu1 = { "Tom", 12, 18, 'A', 136.5 }, *pstu = &stu1;
//读取结构体成员的值
printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.1f!\n",
(*pstu).name, (*pstu).num, (*pstu).age, (*pstu).group, (*pstu).score);
printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.1f!\n",
pstu->name, pstu->num, pstu->age, pstu->group, pstu->score);
return 0;
}
【示例】结构体数组指针的使用。
#include <stdio.h>
struct stu{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在小组
float score; //成绩
}stus[] = {
{"Zhou ping", 5, 18, 'C', 145.0},
{"Zhang ping", 4, 19, 'A', 130.5},
{"Liu fang", 1, 18, 'A', 148.5},
{"Cheng ling", 2, 17, 'F', 139.0},
{"Wang ming", 3, 17, 'B', 144.5}
}, *ps;
int main(){
//求数组长度
int len = sizeof(stus) / sizeof(struct stu);
printf("Name\t\tNum\tAge\tGroup\tScore\t\n");
for(ps=stus; ps<stus+len; ps++){
printf("%s\t%d\t%d\t%c\t%.1f\n", ps->name, ps->num, ps->age, ps->group, ps->score);
}
return 0;
}
结构体指针作为函数参数
结构体变量名代表的是整个集合本身,作为函数参数时传递的整个集合,也就是所有成员,而不是像数组一样被编译器转换成一个指针。
如果结构体成员较多,尤其是成员为数组时,传送的时间和空间开销会很大,影响程序的运行效率。
所以最好的办法就是使用结构体指针,这时由实参传向形参的只是一个地址,非常快速。
【示例】计算全班学生的总成绩、平均成绩和以及 140 分以下的人数。
#include <stdio.h>
struct stu{
char *name; //姓名
int num; //学号
int age; //年龄
char class; //班级
float score; //成绩
}stus[] = {
{"Li ping", 5, 18, 'C', 145.0},
{"Zhang ping", 4, 19, 'A', 130.5},
{"He fang", 1, 18, 'A', 148.5},
{"Cheng ling", 2, 17, 'F', 139.0},
{"Wang ming", 3, 17, 'B', 144.5}
};
float average(struct stu *ps, int len); //这是声明一下,因为该函数写在调用函数下面了
int main(){
int len = sizeof(stus) / sizeof(struct stu);
float avg = average(stus, len);
printf("平均成绩= %f\n",avg);
return 0;
}
float average(struct stu *ps, int len){
int i, num_140 = 0;
float average, sum = 0;
for(i=0; i<len; i++){
sum += (ps + i) -> score;
}
return sum/len;
}
位域
有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。例如开关只有通电和断电两种状态,用 0 和 1 表示足以,也就是用一个二进位。正是基于这种考虑,C语言又提供了一种叫做位域的数据结构。
在结构体定义时,我们可以指定某个成员变量所占用的二进制位数(Bit),这就是位域。
struct bs{
unsigned int m;
unsigned int n: 4; //:后面的数字用来限定成员变量占用的位数。
unsigned char ch: 6;
};
#include <stdio.h>
struct bs{
unsigned int m: 22; //定义变量后冒号对应的数字为它占用多少个位
unsigned int n: 12;
unsigned int p: 4;
};
int main(){
printf("%ld\n",sizeof(struct bs)); //8,如果不指定位数,则打印12,表示12个字节
return 0;
}
C语言标准规定,位域的宽度不能超过它所依附的数据类型的长度。
C语言标准还规定,只有有限的几种数据类型可以用于位域。
在 ANSI C 中,这几种数据类型是 int、signed int 和 unsigned int(int 默认就是 signed int);
到了 C99,_Bool 也被支持了。
但编译器在具体实现时都进行了扩展,额外支持了 char、signed char、unsigned char 以及 enum 类型,所以上面的代码虽然不符合C语言标准,但它依然能够被编译器支持。
位域的存储
C语言标准并没有规定位域的具体存储方式,不同的编译器有不同的实现,但它们都尽量压缩存储空间。
位域的具体存储规则如下:
- 当相邻成员的类型相同时,如果它们的位宽之和小于类型的 sizeof 大小,
那么后面的成员紧邻前一个成员存储,直到不能容纳为止;如果它们的位宽之和大于类型的 sizeof 大小,那么后面的成员将从新的存储单元开始,其偏移量为类型大小的整数倍。
以下面的位域 bs 为例:
#include <stdio.h>
struct bs{
unsigned int m: 6;
unsigned int n: 12;
unsigned int p: 4;
};
int main(){
printf("%ld\n", sizeof(struct bs));
return 0;
}
m、n、p 的类型都是 unsigned int,sizeof 的结果为 4 个字节(Byte),也即 32 个位(Bit)。
m、n、p 的位宽之和为 6+12+4 = 22,小于 32,所以它们会挨着存储,中间没有缝隙。
sizeof(struct bs) 的大小之所以为 4,而不是 3,是因为要将内存对齐到 4 个字节,以便提高存取效率
如果将成员 m 的位宽改为 22,那么输出结果将会是 8,因为 22+12 = 34,大于 32,
n 会从新的位置开始存储,相对 m 的偏移量是 sizeof(unsigned int),也即 4 个字节。
- 当相邻成员的类型不同时,不同的编译器有不同的实现方案,GCC 会压缩存储,而 VC/VS 不会。
#include <stdio.h>
int main(){
struct bs{
unsigned int m: 12;
unsigned char ch: 4;
unsigned int p: 4;
};
printf("%ld\n", sizeof(struct bs));
return 0;
}
在 Linux_GCC 下的运行结果为 4,三个成员挨着存储;在Windows 下的运行结果为 12,三个成员按照最长的类型存储(与不指定位宽时的存储方式相同)。
- 如果成员之间穿插着非位域成员,那么不会进行压缩。例如对于下面的 bs:
struct bs{
unsigned int m: 12;
unsigned int ch;
unsigned int p: 4;
};
在各个编译器下 sizeof 的结果都是 12。
通过上面的分析,我们发现位域成员往往不占用完整的字节,有时候也不处于字节的开头位置,因此使用&获取位域成员的地址是没有意义的,C语言也禁止这样做。地址是字节(Byte)的编号,而不是位(Bit)的编号。
无名位域
位域成员可以没有名称,只给出数据类型和位宽,如下所示:
struct bs{
int m: 12;
int : 20; //该位域成员不能使用
int n: 4;
};
无名位域一般用来作填充或者调整成员位置。因为没有名称,无名位域不能使用。
枚举类型
在实际编程中,有些数据的取值往往是有限的,只能是非常少量的整数,并且最好为每个值都取一个名字,以方便在后续代码中使用,比如一个星期只有七天,一年只有十二个月等。
以每周七天为例,我们可以使用#define命令来给每天指定一个名字:
#include <stdio.h>
#define Mon 1
#define Tues 2
#define Wed 3
#define Thurs 4
#define Fri 5
#define Sat 6
#define Sun 7
int main(){
int day;
scanf("%d", &day);
switch(day){
case Mon: puts("Monday"); break;
case Tues: puts("Tuesday"); break;
case Wed: puts("Wednesday"); break;
case Thurs: puts("Thursday"); break;
case Fri: puts("Friday"); break;
case Sat: puts("Saturday"); break;
case Sun: puts("Sunday"); break;
default: puts("Error!");
}
return 0;
}
-----#define命令虽然能解决问题,但也带来了不小的副作用,导致宏名过多,代码松散,不便于代码维护。
C语言提供了一种枚举(Enum)类型,既可以列出所有可能的取值,又可以给每个值取一个名字。
枚举类型的定义形式为:
enum typeName{ valueName1, valueName2, valueName3, ...... };
例如,列出一个星期有几天:
enum week{ Mon, Tues, Wed, Thurs, Fri, Sat, Sun };
可以看到,我们仅仅给出了名字,却没有给出名字对应的值,
这是因为枚举值默认从 0 开始,往后逐个加 1(递增);
也就是说,week 中的 Mon、Tues … Sun 对应的值分别为 0、1 … 6。
我们也可以给每个名字都指定一个值:
enum week{ Mon = 1, Tues = 2, Wed = 3, Thurs = 4, Fri = 5, Sat = 6, Sun = 7 };
更为简单的方法是只给第一个名字指定值:
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun };
这样枚举值就从 1 开始递增,跟上面的写法是等效的。
枚举变量
枚举是一种类型,通过它可以定义枚举变量:下面的a,b,c是week枚举的变量,通过变量可另赋值。
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun },a,b,c;
enum week a = Mon,b=wed,c=Sat;
//或者
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } a = Mon,b=wed,c=Sat;
枚举变量的意义:“枚举"是指将变量所有可能的取值一一列举出来,变量的取值只限于枚举列出的常量。
【示例】判断用户输入的是星期几
#include <stdio.h>
int main(){
enum week{Mon = 1,Tues ,Wed,Thurs,Fri,Sat,Sun} day; //day是变量,week是枚举类型名
scanf("%d", &day);
switch(day){
case Mon: puts("Monday"); break;
case Tues: puts("Tuesday"); break;
case Wed: puts("Wednesday"); break;
case Thurs: puts("Thursday"); break;
case Fri: puts("Friday"); break;
case Sat: puts("Saturday"); break;
case Sun: puts("Sunday"); break;
default: puts("Error!");
}
return 0
}
需要注意的两点
-
枚举列表中的 Mon、Tues、Wed 这些标识符的作用范围内(这里就是 main() 函数内部),不能再定义与这些标识符名字相同的变量。
-
Mon、Tues、Wed 等都是常量,不能对它们赋值,只能将它们的值赋给其他的变量。
枚举和宏其实非常类似:
宏在预处理阶段将名字替换成对应的值,枚举在编译阶段将名字替换成对应的值。
我们可以将枚举理解为编译阶段的宏。
对于上面的代码,在编译的某个时刻会变成类似下面的样子:
#include <stdio.h>
int main(){
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } day;
scanf("%d", &day);
switch(day){
case 1: puts("Monday"); break;
case 2: puts("Tuesday"); break;
case 3: puts("Wednesday"); break;
case 4: puts("Thursday"); break;
case 5: puts("Friday"); break;
case 6: puts("Saturday"); break;
case 7: puts("Sunday"); break;
default: puts("Error!");
}
return 0;
}
这意味着,Mon、Tues、Wed 等都不是变量,它们不占用数据区(常量区、全局数据区、栈区和堆区)的内存区域,而是直接被编译到命令里面,放到代码区,所以不能用&取得它们的地址。这就是枚举的本质。
最后强调:枚举类型变量只能存放整数进去:
#include <stdio.h>
int main(){
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } day = Mon;
printf("%d, %d, %d, %d, %d\n", sizeof(enum week), sizeof(day), sizeof(Mon), sizeof(Wed), sizeof(int) );
return 0;
}
共用体
通过前面的讲解,我们知道结构体(Struct)是一种构造类型或复杂类型,它可以包含多个类型不同的成员。
在C语言中,还有另外一种和结构体非常类似的类型,叫做共用体(Union),它的定义格式为:
union 共用体名{
成员列表
};
共用体有时也被称为联合或者联合体,这也是 Union 这个单词的本意。
结构体和共用体的区别在于:
- 结构体的各个成员会占用不同的内存,互相之间没有影响;
而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。 - 结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙),
共用体占用的内存等于最长的成员占用的内存。 - 共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉。
共用体也是一种自定义类型,可以通过它来创建变量,例如:
union data{
int n;
char ch;
double f;
};
union data a, b, c; //声明三个变量
上面是先定义共用体,再创建变量,也可以在定义共用体的同时创建变量:
union data{
int n;
char ch;
double f;
} a, b, c;
如果不再定义新的变量,也可以将共用体的名字省略:
union{
int n;
char ch;
double f; } a, b, c;
--------共用体 data 中,成员n 占用的内存最多,为4 个字节,所以 data 类型的变量也占用 4个字节的内存:
#include <stdio.h>
union data{
int n;
char ch;
short m;
};
int main(){
union data a;
printf("%d, %d\n", sizeof(a), sizeof(union data) );
a.n = 0x40;
printf("%#X, %c, %#hX\n", a.n, a.ch, a.m);
a.ch = '9';
printf("%#X, %c, %#hX\n", a.n, a.ch, a.m);
a.m = 0x2059;
printf("%#X, %c, %#hX\n", a.n, a.ch, a.m);
a.n = 0x3E25AD54;
printf("%#X, %c, %#hX\n", a.n, a.ch, a.m);
return 0;
}
//执行结果
4, 4
0X40, @, 0X40 //40对应十进制的64,ASII码对应的@字符
0X39, 9, 0X39
0X2059, Y, 0X2059
0X3E25AD54, T, 0XAD54
这段代码不但验证了共用体的长度,还说明共用体成员之间会相互影响,修改一个成员的值会影响其他成员,。
要想理解上面的输出结果,弄清成员之间究竟是如何相互影响的,就得了解各个成员在内存中的分布。
成员 n、ch、m 在内存中“对齐”到一头,对 ch 赋值修改的是前一个字节,对 m 赋值修改的是前两个字节,对 n 赋值修改的是全部字节。也就是说,ch、m 会影响到 n 的一部分数据,而 n 会影响到 ch、m 的全部数据。
上图是在绝大多数 PC 机上的内存分布情况,如果是 51 单片机,情况就会有所不同:
上面情况不同是因为大小端的问题:
计算机中的数据是以字节(Byte)为单位存储的,每个字节保存在内存里都有不同的地址。
现代 CPU 的位数(可以理解为CUP一次能处理的数据的位数),
PC机、服务器的 CPU 基本都是 64 位的,嵌入式系统或单片机系统仍然在使用 32 位和 16 位的 CPU,
但都超过了 8 位(一个字节),对于一次能处理多个字节的CPU,必然存在着如何安排多个字节的问题,也就是大端和小端模式。
-
大端模式(Big-endian)是指将数据的低位(比如 1234 中的 34 就是低位)放在内存的高地址上,而数据的高位(比如 1234 中的 12 就是高位)放在内存的低地址上。
-
小端模式(Little-endian)是指将数据的低位放在内存的低地址上,而数据的高位放在内存的高地址上。
数据在内存中的存储模式是由硬件决定的,准确点是由 CPU 决定, PC 机上使用的是 X86 结构的 CPU,它是小端模式;51系列单片机则是大端模式;所以上面的代码在小端设备上运行和大端设备上运行结果是有差异的。
共用体的应用
共用体在一般的编程中应用较少,在单片机中应用较多。
对于 PC 机,经常使用到的一个实例是:
现有一张关于学生信息和教师信息的表格。
学生信息包括姓名、编号、性别、职业、分数,
教师的信息包括姓名、编号、性别、职业、教学科目。
请看下面的表格:
f 和 m 分别表示女性和男性,
s 表示学生,t 表示教师。
可以看出,学生和教师所包含的数据是不同的。
现在要求把这些信息放在同一个表格中,并设计程序输入人员信息然后输出。
如果把每个人的信息都看作一个结构体变量的话,那么教师和学生的前 4 个成员变量是一样的,第 5 个成员变量可能是 score 或者 course。
当第 4 个成员变量的值是 s 的时候,第 5 个成员变量就是 score;
当第 4 个成员变量的值是 t 的时候,第 5 个成员变量就是 course。
经过上面的分析,我们可以设计一个包含共用体的结构体,请看下面的代码:
#include <stdio.h>
#include <stdlib.h>
#define TOTAL 4 //人员总数
struct{
char name[20];
int num;
char sex;
char profession;
union{
float score;
char course[20];
} sc;
} bodys[TOTAL];
int main(){
int i;
//输入人员信息
for(i=0; i<TOTAL; i++){
printf("Input info: ");
scanf("%s %d %c %c", bodys[i].name, &(bodys[i].num), &(bodys[i].sex), &(bodys[i].profession));
if(bodys[i].profession == 's'){ //如果是学生
scanf("%f", &bodys[i].sc.score);
}else{ //如果是老师
scanf("%s", bodys[i].sc.course);
}
fflush(stdin);
}
//输出人员信息
printf("\nName\t\tNum\tSex\tProfession\tScore / Course\n");
for(i=0; i<TOTAL; i++){
if(bodys[i].profession == 's'){ //如果是学生
printf("%s\t%d\t%c\t%c\t\t%f\n", bodys[i].name,
bodys[i].num, bodys[i].sex, bodys[i].profession, bodys[i].sc.score);
}else{ //如果是老师
printf("%s\t%d\t%c\t%c\t\t%s\n", bodys[i].name,
bodys[i].num, bodys[i].sex, bodys[i].profession, bodys[i].sc.course);
}
}
return 0;
}
//执行与结果
PS C:\Users\Administrator\Desktop\workspaceC\gouzaoleixing> gcc -g union.c -o union.exe
PS C:\Users\Administrator\Desktop\workspaceC\gouzaoleixing> ./union
请输入每个人的信息:
a 1 f s 90.5
请输入每个人的信息:
b 2 m t english
请输入每个人的信息:
c 3 m t math
请输入每个人的信息:
d 4 f s 100
Nmae Num Sex Profession Score / Course
a 1 f s 90.500000
b 2 m t english
c 3 m t math
d 4 f s 100.000000