找回密码
 立即注册
搜索
热搜: 活动 通知
查看: 751|回复: 0

复合字面值(Compound literals)

[复制链接]

416

主题

315

回帖

3288

积分

管理员

积分
3288
发表于 2024-3-15 12:25:44 | 显示全部楼层 |阅读模式
复合字面值(Compound literals)



复合字面值属于后缀表达式。
有时可能会遇到这种情况:有一个函数fn,它接收一个结构类型的参数。为了调用这个函数,必须先为传递这样的参数创建一个对象。但是,这个对象仅仅是为了调用函数而创建,在程序的其它地方用不到它:
  1. struct t {int i; char c;};
  2. void fn (struct t);

  3. struct t t = {0, 'a'};
  4. fn (t);
  5. /* 后面的代码不再用到t */
复制代码


这样编写的程序可以工作得很好,但总觉得有些违合感。如果fn是用户自己写的函数,可以改进它的参数类型以避免使用结构,但如果是一个位于库中的系统函数呢?复合字面值可以帮用户避开这种问题。

一个用“(”和“)”括住的类型名(参见“类型名”),后面跟着一个前后两边分别是“{”和“}”的初始化器(参见“初始化器”),这样的后缀表达式称为复合字面值。

复合字面值会创建一个对象,但这个对象没有名称。同时,复合字面值对象会有一个初始值,这个值来自于初始化器列表,这个值同时也是复合字面值(表达式)的值。

下例中,复合字面值(以粗体形式给出的部分)创建了一个没有名称的对象。同时,复合字面值作为表达式,也要计算出一个值,这个值来自于它的初始化器列表。所以,该复合字面值所创建的对象初值为0,而表达式(int) {0}的值也是0。进一步地,因为该复合字面值位于赋值运算符=的右侧,所以,它所指示的对象将执行左值转换(参见“左值转换”),转换后的值是该对象的存储值,也就是表达式(int) {0}的值。最后,这个值赋给对象i,赋值后,对象i的值也为0,即
int i = (int) {0};

作为表达式,复合字面值也有自己的类型,这个类型是其类型名所指定的类型。复合字面值会创建没有名称的对象,而“对象”一词则暗示类型名不允许指定函数类型,而只能是对象类型。但是,也并非所有对象类型都是允许的,如变长数组类型、不完整的对象类型就不可以。唯一允许的不完整类型是未指定大小的数组,因为可以从初始化器列表中获得它的大小。在这种情况下,复合字面值是完整的数组类型。

若给定声明:
int n = 3;
void f (int);
则下面是一些复合字面值的例子。其中,D5是非法的,因为它企图创建变长数组类型的复合字面值。
  1. (char) {'x'};                        //D1
  2. (float []) {0.01, 0.02, 0.03};                        //D2
  3. (int *) {0};                         //D3
  4. (int *) {& n};                      //D4
  5. (int [n]) {1, 2, 3};               //D5
  6. (void (*) (int)) {f};             //D6
  7. (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];
因为在当前这种场合下,数组会自动转换为指向其首元素的指针(参见“数组-指针转换”),所以,此行语句等同于下面这行语句。
  1. char * p = (char []) {'a', 'b', 'c'};
复制代码

如果想得到指向数组的指针,那么可以使用下面的方法。
  1. char (* p) [] = & (char []) {'a', 'b', 'c'};
复制代码


下面是另一个完整的示例,用粗体给出的部分是复合字面值,它的类型是long long int。为了验证这一点,这里使用了泛型选择表达式和printf函数,如果复合字面值的类型的确是long long int,则把这个类型名以字符串的形式写到标准输出。
  1. # include <stdio.h>

  2. void f (void)
  3. {
  4.     printf (_Generic ((long long int) {8},
  5.     long long int : "long long int.\n",
  6.     default : "Unknown.\n"));
  7. }
复制代码


现在来考虑一下开始的那个例子。使用复合字面值,可以避开这种做法,这对有“代码洁癖”的程序员来说很有帮助(第一个函数调用里的复合字面值比较特殊,因为它的初始化器部分使用了指示器.i和.c,这种用法的详情可参见“初始化器”):
  1. struct t {int i; char c;};
  2. void fn (struct t);

  3. fn ((struct t) {1, 'x'});
  4. fn ((struct t) {.i = 0, .c = 'a'});
复制代码


下面的例子用于创建指针的数组(元素类型为指针的数组)。这是一个嵌套的复合字面值,外部的复合字面值用于创建一个数组,内部的复合字面值用于创建数组的元素,同时用一元&运算符将其变成指向int的指针(int *)。因为这是一个元素类型为指针的数组(int * []),在将它赋给p时,隐式转换为指向其首元素的指针,即int * *。
  1. int * * p = (int * []) {& (int) {0}, & (int) {1}, & (int) {2},};
复制代码


位于函数外部的复合字面值与块内的复合字面值,它们所创建的对象具有不同的存储期,前者具有静态存储期,在程序启动时创建和初始化;后者具有自动存储期,在程序的执行进入它所在的块时创建,并在程序的执行到达它所在的位置时初始化。具体的讲解和示例参见“存储期”,这里从略。

复合字面值最好的用途是做为函数调用时的参数(且此参数不再使用)。做为学习复合字面值的示例,尽管下面的代码都是合法的,但没有什么意义(这可以做为思考题,请你说说这些代码的功能):
  1. int m = sizeof (int []) {5, 6, 7};
  2. (int) {0} = 99;
  3. int n = 0;
  4. * ((int *) {& n}) = 56;
复制代码


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|鼠侠网 ( 吉ICP备19001332号 )

GMT+8, 2024-12-22 09:13 , Processed in 0.219390 second(s), 19 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表