331

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

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++ 中实现,或者使用这种风格是不好的做法。我喜欢使用这种初始化方式,因为我的结构很大,而且这种风格让我清楚地了解了分配给哪个成员的值。

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

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

  1. 用于 AIX 的 C/C++
  2. 使用变量初始化 C 结构体
  3. 在 C++ 中使用标签进行静态结构初始化
  4. C++11 正确的结构初始化
4

17 回答 17

205

如果您想清楚每个初始化程序的值是什么,只需将其拆分为多行,并在每行上添加注释:

address temp_addres = {
  0,  // street_no
  nullptr,  // street_name
  "Hamilton",  // city
  "Ontario",  // prov
  nullptr,  // postal_code
};
于 2012-07-17T06:03:16.450 回答
117

我的问题没有得到令人满意的结果之后(因为 C++ 没有为结构实现基于标记的初始化),我采用了我在这里找到的技巧:C++ 结构的成员是否默认初始化为 0?

对你来说,这相当于:

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

这肯定是最接近您最初想要的(除了您要初始化的字段之外的所有字段)。

于 2012-07-17T06:22:13.407 回答
49

正如其他人所提到的,这是指定的初始化程序。

此功能是C++20的一部分

于 2017-12-04T16:16:55.973 回答
19

字段标识符确实是 C 初始值设定项语法。在 C++ 中,只需以正确的顺序给出值,而无需字段名称。不幸的是,这意味着您需要全部提供它们(实际上您可以省略尾随的零值字段,结果将是相同的):

address temp_address = { 0, 0, "Hamilton", "Ontario", 0 }; 
于 2012-07-17T05:51:55.530 回答
16

此功能称为指定初始化程序。它是对 C99 标准的补充。然而,这个特性被排除在 C++11 之外。根据 The C++ Programming Language, 4th edition, Section 44.3.3.2 (C Features Not Adopted by C++):

C99 的一些新增内容(与 C89 相比)在 C++ 中故意不采用:

[1] 可变长度数组(VLA);使用向量或某种形式的动态数组

[2] 指定初始化器;使用构造函数

C99 语法具有指定的初始化程序[参见 ISO/IEC 9899:2011,N1570 委员会草案 - 2011 年 4 月 12 日]

6.7.9 初始化

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 14882:2011,N3690 委员会草案 - 2013 年 5 月 15 日]

8.5 初始化器

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 }
    { }

为了达到同样的效果,使用构造函数或初始化列表:

于 2017-05-30T18:02:08.790 回答
12

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

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, member3}; }
        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 变量。唯一的缺点是可维护性差:每次必须使用此方法初始化另一个成员时,都必须更改所有成员初始化方法。

于 2014-10-03T23:56:42.217 回答
11

您可以通过构造函数进行初始化:

struct address {
  address() : city("Hamilton"), prov("Ontario") {}
  int street_no;
  char *street_name;
  char *city;
  char *prov;
  char *postal_code;
};
于 2014-02-14T04:51:36.940 回答
8

我可能在这里遗漏了一些东西,为什么不:

#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);
}
于 2018-09-07T10:20:15.077 回答
7

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

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

免责声明:我不推荐这种风格

于 2012-07-17T09:46:42.950 回答
5

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

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

于 2012-07-17T06:02:34.323 回答
4

我发现这种方法可以用于全局变量,不需要修改原始结构定义:

struct address {
             int street_no;
             char *street_name;
             char *city;
             char *prov;
             char *postal_code;
           };

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

struct temp_address : address { temp_address() { 
    city = "Hamilton"; 
    prov = "Ontario"; 
} } temp_address;

虽然不如 C 风格那么优雅......

对于局部变量,它需要一个额外的 memset(this, 0, sizeof(*this)) 在构造函数的开头,所以它显然不会更糟,@gui13 的答案更合适。

(请注意,“temp_address”是“temp_address”类型的变量,但是这种新类型继承自“address”,并且可以在任何需要“address”的地方使用,所以没关系。)

于 2016-11-07T10:18:25.790 回答
3

在 GNUC++ 中(似乎从 2.5 开始就已经过时了,很久以前 :) 在此处查看答案:使用标签的 C 结构初始化。它有效,但如何?),可以像这样初始化一个结构:

