在编译时查找数组元素位置

– 编辑 –

大家好。 我有一个元素数组,这些元素在程序的所有执行中都不会改变,并且item可以在自己的数组中包含子。 我必须在处理之前准备好arrays。 但是,因为我知道数组不会改变,我想将它声明为const ,并在编译时准备所有这些,所以我可以丢弃整数int son_id[NUM_OF_SONS]prepare_items()函数和数组在我看来,宣言将更清楚。

 #include  #include  #define NUM_OF_SONS 5 struct item{ int id; char *str; int son_id[NUM_OF_SONS]; const struct item *son[NUM_OF_SONS]; }; const struct item *find_item(int id); static struct item items[] = { {4, "FIRST ELEMENT"}, {5, "SECOND ELM"}, {10, "THIRD ELM"}, {15, "FATHER", {5,10}}, {0, 0 } }; const struct item *find_item(int id){ int i; for(i=0; items[i].str != NULL; ++i){ if(items[i].id == id) return &items[i]; } return NULL; } void fill_sons(struct item *item){ int i; for(i=0;ison_id[i]!=0) item->son[i] = find_item(item->son_id[i]); } } void prepare_items(){ int i; for(i=0;iid,item->str); print_sons(item); } void print_sons(const struct item *item){ int i; for(i=0;ison[i]) print_item(item->son[i]); } } int main(){ prepare_items(); print_item(&items[0]); print_item(&items[3]); } 

我喜欢这样的事情:

 static struct item items[] = { {4, "FIRST ELEMENT"}, {5, "SND ELM"}, {10, "THIRD ELM"}, {15, "FATHER", {&items[1],&items[2]}}, {0, 0 } }; 

但是,数组中可能有大约200个元素,我需要能够在其中间插入或删除元素(在编译时)。 所以&items[1],&items[2]应该是ITEM_ID(5),ITEM_ID(10) ,某种预处理器指令。 怎么可能实现呢?

在此先感谢,并为长篇文章感到抱歉。

与C中的模板(我所知道的)最接近的等价物是X-Macros。 我认为你可以实现这个结果,但它需要为每个结构引入另一个标识符( 实际上它没有 – 向下滚动到“编辑”! ),我们可以通过在枚举中声明这些标识符来与数组同步。

首先,我们将初始化元素更改为宏调用的forms。 对于我喜欢的样式,这个宏的名称并不重要,所以我称之为_ 。 所有调用都需要相同数量的元素,因此必要时添加一个空列表。 整个事情都包含在一个大宏中。 这个大宏将接收另一个宏作为参数,它调用每个元素。

 #define DATA(_) \ _(4, "FIRST_ELEMENT", {}) \ _(6, "SECOND_ELEMENT", {}) \ _(10, "FATHER ELEMENT", {15, 20}) \ _(15, "SON ELEMENT 1", {}) \ _(20, "SON ELEMENT 2", {}) \ _(0, NULL, {}) 

现在我们可以通过定义一个用法宏来声明数组数据,该宏用于以数组声明的正确forms发出参数。

 #define CREATE_ARRAY(a, b, c) \ {a, b, c}, struct item items[] = { DATA(CREATE_ARRAY) } 

到目前为止,我们刚刚取得了相同的结果。 但现在它的forms更加灵活。 下一步是添加新ID。

 #define DATA(_) \ _(FIRST, 4, "FIRST_ELEMENT", {}) \ _(SECOND, 6, "SECOND_ELEMENT", {}) \ _(FATHER, 10, "FATHER ELEMENT", {15, 20}) \ _(SON1, 15, "SON ELEMENT 1", {}) \ _(SON2, 20, "SON ELEMENT 2", {}) \ _(END, 0, NULL, {}) 

并调整CREATE_ARRAY宏以考虑新参数。

 #define CREATE_ARRAY(a, b, c, d) \ {b, c, d}, struct item items[] = { DATA(CREATE_ARRAY) }; 

现在有趣的部分。 我们创建另一个宏来生成ID作为枚举值。

 #define CREATE_IDS(a, b, c, d) \ a, enum identifiers { DATA(CREATE_IDS) }; 

