需要改变不同结构中的常见字段

时间:2009-09-18 17:06:03

标签: c pointers function struct

我在这里使用C编程,适用于Windows和各种Unix平台。我有一组具有共同字段的结构,但也有不同的字段。例如:

typedef struct {
    char street[10];
    char city[10];
    char lat[10];
    char long[10];
} ADDR_A;

typedef struct {
    char street[10];
    char city[10];
    char zip[10];
    char country[10];
} ADDR_B;

他们显然不那么简单,我实际上有6种不同的结构,但这是基本的想法。

我想要的是一个函数,我可以将指针传递给其中一个并能够更新公共字段。类似的东西:

static void updateFields( void *myStruct ) {
    strcpy( myStruct->street, streetFromSomethingElse );
    strcpy( myStruct->city, cityFromSomethingElse );
}

显然,我在那里写的东西由于void指针而无法工作,并且某种类型的强制转换是有序的,但是没有一个很好的方法来强制转换(是吗?)。我并不反对以某种方式指定结构类型的附加参数。

由于不相关的原因,我不能改变这些结构或将它们组合或用它们做任何其他事情。我必须按原样使用它们。

一如既往,感谢您的帮助。

编辑:正如我在下面的评论中添加的那样,公共字段不保证全部在开头或全部在结尾或其他任何地方。他们只是保证存在。

11 个答案:

答案 0 :(得分:4)

demeter规则 - 仅暴露最小结构 - 可能适用。

static void updateStreetAndCity ( char *street, char *city ) {
    strcpy( street, streetFromSomethingElse );
    strcpy( city, cityFromSomethingElse );
}

这有一点点开销,你必须用两个指针而不是一个指针调用它,但它适合一个函数的账单,它将适用于你所有的结构类型。

如果您的结构体大小不同,您可以使用宏来提供静态多态性,就像在C99的tgmath.h中所做的那样,但这是相当多的工作。

答案 1 :(得分:4)

#define ASSIGN(type,object,member,value) do { \
    ((type *)object)->member = value; \
    } while (0)

现在你可以做类似的事情:

#include <stdio.h>

struct foo {
    int x;
    char y;
};

struct bar {
    char y;
    int x;
};

int main () {
    struct foo foo;
    struct bar bar;
    ASSIGN(struct foo, &foo, x, 100);
    ASSIGN(struct bar, &bar, y, 'x');
    // ...
}

当然,您可以随时扩展它以合并函数调用来执行操作,因此支持memcpystrcpy或类似操作将是微不足道的。

答案 2 :(得分:2)

只要您要更新的部分是每个结构上的公共前缀,您就可以定义一个列出这些前缀字段的新结构,并将参数转换为此结构。假设您创建的前缀struct的布局是相同的(即没有奇怪的编译器填充问题),更新将适用于具有该前缀的所有结构。

编辑:正如sztomi正确指出的那样,只要字段的相对偏移量相同,就可以访问非前缀元素。但是,我仍然更喜欢创建一个通用结构来充当此功能的“模板”;它可以防止您选择的任何其他结构中的字段名称更改。

答案 3 :(得分:2)

这是我插入C ++的时候,因为这是多态性的一个明显用途。或模板。或函数重载。

但是,如果所有公共字段都是第一个,那么您应该只需将指针转换为ADDR_A并将其视为:{/ p>

static void updateFields( void *myStruct ) {
    ADDR_A *conv = myStruct;
    strcpy( conv->street, streetFromSomethingElse );
    strcpy( conv->city, cityFromSomethingElse );
}

答案 4 :(得分:2)

你可以使用一个宏 - 如果填充亚当建议不起作用

#define UPDATE_FIELDS( myStruct, aStreet, aCity ) \
  strcpy( myStruct->street, aStreet ); \
  strcpy( myStruct->city, aCity );

但是c ++插件更好;)

答案 5 :(得分:1)

