C结构中的默认值


92

我有一个这样的数据结构:

    struct foo {
        int id;
        诠释路线;
        int backup_route;
        int current_route;
    }

还有一个称为update()的函数,用于请求其中的更改。

  更新(42,dont_care,dont_care,new_route);

这确实很长,如果我向结构中添加一些内容,则必须在对update(...)的每个调用中都添加一个“ dont_care”。

我正在考虑将其传递给一个结构,但预先用'dont_care'填充该结构比仅在函数调用中将其拼写更乏味。我可以在默认值dont care的某个地方创建该结构,然后在将其声明为局部变量后设置我关心的字段吗?

    struct foo bar = {.id = 42,.current_route = new_route};
    更新(&bar);

将我想要表达的信息传递给更新功能的最优雅的方法是什么?

我希望其他所有内容都默认为-1(“ dont care”的秘密代码)

Answers:


155

尽管宏和/或函数(如已建议的那样)可以工作(并且可能具有其他积极作用(即调试钩子)),但它们比所需的更为复杂。最简单且可能最优雅的解决方案是只定义用于变量初始化的常量:

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地明确设置了哪些字段,而(安全地)忽略了未设置的字段。


6
这种方法的另一个好处是它不依靠C99功能来工作。
D.Shawley

24
当我更改为500行时,该项目“消失”了。希望我能为此投票两次!
亚瑟·乌尔费尔特09年

4
PTHREAD_MUTEX_INITIALIZER也使用这个。
yingted 2012年

我在下面添加了一个答案,该答案依赖于X-Macros对结构中存在的元素数量具有灵活性。
安东尼奥

15

您可以将您的秘密特殊值更改为0,并利用C的默认结构成员语义

struct foo bar = { .id = 42, .current_route = new_route };
update(&bar);

然后将传递0作为未在初始化程序中指定的bar成员。

或者,您可以创建一个将为您执行默认初始化的宏:

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

FOO_INIT是否起作用?我认为,如果您两次初始化同一成员,编译器应该会抱怨。
乔纳森·莱夫勒

1
我已经用gcc尝试过了,它没有抱怨。另外,我在标准中还没有发现任何与之相反的事实,实际上,有一个示例专门提到了覆盖。
jpalecek

2
C99:open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf:6.7.8.19:初始化应以初始化列表顺序进行,为特定子对象提供的每个初始化均会覆盖先前为该子对象列出的所有初始化子对象
altendky

2
如果是这样,您是否不能定义具有所有初始化程序的DEFAULT_FOO,然后执行struct foo bar = {DEFAULT_FOO,.id = 10}; ?
约翰,

7

<stdarg.h>允许您定义可变参数函数(接受无限数量的参数,例如printf())。我将定义一个函数,该函数接受任意数量的参数对,一个参数指定要更新的属性,另一个参数指定值。使用enum或字符串指定属性的名称。


嗨@Matt,如何将enum变量映射到struct字段?硬编码吗?那么此功能将仅适用于特定的struct
misssprite 2014年

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主义;这是将行保持在一起的一种好方法(安全)。如果以后需要向宏中添加更多内容(例如范围验证检查),则不必担心会破坏诸如if / else语句之类的内容。

根据您的指示,这就是我要做的。这种情况是我开始大量使用python的原因之一;处理默认参数,这样比使用C语言变得简单得多。(我想这是一个python插件,很抱歉;-)


4

gobject使用的一种模式是可变参数函数,以及每个属性的枚举值。该界面如下所示:

update (ID, 1,
        BACKUP_ROUTE, 4,
        -1); /* -1 terminates the parameter list */

编写varargs函数很容易-参见http://www.eskimo.com/~scs/cclass/int/sx11b.html。只需匹配键->值对并设置适当的结构属性即可。


4

由于看起来您只需要该update()函数的结构,因此根本不需要使用任何结构,这只会混淆您对该结构背后的意图。您可能应该重新考虑为什么要更改和更新这些字段,并为此“小”更改定义单独的函数或宏。

例如


#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”,因为您知道仅更改了当前路径。


某些情况下会在同一通话中更改其中3个。
亚瑟·乌尔费尔特09年

您可以按以下顺序进行操作:set_current_rout(id,route); set_route(id,route); apply_change(id); 或类似。我认为您可以为每个设置器调用一个函数。并在以后进行实际计算(或进行任何计算)。
quinmars

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 ++中,类似的模式具有一个名字,我现在不记得了。

编辑:这被称为命名参数成语


我相信它叫做构造函数。
未知

不,我是说你可以做的事情:update(bar.init_id(42).init_route(5).init_backup_route(66));
Reunanen

这似乎是所谓的“链式Smalltalk风格初始化”,至少在这里:cct.lsu.edu/~rguidry/ecl31docs/api/org/eclipse/ui/internal/...
Reunanen

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

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

这里进行讨论之后,还可以通过这种方式定义默认值:

#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,};

并将其用作错误答案其优点是此处的维护更容易,即更改struct成员的数量将自动更新foo_DONT_CARE。请注意,最后一个“虚假”逗号是可以接受的

当我不得不解决这个问题时,我首先学习了X-Macros的概念。

对于将新字段添加到结构中,它非常灵活。如果您有不同的数据类型,则可以dont_care根据数据类型定义不同的值:从这里开始,您可以从第二个示例中用于打印值的函数中获得启发。

如果可以使用全int结构,则可以从中省略数据类型,LIST_OF_foo_MEMBERS而只需将结构定义的X函数更改为#define X(name) int name;


这种技术利用了C预处理程序使用后期绑定的功能,这在您希望一个地方定义某些东西(例如状态),然后100%确保所使用的物品始终处于相同顺序时,新物品非常有用。会自动添加和删除已删除的项目。我不太喜欢使用短名称,例如X通常添加_ENTRY到列表名称的后面#define LIST_SOMETHING LIST_SOMETHING_ENTRY(a1, a2, aN) \ LIST_SOMETHING_ENTRY(b1, b2, bN) ...
hlovdal

1

最优雅的方法是直接更新struct字段,而不必使用该update()函数-但也许有充分的理由来使用它,而这个问题并没有遇到。

struct foo* bar = get_foo_ptr();
foo_ref.id = 42;
foo_ref.current_route = new_route;

或者,您可以像Pukku建议的那样,为结构的每个字段创建单独的访问函数。

否则,我能想到的最佳解决方案是将struct字段中的值“ 0”视为“不更新”标志-因此,您只需创建一个函数即可返回归零的struct,然后使用该函数进行更新。

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是该结构中字段的有效值,则可能不太可行。


网络是一个很好的理由=)但是,如果-1是“ dont care”值,那么只需重新构造empty_foo()函数,然后使用该方法即可?
gnud

抱歉,我误点击了选票,以后才注意到!!!除非进行编辑,否则我现在无法找到任何方法来撤消该操作
Ciro Santilli郝海东冠状病六四事件法轮功
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.