前言
复合字面量是C语言用来创建匿名对象的一种方式,在C99中引入。它实际上是一种语法糖,就算没有它也不影响C语言的表达能力。它常用于函数的参数传递,当程序员不想为只使用一次的对象创建标识符时,即可使用该匿名对象。
因为它是匿名的,没有关联的标识符,所以针对标识符的申明、定义、作用域、名字空间、链接类型等都跟它无关。唯一跟它相关的是对象的生存期。
它是C语言特有的功能,C++不支持复合字面量。
语法
复合字面量的语法是:
(类型) {初始化列表}
复合字面量的类型不能是VLA。
这实际上是一个表达式,它的值就是对应匿名对象的值,并且是左值。
复合字面量的语法和显式类型转换非常相似,二者的区别是有无初始化列表{}
。而且复合字面量是左值,显式类型转换不是左值。
int num = 0;
int *p1 = &(int) { num }; // 复合字面量,左值,可以取地址
int *p2 = &(int)num; // 显式类型转换,非左值,不能取地址,编译错误
初始化
初始化列表的语法在复合字面量中几乎都适用。
数组初始化,包括省略数组大小、指定初始化、字符串字面量初始化等:
int *p = (int[3]) { 0 };
int *p = (int[]) { 1, 2, 3 };
int *p = (int[5]) { [2] = 5 };
char *s = (char[]) { "hello" };
结构体初始化,包括指定初始化等:
struct S {
int a;
double b;
};
struct S *p = &(struct S) { 1, 0.5 };
struct S *p = &(struct S) { .b = 0.5 };
生存期
如果复合字面量出现在文件范围内,则对应的匿名对象默认具有静态生存期,程序启动时创建,程序退出时销毁;如果该表达式出现在块内,则对应的匿名对象默认具有自动生存期,程序执行进入该块时创建,退出该块时销毁。
例子1:
#include <stdio.h>
int *p = &(int) { 0 };
int main(void) {
printf("%d\n", *p); // print 0
*p = 5;;
printf("%d\n", *p); // print 5
return 0;
}
这个例子使用复合字面量(int) { 0 }
创建了一个匿名对象,并用p
来指向它。该对象具有静态生存期,所以可以在main
函数里访问它,并且因为它是左值,还可以对其进行修改。
例子2:
#include <stdio.h>
int main(void) {
int *p = NULL;
{
p = &(int) { 5 };
}
printf("%d\n", *p); // undefined behavior
}
在这个例子中,(int) { 5 }
出现在子块内,当执行printf
函数时,该子块已经退出,匿名对象已经被销毁,这时对p
解引用结果是未定义的。
C23允许为复合字面量指定存储类型说明符,比如constexpr
、static
、register
、thread_local
,其效果相当于在当前位置使用这些说明符定义一个标识符。(static int) { 0 }
相当于static int id = { 0 };
,只是id
被省略了,只有编译器知道。这个规则也适用于一般的复合字面量。
例子3:
#include <stdio.h>
int main(void) {
int result = 0;
for (int iter = 0; iter < 5; ++iter) {
int *p = &(static int) { 0 };
++*p;
result = *p;
}
printf("%d\n", result);
}
在这个例子中,因为匿名对象具有静态生存期,所以只会初始化一次,每轮循环递增1,最后输出5
。如果把static
去掉,最后输出1
。
使用
因为复合字面量创建的对象是匿名的,没有任何标识符与它关联,所以表达式的值应该马上被使用,比如赋值或者取地址,否则就白创建了。
int a = (int) { 0 }; // 赋值
int *b = &(int) { 0 }; // 取地址
(int) { 0 }; // 无意义
上面的两种情况其实意义不大,还不如直接定义一个int
变量并初始化来的方便。
复合字面量的使用场景主要是作为函数的实际参数。
例子1:
#include <stdio.h>
int sum(int const *array, int count) {
int result = 0;
for (int iter = 0; iter < count; ++iter) {
result += array[iter];
}
return result;
}
int main(void) {
printf("%d\n", sum((int[]) { 1, 2, 3 }, 3));
printf("%d\n", sum((int[]) { 1, 2, 3, 4 }, 4));
return 0;
}
例子2:
#include <stdio.h>
#include <math.h>
struct Point {
double x;
double y;
};
double distance(struct Point const *p1, struct Point const *p2) {
double h = p1->x - p2->x;
double v = p1->y - p2->y;
return sqrt(h * h + v * v);
}
int main(void) {
printf("%f\n", distance(&(struct Point) { 0, 0 }, &(struct Point) { 1, 1 }));
printf("%f\n", distance(&(struct Point) { 0, 0 }, &(struct Point) { 3, 4 }));
return 0;
}
编写测试代码时,经常需要手动构建多个测试数据,然后传递给测试函数。这些数据往往只会使用一次,如果每个数据都进行申明,会非常麻烦,可以使用复合字面量减少代码量。