在另一个结构中声明一个结构(已经是typedef)?

时间:2013-12-06 06:19:00

标签: c struct typedef declaration

我对C的理解是有两个独立的命名空间,一个用于标记(例如用于结构),另一个用于所有其他变量(包括结构)。在struct定义之前使用typedef然后将struct变量视为一个类型,所以如果你使用

struct car_part {/* Code Here */} CarPart;

(CarPart是可选的)
你必须使用

struct car_part engine;

宣布汽车零件。
而如果你使用了带

的typedef
typedef car_part {/* Code Here */} CarPart;

您现在可以使用

CarPart engine;

代替。

typedef struct tag {/* Code here */} struct_name;

1)在块代码之前或之后声明实际变量之间有什么区别吗?即。

typedef struct tag struct_name
{
    /* Code here */
};

VS

typedef struct tag
{
    /* Code here*/
} struct_name;

2)对于结构定义不使用typedef是否有任何好处,即使您不会声明该类型的另一个结构变量?

3)以下代码表示存在标识符Node的语法错误C2061,但我没有看到它有什么问题。我尝试在每个元素声明之前添加关键字struct,但这只会产生更多错误。有什么想法吗?

typedef struct Ticket
{
    char customer_name[20];
    int ticket_number;
} Ticket;

typedef struct Node
{
    Ticket ticket_info;
    Node *next;
    Node *previous;
} Node;

typedef struct Queue
{
    Ticket *front;
    Ticket *rear;
    int queue_count;
} Queue;

编辑:修复前两行代码,以明确说明元素声明的位置。

3 个答案:

答案 0 :(得分:3)

C中实际上有四个名称空间(虽然这取决于特定的计数方式,有些包括宏名称作为第五个空格,我认为这是考虑它们的有效方式):

  • goto标签
  • 代码(structunionenum
  • 结构或联合类型的实际成员(每种类型一个,因此您可以将其视为"许多"而不是"一个"名称空间)
  • 所有其他("普通")标识符,例如函数和变量名称以及通过typedef使其成为同义词的名称。

虽然(理论上)可以为struct vs union设置单独的空格,但C不会,所以:

struct foo; union foo; /* ERROR */

无效。然而:

struct foo { int a, b; };
struct bar { char b; double a; };

很好,表明两个不同struct类型的成员位于不同的名称空间中(因此,这使得" 4名称空间"以上可疑: - ))。

除此之外,C有一些适度(在某些方面不必要)复杂,但在实践中非常可行,结构类型如何工作的规则。

