两个比较容易混淆的概念:Default Memberwise Initialization和Bitwise Copy

一、首先要分清楚这两者之间的关系:两个概念属于不同“集合”(但有部分交集)。

1. Default Memberwise Initialization是与user defined Initialization相对应的。是从编译器(计算机)程序员(用户)的角度出发;

2. bitwise copy 是与memberwise copy相对应的。是两种不同的拷贝方式,编译器通常为了效率会选择bitwise方式拷贝(尤其针对于POD(=Plain Old Data)类型)。

那么为什么这两个概念经常会混淆呢?主要原因是二者有部分交集——在类的对象初始化或者赋值(operator=)时,两个概念会同时出现。

从对象整体角度出发,默认的对象赋值操作和初始化操作(default assignment and initialization ),编译器会选择memberwise方式(这里不是指memberwise copy,更确切的说应该是:individually assignment or initialization)操作,即对构成对象中的每一个成员数据分别进行赋值或者初始化。从对象的数据成员角度出发,具体到对象的每一个数据成员的操作,编译器通常采用(可以认为就是)bitwise copy操作,就像memcpy或者memset函数一样,原样将内存中的数据按位复制一份。

具体操作参见维基百科的例图:

图1表示AB两个对象  

图2表示bitwise拷贝方式   

 

图3表示memberwise拷贝方式

具体的bitwise copymemberwise copy如下:(左图为bitwise copy,右图为memberwise copy)

二、接下来看一下默认构造函数(Default Constructor——由编译器来完成)

这就引出了一个问题:什么情况下需要实现默认的构造函数呢?

自然是编译器需要它的时候(切记不是程序员需要的时候),通常以下四种情况,需要编译器来实现默认的构造成员函数(default constructor):

1) 类中含有成员类对象,并且此类对象含有默认构造函数;

这种情况下,如果没有显示的定义构造函数,那么需要一次构造类中定义的所有成员,当构造成员类对象(member class object)的时候,需要调用此成员类的默认构造函数,所以这时候需要编译器构造出默认的构造函数,来调用成员类的默认构造函数

2) 类的基类中至少有一个含有默认的构造函数;

如果没有显式的定义构造函数,同样编译器构造派生类的时候,必然需要调用基类的构造函数,所以需要编译器在派生类中构造出默认的构造函数。

3) 类中含有虚函数(virtual function);

4) 类中含有虚基类(virtual base class);

由于虚拟机制的原因,这两种情况下,需要编译器来完成虚函数表(vbtl)的初始化和虚表指针(vptr)的初始化,所以如果没有显式的定义构造函数,需要编译器构造默认的构造函数。(本身虚拟机制就是从编译器角度来实现的)

其他比较简单的情况(类的成员数据都是POD=Plain Old Data),在MSVC中经过O2选项优化编译后,简单的类直接被转换为几个连续定义的变量,自然就不需要默认的构造函数了。

三、接下来看一下Memberwise Assignment and Initialization

默认拷贝构造函数(Default Copy Constructor)、默认赋值运算符(operator =)和默认析构函数,是C++类中的六大特殊成员函数中的三个。三者同时遵循一个原则:“一荣俱荣、一损俱损”。如果三者其中的任意一个被显示定义了(defined)那么三者必须都被显式定义。当果三者之一被程序员调用但未没有被显式声明时,编译器会隐含的实现这三个特殊成员函数。当用一个类对象去初始化另一个类对象时,需要用到拷贝构造函数;当用一个类对象去设定另一个类对象时,需要用到赋值运算符。

拷贝构造函数与赋值运算符都遵循“Default Memberwise Assignment&Initialization”原则,即对类中的每一个数据成员进行依次复制,但是通常编译器只采用bitwise copy方式复制(这样能够提高效率)。例如,对于只含有POD成员数据的简单类,bitwise copy方式绰绰有余。但是以下几种情况比较特殊:

1) 当class内含有一个member object时,并且后者的class中声明了一个copy constructor时;

依照“Default Memberwise Assignment&Initialization”原则,初始化member object时,需要编译器调用member class的拷贝构造函数,如果类中没有显式定义拷贝构造函数,就需要编译器构造,来调用成员类的拷贝构造函数。

2) 当类的基类中至少有一个含有拷贝构造函数时;

