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

时间:2014-03-28 19:50:11

标签: c metaprogramming

- 已编辑 -

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

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]);
}

但是,数组中可能有大约200个元素,我需要能够在其中间插入或删除元素(在编译时)。所以static struct item items[] = { {4, "FIRST ELEMENT"}, {5, "SND ELM"}, {10, "THIRD ELM"}, {15, "FATHER", {&items[1],&items[2]}}, {0, 0 } }; 应该是&items[1],&items[2],某种预处理器指令。怎么能实现呢?

提前致谢,对于这篇长篇文章感到抱歉。

3 个答案:

答案 0 :(得分:2)

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

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

#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-macros的更多信息,请参阅this question的答案(其中一个我写道:P)。

答案 1 :(得分:0)

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

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

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

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

它不是一个好的解决方案,但我认为它是关于C在没有编写代码将一个数组的内容加载到另一个数组的情况下所做的最好的事情。在移动过程中跟踪元数据。

答案 2 :(得分:0)

首先,您必须知道,一旦您将items声明为数组到常量项结构(您在编写const struct item items[];时执行此操作),您就无法完成初始化后更改这些结构的内容。

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

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 } },

    ...
};

对不起,如果这不是您要找的东西。我真的很难理解事业。