现在数据可以使用这些标识符来索引数组。

 #define DATA(_) \ _(FIRST, 4, "FIRST_ELEMENT", {}) \ _(SECOND, 6, "SECOND_ELEMENT", {}) \ _(FATHER, 10, "FATHER ELEMENT", {SON1, SON2}) \ _(SON1, 15, "SON ELEMENT 1", {}) \ _(SON2, 20, "SON ELEMENT 2", {}) \ _(END, 0, NULL, {}) 

当然,从结构中删除child_id成员,因为我们的新标识符是所需的数组索引,直接。


编辑。 等一会。 你已经有了标识符。 它们已经是独一无二的了。 所以我们不需要引入新的。 我们可以简单地破坏他们! 还需要__VA_ARGS__来处理子列表中可能的嵌入式逗号。

 #define CREATE_ARRAY(a, b, ...) \ {a, b, __VA_ARGS__ }, #define ID_(x) ID ## x #define CREATE_IDS(a, b, ...) \ ID_(a), #define DATA(_) \ _(4, "FIRST_ELEMENT", {}) \ _(6, "SECOND_ELEMENT", {}) \ _(10, "FATHER ELEMENT", {ID15, ID20}) \ _(15, "SON ELEMENT 1", {}) \ _(20, "SON ELEMENT 2", {}) \ _(0, NULL, {}) enum identifiers { DATA(CREATE_IDS) }; struct item items[] = { DATA(CREATE_ARRAY) }; 

cpp -P输出(添加了换行符):

 enum identifiers { ID4, ID6, ID10, ID15, ID20, ID0, }; struct item items[] = { {4, "FIRST_ELEMENT", {} }, {6, "SECOND_ELEMENT", {} }, {10, "FATHER ELEMENT", {ID15, ID20} }, {15, "SON ELEMENT 1", {} }, {20, "SON ELEMENT 2", {} }, {0, NULL, {} }, }; 

有关X-macros的更多信息,请参阅此问题的答案(其中一个我写道:P)。

关于我能提供的最好的 – 因为C绝对不是为了跟踪这种元数据而设计的 – 是__LINE__常数。

__LINE__将当前行号插入到程序中,您可以将其用作struct item的字段。 显然,您还需要承诺在items定义中没有任何空格,并且还要知道items在文件中的起始位置。 后者,你可以这样做:

 int first_line = __LINE__ + 2; const struct item items[] = { {4, "FIRST_ELEMENT", __LINE__}, 

然后记得从line_id (或任何你想要的line_id )字段中减去first_line

这不是一个好的解决方案,但我认为这是C将要做的最好的事情,而无需编写代码将一个数组的内容加载到另一个数组中,并在移动过程中跟踪元数据。

首先,你必须知道,一旦你将items声明为一个数组到常量项目结构,你在编写const struct item items[];时会这样做const struct item items[]; ,然后,您将无法在初始化后更改这些结构的内容。

因此,例如,您将无法将任何内容分配给items数组中任何一个结构内的child数组的任何元素。 哇,这很大,让我们给出一个代码示例:

 items[2].child[0] = &( items[3] ); // or just ... items + 3; // you won't be able to do this because items[2] is defined as a constant 

我真的不明白你的目标是什么,但这里有一件事可能会帮助你。 您可以执行以下操作:

 #include  #define MaximumChildCount 5 // personal preference, easier to read, change as you wish typedef struct item{ int id; char *str; // I left the childs_id out, since you've implied that you'd like that out struct item *child[MaximumChildCount]; }; int main( ){ const struct item sons[] = { { 15, "SON ELEMENT 1" }, { 20, "SON ELEMENT 2" } }; // creating sons before the parent, biologically nonsense? // well, if you want to keep the record of child elements inside parent // and make them all constants, then you need to have children before const struct item items[] = { { 4, "FIRST_ELEMENT" }, { 6, "SECOND_ELEMENT" }, { 10, "FATHER ELEMENT", { sons, sons + 1 } }, // sons points to first son, sons + 1 points to the second one // assigned them at initialization, just like you had with { 15, 20 } { 0, NULL } }; printf( "%s", items[2].child[1]->str ); return 0; } 

这将打印"SON ELEMENT 2" 。 你可以用以下方式制作儿子:

 const struct item son1 = { 15, "SON ELEMENT 1" }; const struct item son2 = { 20, "SON ELEMENT 2" }; 

然后在初始化期间分配它们,如下所示:

 ... = { ... ... { ..., ..., { &son1, &son2 } }, ... }; 

如果这不是你想要的东西,我很抱歉。 我真的很难理解事业。