C++学习-构造函数初始化列表(4)

本文详细解析了C++中构造函数的两个阶段:初始化阶段与计算阶段,并通过实例介绍了构造函数初始化列表的重要性及其应用场景,包括成员变量的初始化顺序、const与引用类型成员的初始化以及自定义类对象的初始化。
作者:gzshun. 原创作品,转载请标明出处!
来源:http://blog.youkuaiyun.com/gzshun


从概念上讲,可以认为构造函数分两个阶段进行:
1.初始化阶段;
2.普通的计算阶段。(计算阶段由构造函数函数体中的所有语句组成)
不管成员是否在构造函数初始化列表中显示初始化,类类型的数据成员总是在初始化阶段初始化。初始化发生在计算阶段的开始之前。
建议:使用构造函数初始化列表
注:必须对任何const或引用类型成员以及没有默认构造函数的类类型的任何成员使用初始化式。
一般使用构造函数初始化列表,可以避免发生编译错误。
讲解:没有默认构造函数的类?是什么意思?
在大部分编译器中,声明一个类,若类没有显示的声明和定义构造函数,那么编译器就会在编译阶段生成一个默认构造函数。如果用户在该类中声明了一个构造函数,那么编译器就不会生成默认构造函数,而是使用了用户自己定义的构造函数,为了避免编译错误,最好使用构造函数初始化列表对该类的对象进行初始化。
-----摘自于《C++ Primer 中文版 第4版》

类成员的初始化包括类对象成员与类数据成员的初始化。初始化比较关键的是构造函数的初始化列表,在构造函数中成员初始化列表中也需要次序的。只有构造函数才能有成员初始化的效果,普通的成员函数没有这功能,比如:

void CInit::setXY(int x, int y) : mX(x), mY(y) { } 这个初始化是错误的,setXY并非是构造函数,所以普通成员函数只能通过赋值的形式来设置变量或对象的值。

void CInit::setXY(int x, int y) { mX = x; mY = y; } 这里声明一个类CInit,如下:

class CInit { public: CInit(int x, int y); void Show() const; private: int mX; int mY; }; void CInit::Show() const { cout << "mX = " << mX << endl << "mY = " << mY << endl; }
一.构造函数的初始化列表的基本使用
这是正常的初始化列表的用法
初始化:

CInit::CInit(int x, int y) : mX(x), mY(y) { } 达到的结果相当于
赋值:

CInit::CInit(int x, int y) { mX = x; mY = y; }
二.成员初始化的次序
在构造函数初始化列表中,成员初始化的次序就是声明成员的次序。
例子1:张三想先用x初始化mX,再用mX初始化mY

CInit::CInit(int x, int y) : mX(x), mY(mX) { } CInit test(2, 3); test.Show(); 此时的结果是:

mX = 2 mY = 2 mX与mY均被成功的初始化。


例子2:李四想先初始化mY,再用mY初始化mX

CInit::CInit(int x, int y) : mY(y), mX(mY) { } CInit test(2, 3); test.Show(); 此时的结果是:

mX = 2147344384 (不同机器可能不一致) mY = 3 从结果可以很明显的看出,mX没有被初始化,而mY成功被初始化为3。


从这里可以看出,构造函数是以变量的声明顺序来执行初始化的动作,所以例子2中,构造函数先初始化mX,但此时mY是未初始化过的,所以导致这种情况。在构造函数的初始化列表中,最好要按照类中成员变量的声明顺序来初始化。


三.在什么情况下使用构造函数初始化列表?
1.const对象
2.引用类型对象
因为const对象与引用类型对象只能够初始化,不能赋值,所以必须在初始化列表中进行初始化。
3.类对象(下文说明)


四.构造函数中,赋值初始化与初始化列表初始化,哪个效率更高?为什么?
先来看一个例子,便可知分晓:

#include <iostream> using namespace std; class CObj { public: CObj() { cout << "调用默认构造函数" << endl; } CObj(const CObj &obj) { mX = obj.mX; cout << "调用复制构造函数" << endl; } CObj & operator=(const CObj &obj) { if (&obj != this) { mX = obj.mX; } cout << "数据赋值" << endl; return *this; } private: int mX; }; class CInit { public: CInit(const CObj & obj) : mObj(obj) { //mObj = obj; } private: CObj mObj; }; int main() { CObj obj; cout << endl; CInit test(obj); return 0; } 1.若CInit的构造函数为:

CInit(const CObj & obj) : mObj(obj) { } 执行结果为:
调用默认构造函数 调用复制构造函数 在构造函数CInit的初始化列表初始化mObj对象时,调用了复制构造函数1次,总共需要1个行为。

2.若CInit的构造函数为:

CInit(const CObj & obj) { mObj = obj; } 执行结果为:

调用默认构造函数 调用默认构造函数 数据赋值

在构造函数体中赋值mObj对象时,首先调用默认构造函数,其次是调用operator=赋值运算符,总共需要2个行为。

所以可以得出这么一个结论:对于用户自定义的类类型,使用构造函数初始化列表进行初始化的效率,比在构造函数体中赋值初始化的效率更高。对于内置类型,效率是差不多的。

五.构造函数初始化列表的使用例子

