关于类的一篇很好的博客:http://blog.sciencenet.cn/blog-268489-590308.html
类构造函数初始化:概念上有两次
1.初始化列表:这是真正意义上的初始化;有两种情况必须在初始化列表中初始化:
①成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
②const成员或引用类型的成员。因为const对象或引用类型只能初始化,不能对他们赋值。
2.在构造函数体中:其实是在初始化列表初始化后在此赋值;
1.内置数据类型,复合类型(指针,引用)
在成员初始化列表和构造函数体内进行,在性能和结果上都是一样的
2.用户定义类型(类类型)
结果上相同,但是性能上存在很大的差别。因为类类型的数据成员对象在进入函数体前已经构造完成,也就是说在成员初始化列表处进行构造对象的工作,调用构造函数,在进入函数体之后,进行的是对已经构造好的类对象的赋值,又调用个拷贝赋值操作符才能完成(如果并未提供,则使用编译器提供的默认按成员赋值行为)
1.对于任意一个类,如果没有定义任何构造函数和析构函数,则由编译器自动的产生4个缺省的函数:
默认构造函数:A(void); // 缺省的无参数构造函数
默认拷贝构造函数:A(const A &a); // 缺省的拷贝构造函数
默认析构函数:~A(void)//缺省的析构函数
默认的赋值函数:A & operate =(const A &a); // 缺省的赋值函数
析构函数表示清除此对象,默认拷贝构造函数和默认的赋值函数只是浅复制;
一旦类中定义了,编译器就不会再默认创建;
2. 构造函数和拷贝构造函数的区别
构造函数:初始化类的成员参数
拷贝构造函数:用一个类初始化另一个类(Test(Test &c_t)是自定义的拷贝构造函数,拷贝构造函数的名称必须与类名称一致,函数的形式参数是本类型的一个引用变量,且必须是引用)
拷贝构造函数被调用的情况:
一个对象以值传递的方式传入函数体
一个对象以值传递的方式从函数返回
一个对象需要通过另外一个对象进行初始化。
3.深拷贝,浅拷贝
如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝
缺省的拷贝构造函数就是浅拷贝,也称位拷贝:没有拷贝内容,仅仅拷贝了地址。
深拷贝的例子:
#include <iostream>
using namespace std;
class CA
{
public:
CA(int b,char* cstr)
{
a=b;
str=new char[b];
strcpy(str,cstr);
}
CA(const CA& C)
{
a=C.a;
str=new char[a]; //深拷贝
if(str!=0)
strcpy(str,C.str);
}
void Show()
{
cout<<str<<endl;
}
~CA()
{
delete str;
}
private:
int a;
char *str;
};
int main()
{
CA A(10,"Hello!");
CA B=A;
B.Show();
return 0;
}
4. 位拷贝和值拷贝
A.m_data和B.m_data分别指向一段区域,A.m_data="windows",B.m_data=“linux";
如果未重写赋值函数,将B赋给A;则编译器会默认进行位拷贝,A.m_data=B.m_data
则A.m_data和B.m_data指向同一块区域,虽然A.m_data指向的内容会改变成"linux",但是这样容易出现这些问题:
(1):A.m_data原来指向的内存区域未释放,造成内存泄露。
(2):A.m_data和B.m_data指向同一块区域,任何一方改变都会影响另一方
(3):当对象被析构时,B.m_data被释放两次。
5. 组合和继承类的初始化顺序,析构顺序
(1)构造顺序:
从类层次的最根处开始,而在每一层都会调用基类的构造函数
然后调用成员对象构造函数,成员函数的构造顺序与初始化列表无关,而与定义的顺序有关
(2)析构顺序
与构造顺序完全相反