介绍几种typedef与结构体、联合体、枚举、函数指针声明结合使用的经典用法。
以结构体类型的声明和使用为例,C语言提供了struct关键字来定义一个结构体类型。
struct student
{
char name[20];
int age;
double score;
};
struct student stu1 = {"lily", 20 , 99}; /* C语言中 */
student stu2 = {"rose", 21 , 89}; /* C++中 */
我们可以使用typedef关键字,给student声明一个别名student_t和一个结构体指针类型student_ptr,然后就可以直接使用student_t类型去定义一个结构体变量,不再写struct。
typedef struct student
{
char name[20];
int age;
double score;
}student_t, *student_ptr;
int main(void)
{
student_t stu = {"lily", 20, 99};
student_t *p1 = &stu;
student_ptr p2 = &stu;
printf("name:%s\n", p1->name);
printf("name:%s\n",p2->name);
return 0;
}
typedef除了可以和结构体结合使用,还可以与数组结合使用。定义一个数组,通常使用int array[10]即可。我们可以使用typedef先声明一个数组类型,然后使用这个类型去定义一个数组。
typedef int array_t[10];
array_t array;
int main(void)
{
array[9] = 100;
printf("array[9] = %d\n", array[9]);
return 0;
}
typedef还可以与指针结合使用。
typedef char *PCHAR;
int main(void)
{
//char *str = "升职加薪!";
PCHAR str = "升职加薪!";
printf("str:%s\n", str);
return 0;
}
typedef还可以与函数指针结合使用。
typedef int (*func)(int a, int b);
int sum(int a, int b)
{
return a+b;
}
int main(void)
{
func_t fp = sum;
printf("%d\n", fp(1, 2));
return 0;
}
为了增加函数可读性,我们更建议用func_t *fp = sum;这样即使你没有看到func_t的定义,也能清楚知道fp是一个函数指针。
typedef还可以与枚举结合使用。
typedef enum color
{
red,
white,
black,
green,
color_num,
}color_t;
int main(void)
{
enum color color1 = red;
color_t color2 = red;
color_t color_number = color_num;
printf("color1:%d\n", color1);
printf("color2:%d\n", color2);
printf("color num:%d\n", color_number);
return 0;
}
在定义一个结构体、联合、枚举变量时,省去关键字struct,让代码更加简捷。
typedef struct student
{
char name[20];
int age;
float score;
}student_t, *student_ptr;
student_t stu = {"wit", 20, 99};
student_t *p1 = &stu;
student_ptr = &stu;
在不同的编译器和平台下,所分配的存储字长不一样。为了应付各种编译器,最好的办法就是使用自定义数据类型,而不是使用C语言的内置类型。
#ifdef PIC_16
typedef unsigned long U32
#else
typedef unsigned int U32
#endif
在16位的PIC单片机中,int型一般占2字节,long型占4字节,而在32位的ARM环境下,int型和long型一般都是占4字节。若我们在代码中想使用一个32位的固定长度的无符号类型数据,则可以使用上面的方式声明一个U32的数据类型,在程序中就能放心大胆使用U32.
在Linux内核、驱动、BSP等与底层架构平台密切相关的源码中,我们会经常看到size_t、U8、U16、U32等这样的数据类型。在一些网络协议、网卡驱动等对字节宽度、大小端比较关注的地方,也会经常看到typedef被频繁使用。
C语言预处理指令#define用来定义一个宏,而typedef则用来声明一种类型的别名。typedef和宏相比起来,不仅只是简单的字符串替换,而是可以使用该类型同时定义多个同类型对象。
举例:
typedef char *PCHAR1;
#define PCHAR char *;
int main(void)
{
PCHAR1 pch1, pch2;
PCHAR2 pch2, pch4;
printf("sizeof pch1:%d\n", sizeof(pch1));
printf("sizeof pch2:%d\n", sizeof(pch2));
printf("sizeof pch3:%d\n", sizeof(pch3));
printf("sizeof pch4:%d\n", sizeof(pch4));
return 0;
}
在这段代码里,我们想定义4个指向char类型的指针变量,然而运行结果为:
sizeof pch1: 4
sizeof pch2: 4
sizeof pch3: 4
sizeof pch4: 1
pch4经过预处理宏展开后,变成了一个字符型变量,而不是一个指针变量。而经过typedef定义的PCHAR1在语法上等价于相同类型的类型说明符关键字,因此可以在一行代码中定义多个变量。上端代码等价于:
char *pch1, *pch2;
char *pch3, pch4;
一些复杂的指针声明,如函数指针、数组指针、指针数组的声明,往往很复杂,可读性差。如:
int *(*array[10])(int *p, int len, char name[]);
我们使用typedef将其优化一下:先声明一个函数指针类型func_ptr_t,接着定义一个数组,就会大大增加其可读性。
typedef int *(func_ptr_t)(int *p, int len, char name[]);
func_ptr_t array[10];
typedef在语法上等价于C语言的关键字。我们使用typedef为已知的类型声明一个别名,在语法上其实就等价于该类型的类型说明符关键字,而不是像宏一样,仅仅是简单的字符串替换。
举例说明:
如const和类型的混合使用。当const和常见的类型(如int、char)共同修饰一个变量时,const和类型的位置可以互换。但是如果类型为指针,则const和指针类型不能互换,否则其修饰的变量类型就发生了变化。
char b = 10;
char c = 20;
int main(void)
{
char const *p1 = &b;//常量指针:*p1不可变,p1可变
char *const p2 = &b;//指针常量:*p2可变,p2不可变
p1 = &c;//编译正常
*p1 = 20;//error:assignment of read-only location
p2 = &c;//error:assignment of read-only variable p2
*p2 = 20;//编译正常
return 0;
}
当typedef和const一起修饰一个指针类型时,与宏定义的指针类型进行比较。
typdef char *PCHAR2;
#define PCHAR1 char *;
char b = 10;
char c = 20;
int main(void)
{
const PCHAR1 p1 = &b;
const PCHAR2 p2 = &b;
p1 = &c;//编译正常
*p1 = 20;//error:assignment of read-only location
p2 = &c;//error:assignment of read-only variable p2
*p2 = 20;//编译正常
return 0;
}
运行程序,这两段代码会遇到相同的编译错误,原因在于宏展开中仅仅是简单的字符串替换。
其次,typedef也是一个存储类关键字。和常见的存储类关键字,如auto、register、static、extern一样,不能使用一个以上的存储类关键字,否则编译会报错。
typedef static char *PCHAR;//error:multiple classes in declaration of PCHAR
使用typedef声明的类型和普通变量一样,都遵循作用域规则。
typedef char CHAR;
void func(void)
{
#define PI 3.14
typedef short CHAR;
printf("sizeof CHAR in func:%d\n", sizeof(CHAR));
}
int main(void)
{
printf("sizeof CHAR in main:%d\n", sizeof(CHAR));
func();
typedef int CHAR;
printf("sizeof CHAR in main:%d\n", sizeof(CHAR));
printf("PI:%f\n", PI);
return 0;
}
运行结果为:
sizeof CHAR in func:1
sizeof CHAR in main:2
sizeof CHAR in main:4
PI:3.14
当遇到以下情形时,使用typedef会更合适:
1、创建一个新的数据类型
2、跨平台的指定长度的类型,如U32/U16/U8
3、与操作系统、BSP、网络字宽相关的数据类型,如size_t、pid_t等
4、不透明的数据类型,需要隐藏结构体细节,只能通过函数接口访问的数据类型
具体大家可以看Linux Kernel Documentation目录下的CodingStyle文件中关于typedef的使用建议。