代码之家  ›  专栏  ›  技术社区  ›  Morinar

需要改变不同结构中公共字段的方法吗

  •  3
  • Morinar  · 技术社区  · 17 年前

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

    由于不相关的原因,我无法更改这些结构或组合它们,或对它们执行任何其他操作。我必须按原样使用它们。

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

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

    11 回复  |  直到 17 年前
        1
  •  4
  •   Pete Kirkham    17 年前

    德米特法则——仅暴露最小结构——可能适用。

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

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

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

        2
  •  4
  •   ezpz    17 年前
    #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');
        // ...
    }
    

    当然,您始终可以扩展它以合并函数调用来执行操作,从而支持 memcpy strcpy 或者类似的事情都是微不足道的。

        3
  •  2
  •   rlbond    17 年前

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

    但是,如果所有公共字段都是第一个字段,则应该能够将指针强制转换为 ADDR_A

    static void updateFields( void *myStruct ) {
        ADDR_A *conv = myStruct;
        strcpy( conv->street, streetFromSomethingElse );
        strcpy( conv->city, cityFromSomethingElse );
    }
    
        4
  •  2
  •   mmmmmm    17 年前

    如果填充不起作用,可以使用宏

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

        5
  •  2
  •   Adam Wright    17 年前

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

    编辑:正如sztomi正确指出的那样,只要字段的相对偏移量相同,就可以访问非前缀元素。然而,我仍然更喜欢创建一个公共结构作为这个函数的“模板”;它可以防止在您可能选择的任何其他结构中更改字段名。

        6
  •  1
  •   Tamás Szelei    17 年前

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

    1. . 这样做可以访问以其名称作为哈希表中键的字段,而不考虑实际类型。这在C语言中需要很多麻烦,而且只有当您需要访问100个字段时,我才不推荐使用它。

    2. 坚持强键入,并清楚地更新字段 . 是的,这是你不想要的,但是对于中等数量的字段来说,这可能是可以的。

    3. 按照其他人的建议使用宏

    在C语言中把这样的东西拼凑在一起既不容易也不好(没有duck类型)。这是一个典型的例子,在这个例子中,动态语言会更加舒适(也许您可以从代码中调用python?:)

        7
  •  1
  •   sambowry    17 年前

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

    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
  •  1
  •   Community Mohan Dere    9 年前

    一个尚未提及的选项是使用 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)

    您有责任获得正确的描述符并使用正确的描述符。


    另见: What is the purpose and return type of the __builtin_offsetof operator?

        9
  •  0
  •   Carson Myers    17 年前

    如果你不能以任何方式改变结构,我认为传递类型是一个好主意。制作一组函数,对一个地址做一件事,并让它采用地址的类型。

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

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

        10
  •  0
  •   kriss    17 年前

    你呢 do is有几个函数,比如你建议的update_字段,并根据结构类型调用正确的函数( 那个 很简单,可以隐藏在宏中以避免合成噪音)。

    #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"
        }
    

    它也几乎可以(但不是像我知道的那样长)使用 类型

    如果您的结构大小不同,我们甚至可以只使用一个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);
    
        11
  •  -1
  •   codymanix    17 年前
    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*传递给您的函数。

    推荐文章