/*类成员的初始化: 求2点组成的矩形*/ #include <iostream> using namespace std; class CCenterPoint { public: CCenterPoint(int posX, int posY) : mPosX(posX), mPosY(posY) { } void ShowPos() const { cout << "2点之间的中点坐标: (" << mPosX << "," << mPosY << ")" << endl; } private: int mPosX; int mPosY; }; class CArea { public: CArea(int length, int width) : mLength(length), mWidth(width) { } void ShowArea() const { cout << "2点组成的矩形面积: " << mLength * mWidth << endl; } private: int mLength; int mWidth; }; class CRect { public: CRect(int posX1, int posY1, int posX2, int posY2) : mPoint((posX1+posY1)/2, (posX2+posY2)/2), mArea(posX2-posX1, posY2-posY1) { } void Show() const { mPoint.ShowPos(); mArea.ShowArea(); } private: CCenterPoint mPoint; CArea mArea; }; int main() { CRect rect(10, 100, 20, 200); rect.Show(); return 0; } 执行结果:

2点之间的中点坐标: (55,110) 2点组成的矩形面积: 1000
Happy Learning!

<think>好的,我现在需要解决用户关于C++结构体构造函数初始化列表用法的问题。用户已经提供了五个相关的引用资料,我需要仔细阅读这些引用,整理出正确的使用方法,并给出示例。 首先,用户的问题集中在初始化列表的正确使用上。根据引用[2],结构体的构造函数可以使用初始化列表初始化成员变量,例如node(int a, string b, char c) :data(a), str(b), x(c){}。这说明初始化列表是在构造函数参数列表后面用冒号引出,成员变量用逗号分隔,每个成员后面跟括号内的初始化值。同时引用[4]提到初始化列表是用于初始化一组内存位置的值列表,用大括号括起来,但这里可能是指结构体变量的初始化,而不是构造函数初始化列表,需要区分开。 接下来,引用[3]中的例子展示了默认构造函数的使用,虽然没有初始化列表,但可以不经初始化就定义变量。而引用[5]中的示例可能有多个构造函数,其中使用初始化列表的情况需要确认。不过用户提供的引用[5]中的输出结果可能对应不同的构造函数调用,但具体代码没有展示,所以可能需要依赖其他引用来推断。 根据引用[2],当结构体有带参数的构造函数时,数组初始化可能会出问题,因此需要同时提供无参构造函数。这提示在使用初始化列表时,如果需要创建结构体数组,必须确保有无参构造函数,否则会导致编译错误。例如,结构体node提供了无参构造函数node() :x(), str(), data(){},这样在定义数组N[10]时才能正确初始化。 另外,引用[4]提到结构体变量的初始化可以使用初始化列表,但这里的初始化列表是指定义变量时的列表,如student s = {1, 'M'},而构造函数初始化列表是另一种语法。用户的问题是关于构造函数初始化列表,所以需要明确区分这两者。 现在需要整理正确的步骤:结构体构造函数初始化列表写法,如何正确排列成员变量,是否必须按照声明顺序,以及示例。根据C++的规则,成员变量的初始化顺序是按照它们在类中的声明顺序,而不是初始化列表中的顺序。因此,在编写初始化列表时,最好按照成员声明的顺序来写,避免混淆。 示例部分,结合引用[2]和引用[5],可以构造一个结构体,包含不同类型的成员,展示带参数和无参数的构造函数,以及初始化列表的使用。例如,结构体Person,包含age、name、gender,构造函数初始化列表初始化这些成员,同时提供无参构造函数用于数组初始化。 需要注意,如果结构体中有动态分配的内存,析构函数需要处理,但用户的问题可能不涉及这点,所以示例中可以暂时忽略。此外,引用[1]提到构造函数用于自动初始化,析构函数用于清理,但当前问题集中在初始化列表,所以示例中不需要析构函数。 最后,确保示例代码语法正确,比如结构体中的构造函数没有分号,初始化列表的格式正确,以及如何调用这些构造函数创建对象和数组。</think>在C++中,结构体构造函数初始化列表是高效且安全的成员初始化方式。以下为具体用法和注意事项: ### 一、初始化列表核心规则 1. **语法格式**:构造函数参数列表后使用`:`,成员变量按`变量名(初始值)`格式排列 ```cpp struct Student { int id; string name; // 初始化列表构造函数 Student(int i, string n) : id(i), name(n) {} }; ``` 2. **初始化顺序**:成员初始化顺序由结构体内声明顺序决定,与初始化列表书写顺序无关。错误顺序可能导致未定义行为[^2][^5] ### 二、多类型构造函数示例 ```cpp #include <iostream> using namespace std; struct Book { int isbn; string title; float price; // 无参构造(数组初始化必需) Book() : isbn(0), title(""), price(0.0f) {} // 带初始化列表的构造 Book(int i, string t, float p) : isbn(i), title(t), price(p) {} // 混合初始化方式 Book(string t) : title(t) { isbn = -1; // 非初始化列表方式 price = 0.0f; } }; int main() { Book b1; // 调用无参构造 Book b2(978123, "C++ Primer", 99.9f); // 初始化列表构造 Book arr[5]; // 结构体数组必须有无参构造 cout << b2.title << " ¥" << b2.price; // 输出:C++ Primer ¥99.9 return 0; } ``` ### 三、关键注意事项 1. **数组初始化约束**:当存在带参数构造函数时,必须显式声明无参构造函数,否则无法创建结构体数组[^2] 2. **默认值设定**:C++11支持成员变量声明时直接赋默认值 ```cpp struct Device { string type = "Unknown"; int id = -1; // 构造函数仍可覆盖默认值 Device(int i) : id(i) {} }; ``` 3. **动态内存管理**:若成员包含指针,需在析构函数中释放资源 ```cpp struct Matrix { int* data; size_t size; Matrix(size_t s) : size(s) { data = new int[s]; } ~Matrix() { delete[] data; } // 析构函数清理 }; ``` ### 四、性能优势 初始化列表直接对成员进行内存初始化,比在构造函数体内赋值更高效。对于以下类型必须使用初始化列表- `const`成员 - 引用类型成员 - 没有默认构造函数的类类型成员[^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值