【问题标题】:generic function to extract an array of a specific struct data member from an array of structs用于从结构数组中提取特定结构数据成员数组的通用函数
【发布时间】:2014-04-17 18:09:21
【问题描述】:

如果我有一个结构,说

struct mystuff {
    int a;
    int b;
    int c;
}

然后我有一个这些结构的数组,

struct mystuff array[] = {...};

现在说我想将结构数据成员 a 提取到它自己的数组中,

int* get_mystuff_a(struct mystuff *array, int n)
{
    int i;
    int *a_array;

    a_array = malloc(n*sizeof(int));

    for(i=0;i<n;i++) {
        a_array[i] = array[i].a;
    }
    return a;
}

现在如果我想对 b 做同样的事情,我必须编写另一个函数,但除了

b_array[i] = array[i].b;

有没有一种方法可以编写一个通用函数并指定我要提取的数据成员作为参数?

我认为宏函数可能有效,但我不知道你会如何编写它

如果您还可以指定数据类型,例如数据成员的 int、float、double 等 - 使其更加通用

【问题讨论】:

  • 哈哈 - 我知道使用预处理器可以做到这一点 - 我只是没有实际编写它的能力,我没有太多使用预处理器,当我有时我倾向于得到它错了 - 需要朝着正确的方向推进
  • 您可能会使用offsetof 作为通用函数的参数。但在我看来,罗德里戈建议的宏会更安全。
  • 使用宏可以实现令人惊奇的效果——我使用 uthash 和 utarray,它们完全在自己的头文件中仅使用宏实现——但肯定很难理解/调试。我认为有一种方法可以让 gcc 在预处理器运行后输出代码,然后您可以逐步完成,但我还没有尝试过

标签: c function macros struct


【解决方案1】:

把它写成一个宏很容易,并不是说它总是一个好主意。

在这种情况下,我认为最好编写一个构建函数的宏,而不是尝试直接内联完成工作:

  1. 获取您的非通用代码。
  2. 在每一行的末尾添加\,因为宏只有一行。
  3. 在开头添加#define NAME(...) \。
  4. 根据需要替换宏参数。
  5. 如果需要,不要忘记额外的括号。

结果将是:

#define GET_MYSTUFF(TYPE, FIELD) \
TYPE* get_mystuff_##FIELD(struct mystuff *array, int n) \
{ \
    int i; \
    TYPE *res_array; \
    res_array = malloc(n*sizeof(TYPE)); \
    for(i=0;i<n;i++) { \
        res_array[i] = array[i].FIELD; \
    } \
    return res_array; \
}

现在您可以使用以下命令创建所有函数:

GET_MYSTUFF(int, a)
GET_MYSTUFF(int, b)
GET_MYSTUFF(int, c)

您甚至可以将输入数组类型添加为宏的另一个参数,但这留给读者作为练习。

更新:注意这个宏扩展为一个函数定义,你不应该在头文件中定义你的函数,否则你会重复定义错误。有几种解决方案,选择你最好的:

  1. 将您的函数设为静态 (static TYPE* get_mystuff_##FIELD(...)。这样,包含您的函数的每个编译单元都将获得该函数的副本。
  2. 将函数的定义移动到mystuff.c 文件中,并且只在mystuff.h 中写入函数原型。您可以手动编写这些原型或为此创建宏:

    #define GET_MYSTUFF_PROTO(TYPE, FIELD) TYPE* get_mystuff_##FIELD(struct mystuff *array, int n);

  3. 有一点额外的工作,如果你想要一点乐趣,你可以使用X_Macros!

mystuff.h

#ifndef GET_MYSTUFF
#define GET_MYSTUFF(TYPE, FIELD) \
    TYPE* get_mystuff_##FIELD(struct mystuff *array, int n);
#endif

GET_MYSTUFF(int, a)
GET_MYSTUFF(int, b)
GET_MYSTUFF(int, c)

mystuff.c

/* include for prototypes */
#include "mystuff.h"

#undef GET_MYSTUFF
#define GET_MYSTUFF(TYPE, FIELD) \
TYPE* get_mystuff_##FIELD(struct mystuff *array, int n) \
{ \
    int i; \
    TYPE *res_array; \
    res_array = malloc(n*sizeof(TYPE)); \
    for(i=0;i<n;i++) { \
        res_array[i] = array[i].FIELD; \
    } \
    return res_array; \
}

/* include for definitions */
#include "mystuff.h"

【讨论】:

  • 这将减轻很多非常相似的代码,这从 DRY 的角度来看是好的,但我承认这样的函数在 c 中可能是不好的做法。我现在会摸不着头脑,想知道如何使它与结构类型的输入数组无关
  • 这种方法给我带来了很多“多重定义”问题
  • 嗯,你是在头文件中定义你的函数,不是吗?请查看更新后的答案。
  • @bph:关于宏直接干活,不用函数也可以,但是因为是C语法,比较棘手:需要句块,但也需要返回一个值,不知何故,最好用一个函数来完成。如果您的编译器支持,您可以将它们标记为内联。
  • @bph: 如果你使用 X-Macro 头文件一次性声明结构,在另一个声明 get_* 函数,并在另一个定义它们......你将拥有字段声明只在一个地方......还有很多没人想看的代码!
【解决方案2】:

我得出了一个类似于rodrigo 的结论,最简单的方法是使用宏来声明你的“getter”,然后你可以像这样使用它们

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

struct mystuff {
    int a;
    int b;
    int c;
};

#define declare_getstuff_for(f) int* get_mystuff_##f(struct mystuff *array, int n) \
{ \
    int i; \
    int *a_array; \
\
    a_array = malloc(n*sizeof(int)); \
\
    for(i=0;i<n;i++) { \
        a_array[i] = array[i].f;  \
    }  \
    return a_array; \
}

declare_getstuff_for(a)
declare_getstuff_for(b)
declare_getstuff_for(c)


int main(void) {
    int *result1, *result2, *result3;
    struct mystuff array[3];
    struct mystuff el1, el2, el3;
    el1.a = el1.b = el1.c = 1;
    el2.a = el2.b = el2.c = 5;
    el3.a = el3.b = el3.c = 3;

    array[0] = el1;
    array[1] = el2;
    array[2] = el3;

    result1 = get_mystuff_a(array, 3);
    printf("%d, %d, %d\n", result1[0], result1[1], result1[2]);

    result2 = get_mystuff_b(array, 3);
    printf("%d, %d, %d\n", result2[0], result2[1], result2[2]);

    result3 = get_mystuff_c(array, 3);
    printf("%d, %d, %d\n", result3[0], result3[1], result3[2]);

    // Now free your memory :)

    return 0;
}

要记住的相关部分是用于生成函数名称的标记粘贴运算符 ##、通常的宏替换和多行 \ 标记。

除此之外:请注意,此类代码中的错误通常很难调试。尽可能选择干净、可维护的代码,并在绝对必要时使用宏。

适度使用 :)

试试看:http://ideone.com/bbYsJK

【讨论】:

  • 我认为我更喜欢将所需的数据成员作为宏的参数 - 这样看起来更干净
  • 如果模板不再适用,我会同意这一点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-19
相关资源
最近更新 更多