1. 构造函数和析构函数的特点是:当对象创建的时候,自动执行构造函数,当对象销毁的时候,自动执行析构函数
(构造函数主要进行数据的初始化和取得该对象被使用前的所需要的一些资源 析构函数则是释放资源)
在一个对象的声明周期内构造函数和析构函数都执行一次。
创建一个对象一般主要有两种方式:
1>在线程运行的栈中创建
{
.....
Testt;
.....
}
在这个时候,销毁这种对象并不需要显式的执行析构函数,当对象离开作用域的时候即销毁(在花括号之前)[空间便会释放]
2>在全局堆中动态创建
{
.....
Test*pt= newTest();
.....
deletept;
......
}
在这个时候,指针pt所指向对象的内存从全局堆中取得,并将首地址的值赋给pt,(这里pt仍然是在线程栈中,只是对象空间
在全局堆中),这时候,对象需要显式的执行delete语句,(但是pt仍然未销毁,在栈中,当作用域执行完时,才会销毁)
这是的指针称为悬挂指针(不允许访问结果都会出错)
现在分析下Win32下的这个悬挂指针可能会出现的情况:
<1> 第一种情况是该位置所在的“内存页”没有任何对象,堆管理器已经将其返还给操作系统,可能访问时会出现“无法访问”,
进一步导致进程崩溃
<2>第二种情况是该地址所在的“内存页”还有其他对象,该位置被回收以后,尚未被分配出去,这时访问pt会出现无意义的值
进一步也可能出现进程崩溃(反正也是错误的)
<3>第三中情况是该地址所在的“内存页”还有其他对象,该位置被回收以后,已经被分配出去,这时访问可能出现内存对象的
改变,出现意想不到的情况。(反正也是错误的)
2. 创建一个对象一般需要两步,首先取得对象所需要的内存(无论是堆中还是线程栈中),然后在该内存块上执行构造函数。
在构造函数构建对象的时候,也会执行两个步骤,第一会初始化列表,然后执行函数的函数体。
classTest
{
private:
intiVal;
stringsVal;
public:
Test(inti,strings):iVal(i),sVal(s)
{
//......
}
};
首先构造函数会先执行iVal(i),sVal(s)然后执行函数体
3.构造函数的一些认识(几点注意):
(1)构造函数其实是一个递归操作
在每层递归内部的操作遵循严格的次序,递归的模式是首先执行父类的构造函数,父类的执行完毕以后,
然后执行本类中的构造函数。对于构造本类中成员变量(一)严格的按照成员变量在本类中的声明顺序执行
而与其在初始化列表中顺序无关,(二)当有些成员变量或者父类的成员变量没有在初始化列表中时,它们仍然在
初始化操作这一步骤中被初始化。内建的成员变量会被初始化一个值。父类对象和成员变量调用默认的构造函数
进行初始化,一直到此类中的所有父类和父类所含的成员变量都被构造完成后,子类的初始化才算结束。
(2) 父类对象和一些成员变量没有出现在初始化列表中,这些对象仍然被执行构造函数,这是执行的是“默认构造的函数”
因此这些类所属的类必须提供可以调用的默认构造函数,为此,这些类要么不提供构造函数(由编译器提供),要么
自己提供的同时,也必须提供默认的构造函数(说白了,在写自己的构造函数时,不放把默认的构造函数写出来呵呵)
(3) 对于两类成员变量,const和reference类型的数据,所有的数据在执行函数体之前已经被构造,即已经有初始值,
所以const和reference类型的数据,必须在初始化列表中正确的初始化,而不能将其放在构造函数体内。
如下:
classTest
{
private:
constintiVal;
stringsVal;
public:
Test(inti,strings):iVal(i),sVal(s)
{
//iVal= i;//...... //iVal=i放在函数体内是错误的
}
};
(4) 从以上的例子,我们可以看到,在所有父类机器成员变量都必须初始化以后,子类如果再在函数体内执行相关的操作
是不是纯属浪费。如果在构造函数已经知道如何为类的成员变量初始化,那么就应该将这些类的初始化信息放在初始化
列表中,以防止再次初始化
现在通过一个例子说明以上现象:
#include"stdafx.h"
#include<iostream>
usingnamespacestd;
classTest
{
public:
Test(){
cout<<"Test::Test()"<<endl;
}
};
classderTest:publicTest
{
public:
derTest():val(0){
cout<<"derTest::derTest()"<<endl;
}
private:
intval;
};
classFirstClass
{
public:
FirstClass(inti):val(i){
cout<<"FirstTest::FirsetTest()"<<endl;
}
private:
intval;
};
classSecondClass
{
public:
SecondClass(inti=0):val(i){
cout<<"SecondClass::SecondClass()"<<endl;
}
private:
intval;
};
classsunTest:publicderTest
{
private:
SecondClasssecond;
FirstClassfirst;
public:
sunTest(intdata):first(data){ //由于没有给second赋值所以SecondClass的构造函数应该有默认值否则报错
cout<<"sunTest::sunTest()"<<endl;
}
};
int_tmain(intargc,_TCHAR*argv[])
{
sunTestsun(4);
return0;
}
//以上结果你应该认真分析下。
4.析构函数和构造函数的调用也是类似的递归操作。但是执行顺序是相反的,还有只执行函数体内部的模块!
与构造函数类似,如果操作的对象属于一个复杂体系中的末端节点,那么操作将会非常耗时,析构函数类似。
5. 创建和销毁对象也是非常的影响程序性能,动态内存的申请是非常耗时的,因为牵涉到寻找内存大小的内存块,
找到后还可能要进行截断处理,然后还要修改维护全局堆内存使用情况信息的链表等。。
减少对象生成/销毁的一个很简单的方法是在函数声明的时候,传值的方式改为按引用传值
例如intfunc(Testt)-------->intfunc(constTest&t)
前面的会生成临时对象,所以对象不会返回去,所以客户无法改变值的大小
后面的将一个客户对象传递过去,如果对象有所改变,客户中对象也会改变。
5. 在构造函数初始化列表中复制和函数体内赋值的比较,在比较大型的系统中,性能还是不可忽视的。
#include"stdafx.h"
#include<windows.h>
#include<iostream>
usingnamespacestd;
classTest
{
public:
Test(intval=1){
for(inti=0;i<1000;i++)
data[i]= val+i;
}
voidinti(intval=1){
for(inti=0;i<1000;i++)
data[i]= val+i;
}
private:
intdata[1000];
};
classObject
{
public:
//Object(intval):t(val){} //94ms
Object(intval) //156ms
{
t.inti(val);
}
private:
Testt;
};
int_tmain(intargc,_TCHAR*argv[])
{
unsignedlongi,nCount;
nCount= GetTickCount(); //获得当前的时间毫秒
for(i=0;i<10000;i++)
{
Objectobj(2);
}
nCount= GetTickCount()-nCount;
cout<<"timeused: "<<nCount<<endl;
return0;
}