【问题标题】:Find an array element position in compile time在编译时查找数组元素位置
【发布时间】:2014-03-28 19:50:10
【问题描述】:

-- 已编辑--

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

#include <stdlib.h>
#include <stdio.h>

#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;i<NUM_OF_SONS;++i){
            if(item->son_id[i]!=0)
                    item->son[i] = find_item(item->son_id[i]);
    }
}

void prepare_items(){
    int i;
    for(i=0;i<sizeof(items)/sizeof(items[0]);++i){
            fill_sons(&items[i]);
    }
}

void print_sons(const struct item *item);

void print_item(const struct item *item){
    printf("The item %d has the text %s.\n",item->id,item->str);
    print_sons(item);
}

void print_sons(const struct item *item){
    int i;
    for(i=0;i<NUM_OF_SONS;++i){
            if(NULL!=item->son[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 个元素,我需要能够在其中插入或删除元素(在编译时)。所以&amp;items[1],&amp;items[2] 应该是ITEM_ID(5),ITEM_ID(10),某种预处理指令。怎么可能做到这一点?

先谢谢了,很抱歉发了这么长的帖子。

【问题讨论】:

  • 项目在数组中的位置在编译时是已知的,因为它们在源代码的初始化列表中的顺序相同。例如,我可以告诉数组元素 0 具有 ID 号为 4 的 item。请澄清一下,也许有一个您希望喜欢能够编写但不能编写的代码示例t.
  • 我认为 OP 要求的是一种独立于初始化元素顺序的方式。例如,允许他在索引 1 中插入编号为 5 的项目,将项目 5 向下移动到索引 2 等。我不知道编译时的方式,所以我可以建议的最好的方法是索引数组,初始化为运行时的开始,它索引项目数组。例如,如果索引器将指向示例项目数组的索引 2,则索引 10。 (不存在的索引器将包含 NULL。)
  • 请考虑进一步解释您的问题。现在,通过查看上面的部分,在 -- long version -- 行上,我建议这样做 int searchedID = 4; int i = 0; while ( items[i].id != searchedID ) if ( ++i &gt; sizeof items ) { /* doesn't exist */ break; } 和 i 最终将成为包含元素的索引IDsearchedID = 4,但我感觉被问到的东西没这么简单。也许是语言...
  • 对不起,我会尽量解释清楚。
  • @DoxyLover,我不能这样做,因为 id 可能是,例如,50000。我不能保存一个 50000 长度的数组,其中 99% 的数组是 NULL 元素。跨度>

标签: c metaprogramming


【解决方案1】:

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

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

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

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

#define CREATE_ARRAY(a, b, c) \
    {a, b, c},

struct item items[] = {
DATA(CREATE_ARRAY)
}

到目前为止,我们刚刚取得了相同的结果。但现在它的形式更加灵活。下一步是添加新 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 宏的更多信息,请参阅this question 的答案(我写了其中一个:P)。

【讨论】:

  • 哇!我从来没有听说过这个!但它完全符合问题!谢谢!我很抱歉,但我仍然不能投票给你的答案有用,因为我没有足够的声誉(事实上,这是我的第一个问题哈哈)。但这将是我拥有的第一件事!非常感谢!
  • 不客气。这是一个有趣的问题。即使您(还)不能投票,您也可以单击复选标记以接受答案。这比投票更有价值。 :)
【解决方案2】:

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

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

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

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

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

【讨论】:

  • 这如何真正解决问题?他将如何利用它来找出具有.id == 4 的元素的索引?他要做什么,写items[0].line_id - first_line?程序已经知道它会产生0,因为程序已经在方括号内放置了0。
  • 如果我这样做会怎样:我可以用宏定义所有 id,比如说,
    #define FOUR_ELEMENT 4&lt;br&gt; ... &lt;br&gt; DEFINE_ID(FOUR_ELEMENT)。这个DEFINE_ID 宏将在代码中放入 4,但是,它还将 FOUR_ELEMENT_POS 定义为 1,2...N。问题是我必须保留这个preprocessor variable,并找到一种自动递增它的方法。你怎么看? (PD:我不能在这里换行吗?)
  • 这就是我建议__LINE__的原因。它是文件的行号,所以它会自动递增,__LINE__ - first_line 是数组索引,假设您在初始化中没有空行。哦,cmets 中没有换行符,可能是因为复杂的项目应该是编辑或答案。
  • @ThoAppelsin,我认为这个想法是让每条记录都知道它的索引(items[i].line_id - first_line,我们还不知道i),因为程序已经知道每个索引处的记录通过索引。所以,第四条记录存储了它的.line_id,这是它的索引(C 不存储的元数据)加上first_line。
【解决方案3】:

首先,您必须知道,一旦您将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 <stdio.h>
#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 } },

    ...
};

如果这不是您要找的东西,我很抱歉。我真的很难理解原因。

【讨论】:

  • 对于错别字非常抱歉,ThoAppelsin。我应该在写在这里之前仔细检查代码。这一切的重点是我不希望数组发生变化,而且我知道数组的哪些位置包含子元素。但是,数组中可能有大约 200 或 300 个元素,我无法管理这么大的数组。我会尝试重写问题。
猜你喜欢
  • 2015-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多