六、枚举/结构/类型

枚举

enum

使用enum结构来定义一个枚举,下标由0开始
enum COLOR {RED,YELLOW,GREEN};
上述定义了一个名叫COLOR的枚举,其中RED=0,YELLOW=1.GREEN=2

枚举是一种用户定义的数据类型,它用关键字enum来声明
其语法为:
enum <枚举类型名字> {<名字0>,<名字1>…<名字n>} ;

比较重要的是大括号中的名字,它们实际上就是常量符号,类型为int,下标值从0到n

枚举量可以作为值,但是实际上是以整数来做内部计算和外部输入输出的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum color{red,yellow,green} ;

void f(enum color c);

int main(void){
enum color t = red ;
scanf("%d" , &t) ;
f(t);

return 0 ;
}

void f(enum color c){
printf("%d \n" , c) ;
}

套路:计数

可以在枚举的最后采用一个NumXXX来辅助计数

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
```enum COLOR {RED,YELLOW,GREEN,NumColor} ; ```
这里NumColor = 3 ;

#### 指定值
我们可以指定枚举对应的int,比如:
```enum COLOR {RED = 1 , YELLOW , GREEN = 5};```
1.没有声明时,改位默认位前位+1
2.数值可以是离散的
比如此时RED=1,没有指定YELLOW的值,则YELLOW=2
而指定了GREEN = 5 ,则其是不连续的,GREEN = 5而非3

### 小结
现实中,其实更多把**C**里面的枚举当作辅助定义的符号量
当作类型使用的时候反而比较少见
比如有意义上排比的名字,可以用枚举来定义```const int```
枚举比宏(macro)好,枚举有int类型

# 结构

## 结构类型
一个结构是一种复合的数据类型,用许多成员表达**一个变量**
然后就可以只用该变量来表示这些合在一起的数据

### 声明struct
```c
struct date{
int month ;
int day ;
int year ;
};

struct date = today ;

通常在函数外部声明结构类型,这样它就可以被多个函数所使用

和本地变量一样,如果在函数内部声明,该结构类型就只能在函数内部使用

声明结构的形式

A:

1
2
3
4
5
6
struct point{
int x ;
int y ;
};

struct point p1,p2 ;

此时p1和p2都是point,里面有x和y的值

B:

1
2
3
4
struct {
int x ;
int y ;
} p1,p2 ;

p1,p2都是无名结构,里面有x和y
一般用于暂时使用

C:

1
2
3
4
5
struct point{
int x ;
int y ;
}p1,p2 ;

这里p1和p2都是point,里面有x和y的值

对于第一种和第三种形式,都声明了结构point
第二种形式没有声明point,只是定义了p1,p2两个变量

结构变量的使用

在声明结构类型之后可以声明许多属于该类型下的结构变量

结构初始化

有两种办法来赋值
首先是直接给出对应的n个值

1
2
3
4
5
6
7
struct date {
iint month ;
int day ;
int year ;
};

struct date today(01,03,2020) ;

或者具体指明数值
struct date thisMonth = {.month = 7 , .year = 2012}
没给的值就是0

结构成员

使用.运算符来访问成员

1
2
3
4
5
6

可以使用结构变量的名字来访问整个结构
对于整个结构,可以做赋值、取地址,可以传递给函数参数
```c
pl = (struct point){5,10} ; //相当于pl.x = 5 ; pl.y = 10 ;
p1 = p2 ; //相当于p1.1 = p2.x ; p1.y = p2.y ;

和指针/数组不一样,做p1=p2时,二者是得到了不同的位置,是把p2的值,赋给了p1,而非让p1”指向了”p2的值。二者是独立的

结构指针

和数组不同,结构变量的名字并不是结构变量的地址。
因此表示某种结构变量的地址,需要&运算符

结构与函数

结构作用函数参数 int numberOfDay(struct date d)

整个结构可以作为参数的值传入函数
此时是在函数内部新建了一个结构变量,然后去复制调用者的结构的值(本质上传入的还是值)
(也就是说它是一个完全不同的、新的结构变量,这和数组是不同的)

同理,我们也可以返回一个结构

另外,使用scanf的时候不能一次性scanf来读入结构,得一步步来
当我们想要利用函数来返回一个结构体的时候,有两种选择:
一,新建一个结构体,接受值。在函数返回时返回该结构体,并在需要它的地方直接使用p1 = p2
二,利用指针。这是更推荐的方式,因为它消耗的时间和空间都更小(见后文)

指向结构的指针

有->运算符表示指针所指的结构变量中的成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct date{
int month ;
int day ;
int year ;
}myday ;

struct date *p = &myday ; //这里表示p指针取得myday对应的地址

(*p).month = 12 ;
//p是指针,(*p)表示的则是这个结构体,因此可以直接利用 (*p).month来访问结构成员


p->month = 12 ;
//p是指针,->代表p指针所代表的那个结构体中的对应的成员
//->month即该指针对应的结构体中的month成员

->:Arrow , abc所指的xxx

.运算符的操作差不多,可以看作专门用于指针的dot

结构中的结构

结构数组

同其他变量一样,一旦我们声明了某种结构类型,也可以做出其相应的数组

1
2
3
4
struct date dates[100] ; 
struct date dates[] = {
{4,5,2002},{12,3,2021}
};

结构的结构

类似的,也可以把结构加入结构中

1
2
3
4
struct dateAndTime {
struct date sdate ;
struct time stime ;
};

结构中的结构的成员的访问是和单层结构相同的
也是使用.来进行逐渐访问

若有变量定义

1
2
struct rectangle r , *rp ; 
rp = &r;

那么以下的四种形式表达的是一样的

1
2
3
4
r.pt1.x
rp->pt1.x
(e.pt1).x
(rp->rt1).x

注意,不能使用ep->pt1->x,因为这里的pt1并不是指针,而是结构体

类型定义

自定义数据类型

C语言提供了一个叫typedef的功能来声明一个已有的数据类型的新名字
比如typedef int Length;
这样可以使得Lenghth成为int类型的别名
在此之后,Length就可以代替int出现在变量定义和参数声明的地方,如:

1
2
Length a,b,len ; 
Length numbers[10];

新的名字是某种类型的别名,以此改善了程序的可读性
typedef <原类型> <新名字>

同样的,我们可以利用typeof操作,来为自己创建的结构来定义一个名字,这样就不用每次都打上struct xxx来使用了

1
2
3
4
5
typedef struct ADATEexpale {
int month ;
int day ;
int year ;
} Date ;

在之后即可直接写为Date d = {9,1,2005} ;

无论如何,最后的名字才是其别名
typedef *char[10] Strings ;
这里Strings就代表一个十个字符串(*char[10])的类型

联合

(该部分在目前的学习阶段的情况下很少被用到)

定义

存储
所有的成员共享一个空间
同一时间只有一个成员是有效的
union的大小是其最大成员

初始化
对第一个成员做初始化

1
2
3
4
5
6
7
8
union AnElt {
int i ;
char c ;
}elt1 , elt2 ;

elt1.i = 4 ;
elt2. = 'a' ;
elt2.i = 0xEDA23 ;

这时候可以选择那个成员是int i还是 char c 而它们是共用一块内存的