类的构造函数,析构函数与赋值函数
1.每个类只有一个析构函数和一个赋值函数,但是可以有多个构造函数(一个拷贝构造函数,其他为普通构造函数)
2.对于任意一个类A,如果不想编写上类函数,c++编译器会自动生成四个缺省的函数
A(void);
A(const A &a);
~A(void);
A & operator=(const A &a);
如果可以自动生成缺省函数,为什么还要程序员编写这些函数?
(1)如果使用“缺省”的无参构造函数和缺省的析构函数,就等于自动放弃了“初始化”和“清除”的机会,
(2)“缺省的无参构造函数”和“缺省的析构函数”均采用位拷贝而非值拷贝的方式来实现,倘若函数中有指针,必定会出错。
3. 构造函数与析构函数的起源
比起c语言,c++提供了更好的机会来加强类型安全检查,
根据经验,很多难以察觉的程序错误都是由于变量没有被正确初始化或者清除造成的,而且初始化和清除工作很容易被遗忘,所以c++中充分考虑了这种情况,并很好的解决,把对象 的初始化的工作交给构造函数,把对象的清除工作交给析构函数,当对象被创建时,构造函数自动执行,对象被清除时,析构函数自动执行,这下就不用担心对象的初始化和清除工作。
析构函数与构造函数的名字是有规定的,必须让编译器认出才能被自动执行,
所以让构造函数与类同名,析构函数前加~
构造函数与析构函数还有特别之处:没有返回值类型。
4. 构造函数的初始化表
构造函数有个特殊的初始化方式叫做“初始化表达式表”(简称初始化表),初始化表位于函数的参数表之后,但是却在{}之前。
表中的初始化工作发生在函数体内任何代码执行之前。
如果类存在继承关系,派生类必须在其初始化表里调用基类的构造函数
class A
{
Public:
A(int x);
Private:
Int m_data;
};
Class B:public A
{
Public:
B(int x, int y);
};
B::B(int x, int y):A(x)//在初始化表中调用基类的构造函数
{
}
类的const 成员只能在类的初始化表中初始化,因为它不能在函数体内用赋值的方式来初始化。
类的数据成员初始化可以采用初始化表或者函数题内赋值的两种方式进行(重点关注)
1.初始化表
2.函数体内赋值
对于非内部数据类型,应该采用第一种方式进行初始化,效率比较高
对于非内部数据类型,采用两种方式的效率差不多,但是第二种的可读性好,代码清晰。
class A
{
public:
A(void);
A(const A &other);
A& operator=(const A &other);
~A(void);
private:
int m_data;
};
class B
{
public:
B(const A &a);
~B(void);
private:
A m_data;
};
A::A(void)
{
cout<<"A::A(void)"<<endl;
}
A::~A(void)
{
cout<<"A::~A(void)"<<endl;
}
A::A(const A &other)
{
cout<<"A::A(const A &other)"<<endl;
}
A& A::operator=(const A &other)
{
cout<<"A::operator=(const A &other)"<<endl;
return *this;
}
B::B(const A &a):m_data(a)
{
cout<<"B::B(const A &a)"<<endl;
}
B::~B(void)
{
cout<<"B::~B(void)"<<endl;
}
int main()
{
A a;
B b(a);
}
结果:
class A
{
public:
A(void);
A(const A &other);
A& operator=(const A &other);
~A(void);
private:
int m_data;
};
class B
{
public:
B(const A &a);
~B(void);
private:
A m_data;
};
A::A(void)
{
cout<<"A::A(void)"<<endl;
}
A::~A(void)
{
cout<<"A::~A(void)"<<endl;
}
A::A(const A &other)
{
cout<<"A::A(const A &other)"<<endl;
}
A& A::operator=(const A &other)
{
cout<<"A::operator=(const A &other)"<<endl;
return *this;
}
B::B(const A &a)
{
m_data = a;
cout<<"B::B(const A &a)"<<endl;
}
B::~B(void)
{
cout<<"B::~B(void)"<<endl;
}
int main()
{
A a;
B b(a);
}
结果显示:
1. 用初始化列表的方式对成员进行初始化,类B的构造函数在其初始化表里调用A的拷贝构造函数。从而将成员m_a初始化。
2. 第二种用赋值的方式将成员对象m_a初始化,先暗地里创建m_a 对象(调用A的无参构造函数),再调用A的赋值函数,将参数a赋值给m_a.
对于内部数据类型而言,两种初始化的效率没有什么区别,但是用赋值的方式程序的可读性更好。
class C
{
public:
C(int x, int y);
private:
int m_x,m_y;
};
C::C(int x, int y):m_x(x),m_y(y)
{
cout<<"C::C(int x, int y)"<<endl;
}
int main()
{
C c(3,5);
return 0;
}
class C
{
public:
C(int x, int y);
private:
int m_x,m_y;
};
C::C(int x, int y)
{
m_x = x;
m_y = y;
cout<<"C::C(int x, int y)"<<endl;
}
int main()
{
C c(3,5);
return 0;
}
5. 构造与析构的次序
构造从类层次的最根处开始,在每一层中,首先调用基类的构造函数,然后调成员对象的构造函数,析构函数与构造函数的顺序相反,析构的顺序是唯一的。
成员函数的初始化顺序是按照在类中声明的顺序进行的(重点关注)
成员对象的初始化顺序完全不受他们在初始化表中的次序影响,只由成员函数在类中声明的顺序决定。因为类的声明是唯一的,而类的构造函数可以有多个,因此也会产生多个不同的初始化表,如果成员对象按照初始化表的顺序进行构造,这将导致析构函数无法得到唯一的逆序。
6.不要轻视拷贝构造函数与赋值函数(重点关注)
如果不主动编写拷贝构造函数和赋值函数,编译器将以“位拷贝”的方式自动生成缺省的函数,倘若类中有指针变量,那么这两个缺省的函数就引发了错误,
举例:
String类两个对象啊a,b a.data的内容为“hello”,b.data的内容为“world”
将a赋给b,缺省赋值函数的“位拷贝”执行b.data = a.data;
造成三个错误:
1.b.data原来的内存没有被释放,造成内存泄露
2.b.data与a.data指向同一块内存,a或b 的任何一方变动都会影响另一方。
3.析构的时候,同一块空间被析构两次。
拷贝构造函数与赋值函数容易混淆,经常导致错用,
拷贝构造函数是在对象被创建时调用的,
而赋值函数只能被已经存在了的对象调用。
String a(“chen”);
String b(“jie”);
String c = a; //调用拷贝构造函数 应该改成String c(a);加以区分
c=b;//调用赋值函数。
7. 偷懒的办法处理拷贝构造函数与析构函数
如果实在不想编写拷贝构造函数和赋值函数,又不允许别人用编译器生成的缺省函数,把拷贝构造函数和赋值函数声明为私有函数,可以不编写代码。
8. 如何在派生类中实现类的基本函数???
基类的构造函数,析构函数,赋值函数都不能被派生类继承。
如果类之间存在继承关系,在编写函数时应该注意:
派生类的构造函数应该在其初始化表里调用基类的构造函数(前面已经说道)
基类与派生类的构造函数应该都为虚
#include<iostream>
using namespace std;
class Base
{
public:
virtual ~Base(void)
{
cout<<"Base::~Base(void)"<<endl;
}
};
class Derive:public Base
{
public:
virtual~Derive(void)
{
cout<<"Derive::~Derive(void)"<<endl;
}
};
void main()
{
Base *pB = new Derive;
delete pB;
}
在编写派生类的赋值函数时,不要忘记对基类的数据成员重新赋值。
class Base
{
public:
Base &operator = (const Base &other);
private:
int m_i,m_j,m_k;
};
class Derived:public Base
{
public:
Derived& operator=(const Derived &other);
private:
int m_x,m_y,m_z;
};
Derived& Derived::operator=(const Derived& other)
{
if(this == &other)//检查自赋值
return *this;
Base::operator=(other);//对基类的数据成员重新赋值
//对派生类的成员赋值
m_x = other.m_x;
m_y = other.m_y;
m_z = other.m_z;
//返回本类对象的引用
return *this;
}
关于string 类的相关问题,我会单独总结。