在C语言中遍历结构体

4
我正在处理一个涉及通过单向链表实现数据结构的项目。更具体地说,我想知道是否有一种方法可以自动循环(迭代)访问struct中不同数据类型的属性 - 这将在读取输入或添加更多属性时非常有帮助,因为我不必手动更改每个属性的代码。

具体的结构体

typedef struct Student
{
    char regNumber[30];
    char fName[30];
    char lName[30];
    char email[50];
    int phoneNumber;
    short age;
} Student;

例如:attribute[0] 将会是 regNumberattribute[1] 将会是 fNameattribute[n] 将会是第n个元素。

1
https://dev59.com/5mYq5IYBdhLWcg3whQ40 - Rashik
@Rashik 我知道这个解决方案,但它是关于相同数据类型的属性,而在我的情况下,我的一些数据类型是不同的。我知道我可以把它们全部设为 char,但我想知道是否有其他的解决方法。 - Alexandros Voliotis
2
你不能在C语言中这样做。如果你所做的任何事情依赖于类似这样的东西才能工作,那么你很可能是在错误的思路上考虑问题。 - Marco Bonelli
1
你需要的是反射能力。请看这里:https://github.com/loganek/mkcreflect - Robert Harvey
跳过C++不是一个选项吗? - Déjà vu
显示剩余3条评论
1个回答

3

我无法想到任何既不使用未定义行为,又不使用非常奇怪的结构来完成此操作的好方法。而且您希望使用不同类型的字段并不会使它变得更容易。

如果我想要编写这样的代码(其实我并不想),我可能会做类似以下的事情。

void *get_attr(struct Student *student, int field)
{
    switch(field) {
    case 0 : return (void*)&student->regNumber;
    case 1 : return (void*)&student->fName;
    case 2 : return (void*)&student->lName;
    case 3 : return (void*)&student->email;
    case 4 : return (void*)&student->phoneNumber;
    case 5 : return (void*)&student->age;
    }
    return NULL;
}

然后你可以像这样使用它:

int main()
{
    struct Student s = { "xxx333", "Jonny", "BGood", "my@email.com", 12345, 22 };
    printf ("%s\n%s\n%s\n%s\n%d\n%d\n",
            (char*)get_attr(&s, 0),
            (char*)get_attr(&s, 1),
            (char*)get_attr(&s, 2),
            (char*)get_attr(&s, 3),
            *(int*)get_attr(&s, 4),
            *(short*)get_attr(&s, 5)
        );
}

就我个人而言,我没有看到避免那些转换的好方法。一种方式是这样做,但不一定是好方法:

union attr_field {
    char *c;
    int *i;
    short *s;
};

enum attr_type { CHAR, INT, SHORT };

struct attr {
    union attr_field attr;
    enum attr_type type;
};

struct attr get_attr2(struct Student *student, int field)
{
    struct attr ret;
    switch(field) {
    case 0 : ret.attr.c = student->regNumber; ret.type = CHAR; break;
    case 1 : ret.attr.c = student->fName; ret.type = CHAR; break;
    case 2 : ret.attr.c = student->lName; ret.type = CHAR; break;
    case 3 : ret.attr.c = student->email; ret.type = CHAR; break;
    case 4 : ret.attr.i = &student->phoneNumber; ret.type = INT; break;
    case 5 : ret.attr.s = &student->age; ret.type = SHORT; break;
    }
    return ret;
}

void print_attr(struct attr a)
{
    switch(a.type) {
    case CHAR: printf("%s\n", a.attr.c); break;
    case INT: printf("%d\n", *a.attr.i); break;
    case SHORT: printf("%d\n", *a.attr.s); break;
    }
}

int main()
{
    struct Student s = { "xxx333", "Jonny", "BGood", "my@email.com", 12345, 22 };

    for(int i=0; i<6; i++) {
        struct attr a = get_attr2(&s, i);
        print_attr(a);
    }
}

请注意,我有时会将结构体和指向结构体的指针作为函数参数使用。这种选择并没有特别的原因,只是碰巧就这样。你可以任选其一,它们都有各自的优缺点。如果性能是一个问题,我会选择指针。联合体也是同样的情况。我本可以选择字符数组并使用strncpy代替,对于int和short类型也可以跳过指针。在这里我的想法是,如果所有的联合成员都是指针,那么更清晰一些。但你需要自己对所有这些做出决定。如果你选择指针,可能明智的做法是在适当的地方使用const限定符。
如果你真的想这么做,我猜你可以像这样做:
void *attribs[6];
attribs[0] = (void*)s.regNumber;
printf("%s", (char*)attribs[0]);

这可以与上述提到的技术相结合。例如

struct attr attribs[6];
for(int i=0; i<6; i++)
    attribs[i] = get_attr2(&s, i);

3
没有办法在没有额外反射细节的情况下,正确地使用那些 void* 指针并知道该如何处理它们。 - tadman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接