#include <iostream>
using namespace std;
class A{
public:
A(int a,string str)
{
m_a = a;
m_str = str;
}
//A(int a,string str):m_a(a),m_str(str){}
void print()
{
cout << m_a << ' '<< m_str<< endl;
}
private:
int m_a;
string m_str;
};
int main(int argc, char **argv)
{
A a(1,"hello");
a.print();
return 0;
}
运行结果相同,那么两者区别在哪里???
从概念上,我们可以认为构造函数分两个阶段执行:
(1)初始化阶段 (2)普通的计算阶段。
①不管成员是否在构造函数初始化列表中显式初始化, 类类型的数据成员总是会在初始化阶段初始化。 初始化发生在计算阶段开始之前。
②在构造函数初始化列表中没有显示提及的每个成员, 使用与初始化变量相同的规则来进行初始化。 运行该类型的默认构造函数,来初始化类类型的数据成员。
③内置或复合类型的成员的初始值依赖于对象的作用域:在局部作用域中这些成员不被初始化,而在全局作用域中,它们被初始化为0。
④计算阶段由构造函数体内的所有语句构成。在计算阶段中,数据成员的设置被认为是赋值,而不是初始化。没有清楚地认识到这个区别是程序错误和低效的常见源泉。
⑤. 初始化 != 赋值.;初始化代表为变量分配内存. 变量在其定义处被编译器初始化(编译时). 在函数中, 函数参数初始化发生在函数调用时(运行时).,赋值代表"擦除对象当前值, 赋予新值". 它不承担为对象分配内存的义务.
结论:构造函数初始化列表是对类的成员做初始化,而在构造函数体内只是对类的数据成员进行了一次赋值操作。
1. 初始化列表存在的价值:
首先把数据成员按类型分类并分情况说明:
①.内置数据类型,复合类型(指针,引用)
在成员初始化列表和构造函数体内进行,在性能和结果上都是一样的
②.用户定义类型(类类型)
结果上相同,但是性能上存在很大的差别。更受欢迎的实现是用成员初始化表:因为类类型的数据成员对象在进入函数体前已经构造完成,也就是说在成员初始化列表处进行构造对象的工作,调用构造函数,在进入函数体之后,进行的是对已经构造好的类对象的赋值,又调用个拷贝赋值操作符才能完成(如果并未提供,则使用编译器提供的默认按成员赋值行为)
2. 那么什么时候需要构造函数初始化列表呢?
(1) const成员
(2) 引用类型成员
(3) 继承类中调用基类初始化构造函数, 实际上就是先构造基类对象, 必须使用初始化列表.
以上3种情况需要在构造函数初始化列表中对数据成员进行显式初始化。因为const和引用类型成员只能初始化,不能对其进行赋值操作。
#include<iostream>
using namespace std;
class A{
public:
A(int a,int b):m_a(a),m_b(b){}
void print(){
cout << m_a<<' '<< m_b << endl;
}
int m_a;
int m_b;
};
class B:public A{
public:
/* B(int a,int b,int c){
m_a = a;
m_b = b;
m_c = c;
}
*/
// B(int a,int b,int c):m_a(a),m_b(b),m_c(c){}
B(int a,int b,int c):A(a,b),m_c(c){}
void print()
{
cout << m_a<<' '<< m_b<< ' '<< m_c <<endl;
}
int m_c;
};
int main(){
B b(1,2,3);
b.print();
// A a(1,2);
// a.print();
return 0;
}
事实上我们应该尽量使用初始化列表,而不要在构造函数里赋值
3. 初始化顺序
构造函数初始化列表只是指定了成员的初始值,并没有指定初始化顺序,那么成员初始化顺序又是怎样的呢?成员的初始化顺序就是定义成员的顺序,第一个定义的成员首先被初始化,然后是第二个等等。