类的构造、析构和赋值函数是每个类最基本的函数。
每个类只有一个析构、赋值函数;可以有多个构造函数。
例如对于类A,如果没有编写上述函数,C++编译器将自动为类产生四个缺省的函数;
A(void); //缺省的无参数构造函数
A(const A &a); //缺省的拷贝构造函数
~A(void); //缺省的析构函数
A & operate = (const A &a); //缺省的赋值函数
但是如果使用缺省的无参数构造函数和析构函数,等于放弃了自主的“初始化”、“析构”机会。
缺省的拷贝构造函数和缺省的赋值函数,均采用“位拷贝”而非“值拷贝”的方式来实现,倘若类中含有指针变量,这两个类将注定出错。
================================================
构造函数和析构函数的起源:
C++相比C语言提供了更好的机制来增强安全性。C++编译器有更严格的类型安全检查功能,它几乎能找到所有的语法问题。
但是有些高级别的错误隐藏地更深,根据经验,很多错误是由于变量没有被正确初始化或清除造成的。
而初始化和清除工作很容易被人遗忘。
所以C++语言充分地考虑了这个问题:把对象地初始化工作放在构造函数中执行,把对象地清除工作放在析构函数中执行。
当对象被创建时,构造函数被自动执行。当对象消亡时,析构函数被自动执行。这样就不用担心忘掉对象地初始化和清除工作了。
另外构造函数和析构函数地名字不能随便取,必须让编译器认得出来才行。所以其命名方法很简单。
让构造函数和析构函数与类同名。由于析构函数地目的与构造函数相反,所以加上前缀”~“以示区别。
还有就是构造函数和析构函数地没有返回值类型。注意,这与返回值类型位void的函数不同。
================================================
构造函数的初始化表
初始化表位于函数参数表之后,却在函数体{}之前。
这用来说明该表里的初始化工作发生在函数体内的任何代码被执行之前。
接下来看一下函数初始化表的使用示例:
1、如果类存在继承关系,派生类必须在其初始化表里调用基类的构造函数
class A{
A(int x); //A类的构造函数
}
class B:public A{
B(int x, int y); //B的构造函数
}
B::B(int x , int y):A(x) //在初始化表里调用A的构造函数
{
}
2、类的const常量只能在初始化表里进行初始化,因为它不能再函数体内用赋值的方式来初始化。
3、类的数据成员初始化可以采用两种方法:1)采用初始化表;2)函数体内赋值的方式
但是这两种方法的效率不同。
对于非内部数据类型的成员对象应该采用第一种初始化方式,可以获取更高的效率。
class A{
A(void); //无参数构造函数
A(const A &other); //拷贝构造函数
A & operator= (const A &other); //赋值函数
}
class B{
public:
B(const A &a); //B的构造函数
private:
A m_a; //成员对象
}
接下来可以有两种方式来对m_a进行初始化:
B::B(const A &a):m_a(a){
}
B::B(const A &a){
m_a = a;
}
第一种方式是成员对象在初始化表中被初始化
第二种方式是成员对象在函数体内被初始化。这种方式程序版式更清晰。但是对于非内部数据类型对象而言效率较低,这是因为实际上B的构造函数干了两件事,先暗地里创建m_a对象(调用了A的无参数构造函数),再调用了类A的赋值函数,将参数a赋值被m_a。所以这里调用了两次;
不过对于内部数据类型的数据成员而言,这两种初始化方法的效率几乎没有区别。
=================================================
构造和析构的次序
构造是从基类再到派生类;
析构相反,先析构派生类,再析构基类;
=================================================
为什么要重视拷贝构造函数和赋值函数:
举个例子,类String有两个对象a,b。
a.m_data = "hello"
b.m_data="world"
m_data是一个指针变量;
如果用缺省赋值函数进行拷贝的话,例如 b=a
这意味着进行位拷贝,b.m_data = a.m_data;
那么这会导致b.m_data原有的内存没有释放,造成内存泄漏。
其次会导致b.m_data和a.m_data指向同一块内存。a或b的任何一方变动都会影响另一方;
再就是在对象被析构时,这块内存被释放了两次。b.m_data析构,a.m_data也析构;
所以缺省的函数在处理指针变量时会由于默认进行位拷贝而出错。
注意不能混淆拷贝构造函数和赋值函数;
拷贝构造函数是在对象被创建时调用的;
而赋值函数只能被已经存在了的对象调用;
String a("hello");
String b("world");
String c = a; //调用了拷贝构造函数,最好写成c(a); 所以这行的语句风格较差,容易将拷贝构造和赋值混淆
c=b; //调用了赋值函数
如果实在不想写拷贝构造函数和赋值函数
可以将两个函数声明为私有,为空实现的。这样的话,编译器也不会生成缺省函数,别人也就没办法调用了,这就避免了错误产生;
需要注意的是:
基类的构造、析构、赋值函数不能被派生类继承。
如果类之间存在继承关系的话,要注意以下几点:
1、派生类的构造函数应该在初始化表里调用基类的构造函数;
2、基类和派生类的构造函数都应该为虚函数;这样的话才能先调用派生的析构,再调用基类的析构。不然的话只会调用基类的析构;
3、编写派生类的赋值函数时,注意不要忘记对基类的数据成员重新赋值,就是要调用基类的赋值函数;因为我们不能操作;
原文:https://www.cnblogs.com/grooovvve/p/12382024.html