C++结构初始化

C++ Structure Initialization

本文关键字:初始化 结构 C++      更新时间:2023-10-16

是否可以按如下所示初始化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. 使用 VARIABLE 初始化 C 结构
  3. 使用C++中的标记进行静态结构初始化
  4. C++11 正确的结构初始化

如果您想明确每个初始值设定项值是什么,只需将其拆分为多行,并在每行上添加注释:

address temp_addres = {
  0,  // street_no
  nullptr,  // street_name
  "Hamilton",  // city
  "Ontario",  // prov
  nullptr,  // postal_code
};

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

对你来说,这相当于这样做:

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

这当然是最接近您最初想要的(除要初始化的字段之外的所有字段为零(。

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

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

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

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

此功能称为指定的初始值设定项。它是C99标准的补充。但是,此功能被排除在 C++11 之外。根据 The C++ Programming Language,第 4 版,第 44.3.3.2 节(C++ 未采用的 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 }
    { }

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

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

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

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

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

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

struct address {
  address() : city("Hamilton"), prov("Ontario") {}
  int street_no;
  char *street_name;
  char *city;
  char *prov;
  char *postal_code;
};

你甚至可以将 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);

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

它没有在C++中实现。(另外,char*字符串?我希望不会(。

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

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

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

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

作为替代解决方案,我建议使用 lambda 定义宏,以简化初始化,使其看起来几乎像 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 _={}; /* definition... */ ; return _; }()

创建并调用 lambda。宏参数也是逗号分隔的,因此您需要将初始值设定项放入括号中并调用 like

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 宏轻松定义地址宏

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

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

你可以做兰巴闭合:

// 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-",
  );
}

这涉及一些缺点,主要与未初始化的成员有关。 从链接的答案注释中可以看出,它可以有效地编译,尽管我还没有对其进行测试。

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

我发现这种对全局变量执行此操作的方法,不需要修改原始结构定义:

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"类型的变量,但是这种新类型继承自"地址",可以在需要"地址"的每个地方使用,所以没关系。

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

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

你有

  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. struct视为C++类 - 在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", ... }?代码的可读性非常重要。特别是当你有大型结构(更糟 - 嵌套结构(时,到处都有未标记的值只是自找麻烦
  • 可扩展性 - 任何依赖于特定订单的东西都不是一个好主意。缺乏标签也是如此。是否要在结构的地址空间上移或下移成员?祝未标记的初始化列表好运(在结构初始化中寻找交换的值是一场噩梦(......您想添加新成员吗?再次祝任何取决于特定订单的事情好运。

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

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

我最终在结构中使用了一个构造函数,我相信在对您的问题的几个回答中也提出了这个函数。

让构造函数的参数与公共成员变量具有相同的名称可能是不好的做法,需要使用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);
}

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