枚举、结构体与联合
枚举类型、结构体定义与内存对齐、联合体的使用
枚举 Enum
枚举: 关键字:
enum(enumeration) 用法: enum 枚举类型名 {名字 0, 名字 1 ...., 名字 n}; 注意: 枚举类型名通常不使用,用的是大括号内的名字,它们就是常量符号,类型是int,值依次从 0 到 n
如:
enum color {
red, yellow, blue
};注意:
- 大括号内每个常量符号以 逗号 间隔
- 大括号后有 分号
用法示例:
#include<stdio.h>
enum color {
red, yellow, blue
};
void f(enum color x) {
printf("%d\n", x);
}
int main(void) {
// 变量 t 的类型是 enum color
enum color t = yellow;
scanf("%d", &t);//可以当作整数输入输出
f(t);
return 0;
}enum color作为变量类型应该写完整- 变量 t 虽然是 enum color 类型,但可以当作整型来进行内部计算和外部输入输出。
自动计数的枚举
enum color {
red, yellow, blue, NumColors
};原理:像上面这样,在 枚举 的所有元素后增加一个 Num<内容> 表示 枚举 元素的总数 比如上例: red 值为 0
blue 值为 2 那么 NumColors 值为 3 NumColors 其实 表示的就是这个枚举的总个数 好处: 定义数组大小 和 遍历数组的时候就很方便
示例:
enum color {
red, yellow, blue, NumColors
};
int main(void) {
char* ColorNames[NumColors] = {
"red", "yellow", "blue",
};
char* colorName = NULL;
int color = 0;
printf("输入你喜欢的颜色所对应的代码:\n");
scanf("%d", &color);
if (color >= 0 && color < NumColors)
colorName = ColorNames[color];
else
colorName = "Unknowe";
printf("%s\n", colorName);
return 0;
}枚举量
声明枚举量的时候可以指定值 如:
enum COLOR {RED = 1,YELLOW , GREEN = 5 };
例如:
enum COLOR { RED = 1, YELLOW, GREEN = 5, NumColors, };
int main(void) {
printf("%d\n%d", YELLOW, NumColors);
return 0;
}输出:
2
6
枚举是不是 int 类型?
enum color {
red = 1, greem, yellow
};
int main(void) {
enum color t = 0;
printf("%d\n", t);
return 0;
}如果我们让编译器将 int 类型的 0 赋给 enum color 类型的 t
编译器不但不会报错,而且连 warning 都没有
但是我们知道 enum color 其实是一个我们定义出来的新的类型
可能现在你对此理解不深刻,等你学了 C++/Java 知道了 类与对象,你就明白了
因此,我们总结出:
枚举的特点
- 枚举 的类型很少使用
- 某种情况下,用 枚举 比用 很多
const int方便 - 枚举 比 宏 好,因为它有 int 类型
结构 Struct
写在前面: 想理解结构体,不去多看代码,不去自己写是不可能的。
结构和数组,函数,指针混在一起就导致代码可能很长很乱。这没办法,一定要熬过去,要知道这才是 C语言的精髓 ,也是C++的基础。这里我们用的代码还不是很难。希望大家一定要攻克这里。实在想不通了可以后台私信我或者加入QQ群询问。
我写这篇文章看多了这些代码也有点头晕,为了小伙伴们,我也是拼了。
加油。

