| 目录 |

10 typedef:创建新类型

嗯,与其说是创建新类型,不如说是给已有类型取新名字。表面上看这似乎没什么意义,但我们确实可以用它让代码更整洁。

10.1 typedef 理论

基本上,你拿一个已有的类型,用 typedef 给它起个别名。

像这样:

typedef int antelope;  // 让 "antelope" 成为 "int" 的别名

antelope x = 10;       // 类型 "antelope" 与类型 "int" 相同

你可以对任何已有类型这么做。甚至可以用逗号列表一次创建多个类型:

typedef int antelope, bagel, mushroom;  // 这些都是 "int"

这真的很有用,对吧?你能打 mushroom 而不是 int?你肯定对这个特性超级兴奋吧!

好吧,讽刺教授——我们马上就会看到它更常见的用法。

10.1.1 作用域

typedef 遵循常规的作用域规则。

正因如此,把 typedef 放在文件作用域(“全局”)中相当常见,这样所有函数都能随意使用这些新类型。

10.2 typedef 实践

所以把 int 改名成别的,并没多令人兴奋。我们来看看 typedef 通常出现在哪些地方。

10.2.1 typedef 与 struct

有时会用一个新名字 typedef 一个 struct,这样你就不必一遍遍地敲 struct 这个词。

struct animal {
    char *name;
    int leg_count, speed;
};

//  原名              新名
//            |         |
//            v         v
//      |-----------| |----|
typedef struct animal animal;

struct animal y;  // 这样可以
animal z;         // 这样也可以,因为 "animal" 是别名

就我个人而言,我不太喜欢这种做法。我喜欢在类型前加上 struct 时,代码所具有的那份清晰感:程序员知道自己拿到的是什么。但这种做法真的很常见,所以我把它收在这里。

现在我想用你可能常见的方式,把完全相同的示例再走一遍。我们要把 struct animal 放进 typedef 里。你可以像这样把它们捏在一起:

//  原名
//            |
//            v
//      |-----------|
typedef struct animal {
    char *name;
    int leg_count, speed;
} animal;                         // <-- 新名称

struct animal y;  // 这样可以
animal z;         // 这样也可以,因为 "animal" 是别名

这和上一个示例完全相同,只是更简洁。

但还不止这些!你可能还会在代码里见到另一种常见简写,用的是所谓的匿名结构体82。事实证明,在许多地方你其实并不需要给结构体命名,而配合 typedef 就是其中之一。

我们用匿名结构体把同一个例子再做一遍:

//  匿名结构体!它没有名字!
//         |
//         v
//      |----|
typedef struct {
    char *name;
    int leg_count, speed;
} animal;                         // <-- 新名称

//struct animal y;  // 错误:这不再有效——没有这样的结构体!
animal z;           // 这样可以,因为 "animal" 是别名

再举个例子,我们可能会见到这样的东西:

typedef struct {
    int x, y;
} point;

point p = {.x=20, .y=40};

printf("%d, %d\n", p.x, p.y);  // 20, 40

10.2.2 typedef 与其他类型

把 typedef 用在 int 这样的简单类型上并不是完全没用……它帮你把类型抽象出来,以便日后更容易修改。

例如,如果你的代码里有一亿个地方都写着 float,而后来因为某些原因你发现必须把它们全改成 double,那可就痛苦了。

但如果你事先稍微准备一下,写成:

typedef float app_float;

// 以及

app_float f1, f2, f3;

那么之后如果你想换成别的类型,比如 long double,只需改一下 typedef:

//        大功告成!
//      |---------|
typedef long double app_float;

// 而且无需修改这一行:

app_float f1, f2, f3;  // 现在这些都是 long double

10.2.3 typedef 与指针

你可以创建一个本身是指针的类型。

typedef int *intptr;

int a = 10;
intptr x = &a;  // "intptr" 是 "int*" 类型

我真的很不喜欢这种做法。它掩盖了 x 是指针类型这一事实,因为你在声明里看不到 *。

依我看,最好明确表示出你声明的是指针类型,这样其他开发者一眼就能看出来,不会误以为 x 是非指针类型。

但据最近一次统计,大概有 832,007 个人持不同意见。

10.2.4 typedef 与大小写

我见过 typedef 上五花八门的大小写写法。

typedef struct {
    int x, y;
} my_point;          // 小写蛇形命名

typedef struct {
    int x, y;
} MyPoint;          // 驼峰命名

typedef struct {
    int x, y;
} Mypoint;          // 首字母大写

typedef struct {
    int x, y;
} MY_POINT;          // 全大写蛇形命名

C11 规范没有强行规定哪一种,示例里既有全大写也有全小写。

K&R2 以首字母大写为主,但也给出了一些全大写和蛇形命名(带 _t)的示例。

如果你手头有在用的风格指南,就照它来。如果没有,去找一份,然后照它来。

10.3 数组与 typedef

语法有点古怪,而且据我的经验很少见到,但你可以 typedef 一个包含若干元素的数组。

// 让 five_ints 类型成为含 5 个 int 的数组
typedef int five_ints[5];

five_ints x = {11, 22, 33, 44, 55};

我不喜欢它,因为它掩盖了变量的数组属性,但这样做是可行的。


| 目录 |