用户自己建立数据类型
A program without a loop and a structured variable isn’t worth writing. [^1]
[TOC]
一.结构变量
前面我们说过数组有两个重要特性:
- 数组所有的元素具有相同的数据类型
- 选择数组元素需要指明元素的位置(下标)
结构和数组有很大不同。结构的元素(C 语言中的说法是成员)可以具有不同类型。而且每个结构成员都有名字,访问结构体成员需要指明结构成员的名字而不是位置。
在一些编程语言中,经常把结构体称为记录(record),把结构体的成员称为字段(field)。
1. 结构变量的声明
假如需要记录存储在仓库中的零件。我们可能需要记录零件的编号,名称和数量。我们可以使用结构体:
| struct{ int number; char name[NAME_LEN + 1]; int on_hand; }part1, part2;
|
struct{...}
指明类型,part1,part2
是这种类型的变量。
结构体在内存中是按照声明顺序存储的。


(1) 结构体类型与结构体变量是不同的概念,不要混淆。只能对变量赋值、存取或运算,而不能对一个类型赋值、存取或运算。在编译时,对类型是不分配空间的,只对变量分配空间。
(2) 结构体类型中的成员名可以与程序中的变量名相同,但二者不代表同一对象。
(3) 对结构体变量中的成员(即“域”),可以单独使用,它的作用与地位相当于普通变量。
2. 结构变量的初始化
我们可以在定义结构体的同时初始化:
1 2 3 4 5 6
| struct{ int number; char name[NAME_LEN + 1]; int on_hand; }part1 = {528, "Disk drive", 10} part2 = {914, "Printer cable", 5};
|
初始化式中的值必须按照结构体成员的顺序进行显示。
结构初始化式遵循的原则类似于数组的。初始化式必须是常量(C99 中允许使用变量)。初始化式中的成员可以少于它所初始化的结构,“剩余的”成员用 0 作为初始值。特别的,剩余的字符串应为空字符串。
2. 指定初始化(C99)
特性和数组一样,比如:
1 2 3 4 5
| struct { int number; char name[NAME_LEN + 1]; int on_hand; }part1 = {.name = "Disk", 123};
|
number 被默认为 0,.name
直接跳过 number 初始化 name,123 初始化的成员为 .name 后一个成员。
3. 对结构的操作
访问成员方式如下:
1 2 3
| printf("Part number: %d\n", part1.number); printf("Part name: %s\n", part1.name); printf("Quantity on hand: %d\n", part.on_hand);
|
结构的成员是左值,所以可以出现在赋值运算的左侧:
1 2
| part1.number = 258; part1.on_hand++;
|
.
其实就是一个 C 语言的运算符。.
运算符的优先级几乎高于所有其他运算符,所以思考:
1
| scanf("%d", &part1.on_hand);
|
&
计算的是 part1.on_hand
的地址
赋值运算:
等价于:
1 2 3
| part1.number = part2.number; strcpy(part1.name, part2.name); part1.on_hand = part2.on_hand;
|
如果这个结构内含有数组,数组也会被复制。
💥 ❗️ ❗ 但是不能使用 ==
和 !=
运算符判定两个结构是否相等。
程序一
把一个学生的信息(包括学号、姓名、性别、住址)放在一个结构体变量中,然后输出这个学生的信息。
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <stdio.h> int main() { struct Student //声明结构体类型struct Student { long int num; char name[20]; char sex; char addr[20]; }a={10101,"Li Lin",'M',"123 Beijing Road"}; printf("NO.:%ld\nname:%s\nsex:%c\naddress:%s\n",a.num,a.name,a.sex,a.addr); return 0; }
|
程序二
输入两个学生的学号、姓名和成绩,输出成绩较高的学生的学号、姓名和成绩。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <stdio.h> int main() { struct Student //声明结构体类型struct Student { int num; char name[20]; float score; }student1,student2; scanf("%d%s%f",&student1.num,student1.name,&student1.score); scanf("%d%s%f",&student2.num,student2.name,&student2.score); printf("The higher score is:\n"); if(student1.score>student2.score) printf("%d %s %6.2f\n",student1.num,student1.name,student1.score); else if(student1.score<student2.score) printf("%d %s %6.2f\n",student2.num,student2.name,student2.score); else { printf("%d %s %6.2f\n",student1.num,student1.name,student1.score); printf("%d %s %6.2f\n",student2.num,student2.name,student2.score); } return 0; }
|
4.注意事项
结构体变量的初始化和引用注意事项


二. 使用结构体数组
所谓结构体数组,是指数组中的每个元素都是一个结构体。在实际应用中,C语言结构体数组常被用来表示一个拥有相同数据结构的群体,比如一个班的学生、一个车间的职工等。
1. 结构体数组定义与初始化
在C语言中,定义结构体数组和定义结构体变量的方式类似,请看下面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| struct stu{ char *name; int num; int age; char group; float score; }class[5];
struct stu{ char *name; int num; int age; char group; float score; }; struct stu class[5];
|
表示一个班级有5个学生。
结构体数组在定义的同时也可以初始化,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| struct stu{ char *name; int num; int age; char group; float score; }class[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} };
|
结构体数组的使用也很简单,例如,获取 Wang ming 的成绩:class[4].score;
修改 Li ping 的学习小组: class[0].group = 'B';
程序一
计算全班学生的总成绩、平均成绩和以及 140 分以下的人数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <stdio.h> struct{ char *name; int num; int age; char group; float score; }class[] = { {"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 i, num_140 = 0; float sum = 0; for(i=0; i<5; i++){ sum += class[i].score; if(class[i].score < 140) num_140++; } printf("sum=%.2f\naverage=%.2f\nnum_140=%d\n", sum, sum/5, num_140); return 0; }
|
程序二
有n个学生的信息(包括学号、姓名、成绩),要求按照成绩的高低顺序输出各学生的信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <stdio.h> struct Student //声明结构体类型struct Student { int num; char name[20]; float score; }; int main() { struct Student stu[5]={{10101,"Zhang",78},{10103,"Wang",98.5},{10106,"Li",86}, {10108,"Ling",73.5},{10110,"Sun",100}}; struct Student temp; const int n=5; int i,j,k; printf("The order is:\n"); for(i=0;i<n-1;i++) { k=i; for(j=i+1;j<n;j++) if(stu[j].score>stu[k].score) k=j; temp=stu[k]; stu[k]=stu[i]; stu[i]=temp; } for(i=0;i<n;i++) printf("%6d %8s %6.2f\n",stu[i].num,stu[i].name,stu[i].score); printf("\n"); return 0; }
|
三.结构体指针
所谓结构体指针就是指向结构体变量的指针,一个结构体变量的起始地址就是这个结构体变量的指针。如果把一个结构体变量的起始地址存放在一个指针变量中,那么,这个指针变量就指向该结构体变量。
C语言结构体指针的定义形式一般为:
1. 定义结构体指针实例
1 2 3 4 5 6 7 8 9 10
| struct stu{ char *name; int num; int age; char group; float score; } stu1 = { "Tom", 12, 18, 'A', 136.5 };
struct stu *pstu = &stu1;
|
也可以在定义结构体的同时定义结构体指针:
1 2 3 4 5 6 7
| struct stu{ char *name; int num; int age; char group; float score; } stu1 = { "Tom", 12, 18, 'A', 136.5 }, *pstu = &stu1;
|
💥 ❗️ ❗ 注意,结构体变量名和数组名不同,数组名在表达式中会被转换为数组指针,而结构体变量名不会,无论在任何表达式中它表示的都是整个集合本身,要想取得结构体变量的地址,必须在前面加&
,所以给将指针pstu指向结构体只能写作:
1
| struct stu *pstu = &stu1;
|
而不能写作:
1
| struct stu *pstu = stu1;
|
2. 获取结构体成员
通过结构体指针可以获取结构体成员,一般形式为:
1 2 3 4 5 6
| (*pointer).memberName
pointer->memberName
|
第一种写法中,.
的优先级高于*
,所以(*pointer)两边的括号不能少。如果去掉括号写作*pointer.memberName,那么就等效于*(pointer.memberName),这样意义就完全不对了。
第二种写法中,->
是一个新的运算符[指向运算符],习惯称它为“箭头”[arrow operator],有了它,可以通过结构体指针直接取得结构体成员;这也是->
在C语言中的唯一用途。
上面的两种写法是等效的,我们通常采用后面的写法,这样更加直观。
3.指向结构体成员的方法总结
1 2 3 4 5 6 7 8 9
| struct Student //声明结构体类型struct Student { long num; char name[20]; char sex; float score; }; struct Student stu_1; struct Student *p; p=&stu_1;
|

如果p指向一个结构体变量stu_1,以下3种用法等价:
1 2 3 4 5 6 7
| stu.num
(*p).num
p->num
|
4.实例2
有3个学生的信息,放在结构体数组中,要求输出全部学生的信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <stdio.h> struct Student //声明结构体类型struct Student { int num; char name[20]; char sex; int age; }; struct Student stu[3]={{10101,"Li Lin",'M',18},{10102,"Zhang Fang",'M',19},{10104,"Wang Min",'F',20}};
int main() { struct Student *p; printf(" No. Name sex age\n"); for (p=stu;p<stu+3;p++) printf("%5d %-20s %2c %4d\n",p->num, p->name, p->sex, p->age); return 0; }
|

运行结果如下:

5.用结构体变量和结构体变量的指针作函数参数
将一个结构体变量的值传递给另一个函数,有3个方法:
(1) 用结构体变量的成员作参数。
例如,用stu[1].num或stu[2].name作函数实参,将实参值传给形参。用法和用普通变量作实参是一样的,属于“值传递”方式。应当注意实参与形参的类型保持一致。
(2) 用结构体变量作实参。
用结构体变量作实参时,采取的也是“值传递”的方式,将结构体变量所占的内存单元的内容全部按顺序传递给形参,形参也必须是同类型的结构体变量。在函数调用期间形参也要占用内存单元。这种传递方式在空间和时间上开销较大,如果结构体的规模很大时,开销是很可观的。此外,由于采用值传递方式,如果在执行被调用函数期间改变了形参(也是结构体变量)的值,该值不能返回主调函数,这往往造成使用上的不便。因此一般较少用这种方法。
(3) 用指向结构体变量(或数组元素)的指针作实参,将结构体变量(或数组元素)的地址传给形参。
示例
【教材例9.7】有n个结构体变量,内含学生学号、姓名和3门课程的成绩。要求输出平均成绩最高的学生的信息(包括学号、姓名、3门课程成绩和平均成绩)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #include <stdio.h> #define N 3 struct Student //建立结构体类型struct Student { int num; char name[20]; float score[3]; float aver; }; int main() { void input(struct Student stu[]); struct Student max(struct Student stu[]); void print(struct Student stu); struct Student stu[N],*p=stu; input(p); print(max(p)); return 0; } void input(struct Student stu[]) { int i; printf("请输入各学生的信息: 学号、姓名、三门课成绩:\n"); for(i=0;i<N;i++) { scanf("%d %s %f %f %f",&stu[i].num,stu[i].name, &stu[i].score[0],&stu[i].score[1],&stu[i].score[2]); stu[i].aver=(stu[i].score[0]+stu[i].score[1]+stu[i].score[2])/3.0; } } struct Student max(struct Student stu[]) { int i,m=0; for(i=0;i<N;i++) if(stu[i].aver>stu[m].aver) m=i; return stu[m]; }
void print(struct Student stud) { printf("\n成绩最高的学生是:\n"); printf("学号:%d\n姓名:%s\n三门课成绩:%5.1f,%5.1f,%5.1f\n平均成绩: %6.2f\n",stud.num,stud.name,stud.score[0],stud.score[1],stud.score[2],stud.aver); }
|
四.使用枚举类型
1. 枚举型定义
枚举类型的定义形式为:
1
| enum typeName{ valueName1, valueName2, valueName3, ...... };
|
enum
是一个新的关键字,专门用来定义枚举类型,这也是它在C语言中的唯一用途;typeName
是枚举类型的名字;valueName1, valueName2, valueName3, ......
是每个值对应的名字的列表。注意最后的;
不能少。
- 声明枚举类型用enum开头。花括号中的元素称为枚举元素或枚举常量。
- 枚举值默认从 0 开始,往后逐个加 1(递增)
例如,列出一个星期有几天:
1
| 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 开始递增,跟上面的写法是等效的。
注意
- C编译对枚举类型的枚举元素按常量处理,故称枚举常量。不要因为它们是标识符(有名字)而把它们看作变量,不能对它们赋值。
- 每一个枚举元素都代表一个整数,C语言编译按定义时的顺序默认它们的值为0,1,2,3,4,5…。也可以在定义枚举类型时显式地指定枚举元素的数值。
- 枚举元素可以用来作判断比较。枚举元素的比较规则是按其在初始化时指定的整数来进行比较的。
2. 枚举变量定义
枚举变量定义如下:
也可以在定义枚举类型的同时定义变量:
1
| enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } a, b, c;
|
有了枚举变量,就可以把列表中的值赋给它:
1 2
| enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun }; enum week a = Mon, b = Wed, c = Sat;
|
或者:
1
| enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } a = Mon, b = Wed, c = Sat;
|
枚举运用示例1
【示例】判断用户输入的是星期几。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <stdio.h> int main(){ enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } 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; }
|
需要注意的两点是:
- 枚举列表中的 Mon、Tues、Wed 这些标识符的作用范围是全局的(严格来说是 main() 函数内部),不能再定义与它们名字相同的变量。
- Mon、Tues、Wed 等都是常量,不能对它们赋值,只能将它们的值赋给其他的变量。
枚举运用示例2
【教材例9.12】口袋中有红、黄、蓝、白、黑5种颜色的球若干个。每次从口袋中先后取出3个球,问得到3种不同颜色的球的可能取法,输出每种排列的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #include <stdio.h> int main() { enum Color {red,yellow,blue,white,black}; enum Color i,j,k,pri; int n,loop; n=0; for(i=red;i<=black;i++) for(j=red;j<=black;j++) if(i!=j) { for (k=red;k<=black;k++) if ((k!=i) && (k!=j)) { n=n+1; printf("%-4d",n); for(loop=1;loop<=3;loop++) { switch (loop) { case 1: pri=i;break; case 2: pri=j;break; case 3: pri=k;break; default:break; } switch (pri) { case red:printf("%-10s","red");break; case yellow: printf("%-10s","yellow");break; case blue: printf("%-10s","blue");break; case white: printf("%-10s","white");break; case black: printf("%-10s","black"); break; default:break; } } printf("\n"); } } printf("\ntotal:%5d\n",n); return 0; }
|