枚举 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 -9

p1 = (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 的内存

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的内存存储情况:
chi的内存
我们用计算机看一下 16 进制的 1234 是什么样子:
16 进制的 1234
chi的大小是 4个字节,我们可以表示 chi.i 为
00 00 04 D2

这种 高位(d2) 放在 低地址; 低位 (00)放在 高地址的存储模式叫做 小端
我们现在用的 x86 的机器 基本都是这种存储方式。

我们可以利用 union 得到一个 int 或者 double 等等的内部的字节
这是一个有用的工具。当我们讲到文件时候,大家就对它的功能有更熟悉的理解了。