认识结构体
声明与使用
struct date {
int year;
int month;
int day;
};
int main(void) {
struct date today;
today.year = 2020;
today.month = 2;
today.day = 9;
printf("%d年 - %d月 - %d日\n", today.year, today.month, today.day);
return 0;
}声明的三种方式
新手法
struct point {
int x;
int y;
};
struct point p1, p2;狠人法
struct {
int x;
int y;
}p1, p2;
//用这种方式后面就无法继续声明 struct 变量了,struct绝种了常用方法
struct date {
int x;
int y;
}p1, p2;从 数组 看 结构体
结构体的初始化
struct date {
int year;
int month;
int day;
};
int main(void) {
struct date today = { 2020, 2, 9 };
struct date tomorrow = { .month = 2, .day = 9 };
printf("%d - %d - %d\n", today.year, today.month, today.day);
printf("%d - %d - %d\n", tomorrow.year, tomorrow.month, tomorrow.day);
return 0;
}输出:
2020 - 2 - 9
0 - 2 - 9可以看到,有两种初始化方法:
当你要初始化 struct 内的所有成员时:
1.struct date today = { 2020, 2, 9 };
当你只想给 struct 内的某几个成员赋值时:
2.struct date tomorrow = { .month = 2, .day = 9 };
没有被赋值的成员被初始化为 0(类似数组)
结构运算
struct date {
int year;
int month;
int day;
}p1, p2;
int main(void) {
p1 = (struct date){ 2020, 2, 9 };
p2 = (struct date){ 1010, 1, 5 };
p2 = p1;
printf("%d - %d - %d\n", p2.year, p2.month, p2.day);
return 0;
}输出:
2020 - 2 -9p1 = (struct date){2020, 2, 9}
等价于 p1.year = 2020,p1.month = 2,p1.day = 9;
p1 = p2
等价于 p1.year = p2.year ....
数组是无法无法做这两种运算的
指针: 结构体需要取地址
struct date today;
struct date* ptoday = &today;数组名本身就是地址,所以数组可以不用 & 符号
结构体作为函数参数
明天的日期
写一个程序:输入今天的日期,求明天的日期
看着这个问题简单,实际上稍微有点技巧.
给出下面四个特殊日期,大家可以思考一下:
2020 - 1 -31 2020 - 11 - 31 2020 - 12 - 31 2000 - 2 - 28
目测下面这个代码会被我这种喜欢空行的选手写的将近100行, 放在文中会影响阅读, 辛苦一下大家,**公众号后台回复 [0209] **获取代码 附:我觉得这个代码不难,不会的加Q群问吧,Q群关注我的公众号即可看到
结构指针作为参数
K & R 说过 (p.131) "if a large structure is to be passed to a function,in is generally more efficent to pass a pointer than to copy the whole structure" 大的结构体指针传参更为高效
指针所指的结构变量的成员访问
struct date {
int month;
int day;
int year;
}myday;
int main(void) {
struct date* p = &myday;
//两种访问指针结构体变量成员方式
(*p).month = 12;
p->month = 12;
return 0;
}通常我们用第二种方法,即:
p->month读作 p 所指的 month (英文读作 arrow 箭头)
来实操一下吧,请听题:
问题描述: 我们 输入整型,浮点型可以直接用
scanf("%d")或者scanf("%lf")那么我们可以直接像这样输入一个结构体吗? 答案是我们需要自己写一个函数。 下面程序提供一个思路,可以自己改善/创造你的函数
struct point {
int month;
int day;
int year;
};
struct point* getStruct(struct point* p);//输入结构体函数
void outputStruct(struct point p);//输出结构体函数
void printStruct(const struct point* p);//输出结构体函数
int main(void) {
struct point y = { 0, 0, 0 };
*getStruct(&y);
// *getStruct(&y) = (struct point){ 0, 0, 0 };
outputStruct(y);
outputStruct(*getStruct(&y));
printStruct(&y);
return 0;
}
struct point* getStruct(struct point* p) {
printf("Input a date\n");
scanf("%d", &p->month);
scanf("%d", &p->day);
scanf("%d", &p->year);
return p;
}
void outputStruct(struct point p) {
printf("outputStruct:\n");
printf("%d - %d - %d\n", p.month, p.day, p.year);
}
void printStruct(const struct point* p) {
printf("printStruct:\n");
printf("%d - %d - %d\n", p->month, p->day, p->year);
}
结构数组
初始化方法
struct date {
int month;
int day;
int year;
};
struct date dates[100];
struct date datess[2] = { {2, 9, 2020}, {2, 10, 2020} };问题描述: 用一个结构数组记录 5 组时间,请求出这组时间下一秒的时间。 和上面年的问题一样,需要考虑进制问题。请思考一下,尝试自己写出。
struct time {
int hour;
int minute;
int second;
};
struct time* timeUpdate(struct time* p) {
if (p->second != 59) {
p->second++;
}
else if (p->minute != 59) {
p->second = 0;
p->minute++;
}
else if(p->hour != 23){
p->second = 0;
p->minute = 0;
p->hour++;
}
else {
p->second = 0;
p->minute = 0;
p->hour = 0;
}
return p;
}
int main(void) {
struct time testTimes[5] = {
{11, 59, 59}, {12, 0, 0}, {1, 29, 59}, {23, 59, 59}, {19, 12, 27}
};
int i = 0;
for (i = 0; i < 5; i++) {
printf("Now the time is:%d - %d - %d\n",
testTimes[i].hour, testTimes[i].minute, testTimes[i].second);
testTimes[i] = *timeUpdate(&testTimes[i]);
printf("one second letter:%d - %d - %d\n",
testTimes[i].hour, testTimes[i].minute, testTimes[i].second);
}
return 0;
}嵌套的结构
嵌套的结构引入
//这个结构体表示 二维坐标系中点的坐标
struct point {
int x;
int y;
};
//这个结构体表示一个矩形 (两点确定一个矩形)
struct rectangle {
struct point p1;
struct point p2;
};
int main(void) {
struct rectangle r;
r.p1.x;
r.p2.y;
return 0;
}嵌套的结构的成员访问
struct point {
int x;
int y;
};
struct rectangle {
struct point p1;
struct point p2;
struct point* p3;
};
int main(void) {
struct rectangle r, *pr;
r.p1.x;
(r.p1).x;
pr->p1.x;
(pr->p1).x;
//为什么访问 point 不需要用 -> 呢?
//因为在 rectangle 中 point 并不是指针结构体
pr->p3->x;
return 0;
}结构中有结构的数组

