C ++结构初始化


279

是否可以按如下所示在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正确的结构初始化

20
个人对世界的看法:在C ++中,不需要这种类型的对象初始化,因为您应该使用构造函数。
菲利普·肯德尔

7
是的,我想到了这一点,但是我有很多大型结构。使用这种方式对我来说将很容易阅读。您是否有使用Constructor进行初始化的任何样式/良好实践,并且也提供了更好的可读性。
Dinesh PR

2
您是否考虑过将boost参数库与构造函数结合使用?boost.org/doc/libs/1_50_0/libs/parameter/doc/html/index.html
Tony Delroy,

18
编程无关:此地址仅在美国有效。在法国,我们没有“省”,在世界其他地方,没有邮政编码,一个朋友的祖母住在一个小村庄里,她的地址是“ X女士,邮政编码”小村庄名称”(是,没有街道)。因此,请仔细考虑您将其应用于市场的有效地址是什么;)
Matthieu M.

5
@MatthieuM。美国没有省份(可能是加拿大格式?),但是有些州,领地甚至是很小的村庄都不用为街道命名。因此,地址一致性问题也适用于此。
蒂姆(Tim)

Answers:


166

如果要弄清楚每个初始化器的值是什么,只需将其拆分成多行,并在每个注释上加上注释:

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

7
我个人喜欢并推荐这种风格
Dinesh PR

30
这样做与实际使用点符号来更准确地访问字段本身有什么区别,如果这就是要关注的地方,那不像您要节省任何空间。在一致性和编写可维护代码方面,我真的没有C ++程序员,他们似乎总是想做一些不同的事情来使他们的代码脱颖而出,这些代码旨在反映所解决的问题,不应该这样做。它本身就是一个习惯用法,旨在提高可靠性并简化维护。

5
@ user1043000好,其中一个例子,在这种情况下,您放置成员的顺序至关重要。如果在结构的中间添加一个字段,则必须返回此代码,并寻找要在其中插入新的初始化的确切位置,这很麻烦而且很无聊。使用点符号,您可以简单地将新的初始化放在列表的末尾,而不必理会顺序。如果您碰巧char*在结构中的上方或下方添加与其他成员之一相同的类型(例如),则点号表示法会更安全,因为没有交换它们的风险。
Gui13 '16

6
orip的评论。如果数据结构定义被更改,并且没有人考虑寻找初始化,或者找不到所有初始化,或者在编辑它们时出错,一切都会崩溃。
爱德华·福尔克

3
大多数(如果不是全部)POSIX结构没有定义的顺序,只有定义的成员。(struct timeval){ .seconds = 0, .microseconds = 100 }将始终为100微秒,但timeval { 0, 100 }可能为100 。您不想艰难地找到类似的东西。
yyny

99

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

对您而言,这相当于:

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

这肯定是最接近您最初想要的内容(将要初始化的所有字段都清零)。


10
这不适用于静态
初始化的

5
static address temp_address = {};将工作。事后填充它取决于运行时,是的。您可以通过提供一个为您执行init的静态函数来绕过此操作static address temp_address = init_my_temp_address();
Gui13 2014年

在C ++ 11中,init_my_temp_address可以是lambda函数:static address temp_address = [] () { /* initialization code */ }();
dureuill 2015年

3
好主意,它违反了RAII原则。
hxpax

2
确实是个坏主意:向您的成员中添加一个成员address,您将永远不会知道所有创建的位置,address而且现在不会初始化您的新成员。
mystery_doctor


17

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

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

1
是的,您始终可以使用对齐的结构初始化。
texasbruce

3
是的,当前我仅使用此方法(对齐的结构初始化)。但是我觉得可读性不好。由于我的结构很大,因此初始化程序具有大量数据,因此我很难跟踪将哪个值分配给了哪个成员。
Dinesh PR

7
@ DineshP.R。然后写一个构造函数!
李斯特先生,2012年

2
@MrLister(或其他任何人)也许我现在被愚蠢的乌云所笼罩,但是要解释一下构造函数会好得多吗?在我看来,为初始化器列表提供一堆与订单相关的未命名值或向构造函数提供一堆与订单相关的未命名值之间没有什么区别。
yano

1
@yano老实说,我真的不记得为什么我认为构造函数会解决这个问题。如果我记得的话,我会再找你。
李斯特先生,

13

此功能称为指定的初始化程序。它是C99标准的补充。但是,此功能不在C ++ 11中。根据C ++编程语言,第4版,第44.3.3.2节(C ++不采用的C功能):

C99故意不对C99进行一些补充(与C89相比):

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

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


9

您可以通过ctor进行初始化:

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

9
仅当您控制的定义时,才是这种情况struct address。同样,POD类型通常有意地没有构造函数和析构函数。
user4815162342 2014年

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

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


这仍然很危险,因为它允许您向中添加成员,address并且仅初始化原始的五个成员,代码仍然可以编译一百万个位置。结构初始化的最好部分是可以拥有所有成员const,这将迫使您将它们全部初始化
mystery_doctor

7

我知道这个问题已经很老了,但是我发现了使用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,     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变量。唯一的缺点是可维护性差:每次必须使用此方法初始化另一个成员时,都必须更改所有成员初始化方法。


1
这是构建器模式。成员方法可以返回对要修改的属性的引用,而不是每次都创建新的结构
phuclv

6

我可能在这里想念一些东西,为什么呢?

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

我使用MinGW C ++编译器和Arduino AVR C ++编译器编译了上述程序,并且都按预期运行。请注意#include <cstdio>
run_the_race

4
@run_the_race,这是关于c ++标准所说的,而不是给定编译器的行为。但是,此功能在c ++ 20中提供。
乔恩

仅当结构为POD时才有效。因此,如果向其添加构造函数,它将停止编译。
oromoiluig

5

它不是用C ++实现的。(也,char*字符串?我希望不是)。

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


6
“(还有,char*琴弦?我希望不是)。” -好吧,这是一个C语言示例。
Ed S.

我们不能在C ++中使用char *吗?目前,我正在使用它,并且它正在工作(可能是我做错了什么)。我的假设是,编译器将创建“ Hamilton”和“ Ontario”的常量字符串,并将其地址分配给struct成员。改用const char *是否正确?
Dinesh PR

8
您可以使用,char*const char*类型安全得多,并且每个人都可以使用,std::string因为它更加可靠。
小狗

好。当我阅读“如下所述”时,我认为这是一个从某处复制的示例。
Ed S.

2

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

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

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

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”的每个位置使用,因此可以。)


1

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

我最终在struct中使用了构造函数,我认为在您的问题的一些答案中也建议使用该函数。

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

1

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


1

在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宏扩展为

[] { 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)


0

受到这个非常简洁的答案的启发:(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-",
  );
}

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

总体而言,我只是认为这是一种巧妙的方法。

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.