(翻译文,内容有删改)
1. typedef的定义与作用域
typedef是允许我们为现有类型或用户定义的类型创建别名,格式为:
typedef <existing_names_of_datatype> <alias__userGiven_name>;
示例:
typedef int myint;
myint 是 int 的别名。从这时起,我们可以使用 myint 代替 int 定义新的int变量:
myint i = 0; // this statement is equivalent to int i = 0;
我们甚至可以为同一类型创建多个别名。例如:
typedef int myint, integer;
该语句为int类型创建了两个别名,即myint和integer。
我们可以在任何允许声明的地方编写typedef声明。但是,需要注意的是,声明的范围取决于typedef语句的位置。如果定义放在所有函数之外,那么作用域是全局的,任何函数都可以使用别名而不是原始名称。另一方面,如果定义是在函数中声明的,那么作用域是本地的,只有本函数才能使用别名。
示例 1:使用typedef声明本地别名
#include<stdio.h>
void foo(void);
int main()
{
typedef unsigned char uchar;
uchar ch = 'a';
printf("ch inside main() : %c\n", ch);
foo();
return 0;
}
void foo(void)
{
// uchar ch = 'a'; // Error
unsigned char ch = 'z';
printf("ch inside foo() : %c\n", ch);
}
输出:
ch inside main() : a
ch inside foo(): z
typedef在main()函数中定义,因此我们只能在main()中使用别名 uchar。尝试取消对第15行的注释并编译程序,您将从编译器中得到一个错误,因为在foo()函数中别名uchar不可用。
示例 2:使用typedef声明全局别名
#include<stdio.h>
typedef unsigned char uchar;
void foo(void);
int main()
{
uchar ch = 'a';
printf("ch inside main() : %c\n", ch);
foo();
return 0;
}
void foo(void)
{
uchar ch = 'z';
printf("ch inside foo() : %c\n", ch);
}
输出:
ch inside main() : a
ch inside foo(): z
这里typedef声明高于所有函数,因此任何函数都可以使用别名 uchar声明unsigned char类型的变量。
2. 为指针定义别名
typedef int * iptr;
在这个语句之后,iptr是指向int或(int*)的指针的别名。
示例:
iptr a, *b; // same as int *a, **b;
iptr arr[10]; // same as int *arr[10];
在第一个声明中,a是指向int的指针,b是指向int的指针。在第二个声明中,arr是10个整数指针的数组。
示例:
#include <stdio.h>
typedef int * iptr;
int main()
{
iptr arr[10];
int a;
arr[1] = &a;
a = 56;
printf("%d\n", a);
printf("%d\n", *(arr[1]));
return 0;
}
在这里,arr是一个数组,每个元素保存的是指针,输出结果:
56
56
3. 为数组定义别名
typedef int iarr[10];
iarr是由10个整数元素组成的数组的别名。
iarr a, b, c[5]; // same as int a[10], b[10], c[10][5];
在这个声明中,a和b是10个整数的数组,c是10*5的二维数组。
示例:
#include<stdio.h>
typedef int iarr[10];
int main()
{
int i;
// same as int a[10] = {12,43,45,65,67,87,89,91,14,19}
iarr a = {12,43,45,65,67,87,89,91,14,19};
for(i = 0; i < 10; i++)
{
printf("a[%d] = %d\n",i ,a[i]);
}
return 0;
}
输出:
a[0] = 12
a[1] = 43
a[2] = 45
a[3] = 65
a[4] = 67
a[5] = 87
a[6] = 89
a[7] = 91
a[8] = 14
a[9] = 19
4. 为结构体定义别名
struct book
{
char title[20];
char publisher[20];
char author[20];
int year;
int pages;
};
typedef struct book Book;
在这个声明之后,Book是struct book的别名。所以不需要用struct book来声明新的结构变量,我们可以直接使用Book。
Book b1 = {"The Alchemist", "TDM Publication" , "Paulo Coelho", 1978, 331 };
我们还可以将结构体的定义和typedef声明结合起来。其语法如下:
typedef struct tagname
{
data_type member1;
data_type member1;
...
} newname;
让我们用typedef的新语法重写结构体book的定义:
typedef struct book
{
char title[20];
char publisher[20];
char author[20];
int year;
int pages;
} Book;
下面的程序演示了如何在结构体中使用typedef:
#include<stdio.h>
typedef struct book
{
char title[20];
char publisher[20];
char author[20];
int year;
int pages;
} Book;
int main()
{
Book b1 = {
"The Zahir",
"Harper Perennial" ,
"Paulo Coelho",
2005,
336
};
printf("Title: %s\n", b1.title);
printf("Author: %s\n", b1.author);
return 0;
}
输出:
Title: The Zahir
Author: Paulo Coelho
类似地,我们可以将typedef与联合一起使用。
5. typedef vs #define
必须指出的是typedef不是预处理器指令,因此它的解释是由编译器处理的,而不是由预处理器处理的。回想一下,#define指令允许我们为任何文本定义扩展,另一方面typedef用于为任何数据类型创建别名。
但是,在某些情况下,#define和typedef会产生相同的结果,示例如下:
#define指令 | typedef声明 | |
---|---|---|
#define uchar unsigned char | typedef unsigned char uchar; | |
测试语句 | uchar ch; | uchar ch; |
转换后的结果 | unsigned char ch; | unsigned char ch; |
以下是#define和typedef产生不同结果的情况:
#define指令 | typedef声明 | |
---|---|---|
#define fp float * | typedef float * fp; | |
测试语句 | fp a, b, c; | fp a, b, c; |
转换后的结果 | float *a, b, c; | float *a, *b, *c; |
第二种情况下,当预处理器看到语句
fp a, b, c;
它将fp替换为float *。所以上述声明变成:
float *a, b, c;
另一方面,typedef具有更多的语义含义,因此编译器不会像预处理器那样进行替换。
下面的程序演示了#define和typedef之间的区别:
#include<stdio.h>
#define ptr int * // replace occurence of ptr by int *
typedef int * iptr; // iptr is an alias of pointer to int or int*
int main()
{
ptr a, b, c; // same as int *a, b, c;
iptr p1, p2, p3; // same as int *p1, *p2, *p3
b = 10;
c = 20;
a = &b;
p1 = &b;
p2 = &c;
p3 = &c;
printf("Value at a = %d\n", *a); // print value of b
printf("Value at p2 = %d\n", *p2); // print value of b
return 0;
}
输出:
Value at a = 10
Value at p2 = 20
6. typedef的优点
它使程序更具可读性。当然,Book b1 比 struct book b1 更具可读性和直观性。
它使程序可移植。让我来解释一下,看看sizeof()操作符和malloc()函数的原型。
size_t sizeof(type);
void *malloc(size_t size);
正如您所知,两个原型都使用size_t类型,并且我们已经告诉您将size_t视为unsigned int,但这并不完全正确。C标准规定sizeof()必须返回一个整数,但由系统决定返回哪种类型。这是因为C标准委员会觉得,不提供选择可能是每个平台的最佳选择。因此,他们创建了新类型,如size_t、time_t等,并让系统使用typedef将这些名称设置为某个特定类型。所以一个系统的size_t的类型可以是unsigned int,另一个系统的size_t的类型可以是unsigned long int。
参考文档
[1]OverIQ.com.typedef statement in C[EB/OL].https://overiq.com/c-programming-101/typedef-statement-in-c/,2020年7月27日.