考虑到结构没有至少相似的布局,实现这一点的方法有限,既不简短也不优雅:

  1. 使用哈希表而不是结构来存储数据。这样做可以访问其名称作为哈希表中的键的字段,而不管实际类型如何。这在C中需要很多麻烦,只有当你需要访问一百个字段时,我才会推荐它。

  2. 坚持强打字并明确更新字段。是的,这是你不想要的,但对于适量的领域,这可能没问题。

  3. 使用其他人建议的宏。但是我认为最好尽可能避免使用宏。这不值得。

  4. 在C中一起破解这样的东西并不容易也不好(没有鸭子打字)。这是一个典型的例子,动态语言会更舒服(也许你可以从你的代码中调用python?:))

答案 6 :(得分:1)

尚未提及的一个选项是使用offsetof()中的<stddef.h>宏为字段创建描述符。然后,您可以将适当的描述符传递给修改函数。

typedef struct string_descriptor
{
    size_t offset;
    size_t length;
} string_descriptor;

在C99中,您可以执行以下操作:

int update_string(void *structure, const string_descriptor d, const char *new_val)
{
    char *buffer = (char *)structure + d.offset;
    size_t len = strlen(new_val);
    size_t nbytes = MIN(len, d.length);
    // Optionally validate lengths, failing if the new value won't fit, etc
    memcpy(buffer, new_val, nbytes);
    buffer[nbytes] = '\0';
    return(0);  // Success
}

void some_function(void)
{
    ADDR_A a;
    ADDR_B b;
    static const string_descriptor a_des =
        { .offset = offsetof(ADDR_A, street), .length = sizeof(a.street) };
    static const string_descriptor b_des =
        { .offset = offsetof(ADDR_B, street), .length = sizeof(b.street) };

    update_string(&a, a_des, "Regent St."); // Check return status for error!
    update_string(&b, b_des, "Oxford St."); // Check return status for error!
}

显然,对于单一类型,这看起来很笨拙,但仔细推广后,您可以使用多个描述符,描述符可以更大(更复杂)但通过引用传递,并且只需要定义一次,等等。在描述符中,相同的update_string()函数可用于更新两个结构中任何一个的任何字段。棘手的一点是长度初始化器sizeof(b.street);我认为这需要一个正确类型的变量来提供正确的信息,尽管我很高兴采取(标准C99)修订来删除该限制。非最小通用描述符可以包含类型指示符和成员名称以及可能相关的其他信息 - 尽管有许多想法,我仍然坚持一个非常简单的结构。您可以使用函数指针为不同的字符串提供不同的验证 - 纬度和经度的验证与街道名称的验证非常不同。

您还可以编写带有描述符数组并执行多个更新的函数。例如,您可以为ADDR_A中的字段创建一个描述符数组,为ADDR_B创建另一个描述符数组,然后为新字符串数组中的字段传递新值等。

你有责任让描述符正确并使用正确的描述符。


另请参阅:What is the purpose and return type of the __builtin_offsetof operator?

答案 7 :(得分:1)

只是一个未经考验的想法:

struct ADDR_X_offset {
  int street;
  int city;
} ADDR_A_offset = { offsetof(ADDR_A,street), offsetof(ADDR_A,city) },
  ADDR_B_offset = { offsetof(ADDR_B,street), offsetof(ADDR_B,city) };

static void updateFields( void *myStruct, struct ADDR_X_offset *offset ) {

    strcpy( myStruct+offset->street, streetFromSomethingElse );

    strcpy( myStruct+offset->city,   cityFromSomethingElse );

}

答案 8 :(得分:0)

如果你没有传递结构的类型(比如你的void *建议),基本上你注定要失败。它们只是一堆带有起始地址的内存。

可以做的事情有几个函数,比如你建议的update_fields,并且根据结构的类型以某种方式调用正确的函数( 很容易且可以很好隐藏在宏中以避免语法噪音。)

这样做可以获得如下代码:

