这里的六个默认的成员函数是在c++98中,而在c++11中默认成员函数已经增加到了八个(新增移动构造函数和移动赋值操作符重载)
一:构造函数
1.基本概念
- 构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时,由编译器自动调用,在对象的生命周期内只调用一次,保证每个数据成员都有一个合适的初始值。
class Date
{
public:
Date(int year, int month, int day)
:_year(year)//初始化列表,可以不用
, _month(month)
, _day(day)
{
cout << "Date(int, int, int)" << this << endl;
}
Date(int year, int month)
:_year(year)
,_month(month)
{
cout << "Date(int, int)" << this << endl;
}
Date()
{
cout << "Date()" << this << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d3(2018, 8, 15);
Date d1(2018, 8);
Date d2;//如果不传参不要写成d2()
return 0;
}
2.特征
- 函数名与类名相同,且没有返回值(实际上返回值为指向当前对象的this指针)
- 新对象被创建时,由编译器自动调用,且在对象的声明周期内仅调用一次
- 构造函数可以重载,实参决定了调用那个构造函数
- 无参构造函数和带有缺省值的构造函数(这里指全缺省)都认为是缺省的构造函数,并
且缺省的构造函数只能有一个 - 有初始化列表(可以不用)
- 如果没有显式定义时,编译器会合成一个默认的构造函数(在有必要时才会构造,像该类中有类类型的成员变量,且该类类型的成员变量有缺省的构造函数)
class Time
{
public:
Time()
{
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
private:
int _year;
int _month;
int _day;
Time _t;
};
int main()
{
Date d1;
return 0;
}
如上:在Date类中,并没有给出构造函数,但是在定义d1时调用了Date类的构造函数,该函数就是由编译器合成的。
- 构造函数不能用const修饰(因为构造函数中可能会为成员变量赋值)
3.初始化列表
- 基本概念:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个”成员变量”后面跟一个放在括号中的初始值或表达式
- 每个成员在初始化列表中只能出现一次
- 初始化列表仅用于初始化类的数据成员,并不指定这些数据成员的初始化顺序,但编译器在编译时会按照类中的声明次序初始化
- 类中如果有以下成员,必须放到初始化列表位置进行初始化:
- 引用成员变量
- const成员变量
- 类类型尘缘(该类没有缺省的构造函数,因为不初始化,编译器不知道传什么值)
4.构造函数的作用
- 构造&初始化对象
- 类型转换:对于单个参数构造函数,可以将其接受参数转化成类类型对象。用explicit修饰构造函数,抑制由构造函数定义的隐式转换,explicit关键字类内部的构建声明上,在类的定义体外部的定义上不再重复
二:拷贝构造函数
1.基本概念
- 只有单个形参,而且该形参是对本类类型对象的引用(常用const修饰),这样的构造函数称为拷贝构造函数。拷贝构造函数是特殊的构造函数,创建对象时使用已存在的同类对象来进行初始化,由编译器自动调用
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{}
Date(const Date& d)//拷贝构造函数
:_year(d._year)
, _month(d._month)
, _day(d._day)
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2018, 9, 10);
Date d2(d1);
return 0;
}
2.特征
- 构造函数的重载,构造函数的性质拷贝构造函数均满足
- 参数必须使用类类型对象引用传递(因为传值会创建临时变量,创建临时变量也会调用拷贝构造函数)
- 如果没有显式定义,系统会自动合成一个默认的拷贝构造函数。默认的拷贝构造函数会依次拷贝类的数据成员完成初始化(不一定合成,但是一定会完成拷贝构造函数)
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2018, 9, 10);
Date d2(d1);
return 0;
}
上面的代码中没有显示定义拷贝构造函数,编译器也没有合成,但是依然完成了拷贝工作
三:析构函数
1.基本概念
- 与构造函数功能相反,在对象被销毁时,由编译器自动调用,完成类的一些资源清理和汕尾工作
2.特征
- 析构函数在类名(即构造函数名)加上字符~
- 析构函数无参数无返回值
- 一个类有且只有一个析构函数。若未显示定义,系统会自动生成缺省的析构函数
- 对象生命周期结束时,C++编译系统系统自动调用析构函数
- 注意析构函数体内并不是删除对象,而是做一些清理工作
class Array
{
public:
Array(int capacity)
:_arr(NULL)
,_capacity(capacity)
, _size(0)
{
cout << "Array()" << this << endl;
_arr = (int*)malloc(sizeof(int)*capacity);
}
~Array()//析构函数
{
cout << "~Array()" << this << endl;
free(_arr);
_capacity = _size = 0;
}
private:
int* _arr;
int _capacity;
int _size;
};
int main()
{
Array arr(10);
return 0;
}
运行结果如下:
四:赋值操作符重载
在介绍赋值操作符重载之前,先来认识运算符重载
1.基本概念
- 重载操作符是具有特殊函数名的函数,关键字operator后面接需要定义的操作符符号。操作符重载也是一个函数,具有返回值和形参表。它的形参数目与操作符的操作数目相同,使用运算符重载可以提高代码的可读性。
注:不是所有的运算符都可以重载 - 不可以重载的运算符:
运算符 | 作用 |
---|---|
. | 成员选择符 |
.* | 成员对象选择符 |
:: | 域解析操作符 |
?: | 条件操作符 |
sizeof | 计算指定类型大小 |
3.注意
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型或者枚举类型的操作数(操作数不能为内置类型,没有意义)
- 操作符定义为非类的成员函数时,一般将其定义为类的友元(因为成员变量一般为private)
- 一般将算术操作符定义为非成员函数,将赋值运算符定义成员函数(在vs2013下,如果赋值运算符定义为非成员函数会报错)
- 作为类成员的重载函数,其形参看起来比操作数数目少1成员函数的操作符有一个默认的形参this,限定为第一个形参
- == 和 != 操作符一般要成对重载
- 前置式++/–必须返回被增量或者减量的引用,后缀式操作符必须返回旧值,并且应该是值返回而不是引用返回
class Date
{
friend Date operator+(Date& d1, const Date& d2);//将加法操作符重载定义为类的友元,这样就能访问类的私有成员变量
public:
Date(int year = 2018, int month = 6, int day = 20)
:_year(year)
, _month(month)
, _day(day)
{}
Date(const Date& d)
:_year(d._year)
, _month(d._month)
, _day(d._day)
{}
Date& operator = (const Date& d)//赋值操作符重载
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;//返回引用
}
~Date()
{}
private:
int _year;
int _month;
int _day;
};
Date operator+(Date& d1, const Date& d2)
{
Date d3;
d3._day = d1._day + d2._day;
return d3;
}
int main()
{
Date d1;
Date d2(2019, 6, 5);
Date d3;
d3 = d1 + d2;
return 0;
}
4.++/–操作符重载
class Date
{
public:
Date(int year = 2018, int month = 6, int day = 20)
:_year(year)
, _month(month)
, _day(day)
{}
Date(const Date& d)
:_year(d._year)
, _month(d._month)
, _day(d._day)
{}
Date& operator = (const Date& d)//赋值操作符重载
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;//返回引用
}
Date& operator++()//前置++
{
++_day;
return *this;//返回引用
}
const Date operator++(int)//后置++,这里是为了区别前置与后置函数的区别,形成函数重载
{
Date d(*this);//用当前对象拷贝构造临时变量d
++_day;//利用前置++
return d;//返回值,因为后置++是先使用值,再自增
}
~Date()
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(2019, 6, 5);
Date d3;
d3 = d2++;
d3 = ++d1;
return 0;
}
五:取地址操作符重载和const修饰的取地址操作符重载
class Date
{
public:
Date(int year = 2018, int month = 6, int day = 20)
:_year(year)
, _month(month)
, _day(day)
{}
Date(const Date& d)
:_year(d._year)
, _month(d._month)
, _day(d._day)
{}
Date& operator = (const Date& d)//赋值操作符重载
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;//返回引用
}
Date* operator&()//取地址操作符重载,this指针类型为Date* const this(指向不能改变)
{
return this;
}
const Date* operator&()const//const修饰的取地址操作符重载,this指针类型为
//const Date* const this(除了指向不能改变,指针指向的内容也不能改变)
{
return this;
}
~Date()
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
const Date d2(2019, 6, 5);
cout << &d1 << endl;//调用取地址操作符重载
cout <<&d2 << endl;//调用const修饰的取地址操作符重载
return 0;
}
- 说明:取地址操作符重载和const修饰的取地址操作符重载这两个默认成员函数并没有什么实际的意义。