为什么在以下结构定义中需要显式强制转换
Why is an explicit cast necessary in the following struct definition
struct
通过复合文字初始化,它将自己进行转换。例如:
struct movie {
char title[50];
int year;
};
typedef struct movie Item;
typedef struct node {
Item item;
struct node *next;
} Node;
typedef struct linkedlist {
Node *head;
size_t size;
} LinkedList;
LinkedList movies2 = {
.head=&(Node){{"Avatar", 2010}, NULL},
.size=1
};
但是,如果我分开定义,我必须添加一个显式转换:
LinkedList movies2;
movies2 = (LinkedList) {
.head=&(Node){{"Avatar", 2010}, NULL},
.size=1
};
代码:https://godbolt.org/z/dG8nMh
如果我在第二个中遗漏了 (cast_type)
,我将得到类似 error: expected expression before ‘{’ token
的错误。为什么会这样?
也就是说,为什么初始化不需要强制转换而另一个定义需要?我的想法是第二个版本应该能够在没有显式转换的情况下自行解析,但显然这是不正确的。
因为在第一个片段中,您正在使用这些值初始化您的结构。
但是在你的第二个片段中,你正在创建一个 compound literal 然后将它复制到你的结构中。
在C语言中,左值和右值应该是同一类型。在表达式
movies2 = (LinkedList) {
.head=&(Node){{"Avatar", 2010}, NULL},
.size=1 };
您正在确保右值是 LinkedList 类型。如果删除 (LinkedList),则右值的类型未知。这就是错误的原因。
在这两种情况下,您都使用复合文字。
声明中的第一种情况
LinkedList movies2 = {
.head=&(Node){{"Avatar", 2010}, NULL},
.size=1
};
您正在初始化列表中使用 Node
类型的复合文字 (Node){{"Avatar", 2010}, NULL}
,其地址用作结构 head
的数据成员 head
的初始化器=20=].
在第二种情况下,您首先创建了一个 LimkedList
类型的对象
LinkedList movies2;
然后您使用赋值运算符对创建的对象使用 LinkedList
类型的复合文字
(LinkedList) {
.head=&(Node){{"Avatar", 2010}, NULL},
.size=1
}
即
movies2 = (LinkedList) {
.head=&(Node){{"Avatar", 2010}, NULL},
.size=1
};
那是没有任何铸造。使用了两种不同的复合文字。一种是 Node 类型,另一种是 LinkedList 类型。
说清楚。考虑一个简单的例子
int x = { 10 };
在上面的声明中,变量 x
由整数常量 10 初始化。
你也可以这样写
int tmp = { 10 };
int x;
x = tmp;
这里创建了一个中间变量来初始化变量x
。复合文字实际上是一个未命名的对象。上面的代码可以改写成
int x;
x = ( int ){ 10 };
这里没有任何铸造。这个表达式 ( int ){ 10 }
创建了一个 int
类型的未命名对象,它由整数常量 10 初始化。这个新创建的未命名对象被分配给变量 x
.
另请参阅以下问题
似乎是 显式转换 是复合文字语义学 (type_name){ }
.
的一部分
复合文字的语法可能会与类型转换混淆,但是转换是非左值表达式,而复合文字是左值。
C11 N1570 §6.5.2.5
Semantics
- A postfix expression that consists of a parenthesized type name followed by a brace-enclosed list of initializers is a compound literal. It provides an unnamed object whose value is given by the initializer list.
第一个是初始化器,不是赋值,所以不需要它。
我还要注意,在表达式 .head = &(Node){{"Avatar", 2010}, NULL}
中,复合文字是一个左值,可以通过地址获取,但它的生命周期仅限于它所属的范围,在该范围之外访问它会调用 undefined行为,即:
LinkedList getLinkedList()
{
LinkedList movies2;
movies2 = (LinkedList){
.head = &(Node){{"Avatar", 2010}, NULL},
.size = 1};
return movies2;
}
LinkedList l = getLinkedList();
printf("%s", l.head->item.title); // undefined behavior
其他答案涵盖了复合文字的初始化和赋值之间的区别。然而,还有另一个概念点需要提出:
LinkedList movies2 = {
.head=&(Node){{"Avatar", 2010}, NULL},
.size=1
};
在任一情况下,您正在创建一个链表并将头部设置为指向一个节点,该节点不是动态分配的,也不是某个应用程序定义的池的一部分。链表的成员通常通过 malloc
动态创建或从池中获取。
您现在拥有的是列表中的一个“特殊”节点,因为它没有像添加其他节点那样添加到列表中,这意味着您需要额外的逻辑来跟踪如果它被删除,它的处理方式与列表中的其他节点不同。
最好将列表的头部初始化为NULL
。然后你的添加/删除功能需要知道列表是否为空。
struct
通过复合文字初始化,它将自己进行转换。例如:
struct movie {
char title[50];
int year;
};
typedef struct movie Item;
typedef struct node {
Item item;
struct node *next;
} Node;
typedef struct linkedlist {
Node *head;
size_t size;
} LinkedList;
LinkedList movies2 = {
.head=&(Node){{"Avatar", 2010}, NULL},
.size=1
};
但是,如果我分开定义,我必须添加一个显式转换:
LinkedList movies2;
movies2 = (LinkedList) {
.head=&(Node){{"Avatar", 2010}, NULL},
.size=1
};
代码:https://godbolt.org/z/dG8nMh
如果我在第二个中遗漏了 (cast_type)
,我将得到类似 error: expected expression before ‘{’ token
的错误。为什么会这样?
也就是说,为什么初始化不需要强制转换而另一个定义需要?我的想法是第二个版本应该能够在没有显式转换的情况下自行解析,但显然这是不正确的。
因为在第一个片段中,您正在使用这些值初始化您的结构。 但是在你的第二个片段中,你正在创建一个 compound literal 然后将它复制到你的结构中。
在C语言中,左值和右值应该是同一类型。在表达式
movies2 = (LinkedList) {
.head=&(Node){{"Avatar", 2010}, NULL},
.size=1 };
您正在确保右值是 LinkedList 类型。如果删除 (LinkedList),则右值的类型未知。这就是错误的原因。
在这两种情况下,您都使用复合文字。
声明中的第一种情况
LinkedList movies2 = {
.head=&(Node){{"Avatar", 2010}, NULL},
.size=1
};
您正在初始化列表中使用 Node
类型的复合文字 (Node){{"Avatar", 2010}, NULL}
,其地址用作结构 head
的数据成员 head
的初始化器=20=].
在第二种情况下,您首先创建了一个 LimkedList
LinkedList movies2;
然后您使用赋值运算符对创建的对象使用 LinkedList
(LinkedList) {
.head=&(Node){{"Avatar", 2010}, NULL},
.size=1
}
即
movies2 = (LinkedList) {
.head=&(Node){{"Avatar", 2010}, NULL},
.size=1
};
那是没有任何铸造。使用了两种不同的复合文字。一种是 Node 类型,另一种是 LinkedList 类型。
说清楚。考虑一个简单的例子
int x = { 10 };
在上面的声明中,变量 x
由整数常量 10 初始化。
你也可以这样写
int tmp = { 10 };
int x;
x = tmp;
这里创建了一个中间变量来初始化变量x
。复合文字实际上是一个未命名的对象。上面的代码可以改写成
int x;
x = ( int ){ 10 };
这里没有任何铸造。这个表达式 ( int ){ 10 }
创建了一个 int
类型的未命名对象,它由整数常量 10 初始化。这个新创建的未命名对象被分配给变量 x
.
另请参阅以下问题
似乎是 显式转换 是复合文字语义学 (type_name){ }
.
复合文字的语法可能会与类型转换混淆,但是转换是非左值表达式,而复合文字是左值。
C11 N1570 §6.5.2.5
Semantics
- A postfix expression that consists of a parenthesized type name followed by a brace-enclosed list of initializers is a compound literal. It provides an unnamed object whose value is given by the initializer list.
第一个是初始化器,不是赋值,所以不需要它。
我还要注意,在表达式 .head = &(Node){{"Avatar", 2010}, NULL}
中,复合文字是一个左值,可以通过地址获取,但它的生命周期仅限于它所属的范围,在该范围之外访问它会调用 undefined行为,即:
LinkedList getLinkedList()
{
LinkedList movies2;
movies2 = (LinkedList){
.head = &(Node){{"Avatar", 2010}, NULL},
.size = 1};
return movies2;
}
LinkedList l = getLinkedList();
printf("%s", l.head->item.title); // undefined behavior
其他答案涵盖了复合文字的初始化和赋值之间的区别。然而,还有另一个概念点需要提出:
LinkedList movies2 = {
.head=&(Node){{"Avatar", 2010}, NULL},
.size=1
};
在任一情况下,您正在创建一个链表并将头部设置为指向一个节点,该节点不是动态分配的,也不是某个应用程序定义的池的一部分。链表的成员通常通过 malloc
动态创建或从池中获取。
您现在拥有的是列表中的一个“特殊”节点,因为它没有像添加其他节点那样添加到列表中,这意味着您需要额外的逻辑来跟踪如果它被删除,它的处理方式与列表中的其他节点不同。
最好将列表的头部初始化为NULL
。然后你的添加/删除功能需要知道列表是否为空。