构造函数
成员变量为私有的,要对它们进行初始化,必须用一个公有成员函数来进行。同时这个函数应该有且仅在定义对象时自动执行一次,这时
调用的函数称为构造函数(constructor)。
构造函数是特殊的成员函数,其特征如下:
- 函数名与类名相同,无返回值。(例:Test(){})
- 对象构造(对象实例化)时系统自动调用对应的构造函数完成初始化。
- 构造函数可以重载。
- 构造函数可以在类中定义,也可以在类中声明,类外定义。
- 如果类定义中没有给出构造函数,则C++编译器自动产生一个缺省的构造函数,但只要我们定义了一个构造函数,系统就不会自动生成缺省的构造函数,如果找不到对应的构造函数将报错。
- 无参的构造函数和全缺省值的构造函数都认为是缺省构造函数,并且缺省的构造函数只能有一个。如果既有无参构造又有缺省构造将报错,因为编译器不知道调用哪一个构造函数,就认为没有完成初始化。
举个栗子:
class Date
{
public:
// 1.无参构造函数
Date()
{}
// 2.带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//3.全缺省构造函数,与无参构造互斥,将报错
//Date(int year=2019, int month=1, int day=1)
//{
// _year = year;
// _month = month;
// _day = day;
//}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; // 调用无参构造函数
Date d2(2015, 1, 1); // 调用带参的构造函数
//Date d3(); // 注意这里没有调用d3的构造函数定义出 d3
return 0;
}
拷贝构造函数
创建对象时使用同类对象来进行初始化,这时所用的构造函数称为拷贝构造函数(Copy Constructor),拷贝构造函数是特殊的构造函
数。
特征:
- 拷贝构造函数其实是一个构造函数的重载。
- 拷贝构造函数的参数必须使用引用传参,使用传值方式会引发无穷递归调用。
- 若未显示定义,系统会默认缺省的拷贝构造函数,此时是浅拷贝。缺省的拷贝构造函数会依次拷贝类成员进行初始化。
- 在堆上的拷贝构造函数进行浅拷贝时,对象同时指向一块儿空间,当其中一个对象完成析构,则堆上所开的空间释放,另一个对象再析构,程序将崩溃。
举个栗子:
class Date
{
public:
//无参构造函数
Date()
{}
//拷贝构造函数
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
// 下面两种用法都是调用拷贝构造函数,是等价的。
Date d2(d1); // 调用拷贝构造函数
Date d3 = d1; // 调用拷贝构造函数
return 0;
}
析构函数
当一个对象的生命周期结束时,C++编译系统会自动调用一个成员函数,这个特殊的成员函数即析构函数(destructor)
构造函数是特殊的成员函数,其特征如下:
- 析构函数在类名加上字符~,析构函数无参数无返回值。
- 一个类有且只有一个析构函数。若未显示定义,系统会自动生成缺省的析构函数。
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
- 当自定义类型在堆上开辟了空间,且没有自定义实现析构函数时,系统调用默认的构造函数时,不会对开辟的空间进行释放。
- 注意析构函数体内并不是删除对象,而是做一些清理工作。
举个栗子:
class Array
{
public :
Array (int size)
{
_ptr = (int *)malloc( size*sizeof (int));
}
// 这里的析构函数需要完成清理工作(即对自定义类型申请的空间进行释放)。
~ Array ()
{
if (_ptr )
{
free(_ptr );
_ptr = 0;
}
}
private :
int* _ptr ;
};
赋值操作符重载
运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
- 与函数重载没有关系
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型或者枚举类型的操作数
- 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数
- 操作符有一个默认的形参this,限定为第一个形参
- .* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
赋值运算符重载是操作符重载的一个特殊形式,拷贝构造函数是创建对象,使用一个已有对象来初始化这个准备创建的对象,赋值运算符重载是对一个已存在的对象进行拷贝赋值。若无,编译器默认一个值拷贝的赋值运算符重载。
特点:
- 参数可以传引用也可以传值(传值相当于形参调拷贝构造函数,拷贝构造函数参数为引用,则不会引起无穷递归的问题)
- 返回类型为:类型&(若有返回值则返回*this,返回类型尽量使用引用,因为this指针在函数生命周期之外) 或 void
- 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝
- 既然编译器生成的默认赋值重载函数已经可以完成字节序的值拷贝了,但在遇到自定义类型需要开空间时,是需要自己实现赋值运算符重载的
举个栗子:
class Date
{
public:
//无参构造函数
Date()
{}
//拷贝构造函数
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void operator = (const Date& d)
{
if (this != &d)
{
this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
}
//return *this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d3;
Date d2 = d1; // 调用拷贝构造函数
d3 = d1; // 调用赋值运算符的重载
return 0;
}
取地址操作符重载
与赋值操作符重载类似,用operator&()实现
一般实现如下:
Date* operator&()
{
return this ;
}
const修饰的取地址操作符重载
加了const修饰的取地址操作符重载也是如此
实现如下:
const Date* operator&()const//const Date* operator&(const Date* this)
{
return this ;
}
这两个运算符一般不需要重载,编译器默认会生成,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如不想让别人获取到指定的内容等等
小结
构造函数 | 析构函数 |
---|---|
初始化 | 清理释放 |
拷贝构造函数 | 赋值运算符重载 |
---|---|
未初始化对象,构造函数的重载 | 已初始化对象,运算符重载的特殊使用 |