首页 > 编程笔记 > C语言笔记 阅读:2

typedef在C语言中的用法(非常详细,附带实例)

在 C语言中,typedef 是一个关键字,用于为类型定义新的别名。为类型起别名的意义何在呢?

我们知道,C语言标准并未规定整型数据类型的大小范围,而是将具体实现交由编译器和平台决定。即 int 在 Visual Studio 平台中占用 4 字节,数据取值为 -2147483648~2147483647。然而,在另一个平台上,int 可能仅占用 2 字节,数据取值为 -32768~32767。

为了确保程序在不同平台上都能正确运行,不会因整型数据取值范围的差异导致错误,我们可以为整型定义一些别名。

下表展示了在 Visual Studio 平台上为整型定义的别名:

表:在 Visual Studio 平台上为整型定义的别名
整型类型 空间大小 别名
int 4 int32_t
short 2 int16_t
char 1 int8_t

下表展示了在其他平台上为整型定义的别名:

表:在其他平台上为整型定义的别名
整型类型 空间大小 别名
long 4 int32_t
int 2 int16_t
char 1 int8_t

别名 int32_t 表示占用 32 位二进制数、4 字节的整型。在 Visual Studio 平台中,int 类型占用 4 字节,因此别名 int32_t 对应的类型为 int。在另一个平台中,int 类型仅占用 2 字节,long 占用 4 字节。因此,为了保持大小一致,在另一个平台中,别名 int32_t 对应的类型为 long。

通过将整型类型用别名替代,我们在不同平台上进行编译时仅需更改别名对应的实际类型,就可以避免由于不同平台上整型数据取值范围的差异而导致的数据溢出问题。

C语言typedef关键字的概念

C语言提供了 typedef 关键字,用于定义类型别名。typedef 的语法格式如下:
typedef 原有类型名 新的类型名;

例如,定义 int 的别名为 int32_t。
typedef int int32_t;
需要注意的是,这个别名的命名规则同样需要遵循标识符命名规则:只能使用字母、数字、下画线,且首字符不能是数字。

下面程序展示了一个使用别名的完整示例:
#include <stdio.h>
int main()
{
   typedef int int32_t;
   int32_t n = 123;
   printf("n = %d\n", n);
   return 0;
}
运行结果为:

n = 123

我们可以像使用普通类型一样使用类型别名。

如果别名定义在代码块中,则具有块作用域。别名的作用域从声明开始,直至包含声明的代码块结束。查看下图,函数 add() 不在别名 int32_t 的作用域内,因此在函数 add() 中无法使用别名 int32_t。


图 1 块作用域

如果别名定义在代码块外,则具有文件作用域。查看下图,别名的作用域从声明开始,直至该源文件结束,在整个作用域内均可使用别名 int32_t。


图 2 文件作用域

C语言typedef和struct的关系

在 C语言中,typedef 经常用于为 struct 关键字定义别名,使得使用结构体变量更加方便和易读。

在定义结构体类型时,我们可以使用 typedef 将结构体与新类型名绑定在一起,进而定义一个新的结构体类型别名。例如:
typedef struct{
    char name[20];
    int gender;
    double height;
    double weight;
} Person;
这个例子使用 typedef 关键字定义了一个新的结构体类型别名 Person。通过这样的方式,我们可以使用 Person 作为一个新的类型名来定义结构体变量,并且声明结构体变量无须使用关键字 struct。
Person p = {"timmy", 1, 170.00, 60.00};
下面展示了完整的代码:
#include <stdio.h>
typedef struct {
    char name[20];
    int gender;
    double height;
    double weight;
} Person;
int main()
{
    Person p = {"timmy", 1, 170.00, 60.00};  // 无须关键字 struct
    printf("name:%s\n", p.name);
    printf("gender:%d\n", p.gender);
    printf("height:%.2f\n", p.height);
    printf("weight:%.2f\n", p.weight);
    return 0;
}
运行结果如下图所示:
name:timmy
gender:1
height:170.00
weight:60.00
使用 typedef 时要注意,typedef 并没有创建任何新类型,它只是为某个已存在的类型增加了一个方便使用的别名。

C语言typedef与#define的区别

在 C语言中,typedef 和 #define 都是用于定义新的类型名或常量的关键字,但是它们的作用和使用方式有所不同。

1) 类型定义

typedef 用于给已有类型定义新的别名,从而创建一个新的类型名。例如:
typedef int Int32;
#define 用于定义预处理宏,它可以将一个标识符定义为某个常量或表达式,例如:
#define PI 3.1415926
注意,#define 可以为值设置一个别名,而 typedef 只能用于给类型取别名。

2) 处理方式

#define 是由预处理器处理的,并且可以修改替换代码。代码经由预处理器处理后,再交由编译器进行编译。

typedef 是由编译器进行解释的,它不受预处理影响,在编译时直接由编译器处理。

C语言typedef提高整型可移植性

前面已经讨论了如何保证整型在不同平台上都能保证数值范围一致。事实上,C语言标准已经考虑到了这个问题,因此整型类型的别名无须我们自己定义,编译器会根据本平台的整型范围大小,设置对应的别名。我们只需要包含头文件 <stdint.h>,即可使用这些别名。

下图展示了 Visual Studio 中各整型类型的别名,只需要打开头文件 <stdint.h>,即可看到这些别名的定义:


图 3 Visual Studio中的头文件stdint.h

即使我们使用另一个编译器,它也会有自己的头文件 stdint.h。虽然它的写法可能与 Visual Studio 中的写法不一致,但是它一定能保证 int32_t 是 32 位、4 字节的有符号整型的别名。

下面的程序是一个使用整型别名的示例:
#include <stdio.h>
#include <stdint.h>
int main()
{
    int32_t n = 123;
    printf("n = %d\n", n);
    return 0;
}
另一个问题是如何保证 printf() 函数的转换规范的可移植性。

例如,上面程序的代码中,使用了 %d 输出 int32_t。如果 int32_t 是整型 int 的别名,那么代码没有问题。但是,如果 int32_t 是整型 long 的别名,那么应该使用 %ld 进行输出。

为了保证转换规范的可移植性,我们需要另一个头文件 inttypes.h。以 Visual Studio为例,打开头文件 inttypes.h,可以找到以下定义:
//  有符号
#define PRId8   "hhd"
#define PRId16  "hd"
#define PRId32  "d"
#define PRId64  "lld"
//  无符号
#define PRIu8   "hhu"
#define PRIu16  "hu"
#define PRIu32  "u"
#define PRIu64  "llu"
在 Visual Studio 中,int32_t 是整型 int 的别名。因此,输出 32 位有符号整型的宏 PRId32 的定义为 "d"。

在其他平台中,头文件 <inttypes.h> 将根据本平台中整型的别名定义对应的转换规范。若 int32_t 是整型 long 的别名,则输出 32 位有符号整型的宏 PRId32 的定义为 "ld"。

下面的程序是一个使用转换规范别名的示例:
#include <stdio.h>
#include <inttypes.h>
int main()
{
    int32_t n = 123;
    printf("n = %" PRId32 "\n", n);
    return 0;
}
在 Visual Studio 中,"n = %" PRId32 "\n" 会被替换为 "n = %" "d" "\n",而相邻的字符串将会被拼接为一个字符串,即 "n = %d\n"。

在其他的平台中 int32_t 是整型 long 的别名,"n = %" PRId32 "\n" 会被替换为 "n = %""ld""\n",而相邻的字符串将会被拼接为一个字符串,即 "n = %ld\n"。

相关文章