【问题标题】:Default values in a C StructC 结构中的默认值
【发布时间】:2010-10-19 10:47:06
【问题描述】:

我有这样的数据结构:

struct foo {
    int id;
    int route;
    int backup_route;
    int current_route;
}

还有一个名为 update() 的函数,用于请求对其进行更改。

update(42, dont_care, dont_care, new_route);

这真的很长,如果我在结构中添加一些东西,我必须在每次调用 update(...) 时添加一个“dont_care”。

我正在考虑将它传递给一个结构,但事先用'dont_care'填充结构比在函数调用中拼写出来更乏味。我可以在某处创建默认值不关心的结构,并在我将其声明为局部变量后设置我关心的字段吗?

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

将我希望表达的信息传递给更新函数的最优雅的方式是什么?

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

【问题讨论】:

    标签: c initialization


    【解决方案1】:

    怎么样:

    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

    【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 嗨@Matt,如何将enum 变量映射到struct 字段?硬编码吗?那么这个函数将只适用于特定的struct
    【解决方案3】:

    最优雅的方法是直接更新结构字段,而不必使用 update() 函数 - 但也许有充分的理由使用它,而问题中没有提到。

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

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

    否则我能想到的最佳解决方案是将结构字段中的“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 是结构中字段的有效值,这可能不太可行。

    【讨论】:

    • 网络是一个很好的理由 =) 但如果 -1 是“不关心”值,那么只需重构 empty_foo() 函数,并使用该方法?
    • 对不起,我误点了downvote,后来才注意到!!!我现在找不到任何方法来撤消它,除非有编辑...
    【解决方案4】:

    gobject 使用的一种模式是可变参数函数,以及每个属性的枚举值。界面类似于:

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

    编写可变参数函数很容易——请参阅http://www.eskimo.com/~scs/cclass/int/sx11b.html。只需匹配 key -> value 对并设置适当的结构属性。

    【讨论】:

      【解决方案5】:

      您可以将您的秘密特殊值更改为 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 有效吗?我认为,如果您将同一个成员初始化两次,编译器应该会抱怨。
      • 我用 gcc 试过了,它没有抱怨。另外,我在标准中没有发现任何反对它的东西,事实上,有一个例子特别提到了覆盖。
      • C99: open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf: 6.7.8.19: 初始化应按初始化程序列表顺序进行,为特定子对象提供的每个初始化程序将覆盖同一子对象的任何先前列出的初始化程序;
      • 如果是这样,你能不能不定义 DEFAULT_FOO,它包含所有的初始化器,然后执行 struct foo bar = { DEFAULT_FOO, .id=10 }; ?
      【解决方案6】:

      我对结构感到生疏,所以我可能在这里遗漏了一些关键字。但是为什么不从初始化默认值的全局结构开始,将其复制到本地变量,然后修改它呢?

      一个像这样的初始化器:

      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
      

      【讨论】:

        【解决方案7】:

        或许可以考虑使用预处理器宏定义:

        #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 插件,抱歉 ;-)

        【讨论】:

          【解决方案8】:

          虽然宏和/或函数(如前所述)可以工作(并且可能具有其他积极影响(即调试挂钩)),但它们比需要的复杂。最简单也可能是最优雅的解决方案是只定义一个用于变量初始化的常量:

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

          【讨论】:

          • 这种方法的另一个好处是它不依赖 C99 功能来工作。
          • 当我更改为这 500 行时,项目“退出”了。希望我能对这个投票两次!
          • PTHREAD_MUTEX_INITIALIZER 也使用这个。
          • 我已经添加了an answer below,它依赖于 X-Macros 来灵活地控制结构中存在的元素数量。
          【解决方案9】:

          因为看起来你只需要 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 个。
          • 你可以有这个序列:set_current_rout(id, route); set_route(id, 路线); apply_change(id);或类似的。我认为您可以为每个 setter 调用一个函数。并在稍后进行真正的计算(或其他任何计算)。
          【解决方案10】:

          您可以通过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

          我第一次了解 X-Macros 的概念是在我必须解决 this problem 时。

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

          如果您对全部int 结构没问题,那么您可以省略LIST_OF_foo_MEMBERS 中的数据类型,只需将结构定义的X 函数更改为#define X(name) int name;

          【讨论】:

          • 这项技术利用了 C 预处理器使用 late binding,当您希望在一个地方定义某些东西(例如状态),然后 100% 确保在使用项目时到处都是总是以相同的顺序,自动添加新项目并删除删除项目。我不太喜欢使用像 X 这样的短名称,并且通常将 _ENTRY 附加到列表名称中,例如#define LIST_SOMETHING LIST_SOMETHING_ENTRY(a1, a2, aN) \ LIST_SOMETHING_ENTRY(b1, b2, bN) ....
          猜你喜欢
          • 2012-11-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-01-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多