我有这样的数据结构:
struct foo { int id; int route; int backup_route; int current_route; }
和一个名为update()的函数,用于请求对其进行更改。
update(42, dont_care, dont_care, new_route);
这真的很长,如果我在结构中添加一些东西,我必须在每次调用更新(...)时添加'dont_care'。
我正在考虑将结构传递给它,但事先用'dont_care'填充结构比在函数调用中拼写它更加繁琐。我可以使用默认值dont care在某处创建结构,并在我将其声明为局部变量后设置我关心的字段吗?
struct foo bar = { .id = 42, .current_route = new_route }; update(&bar);
将我希望表达的信息传递给更新功能的最优雅的方法是什么?
我希望其他所有内容都默认为-1('不关心'的秘密代码)
答案 0 :(得分:152)
虽然宏和/或函数(如已经建议的)将起作用(并且可能具有其他积极效果(即调试挂钩)),但它们比需要的更复杂。最简单且可能最优雅的解决方案是仅定义用于变量初始化的常量:
const struct foo FOO_DONT_CARE = { // or maybe FOO_DEFAULT or something
dont_care, dont_care, dont_care, dont_care
};
...
struct foo bar = FOO_DONT_CARE;
bar.id = 42;
bar.current_route = new_route;
update(&bar);
这段代码几乎没有理解间接的精神开销,很清楚bar
中哪些字段是明确设置的,而(安全地)忽略了那些你未设置的字段。
答案 1 :(得分:15)
您可以将您的秘密特殊值更改为0,并利用C的默认结构成员语义
struct foo bar = { .id = 42, .current_route = new_route };
update(&bar);
然后将作为初始化程序中未指定的bar成员传递0。
或者您可以创建一个将为您执行默认初始化的宏:
#define FOO_INIT(...) { .id = -1, .current_route = -1, .quux = -1, ## __VA_ARGS__ }
struct foo bar = FOO_INIT( .id = 42, .current_route = new_route );
update(&bar);
答案 2 :(得分:7)
<stdarg.h>
允许您定义可变参数函数(它接受无限数量的参数,如printf()
)。我将定义一个函数,它接受任意数量的参数对,一个指定要更新的属性,另一个指定值。使用enum
或字符串指定属性的名称。
答案 3 :(得分:5)
或许可以考虑使用预处理器宏定义:
#define UPDATE_ID(instance, id) ({ (instance)->id= (id); })
#define UPDATE_ROUTE(instance, route) ({ (instance)->route = (route); })
#define UPDATE_BACKUP_ROUTE(instance, route) ({ (instance)->backup_route = (route); })
#define UPDATE_CURRENT_ROUTE(instance, route) ({ (instance)->current_route = (route); })
如果你的(struct foo)实例是全局的,那么你当然不需要参数。但我假设你可能有不止一个实例。使用({...})块是适用于GCC的GNU-ism;这是一种将线条保持在一起的好方法(安全)。如果您以后需要向宏添加更多内容,例如范围验证检查,则不必担心会破坏if / else语句等等。
根据您指出的要求,我会这样做。像这样的情况是我开始大量使用python的原因之一;处理默认参数,这比使用C更简单。(我猜这是一个python插件,对不起; - )
答案 4 :(得分:4)
如下:
struct foo bar;
update(init_id(42, init_dont_care(&bar)));
使用:
struct foo* init_dont_care(struct foo* bar) {
bar->id = dont_care;
bar->route = dont_care;
bar->backup_route = dont_care;
bar->current_route = dont_care;
return bar;
}
和
struct foo* init_id(int id, struct foo* bar) {
bar->id = id;
return bar;
}
并相应地:
struct foo* init_route(int route, struct foo* bar);
struct foo* init_backup_route(int backup_route, struct foo* bar);
struct foo* init_current_route(int current_route, struct foo* bar);
在C ++中,类似的模式有一个我不记得的名字。
编辑:它被称为Named Parameter Idiom。
答案 5 :(得分:4)
gobject使用的一种模式是可变参数函数,以及每个属性的枚举值。界面看起来像:
update (ID, 1,
BACKUP_ROUTE, 4,
-1); /* -1 terminates the parameter list */
编写varargs函数很简单 - 请参阅http://www.eskimo.com/~scs/cclass/int/sx11b.html。只需匹配键 - &gt;值对并设置适当的结构属性。
答案 6 :(得分:4)
因为看起来你只需要update()
函数的这个结构,所以根本不要使用这个结构,它只会模糊你的构造背后的意图。您应该重新考虑更改和更新这些字段的原因,并为这些“小”更改定义单独的函数或宏。
e.g。
#define set_current_route(id, route) update(id, dont_care, dont_care, route)
#define set_route(id, route) update(id, dont_care, route, dont_care)
#define set_backup_route(id, route) update(id, route, dont_care, dont_care)
甚至更好地为每个变更案例编写一个函数。正如您已经注意到的那样,您不会同时更改每个属性,因此可以一次只更改一个属性。这不仅可以提高可读性,还可以帮助您处理不同的情况,例如:您不必检查所有“dont_care”,因为您知道只有当前路线发生了变化。
答案 7 :(得分:2)
我的结构生锈了,所以我可能在这里遗漏了一些关键词。但是为什么不从初始化默认值的全局结构开始,将其复制到本地变量,然后修改它?
初始化程序如:
void init_struct( structType * s )
{
memcopy(s,&defaultValues,sizeof(structType));
}
然后当你想要使用它时:
structType foo;
init_struct( &foo ); // get defaults
foo.fieldICareAbout = 1; // modify fields
update( &foo ); // pass to function
答案 8 :(得分:2)
您可以使用X-Macro
解决问题您可以将结构定义更改为:
#define LIST_OF_foo_MEMBERS \
X(int,id) \
X(int,route) \
X(int,backup_route) \
X(int,current_route)
#define X(type,name) type name;
struct foo {
LIST_OF_foo_MEMBERS
};
#undef X
然后您就可以轻松定义一个灵活的函数,将所有字段设置为dont_care
。
#define X(type,name) in->name = dont_care;
void setFooToDontCare(struct foo* in) {
LIST_OF_foo_MEMBERS
}
#undef X
在讨论here之后,还可以用这种方式定义默认值:
#define X(name) dont_care,
const struct foo foo_DONT_CARE = { LIST_OF_STRUCT_MEMBERS_foo };
#undef X
转化为:
const struct foo foo_DONT_CARE = {dont_care, dont_care, dont_care, dont_care,};
并在hlovdal answer,中使用它,其优点是维护更容易,即更改结构成员的数量将自动更新foo_DONT_CARE
。请注意the last "spurious" comma is acceptable。
当我必须解决this problem时,我首先学习了X-Macros的概念。
将新字段添加到结构中非常灵活。如果您有不同的数据类型,则可以根据数据类型定义不同的dont_care
值:从here,您可以从第二个示例中用于打印值的函数中获取灵感。
如果您对所有int
结构都没问题,那么您可以省略LIST_OF_foo_MEMBERS
中的数据类型,只需将结构定义的X函数更改为#define X(name) int name;
答案 9 :(得分:1)
最优雅的方法是直接更新结构字段,而不必使用update()
函数 - 但可能有充分的理由使用它在问题中没有遇到。
struct foo* bar = get_foo_ptr();
foo_ref.id = 42;
foo_ref.current_route = new_route;
或者你可以像Pukku建议的那样,为结构的每个字段创建单独的访问函数。
否则我能想到的最好的解决方案是将struct字段中的值'0'视为'不要更新'标志 - 所以你只需要创建一个函数来返回一个归零结构,然后用它来更新
struct foo empty_foo(void)
{
struct foo bar;
bzero(&bar, sizeof (struct bar));
return bar;
}
struct foo bar = empty_foo();
bar.id=42;
bar.current_route = new_route;
update(&bar);
但是,如果0是结构中字段的有效值,则这可能不太可行。