枚举(Enumeration)是 C 语言中的一种用户自定义数据类型,用于定义一组相关的常
量。枚举使我们可以为这些常量分配有意义的名字,从而提高代码的可读性和可维护性。
如果我们定义一个变量,用来表示一周中的一天,该变量的值只能从七个有效值中选择,
枚举可以帮助我们限制变量的有效值。
(1)语法格式
枚举使用 enum 关键字来定义,枚举名称和枚举元素名称都是标识符,定义一个枚举
就是定义了一种枚举数据类型,语法如下:
enum 枚举名称
{
枚举元素 1,
枚举元素 2,
...
枚举元素 N
};
(2)枚举常量
枚举元素也称为枚举成员或枚举常量,具有如下特点:
1)枚举元素的值必须在同一枚举中是唯一的
2)枚举元素的值必须是整数类型,通常是 int
3)如果没有为枚举元素指定值,编译器会自动为它们分配值,从 0 开始,依次递增。
4)定义枚举的时候也可以为枚举元素自定义值,需保证唯一性和整型类型。
(3)代码演示
// 定义枚举类型,表示星期一到星期天,枚举元素的值自动分配,从 0 到 6。
enum Weekday
{
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
};
// 定义枚举类型,表示一月到十二月。 给枚举元素 JANUARY 设置了值 1,后面的枚举元
//素从 1 开始自增,分别是 1 到 12
enum Month
{
JANUARY = 1,
FEBRUARY,
MARCH,
APRIL,
MAY,
JUNE,
JULY,
AUGUST,
SEPTEMBER,
OCTOBER,
NOVEMBER,
DECEMBER
};
// 定义枚举类型,表示北京地区一年四季的平均温度。为每个枚举元素都设置值,自己
//设置的值需要保证唯一性和整型类型
enum Season
{
SPRING = 18,
SUMMER = 30,
AUTUMN = 20,
WINTER = -5
};
(1)什么是枚举变量
定义变量时指定类型是我们所定义的枚举类型,该变量称为枚举变量。枚举变量的值应
该是枚举类型中的枚举常量中的一个,不能是其他值。
(2)定义枚举变量的语法
1)第一种:先定义枚举类型,再定义枚举变量。
// 定义枚举类型
enum DAY
{
MON = 1,
TUE,
WED,
THU,
FRI,
SAT,
SUN
};
// 使用枚举类型定义枚举变量
enum DAY day;
2)第二种:定义枚举类型的同时声明枚举变量。
enum DAY
{
MON = 1,
TUE,
WED,
THU,
FRI,
SAT,
SUN
} day;
3)第三种:定义枚举类型的同时声明枚举变量,且省略枚举名称。
enum
{
MON = 1,
TUE,
WED,
THU,
FRI,
SAT,
SUN
} day;
(3)示例代码
#include
int main()
{
// 定义枚举类型
enum Weekday
{
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
};
// 定义枚举变量并赋值
enum Weekday day = WEDNESDAY;
printf("%d \n", day);
// 修改枚举变量的值
day = FRIDAY;
printf("%d \n", day);
// 修改枚举变量的值
// 100 并不是枚举常量中的一个,这样做虽然不会报错,但失去了枚举的意义,不
//建议这么做
day = 100;
printf("%d \n", day);
return 0;
}
2
4
10
如果枚举常量的值是连续的,我们可以使用循环遍历;如果枚举常量的值不是连续的,
则无法遍历
#include
int main()
{
// 定义枚举类型同时定义枚举变量
enum Weekday
{
MONDAY = 1,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
} day;
// 使用循环遍历出所有的枚举常量
for (day = MONDAY; day <= SUNDAY; day++)
{
printf("%d \n", day);
}
// 如果枚举常量的值并不是连续的数字,则无法遍历
// 定义枚举类型同时定义枚举变量
enum Season
{
SPRING = 18,
SUMMER = 30,
AUTUMN = 20,
WINTER = -5
} season;
for (season = SPRING; season <= WINTER; season++)
{
printf("%d \n", season);
}
return 0;
}
1
2
3
4
5
6
7
(2)枚举在 switch 中使用
枚举可以与 switch 结合使用,请看如下案例:
#include
int main()
{
// 定义枚举类型
enum SEASONS
{
SPRING = 1,
SUMMER,
AUTUMN,
WINTER
};
// 定义枚举变量
enum SEASONS season;
// 获取用户输入
printf("请输入你喜欢的季节: (1. spring, 2. summer, 3. autumn 4winter): ");
scanf("%d", &season);
// 判断
switch (season)
{
case SPRING:
printf("你喜欢的季节是春天");
break;
case SUMMER:
printf("你喜欢的季节是夏天");
break;
case AUTUMN:
printf("你喜欢的季节是秋天");
break;
case WINTER:
printf("你喜欢的季节是冬天");
break;
default:
printf("你没有选择你喜欢的季节");
}
return 0;
}
C 语言内置的数据类型,除了几种原始的基本数据类型,只有数组属于复合类型,可
以同时包含多个值,但是只能包含相同类型的数据,实际使用场景受限。
需求分析 1:
现有一个需求,编写学生档案管理系统,这里需要描述一个学生的信息。该学生的信息
包括学号、姓名、性别、年龄、家庭住址等,这些数据共同说明一个学生的总体情况。
需求分析 2;
隔壁老王养了两只猫咪,一只名字叫小黄,今年 2 岁,橘色;另一只叫小黑,今年 3 岁,
黑色。请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果
用户输入的小猫名错误,则显示老王没有这只猫。
传统的解决方法:
尝试 1:单独定义多个变量存储,实现需求,但是,多个变量,不便于数据的管理。
尝试 2:使用数组,它是一组具有相同类型的数据的集合。但在编程中,往往还需要一
组类型不同的数据,例如猫的名字使用字符串、年龄是 int,颜色是字符串,因为数据类型
不同,不能用一个数组来存放。
C 语言提供了 struct 关键字,允许自定义复合数据类型,将不同类型的值组合在一起,
这种类型称为结构体(structure)类型。
C 语言没有其他语言的对象(object)和类(class)的概念,struct 结构很大程度上提供
了对象和类的功能。
(1)格式
构建一个结构体类型的一般形式为:
struct 结构体名
{
数据类型 1 成员名 1;
数据类型 2 成员名 2;
……
数据类型 n 成员名 n;
}
(2)示例代码
1)举例:学生
// 定义结构体:学生
struct Student
{
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别
char *address; // 家庭住址
};
2)举例:通讯录
// 定义结构体:通讯录
struct Contacts
{
char name[50]; //姓名
int year; //年
int month; //月
int day; //日
char email[100]; //电子邮箱
char mobile_number[12]; //手机号
};
(1)方式 1 先定义结构体,然后再创建结构体变量
// 定义结构体
struct Student
{
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别
char *address; // 家庭住址
};
// 定义结构体变量
struct Student stu1,stu2;
(2)方式 2 在定义结构体的同时定义结构体变量
// 定义结构体的同时定义 stu1 和 stu2 两个变量
struct Student
{
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别
char *address; // 家庭住址
} stu1, stu2;
(3)方式 3 在定义时也可以不给出结构体名
// 不给出结构体的名字
struct
{
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别
char *address; // 家庭住址
} stu1, stu2;
注意:在 C 语言中,结构体(struct)和结构体变量是两个不同的概念。
(1)结构体是一种自定义的数据类型,像一种模板,定义了数据的格式。
(2)结构体变量是根据结构体类型创建的变量,代表了一个具体的对象,用于存储数
据。
成员是结构体的一个组成部分,一般是基本数据类型、也可以是数组、指针、结构体等。
结构体的成员也可以称为属性。
结构体和数组类似,也是一组数据的集合,结构体使用点号 . 获取单个成员,可以进
行赋值和取值。
(1)案例 1:结构体成员逐个赋值
#include
int main()
{
//声明结构体
struct Student
{
int id;
int age;
char *name;
char *gender;
char *address;
};
//声明结构体变量
struct Student stu;
//对这个结构体成员进行赋值
stu.id=1001;
stu.age=19;
stu.name="Jack";
stu.gender='m';
stu.address="chendu";
//获取成员值
printf("id: %d \n", stu.id);
printf("name: %s \n", stu.name);
printf("age: %d \n", stu.age);
printf("gender: %c \n", stu.gender);
printf("address: %s \n", stu.address);
return 0;
}
id: 1001
name: Jack Ma
age: 19
gender: m
address: Changping BeiJing
(2)案例2:使用大括号一次性对结构体所有成员赋值
#include
int main()
{
//声明结构体以及结构体变量,并且给结构体成员赋值
struct Student
{
int id;
int age;
char *name;
char *gender;
char *address;
}stu1={002, 19, "Jim Liu", 'f', "Songjiang Shanghai"},stu2={1003, 16, "Tom Chen", 'm', "Baoan Shenzhen"};
// 获取成员的值
printf("id: %d \n", stu1.id);
printf("name: %s \n", stu1.name);
printf("age: %d \n", stu1.age);
printf("gender: %c \n", stu1.gender);
printf("address: %s \n \n", stu1.address);
printf("id: %d \n", stu2.id);
printf("name: %s \n", stu2.name);
printf("age: %d \n", stu2.age);
printf("gender: %c \n", stu2.gender);
printf("address: %s \n", stu2.address);
return 0;
}
id: 1002
name: Jim Liu
age: 19
gender: f
address: Songjiang Shanghai
id: 1003
name: Tom Chen
age: 16
gender: m
address: Baoan Shenzhen
(1)结构体指针介绍
结构体指针是指向结构体的指针变量,允许间接访问和操作结构体的成员,它提供了一
种方便的方式来处理结构体数据。
声明结构体指针的语法格式:
struct 结构体名 *结构体指针变量名;
(2)声明结构体指针
通常需要先定义一个结构体变量,再创建结构体指针,并取结构体变量的地址作为结构
体指针的值。
// 声明结构体
struct Student
{
int id; // 学号
int age; // 年龄
char *name; // 姓名
char gender; // 性别
char *address; // 家庭住址
};
// 声明结构体变量
struct Student stu;
// 声明结构体指针并初始化
struct Student *ptr = &stu;
(3)通过结构体指针访问成员
结构体指针可以通过“->” 操作符访问结构体的成员。
#include
int main()
{
// 结构体
struct Student
{
char *name;
int age;
char gender;
};
// 结构体变量
struct Student s = {"张三", 20, 'M'};
// 通过结构体变量访问成员
printf("name=%s,age=%d,gender=%c \n", s.name, s.age, s.gender);
// 声明结构体指针
struct Student *ps = &s;
// 结构体指针通过解引用访问成员
printf("name=%s,age=%d,gender=%c\n", (*ps).name, (*ps).age,(*ps).gender);
// 结构体指针使用->访问成员
printf("name=%s,age=%d,gender=%c\n", ps->name, ps->age,ps->gender);
return 0;
}
name=张三,age=20,gender=M
name=张三,age=20,gender=M
name=张三,age=20,gender=M
总结:如果指针变量 p 指向一个结构体变量 stu,以下 3 种用法等价:
(1)stu.成员名
(2)(*p).成员名
(3)p->成员名
(1)小狗案例
1)编写一个 Dog 结构体,包含 name(char *)、age(int)、weight(double)属性。
2)编写一个 say 函数,返回字符串,方法返回信息中包含所有成员值。 3)在 main 函数中,创建 Dog 结构体变量,调用 say 函数,将调用结果打印输出。
#include
// 定义 Dog 结构体
struct Dog
{
char *name;
int age;
double weight;
};
// say函数,返回字符串,信息中包含所有成员值
schar *say(struct Dog dog)ay
{
// 将这个信息放入到一个字符串(字符数组)
static char info[50]; // 局部静态变量
sprintf(info, "name=%s age=%d weight=%.2f", dog.name, dog.age,
dog.weight);
dog.name = "小花";
return info;
}
int main()
{
// 测试
// 定义结构体变量
struct Dog dog;
char *info = NULL;
dog.name = "小黄";
dog.age = 1;
dog.weight = 3.4;
info = say(dog); // 结构体变量默认是值传递
printf("小狗信息:%s \n", info);
printf("main 中小狗名字:%s \n", dog.name);
return 0;
}
(2)盒子案例
1)编程创建一个 Box 结构体,在其中定义三个成员表示一个长方体的长、宽和高,
长宽高可以通过控制台输入。
2)定义一个函数获取长方体的体积(volume)。 3)创建一个结构体指针,打印给定尺寸的长方体的体积。
#include
// 定义 Box 结构体
struct Box
{
double length;
double width;
double height;
};
// 获取立方体体积的函数
double getVolume(struct Box *box)
{
return box->length * box->width * box->height;
}
int main()
{
/ 创建结构体变量
struct Box box;
// 创建结构体指针
struct Box *cube = &box;
printf("Enter length: ");
scanf("%lf", &cube->length);
printf("Enter width: ");
scanf("%lf", &cube->width);
printf("Enter height: ");
scanf("%lf", &cube->height);
// 调用函数获取体积并打印
printf("Volume is: %.2f\n", getVolume(cube));
return 0;
}
3)景区门票案例
1)一个景区根据游人的年龄收取不同价格的门票。
2)请编写游人结构体(Visitor),根据年龄段决定能够购买的门票价格并输出。 3)规则:年龄>18,门票为 20 元,其它情况免费。
4)可以循环从控制台输入名字和年龄,打印门票收费情况,如果名字输入 n,则退
出程序。
#include
#include
// 定义结构体
struct Visitor
{
char name[10];
int age;
double pay; // 应付票价
}
// 编写函数处理业务
void ticket(struct Visitor *visitor)
{
// 判断
if (visitor->age > 18)
{
visitor->pay = 20;
}
else
{
visitor->pay = 0;
}
}
int main()
{
// 测试
// 创建结构体变量(创建一个游客)
struct Visitor visitor;
// 循环的输入名字和年龄
while (1)
{
printf("\n 请输入游客名字:");
scanf("%s", visitor.name);
// 判断如果名字输入 n ,则退出程序
if (!strcmp("n", visitor.name))
{
break;
}
printf("\n 请输入游客年龄:");
scanf("%d", &visitor.age);
// 调用函数 ticket , 获取应付的票价
ticket(&visitor);
printf("\n 该游客应付票价:%.2f\n", visitor.pay);
}
printf("退出程序");
return 0;
}
区分三个概念:结构体、结构体变量、结构体变量的成员。
结构体是自定义的数据类型,表示的是一种数据类型。结构体变量代表一个具体变量。
类比
int num1; // int 是数据类型, 而 num1 是一个具体的 int 变量
struct Car car1; // Car 是结构体数据类型,而 car1 是一个 Car 变量
有时需要一种数据结构,不同的场合表示不同的数据类型。比如,如果只用一种数据结
构表示学生的“成绩”,这种结构就需要有时是整数(80、90),有时是字符('A'、'B'),
有时是浮点数(80.5、60.5)。
C 语言提供了共用体类型(Union 结构),用来自定义可以灵活变更的数据结构。它内
部可以包含各种属性,但同一时间只能有一个属性,因为所有属性都保存在同一个内存地址,
后面写入的属性会覆盖前面的属性。这样做的最大好处是节省空间。
“共用体”与“结构体”的定义形式相似,但它们的含义是不同的。
(1)结构体变量所占内存长度,可以认为是各成员占的内存长度的叠加;每个成员分
别占有其自己的内存单元。
(2)共用体变量所占的内存长度等于最长的成员的长度;几个成员共用一个内存区。
(1)格式:
union 共用体类型名称
{
数据类型 成员名 1;
数据类型 成员名 2;
…
数据类型 成员名 n;
};
(2)举例:
union data
{
int m;
float x;
char c;
};
上例中,union 命令定义了一个包含三个属性的数据类型 data。虽然包含三个属性,但
是同一时间只能取到一个属性。最后赋值的属性,就是可以取到值的那个属性。
( 1)方式 1:先定义共用体类型,再定义共用体变量
// 声明共用体类型
union data
{
short m;
float x;
char c;
};
// 声明共用体变量
union data a, b;
(2)方式 2:定义共用体类型的同时定义共用体变量
union data
{
short m;
float x;
char c;
} a, b;
3)方式 3:在定义时也可以不给出共用体名
union
{
short m;
float x;
char c;
} a, b;
以上一节方式 2 中共用体变量 a 为例,它由 3 个成员组成,分别是 m、x 和 c,系统会
按照最长的成员为它分配内存,由于成员 x 的长度最长,它占 4 个字节,所以共用体变量 a
的内存空间也为 4 个字节。
同结构体一样,共用体也使用点号.获取单个成员,可以进行赋值和取值。
//方式一:
union data a;
a.c = 4;
//方式二:声明共用体变量的同时,给任一成员赋值
union data a = {.c = 4};
方式三:声明共用体变量的同时,给首成员赋值
union data a = {8};
注意:方式 3 不指定成员名,所以只能为第一个成员进行赋值
现有一张关于学生信息和教师信息的表格。学生信息包括姓名、编号、性别、职业、
分数,教师的信息包括姓名、编号、性别、职业、教学科目:可以参考下面的表格。
请利用共用体,只使用一个结构体保存每个人的信息。
#include
#define TOTAL 2 // 人员总数
// 定义了一个结构体 Person
struct Person
{
char name[20]; // name
int num; // 编号
char sex; // 性别 f => 女 m=>男
char profession; // 职业 s=>学生 t=>老师
union
{
float score;
char course[20];
} sc; // sc 是一个共用体变量
};
int main()
{
int i;
struct Person persons[TOTAL]; // 定义了一个结构体数组
// 输入人员信息
for (i = 0; i < TOTAL; i++)
{
printf("Input info: ");
scanf("%s %d %c %c", persons[i].name, &(persons[i].num), &(persons[i].sex), &(persons[i].profession));
if (persons[i].profession == 's')
{
// 如果是学生
printf("请输入该学生成绩:");
scanf("%f", &persons[i].sc.score);
}
else
{
// 如果是老师
printf("请输入该老师课程:");
scanf("%s", persons[i].sc.course);
}
fflush(stdin); // 刷新
}
// 输出人员信息
printf("\nName\t\tNum\tSex\tProfession\tScore / Course\n");
for (i = 0; i < TOTAL; i++)
{
// 如果是学生
printf("%s\t\t%d\t%c\t%c\t\t%f\n", persons[i].name,persons[i].num, persons[i].sex, persons[i].profession,persons[i].sc.score);
}
else
{
// 如果是老师
printf("%s\t\t%d\t%c\t%c\t\t%s\n", persons[i].name,
persons[i].num, persons[i].sex, persons[i].profession,
persons[i].sc.course);
}
}
return 0;
}
C 语言允许为一个数据类型起一个新的别名,就像给人起“绰号”一样。
起别名的目的不是为了提高程序运行效率,而是为了编码方便。例如,有一个结构体的
名字是 student,定义一个结构体变量 stu1,代码如下:
struct student stu1;
struct 看起来就是多余的,但不写又会报错。如果为 struct student 起了一个别名 Student,
书写起来就简单了
Student stu1;
这种写法更加简练,意义也非常明确,不管是在标准头文件中还是以后的编程实践中,
都会大量使用这种别名。
用 typedef 声明数组类型、指针类型,结构体类型、共用体类型等,使得编程更加方便。
(1)为某个基本类型起别名
typedef 命令用来为某个类型起别名。
typedef 类型名 别名;
习惯上,常把用 typedef 声明的类型名的第 1 个字母用大写表示,以便与系统提供的标
准类型标识符相区别。
代码示例:
typedef int Integer;//用 Integer 作为 int 类型别名,作用与 int 相同
Integer a,b;
a=1;
b=2;
typedef unsigned char Byte;//为类型 unsign char 起别名 Byte
Byte c = 'z';
注意:使用 typedef 可以为基本类型一次起多个别名。
typedef int chocolate, doughnut, mushroom; //一次性为 int 类型起了三个别名
(2)为结构体、共用体起别名
为 struct、union 等命令定义的复杂数据结构创建别名,从而便于引用
struct Treenode
{
// ...
};
typedef struct Treenode Tree;//Tree 是 struct Treenode 的别名
typedef 也可以与 struct 定义结构体的命令写在一起。
typedef struct Treenode
{
char *name;
int legs;
int speed;
} Tree;
上例中,自定义数据类型时,同时使用 typedef 命令,为 struct Treenode 起了一个别名
Tree。
这种情况下,C 语言允许省略结构体名。
typedef struct
{
char *name;
int legs;
int speed;
} Tree;
上例相当于为一个匿名的结构体起了别名 Tree,进而:
// 使用 typedef 之前
struct Treenode tr;
// 使用 typedef 之后
Tree tr;
再举例:typedef 命令可以为共用体起别名。
typedef union
{
short count;
float weight;
float volume;
} Quantity;
上例中,union 命令定义了一个包含三个属性的数据类型,typedef 命令为它起别名为
Quantity。
(3)为指针起别名
typedef 可以为指针起别名。
typedef int *intptr;
int a = 10;
intptr x = &a;
上例中,intptr 是 int*的别名。不过,使用的时候要小心,这样不容易看出来,变量 x
是一个指针类型。
再举例:
typedef char *String;
char *str1 = "hello"; // 之前的写法
String str2 = "hello"; // 现在的写法
为字符指针起别名为 String,以后使用 String 声明变量时,就可以轻易辨别该变量是字
符串。
(4)为数组类型起别名
typedef 也可以用来为数组类型起别名。
/ 举例 1 给 int[5] 取别名 fiveInts
typedef int fiveInts[5];
fiveInts x = {11, 22, 33, 44, 55}; // 相当于 int x[5] = {11, 22, 33, 44,55};
// 举例 2 给 int[100] 取别名 Num
typedef int Num[100]; // 声明 Num 为整型数组类型名
Num a; // 相当于 int a[100];
举例,使用 typedef 为数组指针取别名。
// 为 int (*)[5] 取别名为 IntArrayPointer
typedef int (*IntArrayPointer)[5];
// 定义数组
int arr[5] = {1, 2, 3, 4, 5};
// 使用别名定义数组指针
IntArrayPointer ptr = &arr; // 使用别名指向数组
#include
// 给字符指针取别名
typedef char *String;
// 给结构体取别名
typedef struct
{
int id;
String name;
String profile;
} User;
int main()
{
// 声明一个结构体变量
User u;
// 初始化结构体变量的属性赋值
u.id = 101;
u.name = "张三";
u.profile = "一个纯粹的人,一个高尚的人,一个脱离了低级趣味的人。";
// 打印结构体变量的属性
printf("id:%d\n", u.id);
printf("name:%s\n", u.name);
printf("profile:%s\n", u.profile);
return 0;
}
id:101
name:张三
profile:一个纯粹的人,一个高尚的人,一个脱离了低级趣味的人。