每个struct创建一个新类型,除非它引用现有类型。 struct关键字后面可能跟一个标识符,或者只是一个开括号{。如果只有一个开括号,struct会创建一个新类型:

struct { ... } X; /* variable X has a unique type */

如果有标识符,编译器必须查看(单个)标记名称空间以查看是否已定义该名称。如果没有,struct定义了一个新类型:

struct blart { ... } X; /* variable X has type <struct newname>, a new type */

如果标识符 已经存在,通常会返回现有类型:

struct blart Y; /* variable Y has the same type as variable X */

但是有一个特例。如果你在一个新的范围内(例如在一个函数的开头),一个&#34;空的声明&#34; - struct关键字,后跟一个标识符,后跟一个分号 - &#34;清除&#34;以前的可见类型:

void func(void) {
    struct blart; /* get rid of any existing "struct blart" */
    struct blart { char *a; int b; } v;

此处v有一个类型,即使已在struct blart之外定义了func

(这&#34;空洞的声明&#34;技巧在混淆的代码竞赛中非常有用。:-))

如果您在新范围内,则空白声明用于声明该类型存在。这对于解决另一个问题非常有用,我将在稍后介绍。

struct blart;

这里struct blart警告你(和编译器)现在有一个名为&#34; struct blart&#34;的类型。这种类型只是声明,这意味着结构类型是&#34;不完整&#34;,如果struct blart尚未定义。如果已定义struct blart ,则此类型已定义(以及&#34;完成&#34;)所以:

struct blart { double blartness; };

定义它,然后任何更早或更晚的struct blart引用相同的类型。


这就是为什么这种声明有用的原因。在C中,标识符的任何声明都具有范围。有四种可能的范围:&#34;文件&#34;,&#34;块&#34;,&#34;原型&#34;和&#34;功能&#34;。最后一个(函数范围)专门用于goto标签,因此我们可以从此处忽略它。这留下了文件,块和原型范围。文件范围是大多数人认为的全球&#34;的技术术语,与&#34;块范围相对应#34;这是&#34;本地&#34;:

struct blart { double blartness } X; /* file scope */
void func(void) {
    struct slart { int i; } v; /* block scope */
    ...
}

此处struct blart具有文件范围(&#34;全局&#34;变量X),struct slart具有块范围(&#34; local&#34) ;变量v)。

当块结束时,struct slart消失。您不能再按名称引用它;稍后struct slart创建一个新的不同类型,与后来的int v;创建新v的方式完全相同,并且不会引用块中的v范围内的函数func

唉,设计原始C标准的委员会(有充分理由)在功能原型中包含了一个与这些规则相互作用的方式。如果你写一个函数原型:

void proto(char *name, int value);

标识符(namevalue)在右括号后消失,正如您所期望的那样 - 您不希望这样创建一个名为{的块范围变量{1}}。不幸的是,name

也是如此
struct

名称void proto2(struct ziggy *stardust); 消失,但stardust也是如此。如果struct ziggy之前没有出现,那么在原型中创建的新的不完整类型现在已从所有人类范围内删除。它永远无法完成。好的C编译器在这里打印警告。

解决方案是在编写原型之前声明结构 - 是否完整[*] - :

struct ziggy

这一次,struct ziggy; /* hey compiler: "struct ziggy" has file scope */ void proto2(struct ziggy *stardust); 有一个已经存在的可见声明要引用回来,所以它使用现有的类型。

[*例如,在标头文件中,您通常不知道定义 struct ziggy的标头是否已包含在内,但您可以声明< / em>结构本身,然后定义使用指针的原型。]


现在,关于struct ...

typedef关键字在语法上是一个存储类说明符,如typedefregister,但它的行为非常奇怪。它在编译器中设置一个标志:&#34;将变量声明更改为类型名称别名&#34;。

如果你写:

auto

您(和编译器)理解这一点的方式是从删除typedef int TX, TY[3], *TZ; 关键字开始。结果需要在语法上有效,它是:

typedef

这将声明三个变量:

  • int TX, TY[3], *TZ; 的类型为TX
  • int的类型为&#34;数组3 TY&#34;
  • int的类型为&#34;指针TZ&#34;

现在你(和编译器)重新放入int,然后更改&#34;有&#34; to&#34;是&#34;的另一个名字:

  • typedef是类型TX
  • 的另一个名称
  • int是&#34; TY&#34;
  • 的数组3的另一个名称
  • int是&#34;指向TZ&#34;
  • 的另一个名称

int关键字以完全相同的方式与typedef类型一起使用。 它是创建新类型的struct关键字;然后struct更改变量声明来自&#34;具有类型...&#34; to&#34;是类型的另一个名称...&#34;。所以:

typedef

首先创建新类型,或者像往常一样返回现有类型typedef struct ca ca_t; 。然后,不是将变量struct ca声明为类型ca_t,而是将名称声明为类型struct ca的另一个名称。

如果省略struct标记名称,则只剩下两个有效的语法模式:

struct ca

或:

typedef struct; /* note: this is pointless */

在这里,typedef struct { char *top_coat; int top_hat; } zz_t, *zz_p_t; 创建了一个新类型(记住,我们在开始时就这么说了!),然后在结束struct {之后,那些已声明变量的标识符现在变成了类型-ALIASES。 同样,该类型实际上是由}关键字创建的(尽管这次几乎不重要;现在,typedef-names是引用该类型的唯一方法)。

(第一个无意义模式的原因是没有大括号,你在中坚持的第一个标识符是结构标记:

struct

因此你毕竟没有省略标签!)


至于最后一个问题,关于语法错误,这里的问题是C被设计为&#34;单通道&#34;语言,你(和编译器)永远不必看起来很远,以找出什么是什么。当你尝试这样的事情时:

typedef struct tag; /* (still pointless) */

您已经给编译器太多了,无法立即消化。它首先(实际上)忽略typedef struct list { ... List *next; /* ERROR */ } List; 关键字,除了设置更改变量声明方式的标志。这让你:

typedef

名称struct list { ... List *next; /* ERROR */ } 尚不可用。尝试使用List不起作用。最终编译器将到达&#34;变量声明&#34; (并且因为设置了标志,所以改为将其更改为类型别名),但到那时为时已晚;错误已经发生。

解决方案与功能原型相同:您需要&#34;前进声明&#34;。前向声明将为您提供一个不完整的类型,直到您完成List *next;部分的定义,但是确定:C允许您在多个位置使用不完整的类型,包括当您想要声明指针时,包括struct list别名创建。所以:

typedef

这比在任何地方写typedef struct list List; /* incomplete type "struct list" */ struct list { /* begin completing "struct list" */ ... List *next; /* use incomplete "struct list", through the type-alias */ }; /* this "}" completes the type "struct list" */ 所获得的收益相对较少(它节省了一点点打字,但那又是什么?好吧,好吧,我们中的一些人遭受了一些腕管/ RSI问题:-))。 / p>


[注意:这最后一段会引起争议......总是这样。]

事实上,如果你在心理上将struct list替换为struct,那么C代码就会变得更加出色,并且#34;强类型的语言&#34;球迷。而不是可怕的[%],弱酱:

type

他们可以写:

typedef int distance; /* distance is measured in discrete units */
typedef double temperature; /* temperatures are fractional */

这些是不完整的类型,真的是不透明的。要使用距离值创建或销毁任何必须调用一个函数(并且 - 对于大多数变量无论如何;外部标识符有一些例外 - 使用指针,唉):

#define TYPE struct

TYPE distance;
TYPE temperature;

没有人可以写:

TYPE distance *x = new_distance(initial_value);

increase_distance(x, increment);
use_distance(x);
destroy_distance(x);

它只是不会编译。

那些与他们的类型系统相比没有束缚和纪律的人可以通过完成类型来放松约束:

*x += 14; /* 3 inches in a dram, 14 ounces in a foot */

当然,现在&#34;骗子&#34;能做到:

TYPE distance { int v; };
TYPE temperature { double v; };

(好吧,至少最后一条评论是正确的)。

[%我认为并非如此可怕。有些人似乎不同意。]

答案 1 :(得分:2)

1)这两个代码块之间的区别在于第一个代码块是无效语法,而第二个代码块是好的和有用的。我使用第二个来定义结构并同时为结构定义typedef。我的代码有这样的东西:

typedef struct Dog {
  int age, barks;
} Dog;

在该行之后,我可以使用Dog mydog;struct Dog mydog;来定义狗。

重要的是要理解上面的代码正在做两件事。它定义了一个名为struct Dog的类型,然后定义了一个名为Dog的类型,它只引用struct Dog。您可以将其拆分为两个单独的步骤,如下所示:

struct Dog {
  int age, barks;
};    
typedef struct Dog Dog;

2)我总是在第一个代码块中使用如上所示的typedef,并且发现它没有任何问题。我会说放弃typdef没有任何好处。只是为了记录,如果你想省略typedef并且只定义一个结构,那么你的代码将是:

struct Dog {
  int age, barks;
};

如果你这样做,你只能输入struct Dog mydog;来制作新的狗;换句话说,该类型的名称仅为struct DogDog未命名类型。

3)问题是您正在尝试在“节点”的定义中使用“节点”。这将是一个循环定义。您只需按照以下方式编写即可修复所有内容:

struct Node;
typedef struct Node
{
    struct Node * next;
    struct Node * previous;
} Node;

答案 2 :(得分:0)

1)你的第一个例子是无效的语法。正确的方法是:

typedef struct tag {
    /* ... */
} struct_name;

2)对结构使用typedef使它们看起来像原子数据类型。它还允许您使类型不透明(因此其他代码块无法看到结构的内部)。就个人而言,我发现结构的类型定义是一个非常坏的习惯(因为struct标识符有助于区分原子类型的结构和类型的定义)。

3)您正在尝试在其自身内部使用typedef'd版本的节点结构!在自身内部定义结构时,您需要使用struct Node标识符。像这样:

typedef struct Node {
    Ticket ticket_info;
    struct Node *next;
    struct Node *previous;
} Node;