|
复合字面值(Compound literals)
复合字面值属于后缀表达式。
有时可能会遇到这种情况:有一个函数fn,它接收一个结构类型的参数。为了调用这个函数,必须先为传递这样的参数创建一个对象。但是,这个对象仅仅是为了调用函数而创建,在程序的其它地方用不到它:
- struct t {int i; char c;};
- void fn (struct t);
- struct t t = {0, 'a'};
- fn (t);
- /* 后面的代码不再用到t */
复制代码
这样编写的程序可以工作得很好,但总觉得有些违合感。如果fn是用户自己写的函数,可以改进它的参数类型以避免使用结构,但如果是一个位于库中的系统函数呢?复合字面值可以帮用户避开这种问题。
一个用“(”和“)”括住的类型名(参见“类型名”),后面跟着一个前后两边分别是“{”和“}”的初始化器(参见“初始化器”),这样的后缀表达式称为复合字面值。
复合字面值会创建一个对象,但这个对象没有名称。同时,复合字面值对象会有一个初始值,这个值来自于初始化器列表,这个值同时也是复合字面值(表达式)的值。
下例中,复合字面值(以粗体形式给出的部分)创建了一个没有名称的对象。同时,复合字面值作为表达式,也要计算出一个值,这个值来自于它的初始化器列表。所以,该复合字面值所创建的对象初值为0,而表达式(int) {0}的值也是0。进一步地,因为该复合字面值位于赋值运算符=的右侧,所以,它所指示的对象将执行左值转换(参见“左值转换”),转换后的值是该对象的存储值,也就是表达式(int) {0}的值。最后,这个值赋给对象i,赋值后,对象i的值也为0,即
int i = (int) {0};
作为表达式,复合字面值也有自己的类型,这个类型是其类型名所指定的类型。复合字面值会创建没有名称的对象,而“对象”一词则暗示类型名不允许指定函数类型,而只能是对象类型。但是,也并非所有对象类型都是允许的,如变长数组类型、不完整的对象类型就不可以。唯一允许的不完整类型是未指定大小的数组,因为可以从初始化器列表中获得它的大小。在这种情况下,复合字面值是完整的数组类型。
若给定声明:
int n = 3;
void f (int);
则下面是一些复合字面值的例子。其中,D5是非法的,因为它企图创建变长数组类型的复合字面值。
- (char) {'x'}; //D1
- (float []) {0.01, 0.02, 0.03}; //D2
- (int *) {0}; //D3
- (int *) {& n}; //D4
- (int [n]) {1, 2, 3}; //D5
- (void (*) (int)) {f}; //D6
- (int *) {& (int) {0}}; //D7
复制代码
D5之外的复合字面值都是合法的,D1的类型是char;D2的类型是数组,元素类型为float;D3的类型是指向int的指针,它是用空指针常量0初始化的;D4的类型和D3相同,但它是用对象n的地址初始化的;D6的类型是指向函数的指针(指针是完整的对象类型),它和函数f的类型相同,所以它使用指向f的指针来初始化(f是函数指示符,在这里被转为指向函数的指针,参见“函数指示符-指针转换”)。复合字面值可以嵌套,所以,D7是复合字面值嵌套使用的例子,这将创建两个复合字面值对象。
复合字面值是表达式,而且还是左值。也正是因为如此,可以编写如下代码:
char c = (char []) {"2015-06-27"} [0];
上例中加粗的部分是复合字面值,它的类型是数组,这是一个左值,因此可以应用下标运算。
再如下面的例子,字体加粗的部分是复合字面值,它的类型是数组,具有3个元素。这里用运算符[]和&得到其首元素的地址,这是一个指向char的指针(char *),可以合法地赋给指针对象p。
char * p = & (char []) {'a', 'b', 'c'} [0];
因为在当前这种场合下,数组会自动转换为指向其首元素的指针(参见“数组-指针转换”),所以,此行语句等同于下面这行语句。
- char * p = (char []) {'a', 'b', 'c'};
复制代码
如果想得到指向数组的指针,那么可以使用下面的方法。
- char (* p) [] = & (char []) {'a', 'b', 'c'};
复制代码
下面是另一个完整的示例,用粗体给出的部分是复合字面值,它的类型是long long int。为了验证这一点,这里使用了泛型选择表达式和printf函数,如果复合字面值的类型的确是long long int,则把这个类型名以字符串的形式写到标准输出。
- # include <stdio.h>
- void f (void)
- {
- printf (_Generic ((long long int) {8},
- long long int : "long long int.\n",
- default : "Unknown.\n"));
- }
复制代码
现在来考虑一下开始的那个例子。使用复合字面值,可以避开这种做法,这对有“代码洁癖”的程序员来说很有帮助(第一个函数调用里的复合字面值比较特殊,因为它的初始化器部分使用了指示器.i和.c,这种用法的详情可参见“初始化器”):
- struct t {int i; char c;};
- void fn (struct t);
- fn ((struct t) {1, 'x'});
- fn ((struct t) {.i = 0, .c = 'a'});
复制代码
下面的例子用于创建指针的数组(元素类型为指针的数组)。这是一个嵌套的复合字面值,外部的复合字面值用于创建一个数组,内部的复合字面值用于创建数组的元素,同时用一元&运算符将其变成指向int的指针(int *)。因为这是一个元素类型为指针的数组(int * []),在将它赋给p时,隐式转换为指向其首元素的指针,即int * *。
- int * * p = (int * []) {& (int) {0}, & (int) {1}, & (int) {2},};
复制代码
位于函数外部的复合字面值与块内的复合字面值,它们所创建的对象具有不同的存储期,前者具有静态存储期,在程序启动时创建和初始化;后者具有自动存储期,在程序的执行进入它所在的块时创建,并在程序的执行到达它所在的位置时初始化。具体的讲解和示例参见“存储期”,这里从略。
复合字面值最好的用途是做为函数调用时的参数(且此参数不再使用)。做为学习复合字面值的示例,尽管下面的代码都是合法的,但没有什么意义(这可以做为思考题,请你说说这些代码的功能):
- int m = sizeof (int []) {5, 6, 7};
- (int) {0} = 99;
- int n = 0;
- * ((int *) {& n}) = 56;
复制代码
|
|