#include <stdio.h>
#include <string.h>

typedef struct {
    char street[10];
    char city[10];
    char lat[10];
    char lon[10];
} ADDR_A;

typedef struct {
    char street[10];
    char city[10];
    char zip[10];
    char country[10];
} ADDR_B;

#define UPDATEFIELDS(type, value) updateFields_##type(value)

static void updateFields_ADDR_A(ADDR_A *myStruct ) {
        strcpy(myStruct->street, "abc" );
        strcpy(myStruct->city, "def" );
    }

static void updateFields_ADDR_B(ADDR_B *myStruct ) {
        strcpy(myStruct->street, "abc" );
        strcpy(myStruct->city, "def" );
    }

int main(){
    ADDR_A a;
    ADDR_B b;

    updateFields_ADDR_A(&a);
    updateFields_ADDR_B(&b);
    UPDATEFIELDS(ADDR_A, &a);

    printf("%s\n", a.city);
    printf("%s\n", b.city);
}

请注意,您显然不应该在updateFields_XXX函数中编写实际的字段更改代码,而只是使用该代码作为包装器来获取内部字段的正确偏移量。

我还想拥有代码区域,你可以再次使用宏,

#define COMMONBODY strcpy(myStruct->street, "abc" );\
        strcpy(myStruct->city, "def" );


static void updateFields_ADDR_A(ADDR_A *myStruct ) {
    COMMON_BODY
    }

static void updateFields_ADDR_B(ADDR_B *myStruct ) {
    COMMON_BODY
    }

甚至将公共代码放在一个单独的文件中并包含两次(非常不寻常,但工作文件)

static void updateFields_ADDR_A(ADDR_A *myStruct ) {
    #include "commonbody.c"
    }

static void updateFields_ADDR_B(ADDR_B *myStruct ) {
    #include "commonbody.c"
    }

除了名称修改部分之外,使用 typeof 运算符几乎可以(但不是很长)。

如果您的结构大小不同,我们甚至可以只使用一个updateFields函数(带void *),它将结构的大小作为第二个参数,并使用它自动转换为正确的类型。它很脏但可能[如果有人想看到解决方案发展,请发表评论]。

您也可以使用联盟。如果存在公共前缀,则可以使用任何联合成员访问公共字段(但我知道并非如此)。您必须明白,定义此类并集不会改变现有数据结构的任何内容。这基本上定义了ADDR_A或ADDR_B类型,而不是可以使用指针引用。

typedef union {
    ADDR_A a;
    ADDR_B b;
} ADDR_A_or_B;

如果有共同的前缀,您可以使用任何访问者字段设置这些变量:

static void updateFields(ADDR_A_or_B *myStruct ) {
    strcpy( myStruct->a.street, someStreeValueFromSomewhereElse);
    strcpy( myStruct->a.city, someCityValueFromSomewhereElse);
}

你只需要在致电时施展:

ADDR_A a;
updateFields((ADDR_A_or_B *)&a);

ADDR_B b;
updateFields((ADDR_A_or_B *)&b);

答案 9 :(得分:0)

如果你不能以任何方式改变结构,我认为传递这种类型的想法很好。制作一堆对地址做一件事的函数,让它采用地址类型。

int updateStreetAndCity(void *addr, int type);
double getLatitude(void *addr, int type);

等。您可以使用枚举或几个宏定义来创建类型。然后在每个函数中,根据您传入的类型,调用另一个特定于该类型的函数来为您完成工作(避免使用可以执行数千种不同操作的怪物切换语句)。

答案 10 :(得分:-1)

typedef struct {
    char street[10];
    char city[10];
} ADDR_BASE;

typedef struct {
    struct ADDR_BASE addr;
    char lat[10];
    char long[10];
} ADDR_A;

typedef struct {
    struct ADDR_BASE addr;
    char zip[10];
    char country[10];
} ADDR_B;

这样您可以将ADDR_BASE *传递给您的函数。