Kodewick
第 25 课,共 30 课目录

typedef 和 enum

第 25 课,共 30 课 · 第 5 部分 · 自己的类型 · 约 15 分钟

前两课每次用结构体都要写 struct student,有点啰嗦。typedef 能给一个类型起个新名字,写起来短一些:

main.c
1#include <stdio.h>2 3typedef struct {4    char name[20];5    int score;6} Student;7 8int main(void) {9    Student s = { "小林", 95 };10    Student t = { "小周", 88 };11    printf("%s:%d\n", s.name, s.score);12    printf("%s:%d\n", t.name, t.score);13    return 0;14}
输出
小林:95
小周:88

Student 就是这个结构体的名字,前面不用再写 struct。这一课讲 typedef,还有另一样常和它一起用的东西:enum。

typedef 起别名

typedef 的写法是:typedef 原来的类型 新名字;。它不造出新的类型,只是给原来的类型多起一个名字,两个名字用起来完全一样:

main.c
1#include <stdio.h>2 3typedef int Minutes;4 5Minutes total(Minutes a, Minutes b) {6    return a + b;7}8 9int main(void) {10    Minutes run = 25;11    Minutes walk = 40;12    printf("一共 %d 分钟\n", total(run, walk));13    return 0;14}
输出
一共 65 分钟

Minutes 就是 int,显示时照样用 %d。好处是读代码的人一看就知道这个数是什么:是分钟,不是随便一个整数。你早就用过一个这样的名字:strlen 返回的 size_t,就是标准库用 typedef 起的名字,背后是一种不带负数的整数。

typedef struct

最常见的用法是给结构体起名字。开头的例子把定义和起名写在了一起;拆开写是这样,意思一样:

main.c
1#include <stdio.h>2 3struct point {4    int x;5    int y;6};7 8typedef struct point Point;9 10int main(void) {11    Point a = { 3, 4 };12    struct point b = { 1, 2 };13    printf("(%d, %d) 和 (%d, %d)\n", a.x, a.y, b.x, b.y);14    return 0;15}
输出
(3, 4) 和 (1, 2)

Point 和 struct point 是同一个类型,两种写法可以混着用。所以也常见这样一次写完,两个名字都有:

1typedef struct point {2    int x;3    int y;4} Point;

花括号前面的 point 可以不写(开头的例子就没写)。什么时候必须写?结构体里要用到它自己的时候,比如链表的节点要指向下一个节点。下一课就会用到。

enum:有名字的整数

一个订单的状态只有几种:待付款、已付款、已发货、已送达。可以用 0、1、2、3 表示,可过几天再看代码,status == 2 是什么意思就忘了。枚举(enum)给这几个数各起一个名字:

main.c
1#include <stdio.h>2 3enum status { PENDING, PAID, SHIPPED, DELIVERED };4 5int main(void) {6    enum status s = SHIPPED;7    printf("PENDING = %d\n", PENDING);8    printf("DELIVERED = %d\n", DELIVERED);9    printf("s = %d\n", s);10    if (s == SHIPPED) {11        printf("已经发货了\n");12    }13    return 0;14}
输出
PENDING = 0
DELIVERED = 3
s = 2
已经发货了

不写数的话,第一个名字是 0,后面一个比一个大 1。这些名字其实就是 int 常量:能比较、能显示成 %d。习惯上全用大写字母,一看就知道是常量。

自己定数

也可以给名字指定数。指定了的就用指定的,后面没指定的接着加 1。比如让星期一是 1:

main.c
1#include <stdio.h>2 3enum weekday { MON = 1, TUE, WED, THU, FRI, SAT, SUN };4 5int main(void) {6    enum weekday today = WED;7    printf("今天是一周的第 %d 天\n", today);8    printf("离周日还有 %d 天\n", SUN - today);9    return 0;10}
输出
今天是一周的第 3 天
离周日还有 4 天
先猜一猜

这段程序显示什么?

1#include <stdio.h>2 3enum level { LOW = 1, MIDDLE, HIGH = 10, TOP };4 5int main(void) {6    printf("%d %d\n", MIDDLE, TOP);7    return 0;8}

switch 配 enum

enum 和 switch 是天生一对:每种情况一个 case,名字比数清楚多了。下面的函数把状态变成一段文字:

main.c
1#include <stdio.h>2 3typedef enum { PENDING, PAID, SHIPPED, DELIVERED } Status;4 5void show_status(Status s) {6    switch (s) {7        case PENDING:8            printf("待付款\n");9            break;10        case PAID:11            printf("已付款\n");12            break;13        case SHIPPED:14            printf("已发货\n");15            break;16        case DELIVERED:17            printf("已送达\n");18            break;19    }20}21 22int main(void) {23    show_status(PAID);24    show_status(DELIVERED);25    return 0;26}
输出
已付款
已送达

typedef enum { ... } Status; 和 typedef struct 一样,给枚举起了个短名字,之后写 Status 就行。别忘了每个 case 后面的 break(第 5 课)。

放在一起用

结构体里可以有一个枚举成员。一组订单,每张有编号和状态,数一数还有几张没送到:

main.c
1#include <stdio.h>2 3typedef enum { PENDING, PAID, SHIPPED, DELIVERED } Status;4 5typedef struct {6    int id;7    Status status;8} Order;9 10int main(void) {11    Order orders[4] = { { 1001, DELIVERED }, { 1002, SHIPPED }, { 1003, PAID }, { 1004, DELIVERED } };12    int waiting = 0;13    for (int i = 0; i < 4; i++) {14        if (orders[i].status != DELIVERED) {15            printf("订单 %d 还没送到\n", orders[i].id);16            waiting++;17        }18    }19    printf("一共 %d 张\n", waiting);20    return 0;21}
输出
订单 1002 还没送到
订单 1003 还没送到
一共 2 张

点单步看看:每个 Order 是一个框,status 那一格里放的就是一个整数。

小技巧: C 不会检查枚举变量里放的是不是列出来的那几个数:Status s = 7; 照样能编译。所以 switch 里最好加一个 default,碰到意外的数也有个交代。

练一练

写好代码,点检查。提示随便看,实在想不出来再看答案。

练习 1

长方形

用 typedef struct 定义 Rect,成员是 width 和 height(都是 int)。写函数 area,参数是一个 Rect,返回面积。程序要显示 面积:24。

要显示
面积:24
exercise1.c
1#include <stdio.h>2 3// 在这里定义 Rect 和 area4 5int main(void) {6    Rect r = { 4, 6 };7    printf("面积:%d\n", area(r));8    return 0;9}
输出
点「运行」或按 Ctrl+Enter
练习 2

杯子的大小

写函数 price,参数是一个 Size,用 switch 返回价格:SMALL 是 12,MEDIUM 是 15,LARGE 是 18。程序要显示 12 15 18。

要显示
12 15 18
exercise2.c
1#include <stdio.h>2 3typedef enum { SMALL, MEDIUM, LARGE } Size;4 5int price(Size s) {6 7}8 9int main(void) {10    printf("%d %d %d\n", price(SMALL), price(MEDIUM), price(LARGE));11    return 0;12}
输出
点「运行」或按 Ctrl+Enter
练习 3

向右转

方向按顺时针排:北、东、南、西。写函数 turn_right,返回向右转之后的方向:北变东,……,西又变回北。程序从北开始转 5 次,每次显示新的方向,最后一行是 东。

要显示
东
南
西
北
东
exercise3.c
1#include <stdio.h>2 3typedef enum { NORTH, EAST, SOUTH, WEST } Direction;4 5Direction turn_right(Direction d) {6 7}8 9int main(void) {10    char names[4][8] = { "北", "东", "南", "西" };11    Direction d = NORTH;12    for (int i = 0; i < 5; i++) {13        d = turn_right(d);14        printf("%s\n", names[d]);15    }16    return 0;17}
输出
点「运行」或按 Ctrl+Enter

这一课学会了

  • typedef 原来的类型 新名字; 给类型起别名,不造新类型;size_t 就是这样来的。
  • typedef struct { ... } Student; 之后写 Student 就行,不用再写 struct;结构体要用到自己时,花括号前面要写名字。
  • enum 给一组整数起名字:默认从 0 开始,一个比一个大 1;也可以自己指定,后面的接着加 1。
  • 枚举的名字就是 int 常量,能比较、能用 %d 显示,习惯上全大写。
  • switch 配 enum,每种情况一个 case;C 不检查枚举里放的数,最好加 default。