struct inventory_item {
    int bananas;
    int apples;
    int pineapples;
};

inventory_item first_item = {
    bananas: 2,
    apples: 49,
    pineapples: 4
};
于 2019-02-04T10:45:25.413 回答
2

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

这是一个很好的做法,但有时预初始化很方便,就像你的例子一样。OOP 通过抽象类或创建设计模式解决了这个问题。

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

作为替代解决方案,我建议使用 lambdas 定义宏以简化初始化,使其看起来几乎像 C 风格:

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

ADDRESS 宏扩展为

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

它创建并调用 lambda。宏参数也是逗号分隔的,所以你需要把初始化器放在括号里,然后像这样调用

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

您还可以编写通用宏初始化程序

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

但随后通话就不那么漂亮了

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

但是您可以使用通用 INIT 宏轻松定义 ADDRESS 宏

#define ADDRESS(x) INIT(address,x)
于 2016-07-06T11:16:46.740 回答
2

你有

  1. 标准初始化列表

    address temp_address {
        /* street_no */,
        /* street_name */,
        ...
        /* postal_code */
    };
    
    address temp_address2 = {
        /* street_no */,
        /* street_name */,
        ...
        /* postal_code */
    }
    
  2. 点符号

    address temp_address;
    temp_address.street_no = ...;
    temp_address.street_name = ...;
    ...
    temp_address.postal_code = ...;
    
  3. 指定的聚合初始化,其中初始化列表包含从 C++20 开始可用的结构的每个成员的标签(请参阅文档)。

  4. 将 a视为structC++ 类 - 在 C++ 结构中实际上是特殊类型的类,其中所有成员都是public(与标准 C++ 类不同,private如果未明确指定所有成员,则所有成员都是)以及在使用继承时它们默认为public

    struct Address {
        int street_no;
        ...
        char* postal_code;
    
        Address (int _street_no, ... , char* _postal_code)
         : street_no(_street_no),
           ...
           postal_code(_postal_code)
        {}
    }
    
    ...
    
     Address temp_address ( /* street_no */, ..., /* postal_code */);
    

当涉及到初始化结构的方式时,您应该考虑以下方面:

  • 可移植性——不同的编译器、不同程度的 C++ 标准完整性和不同的 C++ 标准确实限制了您的选择。如果您必须使用 C++11 编译器,但又想使用 C++20 指定的聚合初始化,那么您就不走运了
  • 可读性- 什么更具可读性:temp_address.city = "Toronto"temp_address { ..., "Toronto", ... }?代码的可读性非常重要。尤其是当您有大型结构(更糟糕的是嵌套结构)时,到处都有未标记的值只是自找麻烦
  • 可扩展性——任何依赖于特定顺序的东西都不是一个好主意。缺乏标签也是如此。您想在结构的地址空间中向上或向下移动成员吗?祝你有一个未标记的初始化列表好运(在结构初始化中寻找交换的值是一场噩梦)......你想添加一个新成员吗?再次祝您好运,一切取决于特定订单。

虽然点表示法意味着您键入更多使用它获得的好处超过了这个问题,因此我可以推荐它,除非您有一个小型结构,在其结构缺乏变化方面是面向未来的,在这种情况下,您可以负担得起初始化列表。请记住:无论何时与其他人一起编写易于遵循的代码都是必不可少的。

于 2021-12-16T15:44:41.683 回答
1

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

我最终在结构中使用了一个构造函数,我相信在你的问题的一些答案中也提出了这个建议。

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

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;

我在测试函数中使用它来调用正在测试的函数,其中包含如下各种参数:

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);
}
于 2015-04-03T22:45:04.977 回答
1

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

于 2015-08-11T19:58:26.930 回答
1

受到这个非常简洁的答案的启发:(https://stackoverflow.com/a/49572324/4808079

你可以做 Lamba 闭包:

// 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
}

.

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

或者,如果你想变得非常花哨

#define DesignatedInit(T, ...)\
  []{ T ${}; __VA_ARGS__; return $; }()

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

这有一些缺点,主要与未初始化的成员有关。从链接的答案评论说,它编译有效,虽然我没有测试过。

总的来说,我只是认为这是一种巧妙的方法。

于 2019-01-28T00:44:05.663 回答