在C++中,类的默认构造函数有六个,他们分别是构造函数,拷贝构造函数,析构函数,赋值操作符重载,取地址操作符重载和const修饰的取地址操作符重载。今天对其中的构造函数,拷贝构造函数,析构函数,赋值操作符重载几个默认函数进行简单的分析。
定义:构造函数是一个特殊的成员函数,名字与类名相同,创建类型变量时由编译器自动调用,在对象的生命周期内只且只调用一次,以保证每个数据成员都有一个合适的初始值。
下面我们来看一个简单的例子:
在此类中,有两个构造函数,编译器会根据参数的不同来决定调用哪个函数。
1、构造函数的特性:
(1)函数名与类名相同。
(2)没有返回值。
(3)有初始化列表(可以不用)。
(4)新对象被创建,由编译器自动调用,且在对象的生命期
内仅调用一次。
(5)构造函数可以重载,实参决定了调用那个构造函数。
(6)如果没有显式定义时,编译器会提供一个默认的构造函
数。
(7)无参构造函数和带有缺省值得构造函数都认为是缺省构
造函数,并且缺省构造函数只能有一个。
(8)构造函数不能用const来修饰。
下面对构造函数以上的个别几个特性进行解释:
第三条中说有初始化列表,下面对初始化列表进行详细介绍:
定义:初始化列表是以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个数据,成员后面跟一个放在园括号中的初始化式。
例如:
初始化顺序:
1、每个成员在初始化列表中只能出现一次。
2、初始化列表仅用于初始化数据成员,并不指定这些数据成员的初始化顺序,数据成员在类中定义顺序就是在参数列表中的初始化顺序。
3、尽量避免使用成员初始化成员,成员的初始化顺序最好和成员的定义顺序保持一致。(否则容易出现使用未初始化的成员去初始化另一个成员)
类中包含以下成员必须要放在初始化列表中初始化:
(1)引用数据成员
(2)const数据成员
(3)类类型成员(该类没有缺省的构造函数)
第六条中如果没有显示定义构造函数,系统会自动生成默认的构造函数:
2.默认构造函数
类如果没有显式定义构造函数时,编译器会合成一个默认的构造函数,该构造函数中什么工作都不做。只要显式定义了,即使该构造函数什么也不做,编译器也不会为该类合成默认的构造函数。编译器生成的默认构造函数使用与变量初始化相同的规则来初始化成员,具有类类型的成员通过运行各自的默认构造函数来进行初始化。内置和复合类型的成员如指针、数组,只对定义在全局作用域中的对象初始化,当对象定义在局部作用域时,内置和符合类型的成员不进行初始
化。在某些情况下,默认构造函数是由编译器隐式使用的。
那么问题来了,编译器是否任何情况下都会自动生成默认的构造函数呢?下面我们就来验证一下:
上面两幅是一个Date类的源代码和反汇编代码,从源代码中红色圈标记处可以看出调试时不能在那个地方打断点,说明编译器根本没自动生成构造函数,从反汇编代码中也可以看到在创建Date类后函数并没有调用其他函数。
那这个不就和上面说的如果我们没有显示定义构造函数,编译器会自动为我们生成一个默认的构造函数相矛盾了吗?别着急,我们再来看下面一组例子:
class Time
{
public:
Time()
{
cout << "Time()" << endl;
}//无参构造函数
Time(const Time& t)
{
cout << "~Time(const Time& t)" << endl;
}// 构造函数的重载
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
int _year;
int _month;
int _day;
Time t;
};
void FunTest()
{
Date d1;
}
int main()
{
FunTest();
system("pause");
return 0;
}
从这个例子可以看出,编译器又给Date类自动生成了构造函数,那么编译器到底在什么情况下自动生成而在什么情况下不生成呢?下面我们来简单总结一下:
不生成的情况:当编译器觉得构造函数什么也不用做时。
自动生成的情况:
假设有A和B两个类,
A 有自己缺省的构造函数
B 没有显示的定义构造函数
B中的类成员有A类型的变量,此时,系统会为B生成缺省的构造函数。
3.默认构造函数的作用:构建对象,初始化对象,类型转换。
1.定义:只有单个形参,而且该形参是对本类类型对象的引用(常用
const修饰),这样的构造函数称为拷贝构造函数。拷贝构造函数是特殊的构造函数,创建对象时使用已存在的同类对象来进行初始化,由编译器自动调用。
class CDate
{
public:
CDate()
{}
CDate( const int year, const int month, const int day)
{
_iYear = year;
_iMonth = month;
_iDay = day;
}
CDate( const CDate& date) //拷贝构造函数
{
_iYear = date._iYear;
_iMonth = date._iMonth;
_iDay = date._iDay;
}
private:
int _iYear;
int _iMonth;
int _iDay;
};
(1)它是构造函数的重载。
(2)它的参数必须使用同类型对象的引用传递。(不用创建临时变量)
(3)如果没有显式定义,系统会自动合成一个默认的拷贝构造函数。默认的拷贝构造函数会依次拷贝类的数据成员完成初始化。(何时自动生成和上面的构造函数原理相同)
(1)对象实例化对象
CDate d1(1990, 1, 1);
CDate d2(d1);
(2)传值方式作为函数的参数
void FunTest(const CDate date)
{}
(3)传值方式作为函数返回值
CDate FunTest()
{
CDate date;
return date;
}
析构函数:与构造函数功能相反,在对象被销毁时,由编译器自动调用,完成类的一些资源清理和汕尾工作。
a、析构函数在类名(即构造函数名)加上字符~。
b、析构函数无参数无返回值。
c、一个类有且只有一个析构函数。若未显示定义,系统会
自动生成缺省的析构函数。
d、对象生命周期结束时,C++编译系统系统自动调用析构函
数。
e、注意析构函数体内并不是删除对象,而是做一些清理工
作。
重载操作符是具有特殊函数名的函数,关键字operator后面接需要定义的操作符符号。操作符重载也是一个函数,具有返回值和形参表。它的形参数目与操作符的操作数目相同,函数调用操作符可以接受任意数目的操作数。使用运算符重载可以提高代码的可读性。返回类型 operate 操作符(参数列表)。
下面我们以赋值操作符的重载来看:
#include
using namespace std;
class Complex
{
public:
Complex(double r = 0.0, double i = 0.0)
:_real(r)
, _image(i)
{}
Complex& operator=(const Complex& d)
{
return Complex(_real = d._real, _image = d._image);
}
Complex& operator+(const Complex& c)
{
return Complex(_real + c._real, _image + c._image);
}
private:
double _real;
double _image;
};
void FunTest()
{
int a = 10;
int b = 20;
int c = 30;
a = b = c;
Complex c1(1.0, 2.0);
Complex c2(3.0, 4.0);
Complex c3;
c3 = c1 + c2; 重载
}
int main()
{
FunTest();
system("pause");
return 0;
}
结果:
在此程序案例中对赋值运算符进行了重载,实现了对两个类类型的变量的赋值。