需要改变不同结构中的常见字段
对于Windows和各种Unix平台,我在这里用C编程。 我有一组具有共同字段的结构,但也有不同的字段。 例如:
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指针和某种类型的强制转换是有序的,但是没有一个很好的方法来强制转换(是吗?)。 我并不反对以某种方式指定结构类型的附加参数。
由于不相关的原因,我不能更改这些结构或将它们组合或用它们做任何其他事情。 我必须按原样使用它们。
一如既往,感谢您的帮助。
编辑:正如我在下面的评论中添加的那样,公共字段不保证全部在开头或全部在结尾或其他任何内容。 他们只是保证存在。
考虑到结构体至少没有类似的布局,实现这一点的方法有限,既不简短也不优雅:
-
使用哈希表而不是结构来存储数据 。 这样做可以访问其名称作为哈希表中的键的字段,而不管实际类型如何。 这在C中需要很多麻烦,只有当你需要访问一百个字段时,我才会推荐它。
-
坚持强类型并明确更新字段 。 是的,这是你不想要的,但对于适量的领域,这可能没问题。
-
像其他人建议的那样使用宏 。 但是我认为最好尽可能避免使用宏。 这不值得。
在C中一起破解这样的东西并不容易也不好(没有鸭子打字)。 这是一个典型的例子,动态语言会更舒服(也许你可以从你的代码调用python?:))
demeter规则 – 仅暴露最小结构 – 可能适用。
static void updateStreetAndCity ( char *street, char *city ) { strcpy( street, streetFromSomethingElse ); strcpy( city, cityFromSomethingElse ); }
这有一点点开销,你必须用两个指针而不是一个指针调用它,但它适合一个函数的账单,它将适用于所有的struct类型。
如果您的结构具有不同的大小,您可以使用宏来提供静态多态性,就像在C99的tgmath.h中所做的那样,但这是相当多的工作。
#define ASSIGN(type,object,member,value) do { \ ((type *)object)->member = value; \ } while (0)
现在你可以做以下事情:
#include 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
或类似操作将是微不足道的。
这是我插入C ++的时候,因为这是多态性的一个明显用途。 或模板。 或function重载。
但是,如果所有公共字段都是第一个,那么您应该能够将指针ADDR_A
转换为ADDR_A
并将其视为:
static void updateFields( void *myStruct ) { ADDR_A *conv = myStruct; strcpy( conv->street, streetFromSomethingElse ); strcpy( conv->city, cityFromSomethingElse ); }
您可以使用宏 – 如果填充亚当建议不起作用
#define UPDATE_FIELDS( myStruct, aStreet, aCity ) \ strcpy( myStruct->street, aStreet ); \ strcpy( myStruct->city, aCity );
但是c ++插件更好;)
只要您希望更新的部分是每个结构上的公共前缀,您就可以定义一个列出这些前缀字段的新结构,并将参数转换为此参数。 假设您创建的前缀struct的布局是相同的(即没有奇怪的编译器填充问题),更新将适用于具有该前缀的所有结构。
编辑:正如sztomi正确指出的那样,只要字段的相对偏移量相同,那么您就可以访问非前缀元素。 但是,我仍然更喜欢创建一个通用结构来充当此function的“模板”; 它可以防止您可能选择的任何其他结构中的字段名称更改。
只是一个未经考验的想法:
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 ); }
尚未提及的一个选项是使用
的offsetof()
宏为字段创建描述符。 然后,您可以将适当的描述符传递给修改函数。
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)修订来删除该限制。 非最小通用描述符可以包含类型指示符和成员名称以及可能相关的其他信息 – 我坚持一个非常简单的结构,尽管有详细的诱惑。 您可以使用函数指针为不同的字符串提供不同的validation – 纬度和经度的validation与街道名称的validation非常不同。
您还可以编写带有描述符数组并执行多个更新的函数。 例如,您可以为ADDR_A中的字段创建一个描述符数组,为ADDR_B创建另一个描述符数组,然后为新字符串数组中的字段传递新值等。
您有责任让描述符正确并使用正确的描述符。
另请参见: __ builtin_offsetof运算符的用途和返回类型是什么?
如果你不能以任何方式改变结构,我认为传递这种类型的想法很好。 制作一堆对地址做一件事的函数,并使其具有地址类型。
int updateStreetAndCity(void *addr, int type); double getLatitude(void *addr, int type);
您可以使用枚举或几个宏定义来制作类型。 然后在每个函数中,根据您传入的类型,调用特定于该类型的另一个函数来为您完成工作(以避免执行千种不同事物的怪物切换语句)。
如果你没有传递结构的类型(比如你的void *建议),基本上你注定要失败。 它们只是一堆带有起始地址的内存。
你可以做的是有几个函数,比如你建议的update_fields,并且根据结构的类型以某种方式调用正确的函数( 这很容易,并且可以非常隐藏在宏中以避免语法噪声)。
这样做可以获得如下代码:
#include #include 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);
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 *传递给您的函数。