C 语言允许用户使用 typedef 关键字来定义自己习惯的数据类型名称,来替代系统默认的基本类型名称、数组类型名称、指针类型名称与用户自定义的结构型名称、共用型名称、枚举型名称等。
一旦用户在程序中定义了自己的数据类型名称,就可以在该程序中用自己的数据类型名称来定义变量的类型、数组的类型、指针变量的类型与函数的类型等。typedef 关键字定义的名称并不是真的创造了一种数据类型,而是给已有的或者复合型的以及复杂的数据类型取一个我们更容易理解识别的别名。
typedef 关键字怎么使用?又有哪些情况可以使用?接下来我们就对它的几种用法进行说明:
(1)基本数据类型定义
也就是说,我们使用 typedef 关键字对系统默认的基本数据类型名进行重新定义。例如我们使用标准整数的数据类型 uint8_t 和 uint16_t 等时,其实它们的原始定义如下:
typedef unsigned char uint8_t; //无符号8位整数
typedef signed char int8_t; //有符号8位整数
typedef unsigned short uint16_t; //无符号16位整数
typedef signed short int16_t; //有符号16位整数
typedef unsigned long uint32_t; //无符号32位整数
typedef signed long int32_t; //有符号32位整数
显然,使用 typedef 关键字给已有的数据类型定义了一个别名。当我们使用 uint8_t 时,就和使用 unsigned char 是一样的。此外,我们还可以使用这种方法来定义与平台无关的类型,这样,当跨平台移植程序时,我们只需要修改一下 typedef 的定义即可,而不用对其他源代码做任何修改。
(2)指针数据类型定义
有些时候我们也会使用 typedef 关键字定义指针数据类型,用来简化我们所使用的指针变量的声明,例如:
typedef int *pointer;
typedef int (*pointer)[N];
typedef int array[M][N];
上面的第1个例子中,我们定义了一个指针类型,当我们使用 pointer p 声明一个指针变量,就和使用 int *p 的含义是一样的。这个例子是一个指针变量类型,可以演化出很多用法,如定义数组对象可以更明确。有时候,我们也定义指向二维数组的指针,使用 typedef 关键字就可以使得操作更符合我们的常规认知。第2个例子中的 pointer 等价于 int (*)[N],当我们声明 pointer p 就相当于 int (*a)[N]。第3个例子的含义与指针是类似的,比如我们定义 array a 就是声明了一个 M 行 N 列的数组,此时 p = a 是可以的。
我们考虑到数组很多时候可以使用指针表示,所以将其放在一起讨论,而其他一些对象指针我们单独讨论。其实,不光是上述数组和指针可以使用 typedef 关键字定义别名,其他如:一维和多维数组,指针数组等都可以这样使用。
(3)用户对象类型定义
这里的用户对象是指结构体、联合体以及枚举等用户自定义的数据类型。我们可以使用 typedef 关键字给结构体等类型定义一个别名,这样我们可以在多个地方更方便地使用它。例如,使用 typedef 关键字为结构体类型定义一个别名:
typedef struct
{
float Kp; //比例系数
float Ki; //积分系数
float Kd; //微分系数
float Error; //本次误差
float LastError; //上一次误差
float Diff; //PID微分项
float Integral; //PID积分项
float MaxIntegral; //积分上限
float MinIntegral; //积分下限
float Output; //PID输出项
float MaxOutput; //输出上限
float MinOutput; //输出下限
} PID_TypeDef;
这样我们在需要使用结构体类型定义一个对象时,我们可以使用 PID_TypeDef 定义就可以与原始定义相同了,例如:
PID_TypeDef vPID;
同样我们也可以使用 typedef 关键字为枚举定义一个别名:
typedef enum
{
WRITE_REG = ((uint8_t)0x80), //写寄存器功能码
READ_REG = ((uint8_t)0x81), //读寄存器功能码
WRITE_VAR = ((uint8_t)0x82), //写变量功能码
READ_VAR = ((uint8_t)0x83) //读变量功能码
} FunctionCode;
我们需要定义枚举变量时,也是使用 FunctionCode fc 就可以了。当然,我们也可以使用定义的类型声明指针变量,或者直接有 typedef 关键字定义指向对象的指针类型,如定义结构体指针类型。
(4)函数类型定义
typedef 关键字也常用来定义函数类型,用以声明同类型的函数或者函数指针变量。这一种类型定义的函数用作形参等回调的方式使用时,非常有用,具体用法可参考回调函数的使用。
使用 typedef 关键字定义函数类型与前面的各种方式类似。例如:
typedef int fType(void);
这里 fType 等价于 int (void) 类型函数。如果我们使用 fType function 声明函数 function 时,与使用 int function(void) 来声明是一样的。
同样我们也可以使用 fType 来定义函数指针。如果我们使用 fType *fPointer 声明函数指针 fPointer 时,和使用 int (*fPoniter)(void) 来声明是一样的。
当然,我们也可以使用 typedef 关键字直接定义函数指针类型。例如:
typedef int (*fPointer)(void);
这样我们就可以使用 fPointer 来定义一个函数指针。比如我们使用 fPointer fp 声明函数指针就如同使用 int (*fp)(void) 来声明。
(5)typedef 关键字与宏定义
前面我们已经简要说了 typedef 关键字的基本用法,我们发现它的功能似乎宏定义也能实现,所以我们接下来看一下 typedef 关键字和 #define 定义的宏之间究竟有何不同。
从功能范围来讲,typedef 主要是为已经存在的关键字或者类型及其组合取一个我们容易识别的别名。在这一点上 #define 也可以实现,但除此之外 #define 还有其他用处,如果愿意的话你可以使用它定义任何代码,这是 typedef 所不具备的。
从执行时间来讲,对于 #define 定义的宏,其在预处理阶段就会被替换,而 typedef 定义的类型会在编译时处理。
从作用域上来讲,一般来说 #define 定义的宏没有作用域的限制,只要在使用前定义就可以,而 typedef 定义的别名是有作用域的。
从实现效果来讲,我们使用 typedef 定义一个指针类型,然后使用该类型可以同时声明多个变量,而 #define 却不是这样的。例如,typedef (int*) pType; 可以使用pType a, b; 这里 a 和 b 都是指向整数的指针变量。但我们同样定义 #define pType int*, 若使用 pType a, b; 定义 a 和 b,则 a 是指向整数的指针变量,而 b 不是。
还需要特别注意的是,虽然 typedef 并不真正影响对象的存储特性,但在语法上它还是一个存储类的关键字,就像 auto、extern、static 和 register 等关键字一样。因此,像下面这种声明方式是不可行的:
typedef static int INT_STATIC;
原因是不能声明多个存储类关键字,由于 typedef 已经占据了存储类关键字的位置,因此,在 typedef 声明中就不能够再使用 static 或任何其他存储类关键字了。
欢迎关注微信公众号『OpenSSR』