首先了解类对象的构造顺序是怎样的:
1.分配内存,调用构造函数时,隐式/显式的初始化个数据成员
(构造函数列表的初始化方式不是按照列表的顺序,而是按照变量声明的顺序同时初始化显隐 数据成员);
2.进入构造函数后在构造函数中执行一般赋值与计算。
使用初始化列表的原因:
一:只能使用初始化列表
1.初始化 const 修饰的类成员(常变量) 或 初始化引用成员数据;(因为这些变量初始化时必须赋值)
2.子类初始化父类的私有成员;
3.数据成员是对象,并且这个对象只有含参数的构造函数,没有无参数的构造函数;
针对三进行说明:
class Test {
public:
Test() {};
Test(int x) { int_x = x;
std::cout << int_x << std::endl;
};
void show() { std::cout << int_x << std::endl; }
private:
int int_x;
};
class Mytest :public Test {
public:
Mytest():Test(110)
{
//Test(110); // 构造函数只能在初始化列表中被显示调用,不能在构造函数内部被显示调用
};
};
int _tmain(int argc, _TCHAR* argv[])
{
Test *p = new Mytest();
p->show();
return 0;
}
结果:如果在构造函数内部被显示调用输出结果是:-842150451(原因是虽然调用了 Test (int x),但是直接调用构造函数产生了一个临时对象,而不是调用父类的构造函数来构造父类的私有变量,作用域只在一条语句中,所以相当于什么都没做。故而直接打印出一个随机值。)
二:效率问题
①:如果在类的构造函数里面进行赋值:在成员初始化时会调用一次其默认的构造函数,在类构造函数里又会调用一次成员的构造函数再赋值。
②:如果在类构造函数使用初始化列表:仅在初始化列表里调用一次成员的构造函数并且赋值
template<class T>
class My
{
public:
My()
{
//使用了赋值操作符 operator=(LPCTSTR);
m_str = T("hello");
}
My()//使用类成员列表
:my_str(T("hello"))
{}
private:
T* m_str;
};
分析:编译期总是确保所有的成员对象在构造函数体执行之前初始化,因此在第一个例子中编译的代码将调用CString::CString来初始化my_str,这是在控 制到达赋值语句之前完成。在第二个例子中,编译器产生一个对CString::CString(LPCTSTR)的调用并将“hello”传递给这个函数。
产生的结果就是:在第一个例子中调用了两次CString函数(构造函数和赋值操作),而在第二个例子中只调用了一次函数。
不过在CString的例子里这是无所谓的,因为缺省构造函数是内联的,CString只是在需要时为字符串分配内存(当你实际赋值时)。但是一般而言, 重复函数是浪费资源的,尤其是在构造函数和赋值操作符非配内存的时候。
构造函数:
首先,在程序定义变量并初始化的机制中,有两种形式:①传统的初始化-通过赋值运算符初始化②另外一种就是括号赋值。
int a = 10;
char c ='a';
//括号赋值运算
int a(10);
char c('a');
上述初始化是可以的,但他们的唯一不同是:括号运算符进行赋值时只能在初始化时,而等号则可以先定义在赋值。
int a;
a =10;
int a;
a(10);
//编译器会认为这是一个函数:函数名为a参数为10;
1. 构造函数的作用是:创建一个类的对象时,调用它的构造函数来构造这个类对象的数据成员,①给数据成员分配内存空间②是要给函数数据成员初始化(这个顺序是按照数据成员在类中的申明的顺序进行构造)。
冒号初始化: | 给数据成员分配内存空间时就进行了初始化(必须是括号赋值),也就是在分配了内存空间后在进入函数体之前就给数据成员赋值了,也就是初始化这个数据成员时函数体还没执行。 |
构造函数内初始化: | 是在所有数据成员分配空间之后,进入函数体之后才进行赋值的。这样也是有好处的 :有的数据成员需要在构造函数调用之后函数体执行之前进行初始化 如 引用,常量 |
private:
const int i;//这里也可以直接完成初始化,const int i=100,不报错,但是去变量意义
int p;
int &j=p;
};