Typedef

在C和C++程式语言中，`'''typedef'''`是一个关键字。它用来对一个资料类型取一个别名，目的是为了使源代码更易于阅读和理解。它通常用于简化宣告复杂的类型组成的结构，但它也常常在各种长度的整数资料型别中看到，例如`size_t`和`time_t`。

## 语法

`typedef`的语法是 : `'''typedef''' ''typedeclaration'';`

创建 `Length` 作为 `int` 的别名 :

```c++
typedef int Length;
```

创建 `PFI` 作为一个指向“一个拥有两个 `char *` 当作参数并回传 `int` 的函式”的指标的别名

```c++
typedef int (*PFI)(char *, char *);
```

## 使用范例

### 创建一个具有特别意义的资料型别

`typedef`会被用来指定一种资料型别的意义。

例如 : 以下示范一个普通的宣告，速度及分数都被宣告为`int`

```c++
int current_speed ; 
int high_score ; 

void congratulate(int your_score) 
{ 
    if (your_score > high_score) 
        ...
```

通过`typedef`来指定新的资料型别的意义:

```c++
typedef int km_per_hour ;
typedef int points ;

km_per_hour current_speed ;  //"km_per_hour" is synonymous with "int" here,
points high_score ;          //and thus, the compiler treats our new variables as integers.

void congratulate(points your_score) {
    if (your_score > high_score)
        ...
```

前面两段程式码运作状况一样，但是使用`typedef`的第二段程式码更清楚的表示了两个变数(score和speed)，虽然资料型别都是`int`，却是各自代表不同的意义，且他们的资料并不相容。

但请注意，其清楚的表示不同意义只是对于工程师而言，C/C++的编译器认为两个变数都是`int`时，并不会显示警告或错误，如: 以下程式码，使用宣告为速度的变数作为`congratulate`函式的参数 :

```c++
void foo() 
{ 
    km_per_hour km100 = 100; 
    congratulate(km100);
```

但是，虽然在上面的程式码中，编译器认为"km_per_hour"等于`int`，但在使用前缀 `unsigned`, `long`, `signed`时，两者并不能互换使用。

```c++
void foo() {
    unsigned int a;         // Okay
    unsigned km_per_hour b; // Compiler complains
    long int c;             // Okay
    long km_per_hour d;     // Compiler complains
```

另一个例子：

```c
int coxes;
int jaffa;
...
coxes++;
...
if (jaffa == 10)
...
```

现在来看以下程式码：

```c
typedef int Apple;
typedef int Orange;
Apple coxes;
Orange jaffa;
...
coxes++;
...
if (jaffa == 10)
...
```

这两段程式码都做同样的一件事。第二个例子使用了 `typedef`，使其更易于了解将要进行什么。也就是一个变数包含关于苹果的资讯，而另一个包含关于橘子的资讯。

### 简化宣告语法

```c
struct var {
    int data1;
    int data2;
    char data3;
};
```

此处使用者定义一个资料类型 `var`。

像这样建立一个 `var` 类型的变数，程式码必须写为（注意，在 C++ 中宣告一个 `struct` 时，同时也隐含了 `typedef`，C 则没有）：

```c
struct var a;
```

在例子的最末处加入一行语句：

```c
typedef struct var newtype;
```

现在要建立类型 `var` 的变数时，程式码可以写为：

```c
newtype a;
```

这样就更容易阅读了，因为不用再为每一个 var 类型的变数加上关键字 `struct`。

也可以给阵列使用 `typedef` 宣告。

```c++
typedef BaseType NewType [arrSize];
```

这样就可以在宣告一个 `BaseType` 类型和 `arrSize` 大小的新阵列时，将程式码写为：

```c++
NewType array;
```

### 与阵列一起使用

`typedef`可以简单的跟阵列一起使用。例如 :

```c++
typedef char arrType[6];    // type name: arrType
                            // new type: char[6]

arrType arr={1,2,3,4,5,6};  // same as: char arr[6]={1,2,3,4,5,6}

arrType *pArr;              // same as: char (*pArr)[6];
```

在这里，`arrType`是`char[6]`的别称。而`arrType *pArr;`则表示`pArr`是一个指向储存`char[6]`型别内存的指标。

