C ++结构初始化


C++ Structure Initialization

是否可以在C++中初始化结构,如下所示

1
2
3
4
5
6
7
8
9
struct address {
    int street_no;
    char *street_name;
    char *city;
    char *prov;
    char *postal_code;
};
address temp_address =
    { .city ="Hamilton", .prov ="Ontario" };

这里和这里的链接提到,只有在C中才可以使用这种样式,如果是这样,为什么C++中不可能这样做呢?在C++中没有实现它的根本技术原因,还是使用这种样式是不好的实践。我喜欢使用这种初始化方式,因为我的结构很大,而且这种风格使我能够清楚地阅读分配给哪个成员的值。

如果有其他方法可以达到相同的可读性,请与我分享。

在发布这个问题之前,我已经参考了以下链接

  • AIX的C/C++
  • 带变量的C结构初始化
  • C++中带标签的静态结构初始化
  • C++ 11适当结构初始化

  • 如果要使其清楚每个初始值设定项值是什么,只需将其拆分为多行,每个行上都有注释:

    1
    2
    3
    4
    5
    6
    7
    address temp_addres = {
      0,  // street_no
      nullptr,  // street_name
     "Hamilton",  // city
     "Ontario",  // prov
      nullptr,  // postal_code
    };


    在我的问题没有得到令人满意的结果(因为C++没有实现基于标签的init结构)之后,我采用了我在这里发现的技巧:默认情况下,C++结构体的成员初始化为0吗?

    对你来说,这意味着:

    1
    2
    3
    address temp_address = {}; // will zero all fields in C++
    temp_address.city ="Hamilton";
    temp_address.prov ="Ontario";

    这当然是最接近您最初想要的(除了那些您想要初始化的字段之外,所有字段都归零)。


    字段标识符实际上是C初始值设定项语法。在C++中,只需在没有字段名称的情况下以正确的顺序给出值。不幸的是,这意味着您需要全部给出(实际上,您可以省略后面的零值字段,结果将相同):

    1
    address temp_address = { 0, 0,"Hamilton","Ontario", 0 };


    此功能称为指定的初始值设定项。它是对C99标准的补充。但是,这个特性被排除在C++ 11之外。根据C++程序设计语言,第四版,第44.3.3.2节(C++未采用的C特性):

    A few additions to C99 (compared with C89) were deliberately not adopted in C++:

    [1] Variable-length arrays (VLAs); use vector or some form of dynamic array

    [2] Designated initializers; use constructors

    c99语法具有指定的初始值设定项[见ISO/IEC 9899:2011,N1570委员会草案-2011年4月12日]

    6.7.9初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    initializer:
        assignment-expression
        { initializer-list }
        { initializer-list , }
    initializer-list:
        designation_opt initializer
        initializer-list , designationopt initializer
    designation:
        designator-list =
    designator-list:
        designator
        designator-list designator
    designator:
        [ constant-expression ]
        . identifier

    另一方面,C++ 11没有指定的初始化器[参见ISO/IEC 1488∶2011,N3690委员会草案- 2013年5月15日]。

    8.5初始值设定项

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    initializer:
        brace-or-equal-initializer
        ( expression-list )
    brace-or-equal-initializer:
        = initializer-clause
        braced-init-list
    initializer-clause:
        assignment-expression
        braced-init-list
    initializer-list:
        initializer-clause ...opt
        initializer-list , initializer-clause ...opt
    braced-init-list:
        { initializer-list ,opt }
        { }

    为了达到相同的效果,请使用构造函数或初始值设定项列表:


    正如其他人提到的,这是指定的初始值设定项。

    这个特性是C++ 20的一部分。


    我知道这个问题很古老,但我找到了另一种初始化方法,使用constexpr和currying:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    struct mp_struct_t {
        public:
            constexpr mp_struct_t(int member1) : mp_struct_t(member1, 0, 0) {}
            constexpr mp_struct_t(int member1, int member2, int member3) : member1(member1), member2(member2), member3(member3) {}
            constexpr mp_struct_t another_member(int member) { return {member1, member,     member2}; }
            constexpr mp_struct_t yet_another_one(int member) { return {member1, member2, member}; }

        int member1, member2, member3;
    };

    static mp_struct_t a_struct = mp_struct_t{1}
                               .another_member(2)
                               .yet_another_one(3);

    这种方法也适用于全局静态变量,甚至是constexpr变量。唯一的缺点是可维护性差:每次必须使用此方法使另一个成员可初始化时,都必须更改所有成员初始化方法。


    您甚至可以将gui13的解决方案打包到单个初始化语句中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    struct address {
                     int street_no;
                     char *street_name;
                     char *city;
                     char *prov;
                     char *postal_code;
                   };


    address ta = (ta = address(), ta.city ="Hamilton", ta.prov ="Ontario", ta);

    免责声明:我不推荐这种款式


    您可以通过ctor初始化:

    1
    2
    3
    4
    5
    6
    7
    8
    struct address {
      address() : city("Hamilton"), prov("Ontario") {}
      int street_no;
      char *street_name;
      char *city;
      char *prov;
      char *postal_code;
    };


    它不是在C++中实现的。(还有,char*字符串?我希望不是这样。

    通常,如果您有这么多参数,这是一种相当严重的代码味道。但是,为什么不简单地对结构进行值初始化,然后分配每个成员呢?


    我可能错过了一些东西,为什么不呢:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #include <cstdio>    
    struct Group {
        int x;
        int y;
        const char* s;
    };

    int main()
    {  
      Group group {
        .x = 1,
        .y = 2,
        .s ="Hello it works"
      };
      printf("%d, %d, %s", group.x, group.y, group.s);
    }


    我发现这样做是为了全局变量,不需要修改原始结构定义:

    1
    2
    3
    4
    5
    6
    7
    struct address {
                 int street_no;
                 char *street_name;
                 char *city;
                 char *prov;
                 char *postal_code;
               };

    然后声明从原始结构类型继承的新类型的变量,并使用构造函数初始化字段:

    1
    2
    3
    4
    struct temp_address : address { temp_address() {
        city ="Hamilton";
        prov ="Ontario";
    } } temp_address;

    虽然不如C风格优雅…

    对于局部变量也应该是可能的,但需要检查是否需要额外的memset(this,0,sizeof(*this)),然后…

    (请注意,"temp ou address"是"temp ou address"类型的变量,但是此新类型继承自"address",可以在每个需要"address"的地方使用,因此可以。)


    这是可能的,但前提是您正在初始化的结构是pod(纯旧数据)结构。它不能包含任何方法、构造函数甚至默认值。


    在C++中,C风格的初始化器被构造函数取代,编译时间可以确保只执行有效的初始化(即初始化后对象成员是一致的)。

    这是一个很好的实践,但有时预初始化很方便,就像在您的示例中一样。OOP通过抽象类或创造性的设计模式来解决这个问题。

    在我看来,使用这种安全的方法会扼杀简单性,有时安全权衡可能太昂贵,因为简单的代码不需要复杂的设计来保持可维护性。

    作为另一种解决方案,我建议使用lambda定义宏,以简化初始化,使其看起来几乎像C样式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    struct address {
      int street_no;
      const char *street_name;
      const char *city;
      const char *prov;
      const char *postal_code;
    };
    #define ADDRESS_OPEN [] { address _={};
    #define ADDRESS_CLOSE ; return _; }()
    #define ADDRESS(x) ADDRESS_OPEN x ADDRESS_CLOSE

    地址宏扩展到

    1
    [] { address _={}; /* definition... */ ; return _; }()

    它创建并调用lambda。宏参数也是逗号分隔的,因此需要将初始值设定项放在括号中,然后像这样调用

    1
    address temp_address = ADDRESS(( _.city ="Hamilton", _.prov ="Ontario" ));

    还可以编写通用宏初始值设定项

    1
    2
    3
    #define INIT_OPEN(type) [] { type _={};
    #define INIT_CLOSE ; return _; }()
    #define INIT(type,x) INIT_OPEN(type) x INIT_CLOSE

    但是电话就不那么漂亮了

    1
    address temp_address = INIT(address,( _.city ="Hamilton", _.prov ="Ontario" ));

    但是,您可以轻松地使用general init宏定义地址宏。

    1
    #define ADDRESS(x) INIT(address,x)

    在GNUC++中(似乎从2.5开始就过时了,很久以前了:)请参见下面的答案:使用标签初始化C结构。它起作用了,但怎么办呢?),可以这样初始化结构:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    struct inventory_item {
        int bananas;
        int apples;
        int pineapples;
    };

    inventory_item first_item = {
        bananas: 2,
        apples: 49,
        pineapples: 4
    };

    灵感来源于这个非常简洁的答案:(https://stackoverflow.com/a/49572324/4808079)

    您可以执行lamba闭包:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // Nobody wants to remember the order of these things
    struct SomeBigStruct {
      int min = 1;
      int mean = 3 ;
      int mode = 5;
      int max = 10;
      string name;
      string nickname;
      ... // the list goes on
    }

    .

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class SomeClass {
      static const inline SomeBigStruct voiceAmps = []{
        ModulationTarget $ {};
        $.min = 0;  
        $.nickname ="Bobby";
        $.bloodtype ="O-";
        return $;
      }();
    }

    或者,如果你想很花哨的话

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #define DesignatedInit(T, ...)\
      []{ T ${}; __VA_ARGS__; return $; }()


    class SomeClass {
      static const inline SomeBigStruct voiceAmps = DesignatedInit(
        ModulationTarget,
        $.min = 0,
        $.nickname ="Bobby",
        $.bloodtype ="O-",
      );
    }

    这有一些缺点,主要是与未初始化的成员有关。从链接的答案评论中可以看出,虽然我没有测试过,但它能有效地编译。

    总的来说,我只是觉得这是一个很好的方法。


    今天我遇到了一个类似的问题,我有一个结构,我想用测试数据填充它,它将作为参数传递给我正在测试的函数。我想要一个这些结构的向量,并在寻找一个单行方法来初始化每个结构。

    最后,我在结构中使用了一个构造函数,我相信在您的问题的几个答案中也有建议。

    将构造函数的参数与公共成员变量具有相同的名称可能是不好的做法,需要使用this指针。如果有更好的方法,有人可以建议编辑。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    typedef struct testdatum_s {
        public:
        std::string argument1;
        std::string argument2;
        std::string argument3;
        std::string argument4;
        int count;

        testdatum_s (
            std::string argument1,
            std::string argument2,
            std::string argument3,
            std::string argument4,
            int count)
        {
            this->rotation = argument1;
            this->tstamp = argument2;
            this->auth = argument3;
            this->answer = argument4;
            this->count = count;
        }

    } testdatum;

    我在我的测试函数中使用它来调用被测试的函数,参数如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    std::vector<testdatum> testdata;

    testdata.push_back(testdatum("val11","val12","val13","val14", 5));
    testdata.push_back(testdatum("val21","val22","val23","val24", 1));
    testdata.push_back(testdatum("val31","val32","val33","val34", 7));

    for (std::vector<testdatum>::iterator i = testdata.begin(); i != testdata.end(); ++i) {
        function_in_test(i->argument1, i->argument2, i->argument3, i->argument4m i->count);
    }


    你可以使用外部的"C"来编译C++中的C代码。在您的情况下,您可以使用下面的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    extern"C" {
    //write your C code which you want to compile in C++
    struct address {
    int street_no;
    char *street_name;`enter code here`
    char *city;
    char *prov;
    char *postal_code;
    };
    address temp_address ={ .city ="Hamilton", .prov ="Ontario" };
    }