在C语言中,void
类型的引入意味着“不关心”而不是“null”或“nothing”,并且它被用于不同的范围。
void
关键字可以引用void类型
、void引用
、void表达式
、void操作数
或void函数
。它还明确定义了一个没有参数的函数。
让我们来看一些例子。
void
类型
首先,void
对象存在,并且具有一些特殊属性,如ISO / IEC 9899:2017,§6.2.5类型所述:
- void类型包含一组空值;它是一个不完整的对象类型,无法完成。
指针
void*
更有用的void引用
是对不完整类型的引用,但本身已经定义良好,因此是完全类型,具有大小,并且可以像任何其他标准变量一样使用,如ISO/IEC 9899:2017,§6.2.5类型所述:
指向void的指针应具有与指向字符类型的指针相同的表示和对齐要求。
类似地,指向兼容类型的限定或非限定版本的指针应具有相同的表示和对齐要求。
所有指向结构体类型的指针应具有彼此相同的表示和对齐要求。
所有指向联合类型的指针应具有彼此相同的表示和对齐要求。
指向其他类型的指针不需要具有相同的表示或对齐要求。
强制转换为void
它可以用作转换以使表达式无效,但允许完成任何这种表达式的副作用。该概念在标准中解释,位于ISO/IEC 9899:2017,§6.3 Conversions,§6.3.2.2 void:
(不存在的)void表达式(具有void类型的表达式)的值不得以任何方式使用,并且(除void之外的)隐式或显式转换不得应用于此类表达式。
如果将任何其他类型的表达式评估为void表达式,则其值或指示器将被丢弃。(void表达式将被评估其副作用。)
一个实际的例子是它被用于防止函数定义中未使用的参数警告:
int fn(int a, int b)
{
(void)b;
...
return 0;
}
上面的代码片段展示了抑制编译器警告的标准做法。将参数
b强制转换为
void
类型作为一个有效的表达式,不会生成代码,并将
b标记为已使用,避免编译器产生警告信息。
void
函数
标准规范中的§6.3.2.2 void段落还涵盖了一些有关void
函数的解释。这些函数不返回任何在表达式中可用的值,但是调用函数时仍会实现副作用。
void
指针属性
正如之前所说,指向void
的指针更加实用,因为它们允许以通用的方式处理对象引用,这是由其在ISO/IEC 9899:2017, §6.3.2.3 Pointers中解释的特性所决定的:
指向void的指针可以转换为任意对象类型的指针。
任何对象类型的指针都可以转换为指向void的指针,并且再次转换结果应该与原始指针相等。
作为实际例子,想象一个函数根据输入参数返回指向不同对象的指针:
enum
{
FAMILY,
VERSION,
NAME
} eRelease;
void *GetSoftwareInfo(eRelease par)
{
static const int iFamily = 1;
static const float fVersion = 2.0;
static const *char szName = "Rel2 Toaster";
switch(par)
{
case FAMILY:
return &iFamily;
case VERSION:
return &fVersion;
case NAME:
return szName;
}
return NULL;
}
在这个片段中,您可以返回一个通用指针,该指针可能取决于输入参数
par
的值。
void
作为函数参数
在所谓的ANSI标准之后引入了在函数定义中使用void
参数,以有效地区分具有可变参数和没有参数的函数。
来自标准ISO/IEC 9899:2017,6.7.6.3函数声明符(包括原型):
- 类型为
void
的未命名参数作为列表中唯一的项的特殊情况指定函数没有参数。
实际编译器仍然支持使用空括号进行函数声明,以实现向后兼容性,但这是一个过时的功能,将在未来版本的标准中被删除。请参阅未来方向- §6.11.6函数声明符:
- 使用带有空括号的函数声明符(不是原型格式的参数类型声明符)是一个过时的功能。
考虑以下示例:
int foo(); //prototype of variable arguments function (backward compatibility)
int bar(void); //prototype of no arguments function
int a = foo(2); //Allowed
int b = foo(); //Allowed
int c = bar(); //Allowed
int d = bar(1); //Error!
现在,假设我们按照以下方式调用函数
bar
,与你的测试类似:
int a = 1;
bar((void)a);
触发错误,因为将一个对象强制转换为void
并不会将其设置为null。因此,您仍然在尝试将void
对象作为参数传递给没有任何参数的函数。
副作用
根据要求,这是有关副作用概念的简短说明。
副作用是指执行语句所产生的对象和值的任何改变,这些改变不是直接期望的效果。
int a = 0;
(void)b = ++a;
在上面的代码片段中,void表达式失去了直接作用,赋值b,但是作为副作用增加了a的值。
唯一解释其含义的参考资料可以在5.1.2.3程序执行中找到:
访问易失对象、修改对象、修改文件或调用执行这些操作的函数都是副作用,即执行环境状态的更改。一般情况下,表达式的评估包括值计算和副作用的启动。
void* thread_func((void)args)
,然后在thread_func函数中使用(struct args)args
。 - Auxilusvoid
传递给不兼容类型的struct args
参数而出现错误。 - explogxvoid
类型,它是一种无法完成的不完整类型,这与完整类型“指向void
的指针”不同。 - explogxvoid
是实际的类型;然而,函数参数列表中的(void)
表示该函数不带参数,而不是它带有一个void
类型的参数。(不能使用void
类型的参数)。我的翻译保留了原文意思并尽可能通俗易懂,没有添加解释或其他内容。 - M.M