### 与指标一起使用

可以使用typedef来定义一个新的指标型别 :

```c++
typedef int *intptr;   // type name: intptr
                       // new type: int*

intptr ptr;            // same as: int *ptr
```

在上面那段程式码中，`intptr`是一个 指标型态`int*`的 新的别名。`intptr ptr;`宣告了一个变数(`ptr`)，其资料型别是`int*`。如此一来`ptr`就是一个 可以指向一段储存`int`资料的内存 的指标了。

使用`typedef`来定义一个新的指标型别有时候会造成一些困惑 :

```c++
typedef int *intptr;

intptr cliff, allen;        // both cliff and allen are int* type

intptr cliff2, *allen2;     // cliff2 is int* type, but allen2 is int** type
                            // same as: intptr cliff2;
                            //          intptr *allen2;
```

在上面的程式码中,`ntptr cliff, allen;`表示宣告两个变数，其资料型别是`int*`，而`intptr *allan2`则使`allen2`的型别成为`int**`

### 与结构指标一起使用

`Typedef`可以跟结构体指标一起使用。如下 :

```c++
struct Node {
    int data;
    struct Node *nextptr;
};
```

使用`typedef`可以改写成如下 :

```c++
typedef struct Node Node;
struct Node {
    int data;
    Node *nextptr;
};
```

在C语言中，可以在一行中宣告复数的变数，不管其是不是指标。不管如何，如果你要宣告指标，必须在每个变数前面加上星号。

在下面的程式码中，工程师可能会以为`errptr`是一个指标，这个可能会引起一些错误。

```c++
struct Node *startptr, *endptr, *curptr, *prevptr, errptr, *refptr;
```

如果你用`typedef`定义一个`Node *`，这可以保证所有的变数都是一个指向一个`structure type`的指标。

```c++
typedef struct Node* NodePtr;
...
NodePtr startptr, endptr, curptr, prevptr, errptr, refptr;
```

### 与函式指标一起使用

先看看以下这段尚未使用`typedef`的程式码：

```c++
int do_math(float arg1, int arg2) {
    return arg2;
}

int call_a_func(int (*call_this)(float, int)) {
    int output = call_this(5.5, 7);
    return output;
}

int final_result = call_a_func(&do_math);
```

这段程式码可以被改写成如下：

```c++
typedef int (*MathFunc)(float, int);

int do_math(float arg1, int arg2) {
    return arg2;
}

int call_a_func(MathFunc call_this) {
    int output = call_this(5.5, 7);
    return output;
}

int final_result = call_a_func(&do_math);
```

在这里，`MathFunc`是一个指标，指向一个回传`int`并以一个`float`和一个`int`作为参数使用的函式。当一个函式被当作参数使用时，如果少了`typedef`它可能会变得难以了解。

以下是`signal(3)`(来自FreeBSD)的函数原型：

```c++
void (*signal(int sig, void (*func)(int)))(int);
```

上面宣告的函式相当的神秘，因为它没有清楚的显示它以什么函式当作参数，或回传了什么资料型别。一个初心者工程师甚至可能以为它接收一个`int`作为参数，并不回传任何东西。但它其实接收了一个`int`和一个`function pointer`作为参数，并回传了一个`function pointer`。它可以被改写成以下程式码：

```c++
typedef void (*sighandler_t)(int);
sighandler_t signal(int sig, sighandler_t func);
```

### 用来型别转换

`typedef`同时可以用来类型转换。例如：

```c++
typedef int (*funcptr)(double);         // pointer to function taking a double returning int
funcptr x = (funcptr) NULL;             // C or C++
funcptr y = funcptr(NULL);              // C++ only
funcptr z = static_cast<funcptr>(NULL); // C++ only
```

左侧，`funcptr`用来宣告变数；右侧，`funcptr`则用来转换值的型态。

如果少了`typedef`，替换使用宣告语法和型别转换语法是几乎不能做到的。例如：

```c++
void *p = NULL;
int (*x)(double)  = (int (*)(double)) p; // This is legal
int (*)(double) y = (int (*)(double)) p; // Left-hand side is not legal
int (*z)(double)  = (int (*p)(double));  // Right-hand side is not legal
```

## 参照

- 抽象资料型别
- C语言语法