当前位置: C语言 -- 基础 -- 表达式

表达式(七)

5、复合字面量

复合字面量(compound literals)的语法格式如下所示:

(storage-class-specifiersopt type-name) {initializer-list}

其中storage-class-specifiers是存储类说明符,opt表示可选;type-name是类型名,复合字面量的类型应为完整对象类型或者未知大小的数组类型,但不能是变长数组类型(variable length array type);initializer-list是初始化列表,如果存在多个初始化器,相互之间使用逗号(,)分隔。

(unsigned char []){"China"};    //合法的复合字面量。

int n = 2;
(int [n]){3,6}; //非法的复合字面量,因为类型是变长数组类型。

如果复合字面量与函数原型作用域相关联:

-- 类型的确定与块作用域相同,但不会创建对象。

/*以下两个函数类型是兼容类型。*/
int f(int *);
int g((int []){3,5});

-- 如果复合字面量是复合字面常量(compound literal constant),复合字面量在编译时评估。

存在存储类说明符constexpr的复合字面量是复合字面常量;后缀表达式使用成员访问运算符(.)访问结构或者联合类型的复合字面常量得到的也是复合字面常量,这种关系可以递归应用。

struct s{
  int i;
  double d;
};
...
(constexpr struct s){5, 3.14};    //复合字面常量。
(constexpr struct s){2, 1.23}.d;  //复合字面常量。

-- 如果复合字面量不是复合字面常量,则复合字面量不会被评估,其初始化器也不会被评估。


如果复合字面量与文件作用域或者块作用域相关联,存储类说明符、类型名、初始化列表应分别是文件作用域或者块作用域中对象定义的有效说明符,对象定义格式如下所示:

storage-class-specifier typeof(type-name) ID = {initializer-list};

其中ID是整个程序中唯一的标识符;初始化列表(可能为空)包含嵌套结构、指示符、值和类型作为复合字面量的初始化列表;存储类说明符的所有限制适用于复合字面量;如果复合字面量与函数原型作用域相关联,则适用与块作用域相同的限制。


与文件作用域或者块作用域相关联的复合字面量提供了对未命名对象的访问,其值是与未命名对象对应的左值的值。

struct s{
  int i;
  double d;
};
...
struct s *ptr = &(struct s){5, 3.14};
printf("%d\n", ptr->i);   //输出5。
printf("%f\n", ptr->d);   //输出3.140000。

如果不存在存储类说明符,或者存在constexprstaticregister或者thread_local存储类说明符,复合字面量的行为与相应作用域内使用这些存储类说明符声明和初始化对象相同;如果存在其它存储类说明符,其行为是未定义的。

如果存在constexpr存储类说明符,初始化器在编译时评估;如果存储期限是自动存储期限,每次评估复合字面量时评估初始化器;如果存储期限是静态存储期限或者线程存储期限,初始化器在程序启动前评估。

(constexpr struct s){5, 3.14}   //编译时评估初始化器。
(static struct s){5, 3.14}      //程序启动前评估初始化器。
(struct s){5, 3.14}   //与块作用域相关联时,每次评估复合字面量时评估初始化器。

如果复合字面量的类型名表示未知大小数组,数组大小由初始化列表决定,复合字面量的类型是完整数组类型;如果复合字面量的类型名表示对象类型,复合字面量的类型是类型名指定的类型。无论哪种情况,复合字面量都是左值。

struct s{
  int i;
  double d;
};
...
(int []){3, 6, 9};    //类型是包含三个int类型元素的数组类型。
(struct s){6, 3.14};  //类型是struct s类型。

如果复合字面量出现在函数体外,且不在函数原型内,其具有静态存储期限(static storage duration);如果复合字面量具有自动存储期限(automatic storage duration),其生存期与其所在代码块的执行周期一致。 具有静态存储期限的复合字面量中的表达式应都是常量;具有自动存储期限的复合字面量中的表达式不一定是常量。

struct s{
  int i;
  double d;
};

/*具有文件作用域的复合字面量。*/
struct s *ptr = &(struct s){6, 3.14};     //A
...
/*具有块作用域的复合字面量。*/
int m = 10;
double n = 1.23;

struct s *p = &(struct s){m, n};          //B

A处的复合字面量具有静态存储期限,其中的表达式应都是常量;B处的复合字面量具有自动存储期限,其中的表达式可以不是常量。


C语言中初始化规则适用于复合字面量,例如:复合字面量中没有显式初始化的子对象将初始化为0

struct s{
  int i;
  double d;
};

struct s *ptr = &(struct s){.i=5};  //ptr->i=5, ptr->d=0.0。

字符串字面量与const类型限定符限定的复合字面量(包括存在存储类说明符constexpr的复合字面量。)不需要表示不同对象。对于具有相同表示形式或者重叠表示形式的字符串字面量和常量复合字面量,实现可以共享存储空间。

(const char []){"China"} == "China"   //表达式的值可能为1,也可能为0。

复合字面量与字符串字面量的比较
复合字面量 字符串字面量
存储期限 与位置和存储类说明符有关,可能具有静态存储期限、自动存储期限或者线程存储期限。 静态存储期限。
是否是左值
是否可以修改 如果存在const类型限定符或者constexpr存储类说明符,不可以修改;否则可以修改。 不可以修改。

由于复合字面量是未命名对象,复合字面量不能指定循环链接对象,因此无法编写自引用的复合字面量,例如:下面的代码使用复合字面量无法实现。

struct node{
  int id;
  struct node * next;
};

struct node head = {1, &head};

对于迭代语句,块作用域未命名对象的生存期仅存在于循环体内。

int i;

for(unsigned u=0; u<5; u++)
{
  i = ++(int){5};
  printf("i = %d\n", i);  //每次都输出i = 6。
}

每次进入循环体,(int){5}都是一个新的未命名对象。


复合字面量不同于强制类型转换表达式(cast expression),主要区别如下表所示:

复合字面量与强制类型转换表达式的差异
复合字面量 强制类型转换表达式
适用类型 适用于完整对象类型和未知大小的数组类型,但不能是变长数组类型。 适用于标量类型(scalar types)和void类型。
是否是左值

int i = ++(int){5};   //合法,i的值为6。
int n = ++(int)5;     //非法,(int)5不是左值。