typedef数组类型,以及typedef指针的陷阱

文章介绍了C语言中typedef关键字的用法,主要用于创建数据类型的别名,包括结构体、数组和指针类型。typedef可以使代码更简洁,例如在定义结构体时可以减少冗长的struct。对于数组,C语言中不能直接用typedef定义数组别名,但在C++中可以。长度为1的数组有时用于简化函数调用,但其名称不可变。typedef定义的指针类型在复杂指针声明中很有用,但应注意const的使用,避免产生误解。
摘要由CSDN通过智能技术生成

C语言允许用户使用 typedef 关键字自定义数据类型的别名,比如基本类型名称、数组类型名称、指针类型,以及用户自定义的结构型名称、共用型名称、枚举型名称等。在这里只对结构体类型,数组类型,以及指针类型进行说明。

1. typedef结构体

typedef struct list_node_struct
{
    int data;
    struct list_node_struct* next;
} list_node;

在C语言中使用typedef定义一个结构体的别名,一般是为了在编码时少写一些代码,比如这里,就可以使用list_node node;来代替struct list_node_struct node;这样冗长的代码。

但是在结构体定义的内部,仍需使用struct关键字声明指向结构体自己指针,这是因为此时typedef语句还没有建立完全,list_node暂时还不存在。另外,在结构体的内部,struct list_node_struct也还没完全建立,属于不完整的类型。C语言不允许使用不完整的类型,因为不完整类型的占用空间大小是未知的;但C语言允许声明不完整类型的指针,比如上述的struct list_node_struct* next。这是因为C语言中所有数据类型的指针所占用的存储空间大小都是固定的,所以即使一个指针指向不完整的类型,但它本身却仍是一个完整的类型。于是在这里,在定义一个list_node时C语言编译器就不会为了该申请多少内存而苦恼。

2. typedef数组

typedef list_node list_node_array[]
typedef list_node list_node_t[1];
typedef list_node list_node_array_5[5];

可能会疑惑为什么C语言不能使用typedef list_node[] list_node_array来定义数组类型,因为C语言定义一个数组的语法就是<数据类型> <数组名称>[<数组长度>],而typedef的官方定义是:

Typedef does not work like typedef [type] [new name]. The [new name] part does not always come at the end
You should look at it this way: if [some declaration] [declares] a variable, typedef [same declaration] would define a type.

翻译过来就是:任何声明变量的语句前面加上typedef之后,原来是变量的都变成一种类型,不管这个声明中的标识符号出现在中间还是最后。换句话说,typedef借用了声明变量的语法,使原本的变量名变为了一种类型。不过在C++中可以使用using list_node_t = list_node[1]这样的语法来为“长度为1的list_node数组”取别名,这是因为C++支持list_node* node = new list_node[1]这样的语法。

另外,有部分人认为,定义长度为1的数组只相当于定义了一个变量,失去了数组定义多变量和方便使用并表示变量这一优势,因此毫无实际意义。其实不然,现在假设有一个函数,用于初始化一个list_node,容易想到的版本是:

void init_list_node(list_node* node)
{
	if(node != NULL)
	{
		node->data = 0;
		node->next = NULL;
	}
}

那么,我们可以使用以下方式来调用这个函数:

int main()
{
	list_node node1;
	init_list_node(&node1); /* 第1种方式 */
	list_node* node2 = (list_node*)malloc(sizeof(list_node));
	init_list_node(node2);  /* 第2种方式 */
	...
	free(node2);
	return 0;
}

但是,如果改用长度为1的list_node数组类型list_node_t进行变量定义,就可以采用如下编码方式调用init_list_node函数:

int main()
{
	list_node_t node;
	init_list_node(node);
	return 0;
}

由于node是数组名,它代表了该数组首元素的地址,所以node的数据类型兼容init_list_node的参数列表,于是就可以省去了&的编写,提升了代码的可读性。并且由于node存储于栈上,所以不需要操心堆内存的释放问题。

不过使用长度为1的数组有一个明显的缺陷就是数组名称是个常量,它不能被改变。于是下面的语句就不能通过编译:

int main()
{
	list_node_t a;
	list_node node;
	list_node* b = &node;
	list_node* c = a; /* 允许 */
	a = b; /* 不允许 */
	b = a; /* 允许 */
}

另外,长度为1的数组还被用于C99之前的柔性数组成员的替代方案,见[5]。

3. typedef指针

typedef list_node* list_node_ptr;
typedef char* (*write_func)(char*, int, int);

对于第一条语句,可能会觉得使用typedef定义一个新的别名意义不大。但是在复杂指针类型的变量声明中,使用typedef将会非常有意义,比如以下代码:

char* (*write_funcs[5])(char*, int, int);

相当于:

write_func write_funcs[5];

哪种方式更优不言而喻。

不过,要小心使用typedef带来的陷阱:

typedef char* string;
int strcmp(const string, const string);

在上述代码中const string是否相当于const char*呢?答案是否定的。typedef不同于宏定义,它不是简单的字符串替换。所以即使string是char*的别名,编译器也仍会认为string是一个非指针类型。而给一个非指针类型加上const限定符,相当于限制变量本身是只读的。因此,此时相当于定义了一个指针常量,即char* const。

4. typedef是一个存储类关键字

还需要特别注意的是,虽然 typedef 并不真正影响对象的存储特性,但在语法上它还是一个存储类的关键字,就像 auto、extern、static 和 register 等关键字一样。因此,像下面这种声明方式是不可行的:

typedef static int static_i32;

不可行的原因是不能声明多个存储类关键字,由于 typedef 已经占据了存储类关键字的位置,因此,在 typedef 声明中就不能够再使用 static 或任何其他存储类关键字了。当然,编译器也会报错,如在 MSVC 中的报错信息为“不能指定多个存储类”。

参考资料

[1] typedef的用法,C语言typedef详解

[2] Typedef declaration

[3] 不完整类型

[4] Incomplete types

[5] One element array in struct

[6] Arrays of Length Zero

[7] typedef和#define 的区别?

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值