600 likes | 733 Views
第十一章 结构体与共同体. 本章要点:. 1. 掌握结构和联合类型数据的定义方法和引用方法; 2. 了解枚举类型数据的定义方法和引用方法。. 11.1 概述. 有时,需将不同类型的数据组合成一个有机的整体,以便于引用。这些数据是相互联系的。如一个学生的有关信息:. 可采用结构体数据结构描述上述信息。. 结构体是 一种 构造 数据类型 用途:把 不同类型 的数据组合成一个整体------- 自定义 数据类型 结构体类型定义. struct [ 结构体名] { 类型标识符 成员名; 类型标识符 成员名; …………….
E N D
本章要点: • 1. 掌握结构和联合类型数据的定义方法和引用方法; • 2. 了解枚举类型数据的定义方法和引用方法。
11.1 概述 • 有时,需将不同类型的数据组合成一个有机的整体,以便于引用。这些数据是相互联系的。如一个学生的有关信息: 可采用结构体数据结构描述上述信息。
结构体是一种构造数据类型 • 用途:把不同类型的数据组合成一个整体-------自定义数据类型 • 结构体类型定义 struct [结构体名] { 类型标识符 成员名; 类型标识符 成员名; ……………. }; 合法标识符 可省:无名结构体 对各成员都要进行类型说明; 成员名定名规则与变量名同。 struct是关键字,不能省略 成员类型可以是 基本型或构造型
是类型,不是变量名 例 struct student { int num; char name[20]; char sex; int age; float score; char addr[30]; }; 结构体类型定义描述结构 的组织形式,不分配内存 结构体类型定义的作用域
11.2 定义结构体类型变量的方法 方法一:先定义结构体类型再定义变量名 struct student {int num; char name[20]; char sex; int age; char addr[30]; }; struct student student1, student2; 不能只指定一个变 量为“struct型”而不 指定结构体名 定义studet1和 sudent2为 struct student 类型变量
有时,可用符号常量代表一个结构体类型,如:有时,可用符号常量代表一个结构体类型,如: #define STUDENT struct student STUDENT {int num; char name[20]; char sex; int age; char addr[30]; }; 这样,可直接用STUDENT 定义变量,如: STUDENT student1, student2; 此时,不必再写关键字struct
方法二:在定义类型的同时定义变量,如: struct student {int num; char name[20]; char sex; int age; char addr[30]; }student1, student2; 一般形式是: struct 结构体名 { 成员表列 }变量名表列;
方法三:直接定义结构类型变量。 其一般形式是: struct { 成员表列 }变量名表列; 此时,不出现结构体名 用无名结构体直接定义变量只能一次
几点说明: 1.类型与变量是不同概念,不要混淆; • 类型:不分配内存; 变量:分配内存 • 类型:不能赋值、存取、运算; 变量:可以 2. 结构体中的成员,可以单独使用,其作用与地位相当于普通变量; 3.结构体可嵌套,成员也可以是一个结构体变量;例如: Struct student {int num; char name[20]; int age; struct date birthday; }student1,student2; struct date {int month; int day; int year; }; 4. 成员名可以与程序中的变量名相同,二者不代表同一对象。
11.3 结构体类型变量的引用 规则: 1.不能将一个结构体变量作为一个整体进行赋值和输出;只能对其各个成员分别输出(引用形式为:结构体变量名.成员名)。 错! 正确! printf(“………..”,student1); printf(“ %d”, student1.num); 输出 10010 成员(分量)运算符 优先级: 1 结合性:从左向右 2 .若成员本身又属一个结构体类型,只能对最低级的成员进行赋值或存取以及运算。 如:student1.birthday.year
例 struct student { int num; char name[20]; char sex; int age; float score; char addr[30]; }stu1,stu2; stu2=stu1; ( ) 输入student1.num的值 3.对成员变量可以象普通变量一样进行各种运算,如:sumage=student1.age+student2.age; 4.可以引用成员的地址,也可以引用结构体变量的地址,如 scanf(“%d”,& student1.num); printf(“%o”,&student1); scanf(“%d,%s,%c,%d,%s”,&student1); 输出student1的首地址 错! 5.可以将一个结构体变量赋值给另一个结构体变量
11.4 结构体变量的初始化 struct 结构体名 { 类型标识符 成员名; 类型标识符 成员名; ……………. }; struct 结构体名 结构体变量={初始数据}; • 形式一: 例 struct student { int num; char name[20]; char sex; int age; char addr[30]; }; struct student stu1={112,“Wang Lin”,‘M’,19, “200 Beijing Road”};
形式二: struct 结构体名 { 类型标识符 成员名; 类型标识符 成员名; ……………. }结构体变量={初始数据}; 例 struct student { int num; char name[20]; char sex; int age; char addr[30]; }stu1={112,“Wang Lin”,‘M’,19, “200 Beijing Road”};
形式三: struct { 类型标识符 成员名; 类型标识符 成员名; ……………. }结构体变量={初始数据}; 例 struct { int num; char name[20]; char sex; int age; char addr[30]; }stu1={112,“Wang Lin”,‘M’,19, “200 Beijing Road”};
(一)对外部存储类型的结构体变量初始化: 运行结果为: No.:9801 name:Wang hong sex:W address:2 Linggong Road struct student {long int num; char name[20]; char sex; char addr[20]; } a={9801,”Wang hong”,’W’,”2 Linggong Road”}; main( ) {printf(“No.:%ld\nname:%s\nsex:%c\naddress:%s\n”,a.num,a.name,a.sex,a.addr); }
(二)对静态存储类型的结构体变量初始化,如:(二)对静态存储类型的结构体变量初始化,如: main( ) {static struct student {long int num; char name[20]; char sex; char addr[20]; }a={9801,”Wang hong”,’W’,”2 Linggong Road”}; printf(“No.:%ld\nname:%s\nsex:%c\naddress:%s\n” , a.num,a.name,a.sex,a.addr); }
11.5 结构体数组(每个数组元素都是一个结构体类型的数据) (一)结构体数组的定义,如 struct student {int num; char name[20]; char sex; int age; char addr[30]; }; struct student stu[3]; 也可直接定义,如 struct student {int num; … } stu[3]; 或struct {int num; … }stu[3];
(二)结构体数组的初始化 struct student {int num; char name[20]; char sex; int age; char addr[30]; } stu[3]={{111,”Li”,’M’,18,”Dalian”},{…},{…}}; 也可采用:struct student {int num; … }; struct student stu[]={{…},{…},{…}}; 结构体数组的初始化的一般形式是在 定义数组后面加上: ={初值表列};
例题:设有三个候选人,每次输入一个得票的候选人的名字,要求最后输出各人得票结果。例题:设有三个候选人,每次输入一个得票的候选人的名字,要求最后输出各人得票结果。 struct person { char name[20]; int count; }leader[3]={“Li”,0,”zhang”,0,”Liu”,0}; main( ) { int i, j; char leader_name[20]; for( i=1;i<=10;i++) { scanf(“%s”,leader_name); for(j=0;j<3;j++) if(strcmp(leader_name,leader[j].name)= =0)leader[j].count++); } for(i=0;i<3;i++) printf(“%5s:%d\n”,leader[i].name,leader[i].count); } name count 0 Li 0 Zhang 0 Wang 应用方式: 结构体数组名[下标].成员名
11.6 指向结构体类型数据的指针 stu.num=9901; strcpy(stu_1.name,”Li Min”); stu_1.sex=‘W’; printf(“No.:%ld\nname%s\n sex:%c\n”,stu_1.num,stu_1.name, stu_1.sex); printf(“\nNo.:%ld\nname%s\n sex:%c\n”,(*p).num,(*p).name, (*p).sex); } 结构体变量的指针:是该结构体变量所占居的内存段的起始地址。例如: main( ) { struct student {long int num; char name[20]; char sex; }; struct student stu_1; struct student *p; p=&stu_1;
指向运算符。其优先级高于自增、自减运算符 引用结构体成员的三种形式: • 结构体变量名.成员名 • (*p).成员名 • p- >成员名:直观,表示*p所指向的结构变量中的成员 printf(“\nNo.:%ld\nname%s\nsex:%c\n”,p->num, p-> name, p->sex); 试分析以下运算: p->n得到p指向的结构体变量中的成员n的值 p->n++ 得到p指向的结构体变量中的成员n的值, 用完后使它加1; ++p->n 得到p指向的结构体变量中的成员n的值 使其先加1
p num name stu[0] sex age p+1 stu[1] stu[2] • 指向结构体数组的指针 例 指向结构体数组的指针 struct student { int num; char name[20]; char sex; int age; }stu[3]={{10101,"Li Lin",'M',18}, {10102,"Zhang Fun",'M',19}, {10104,"Wang Min",'F',20}}; main() { struct student *p; for(p=stu;p<stu+3;p++) printf("%d%s%c%d\n",p->num,p->name,p->sex,p->age); }
用指向结构体的指针作函数参数 • 用结构体变量的成员作参数----值传递 • 用指向结构体变量或数组的指针作参数----地址传递 • 用结构体变量作参数----多值传递,效率低
(main) (main) (main) a :27 a :27 a :27 a :27 a :18 a :27 arg arg arg b: 3 b: 3 b: 3 b: 5 b: 3 b: 3 (main) c :30 c :30 c :30 c :30 c :30 c :90 arg (func) (func) parm parm 例 用结构体变量作函数参数 struct data { int a, b, c; }; main() { void func(struct data); struct data arg; arg.a=27; arg.b=3; arg.c=arg.a+arg.b; printf("arg.a=%d arg.b=%d arg.c=%d\n",arg.a,arg.b,arg.c); printf("Call Func()....\n"); func(arg); printf("arg.a=%d arg.b=%d arg.c=%d\n",arg.a,arg.b,arg.c); } void func(struct data parm) { printf("parm.a=%d parm.b=%d parm.c=%d\n",parm.a,parm.b,parm.c); printf("Process...\n"); parm.a=18; parm.b=5; parm.c=parm.a*parm.b; printf("parm.a=%d parm.b=%d parm.c=%d\n",parm.a,parm.b,parm.c); printf("Return...\n"); } copy
(main) (main) (main) a :27 a :18 a :18 a :27 (func) (func) arg arg arg b: 5 b: 3 b: 3 b: 5 (main) parm parm **** **** c :30 c :90 c :30 c :90 arg 例 用结构体指针变量作函数参数 struct data { int a, b, c; }; main() { void func(struct data *parm); struct data arg; arg.a=27; arg.b=3; arg.c=arg.a+arg.b; printf("arg.a=%d arg.b=%d arg.c=%d\n",arg.a,arg.b,arg.c); printf("Call Func()....\n"); func(&arg); printf("arg.a=%d arg.b=%d arg.c=%d\n",arg.a,arg.b,arg.c); } void func(struct data *parm) { printf("parm->a=%d parm->b=%d parm->c=%d\n",parm->a,parm->b,parm->c); printf("Process...\n"); parm->a=18; parm->b=5; parm->c=parm->a*parm->b; printf("parm->a=%d parm->b=%d parm->c=%d\n",parm->a,parm->b,parm->c); printf("Return...\n"); }
head 1249 1356 1475 1021 1249 A B C D 1356 1475 1021 NUll 11.7 用指针处理链表 • 链表概述 • 一种数据结构:相互之间存在一种或者多种特定关系的数据元素的集合。 • 顺序存储结构:数据元素在存储单元中顺序存储,数组 • 非顺序存储结构——链式结构:借助指示数据元素存储地址的指针来表示数据元素的逻辑关系,在内存单元随机存储,链表
head 1249 1356 1475 1021 1249 A B C D 1356 1475 1021 NUll 简单链表介绍: 头指针:head表示,存放地址,该地址指向链表的第一个元素 结点: 链表中的每个元素,每个结点包括用户的实际数据和 指向下一个结点的指针 表尾: 最后一个结点,地址部分放一个“NULL”指针。 几点说明: 1、链表中的各元素在内存中不是连续存放的,必须先找到上一个元素,根据其提供的地址才能找到下一个元素。 2、利用结构体变量最为链表中的结点。
建立和输出一个简单链表 #define NULL 0 struct student {long num; float score; struct student *next; }; main() {struct student a,b,c,*head,*p; a.num=99101;a.score=89.5; b.num=99103;b.score=90; c.num=99107;c.score=85; head=&a; a.next=&b; b.next=&c; c.next=NULL; p=head; do {printf("%ld %5.1f\n",p->num,p->score); p=p->next; }while(p!=NULL);} 本例式比较简单的,所有结点在程序中定义了,不是临时开辟的,用完也不能释放,这种链表成为“静态链表”。 还有一种是动态链表——动态分配内存
动态存储分配 在数组一章中,曾介绍过数组的长度是预先定义好的, 在整个程序中固定不变。C语言中不允许动态数组类型。例如: int n; scanf("%d",&n); int a[n]; 用变量表示长度,想对数组的大小作动态说明,这是错误的。但是在实际的编程中,往往会发生这种情况,即所需的内存空间取决于实际输入的数据,而无法预先确定。对于这种问题,用数组的办法很难解决。 为了解决上述问题,利用动态链表解决这个问题。C语言提供了一些内存管理函数,这些内存管理函数可以按需要动态地分配内存空间给结点,也可把不再使用的结点空间回收待用,为有效地利用内存资源提供了手段。
1. 分配内存空间函数malloc 调用形式: (类型说明符*) malloc (size) 功能:在内存的动态存储区中分配一块长度为“size”字节的连续区域。函数的返回值为该区域的首地址。“类型说明符”表示把该区域用于存储何种数据类型。(类型说明符*)表示把返回值强制转换为该类型指针。“size”是一个无符号数。 例如:pc=(char *) malloc (100); 表示分配100个字节的内存空间,并强制转换为字符数组类型,函数的返回值为指向该字符数组的指针,把该指针赋予指针变量pc。
2.分配内存空间函数 calloccalloc 也用于分配内存空间。 调用形式: (类型说明符*)calloc(n,size) 功能:在内存动态存储区中分配n块长度为“size”字节的连续区域。函数的返回值为该区域的首地址。(类型说明符*)用于强制类型转换。calloc函数与malloc 函数的区别仅在于一次可以分配n块区域。 例:ps=(struet stu*) calloc(2,sizeof (struct stu)); 其中的sizeof(struct stu)是求stu的结构长度。因此该语句的意思是:按stu的长度分配2块连续区域,强制转换为stu类型,并把其首地址赋予指针变量ps。
3. 释放内存空间函数free 调用形式: free(void*ptr); 功能:释放ptr所指向的一块内存空间,ptr 是一个任意类型的指针变量,它指向被释放区域的首地址。被释放区应是由malloc或calloc函数所分配的区域: main(){ struct stu { int num; char *name; char sex; float score; } *ps; ps=(struct stu*)malloc(sizeof(struct stu)); ps->num=102; ps->name="Zhang ping"; ps->sex='M'; ps->score=62.5; printf("Number=%d\nName=%s\n",ps->num,ps->name); printf("Sex=%c\nScore=%f\n",ps->sex,ps->score); free(ps); }
动态链表的作用: 如在学籍管理,有多少个学生就应该申请分配多少块内存空间,也就是说要建立多少个结点。当然用结构数组也可以完成上述工作,但如果预先不能准确把握学生人数,也就无法确定数组大小。而且当学生留级、退学之后也不能把该元素占用的空间从数组中释放出来。用动态链表的方法可以很好地解决这些问题。有一个学生就分配一个结点,无须预先确定学生的准确人数,某学生退学可删去该结点,并释放该结点占用的存储空间。从而节约了宝贵的内存资源。另一方面,用数组的方法必须占用一块连续的内存区域。 而使用动态链表时,每个结点之间可以是不连续的(结点内是连续的)。
链表的基本操作,对链表的主要操作有以下几种: 1.建立链表; 2.结构的查找与输出; 3.插入一个结点; 4.删除一个结点; 下面通过例题来说明这些操作。建立一个三个结点的链表,存放学生数据。为简单起见,我们假定学生数据结构中只有学号和年龄两项。可编写一个建立链表的函数creat。程序如下: #define NULL 0 #define TYPE struct stu #define LEN sizeof (struct stu) struct stu { int num; int age; struct stu *next; };
1249 1021 1356 i=0 i=1 i=2 pb 001 pb 002 pb 003 19 20 19 head 1021 1021 pf pf pf 1. 建立链表 TYPE *creat(int n)/*定义函数,该函数返回指向链表头的指针*/ { struct stu *head,*pf,*pb; /*定义三个指向结构体指针*/ int i; for(i=0;i<n;i++) { pb=(TYPE*) malloc(LEN); /*开辟内存单元并将首地址赋给指针pb*/ printf("input Number and Age\n"); scanf("%d%d",&pb->num,&pb->age); if (i==0) pf=head=pb; else pf->next=pb; pb->next=NULL; pf=pb; } return(head); } null null null
在函数外首先用宏定义对三个符号常量作了定义。这里用 TYPE表示struct stu,用LEN表示sizeof(struct stu)主要的目的是为了在以下程序内减少书写并使阅读更加方便。结构stu定义为外部类型,程序中的各个函数均可使用该定义。 creat函数用于建立一个有n个结点的链表,它是一个指针函数,它返回的指针指向stu结构。在creat函数内定义了三个stu结构的指针变量。head为头指针,pf 为指向两相邻结点的前一结点的指针变量。pb为后一结点的指针变量。在for语句内,用malloc函数申请LEN长度(与stu长度相等)的空间作为一结点,首地址赋予pb。然后输入结点数据。如果当前结点为第一结点(i==0),则把pb值 (该结点指针)赋予head和pf。如非第一结点,则把pb值赋予pf 所指结点的指针域成员next。而pb所指结点为当前的最后结点,其指针域赋NULL。 再把pb值赋予pf以作下一次循环准备。 creat函数的形参n,表示所建链表的结点数,作为for语句的循环次数。
p p p p 1249 1021 1475 1356 001 002 004 003 18 19 20 20 head null 1475 1021 1356 2. 结构的查找 TYPE * search (TYPE *head,int n) { TYPE *p; int i; p=head; while (p->num!=n && p->next!=NULL) p=p->next; /* 不是要找的结点后移一步*/ if (p->num==n) return (p); if (p->num!=n&& p->next==NULL) { printf ("Node %d has not been found!\n",n); return NULL; } } 本函数中使用的符号常量TYPE等于struct stu。函数有两个形参,head是指向链表的指针变量,n为要查找的学号。进入while语句,逐个检查结点的num成员是否等于n,如果不等于n且指针域不等于NULL(不是最后结点)则后移一个结点,继续循环。如找到该结点则返回结点指针。 如循环结束仍未找到该结点则输出“未找到”的提示信息。
3. 删除链表中的指定结点 删除一个结点有两种情况: 1). 被删除结点是第一个结点。这种情况只需使head指向第二个结点 即可。即head=pb->next。 2). 被删结点不是第一个结点,这种情况使被删结点的前一结点指向 被删结点的后一结点即可。即pf->next=pb->next。编程如下: TYPE *delete(TYPE * head,int num) { TYPE *pf,*pb; if(head==NULL) /*如为空表, 输出提示信息*/ { printf("\n Empty list!\n"); goto end;} pb=head; while (pb->num!=num && pb->next!=NULL) /*当不是要删除的结点,而且也不是最后一个结点时,继续循环*/
pb pb pb 1021 1356 1475 1249 002 004 001 003 19 20 20 18 head 1356 1475 1021 null pf pf 1021 {pf=pb;pb=pb->next;} /*pf指向当前结点,pb指向下一结点*/ if(pb->num==num) {if(pb==head) head=pb->next; /*如找到被删结点,且为第一结点,则使head指向第二个结点,否则使pf所指结点的指针指向下一结点*/ else pf->next=pb->next; free(pb); printf("The node is deleted\n");} else printf("The node not been foud!\n"); end: return head; }
4. 在链表中指定位置插入一个结点 在一个链表的指定位置插入结点,要求链表本身必须是已按某种规律排好序的。例如,在学生数据链表中,要求学号顺序插入一个结点。设被插结点的指针为pi, 在三种不同情况下插入。 1).原表是空表,只需使head指向被插结点即可。 2). 被插结点值最小,应插入第一结点之前。这种情况下使head指向被插结点,被插结点的指针域指向原来的第一结点则可。即: pi->next=pb; head=pi; 3). 在其它位置插入。这种情况下使插入位置的前一结点的指针域指向被插结点,使被插结点的指针域指向插入位置的后一结点。即为:pi->next=pb; pf->next=pi; 4). 在表末插入。这种情况下使原表末结点指针域指向被插结点,被插结点指针域置为NULL。即:pb->next=pi;pi->next=NULL;
TYPE * insert(TYPE * head,TYPE *pi) { TYPE *pf,*pb; pb=head; if(head==NULL) /*空表插入*/ {head=pi; pi->next=NULL;} else { while((pi->num > pb->num) && (pb->next!=NULL)) /*大于某一结点,且该结点不是最后一个结点,即循环在此末结点*/ {pf=pb; pb=pb->next; } /*找插入位置,指针pf、pb后移*/ if(pi->num <= pb->num) {if(head==pb) head=pi; /*在第一结点之前插入*/ else pf->next=pi; /*在其它位置插入*/ pi->next=pb; } else {pb->next=pi; pi->next=NULL;} /*在表末插入*/ } return head;}
将以上建立链表,删除结点,插入结点的函数组织在一起,再建一个输出全部结点的函数,然后用main函数调用它们。将以上建立链表,删除结点,插入结点的函数组织在一起,再建一个输出全部结点的函数,然后用main函数调用它们。 void print(TYPE * head) { printf("Number\t\tAge\n"); while(head!=NULL) { printf("%d\t\t%d\n",head->num,head->age); head=head->next; } }
main() { TYPE * head,*pnum; int n,num; printf("input number of node: "); scanf("%d",&n); head=creat(n); print(head); printf("Input the deleted number: "); scanf("%d",&num); head=delete(head,num); print(head); printf("Input the inserted number and age: "); pnum=(TYPE *)malloc(LEN); scanf("%d%d",&pnum->num,&pnum->age); head=insert(head,pnum); print(head); }
i ch f 11.8 共用体 11.8.1 共用体的概念 共用体:使几个不同的变量共占同一段内存的结构,称为“共用体”类型的结构。 “共用体”类型变量的定义形式为: union 共用体名 { 类型标识符 成员名; 类型标识符 成员名; ……………. }; 例 union data { int i; char ch; float f; }; 类型定义不分配内存
i i ch ch f f a b • 共用体变量的定义 形式一: union data { int i; char ch; float f; }a,b; 形式二: union data { int i; char ch; float f; }; union data a,b,c,*p,d[3]; 形式三: union { int i; char ch; float f; }a,b,c; 共用体变量任何时刻 只有一个成员存在 共用体变量定义分配内存, 长度=最长成员所占字节数
共用体变量名.成员名 共用体指针名->成员名 (*共用体指针名).成员名 union data { int i; char ch; float f; }; union data a,b,c,*p,d[3]; a.i a.ch a.f p->i p->ch p->f (*p).i (*p).ch (*p).f d[0].i d[0].ch d[0].f • 共用体变量引用 • 引用方式: • 引用规则 • 不能引用共用体变量,只能引用其成员 • 共用体变量中起作用的成员是最后一次存放的成员 例 union { int i; char ch; float f; }a; a=1; () • 不能在定义共用体变量时初始化 例 a.i=1; a.ch=‘a’; a.f=1.5; printf(“%d”,a.i); (编译通过,运行结果不对) • 可以用一个共用体变量为另一个变量赋值 例 union { int i; char ch; float f; }a={1,’a’,1.5}; () 例 float x; union { int i; char ch; float f; }a,b; a.i=1; a.ch=‘a’; a.f=1.5; b=a; () x=a.f; ()
高字节 低字节 01100001 01000001 ch[0] 01000001 ch[1] 01100001 例 将一个整数按字节输出 main() { union int_char { int i; char ch[2]; }x; x.i=24897; printf("i=%o\n",x.i); printf("ch0=%o,ch1=%o\n ch0=%c,ch1=%c\n", x.ch[0],x.ch[1],x.ch[0],x.ch[1]); } 运行结果: i=60501 ch0=101,ch1=141 ch0=A,ch1=a
结构体与共用体 • 区别: 存储方式不同 变量的各成员同时存在 struct node { char ch[2]; int k; }a; ch a k ch b k union node { char ch[2]; int k; }b; 任一时刻只有一个成员存在 结构体类型变量所占内存长度是各成员内存长度之和。 共用体类型变量所占内存长度等于最长的成员的长度。 • 联系: 两者可相互嵌套
class 循环n次 name num sex job position 读入姓名、号码、性别、职务 Li 1011 F S 501 Wang 2086 job==‘s’ M T prof 真 假 job==‘t’ 真 假 读入class 读入 position 输出 “输入错” 循环n次 job==‘s’ 真 假 输出:姓名,号码, 性别,职业,职务 输出:姓名,号码, 性别,职业,班级 例 结构体中嵌套共用体 struct { int num; char name[10]; char sex; char job; union { int class; char position[10]; }category; }person[2];