同样依照“Default Memberwise Assignment&Initialization”原则,需要依次构造所有的基类成员,如果没有显式定义默认拷贝构造函数,那么这部分工作就有编译器来完成。

3) 当类中声明一个或多个virtual functions时;

4) 当类的派生链中有一个或多个virtual base class时;

这里由于虚拟函数的机制,需要初始化vbtlvptr。这部分需要编译器来完成(本身虚拟机制就是从编译器角度来实现的)。

以上几种情况如果程序员未显式定义拷贝构造函数,编译器会自动完成拷贝构造函数的实现,不过当程序中需要调用拷贝构造函数时,编译器自动实现版本是按照bitwise拷贝方式来完成的,所以对于以上几种情况如果程序员不显式定义自己的拷贝构造函数,就会出现错误(尤其是类成员中含有指针、引用、虚函数时)。

从编译器编译连接角度,以上四种情况下如果未定义拷贝构造函数,编译器为了编译工作的顺利进行,会自定义拷贝构造函数;从编程者角度,如果类比较复杂(例如含有指针、引用、虚函数等),单单依靠编译器定义的bitwise版本默认拷贝构造函数,程序是无法达到预定效果的,所以此时往往需要程序员显式定义出自己的拷贝构造函数。

详情参见:《深度探索C++对象模型》

                   《C++反汇编与逆向分析技术揭秘》

                     http://portals.devx.com/tips/Tip/13625

MSDN:http://msdn.microsoft.com/en-us/library/x0c54csc.aspx

                 http://msdn.microsoft.com/en-us/library/x0c54csc.aspx

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
### 回答1: 在 C 语言中,可以使用成员初始化列表来初始化结构体的成员。这种方法叫做成员初始化(memberwise initialization)。 例如,我们可以使用下面的代码来定义一个名为 `Person` 的结构体,并使用成员初始化列表来初始化它的成员: ``` struct Person { char *name; int age; char *address; }; struct Person p = { "John", 30, "New York" }; ``` 在这个例子中,我们使用了成员初始化列表来初始化 `Person` 结构体的三个成员:`name`、`age` 和 `address`。 我们也可以在使用成员初始化列表时省略结构体名,例如: ``` struct Person p = { "John", 30, "New York" }; ``` 在这种情况下,编译器会自动推断出结构体的类型。 希望这对你有帮助! ### 回答2: 在C语言中,结构体(struct)是一种自定义的数据类型,它可以由不同类型的变量组成。Memberwise Initialization是一种初始化结构体的方法,可以一次性为结构体中的每个成员变量赋初值。 例如,我们有一个结构体表示一个学生的信息: ```c struct Student { char name[20]; int age; float score; }; ``` 通过使用Memberwise Initialization,我们可以在定义结构体变量的同时为其成员变量赋初值。例如: ```c struct Student stu = {"Tom", 18, 90.5}; ``` 在上述代码中,我们定义了一个名为stu的结构体变量,并使用Memberwise Initialization为其成员变量赋初值。其中,name成员变量被初始化为"Tom",age成员变量被初始化为18,score成员变量被初始化为90.5。 这样,我们就可以一次性为结构体的每个成员变量赋初值,而不需要逐个赋值。这种方法在初始化结构体变量时非常方便,尤其是当结构体的成员变量较多时。 需要注意的是,Memberwise Initialization只能在定义结构体变量时使用,不能在之后对已存在的结构体变量进行赋值。 ### 回答3: 在C语言中,可以使用成员初始化列表进行成员初始化的操作。 例如,定义一个名为Student的结构体,包含姓名(name)、年龄(age)和成绩(score)三个成员变量。 ```c #include <stdio.h> typedef struct { char name[20]; int age; float score; } Student; int main() { // 成员初始化列表的示例 Student student = { "Tom", 18, 89.5 }; // 输出学生信息 printf("姓名:%s\n", student.name); printf("年龄:%d\n", student.age); printf("成绩:%.1f\n", student.score); return 0; } ``` 在上述代码中,我们通过成员初始化列表的方式对Student结构体的成员进行了初始化。声明和定义一个名为student的Student结构体变量,并在初始化列表中分别为name、age和score赋予了初始值。 最后,通过printf函数将学生的姓名、年龄和成绩输出到控制台上。 这样,我们就成功地使用了成员初始化列表来进行C语言中的成员初始化操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zssure

己欲立而立人,己欲达而达人

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值