struct point {
int x;
int y;
};
struct rectangle {
struct point p1;
struct point p2;
};
void printStruct(struct rectangle* p, int len) {
int i = 0;
for (i = 0; i < len; i++) {
printf("(%d, %d) (%d, %d)\n", p[i].p1.x, p[i].p1.y, p[i].p2.x, p[i].p2.y);
}
}
int main(void) {
struct rectangle rects[] = {
{{2, 2}, {4, 4} },
{{5, 6}, {7, 8} }
};
int len = sizeof(rects) / sizeof(rects[0]);
printStruct(rects, len);
return 0;
}测试一下: 1.有下列代码段,则输出结果为:
struct {
int x, y;
} s[2] = {
{1, 3},
{2, 7}
};
printf("%d\n",s[0].y / s[1].x);A: 0 B: 1 C: 2 D: 3
2.有如下变量定义,则对data中的a的正确引用是:
struct sk {
int a;
float b;
}data , *p = & data;A: (*p).data.a; B: (*p).a; C: p->data.a; D: p.data.a;
3.以下两行代码是否出现在一起?
struct { int x; int y; }x;
struct { int x; int y; }y;A: √ B: ×
公众号回复[0209 2]获取答案。
关于结构体的内存对齐等问题我们以后再说
联合
typedef 关键字
自定义类型
关键字:
typedef
例如:
typedef long int64_t;
typedef struct Adate {
int month;
int day;
int year;
}date;
int main(void) {
int64_t i = (int64_t)1e+10;
date d = { 2, 10, 2020 };
return 0;
}- 新的名字是某种类型的别名
- 改善了程序的可读性
请看下面这两段代码,你能分别出他们的意思是什么吗?
struct {
int month;
int day;
int year;
}Date;typedef struct {
int month;
int day;
int year;
}Date;第一个表示:一个没有名字的结构体类型 它有一个结构体变量 Date 第二个表示:将这个结构体类型重定义为 Date 如果你用你的编译器敲一下这段代码,会发现这两段代码的 Date 的颜色是不一样的
认识 联合
特点:
- 所有成员共享一个空间
- 同一时间只有一个成员是有效的
- union的大小是其最大的成员
怎么去理解?请看下面程序,以32位机器为例
union AnEit {
int i;
char c;
}elt1, elt2;
int main(void) {
elt1.i = 4;
elt2.c = 'a';
elt2.i = 0xDEADBEEF;
//程序运行到这里,union 所占内存中存储的内容如下:
//1. 可以看出 union 的大小为 4 个字节(int 的大小)
//2. elt2 中的 char c 已经被后面赋值 的int i所覆盖
return 0;
}
union 的常用场景
先看一下这段程序:
typedef union {
int i;
char ch[sizeof(int)];
}CHI;
int main(void) {
CHI chi;
int i;
chi.i = 1234;
for (i = 0; i < sizeof(int); i++) {
printf("%02hhx", chi.ch[i]);
//"%02x",是以0补齐2位数,如果超过2位就显示实际的数;
//"%hhx" 是只输出2位数,即便超了,也只显示低两位
}
printf("\n");
return 0;
}它的输出是:
d2040000这说明了chi的内存存储情况:
我们用计算机看一下 16 进制的 1234 是什么样子:
chi的大小是 4个字节,我们可以表示 chi.i 为
00 00 04 D2
这种 高位(d2) 放在 低地址; 低位 (00)放在 高地址的存储模式叫做 小端 我们现在用的 x86 的机器 基本都是这种存储方式。
我们可以利用 union 得到一个 int 或者 double 等等的内部的字节 这是一个有用的工具。当我们讲到文件时候,大家就对它的功能有更熟悉的理解了。