类的6个默认成员函数
空类,当你创建一个类时就比如是这样的
Class Date (){};
它此时占空间的大小为1,而不是0.是因为系统中会给他默认生成6个成员函数
初始化和清理
构造函数主要负责初始化
析构函数主要负责清理
拷贝复制
拷贝构造使同类对象初始化并创建对象
赋值重载把一个对象赋值给另一个对象
取地址重载
普通对象的取地址
const对象的取地址
class Date
{
private:
int _year;
int _month;
int _day;
};
构造函数
构造函数并不是创建一个函数,而是初始化函数
构造函数特性
1.定义时必须和类同名,这样编译器才能把它和类的其他成员函数区分开
2.构造函数没有返回值,因此对它们不可以指定返回类型(void也不行)
3.对象实例化时编译器自动调用对应的构造函数
4.构造函数可以重载
5.当程序员没有在类中显示定义一个构造函数,Cpp编译器才会自动生成一个无参的默认构造函数
6.默认构造函数有无参的构造函数(程序员自己写的,程序员自己没写时编译器自己生成的)、全缺省参数的构造函数且默认构造函数只能有一个
7.建议程序员构造函数要自己写而且在写构造函数的时候,就将其给写成全缺省构造函数,千万不要依赖于Cpp编译器生成的那个构造函数,因为很有可能它在瞎乱的初始化
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)//构造函数
{
_year = year;
_month = month;
_day = day;
}
void CoutDate()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(1919, 2, 3);
Date d2;
//Date d3();//编译时会报错
d1.CoutDate();
d2.CoutDate();
//d3.CoutDate();//表达式包含类类型
}
上述代码是一个具有全省参数的构造函数,这样就可以保证每一次都可以做到将函数给初始化了,最起码在构造函数的时候不会出现随机值的乱入
析构函数
对象在销毁时会自动调用析构函数,完成类的一些资源清理工作
析构函数特性:
1.析构函数名是在类名前加~
2.无参数,无返回值,故析构函数是唯一的
3.一个类析构函数唯一,若程序员未定义,则系统会自动生成一个
4.对象生命周期结束时,编译系统自动调用析构函数
~Date()
{
cout << "~Date()" << this << endl;
}
资源需要清理,构造函数开辟了空间或者fopen了某些文件,则需要析构函数释放空间,fcolse文件
需要析构函数的场景:
常见的数据结构(链表(逐个析构),顺序表(逐个析构),二叉树(递归析构))
还有析构函数具体怎么析构,就具体情况而定
Seqlist s1;
Seqlist s2;
构造顺序:s1,s2;
析构顺序:s2,s1;
原因是栈,先进后出
拷贝构造函数
创建一个与一个对象一某一样的新对象
拷贝构造函数特性:
1.拷贝构造函数是构造函数的一种重载形式
2.拷贝构造函数函数,参数只有一个且必须使用引用传参,若用值传递传参时,就会调用拷贝构造函数来构造临时变量,形成递归
3.若程序员未定义,则系统生成默认的拷贝构造函数默认拷贝构造函数对象,按内存存储、按字节序完成拷贝,即浅拷贝
//Date(Date d)//值传递传参时,就会调用拷贝构造函数来构造临时变量,调用拷贝构造函数需要先传参,传参又是一个拷贝构造,调用拷贝构造函数,所以就会形成递归
//{ //应改为Date(Date& d)拷贝构造,需要传参,而d是d1的别名
// //但为了更保险一点应该改为Date(const Date& d)
// //这样就这样保证d的值不会被修改
// _year = d._year;
// _month = d._month;
// _day = d._day;
//}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
由于上述是浅拷贝**(按字节序进行拷贝)**,所有会让多个对象指向同一个地址,当析构时,假设s1
析构后,若此时进程中的该线程将这一部分地址还给了系统且系统又将这部分地址给了另一个线程,之后s2
析构时就是在析构别人的地址,当然该类为日期类并未涉及指针,所以,没有深浅拷贝之说
浅拷贝:按字节序进行拷贝
class SeqList
{
public:
SeqList(size_t N = 10)
{
_arr = (int*)malloc(N * sizeof(int));//这个是关键,因为他重新申请了一个空间
_capacity = N;
_size = 0;
}
SeqList()
{
free(_arr);
_arr = nullptr;//最好置空
_size = _capacity = 0;//最好置空,可以防止野指针
}
private:
int* _arr;
size_t _size;
size_t _capacity;
};
运算符重载
运算符重载的意义
便于可读,增强代码可读性
关键字operator
后面接需要重载的运算符
1.不能通过连接其他符号来创建新的操作符:比如operator@
2.重载操作符必须有一个类类型或者枚举类型的操作数
3.用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义
4..
(成员访问运算)、.*
、(成员指针访问运算符)::
(域运算符) 、sizeof
(长度运算符)、?:
(条件运算符)
注意以上5个运算符不能重载,
前两个运算符不能重载是为了保证访问成员的功能不能被改变
域运算符合sizeof运算符的运算对象是类型而不是变量或一般表达式,不具备重载的特征
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
赋值运算符重载
对一个已经存在的对象进行赋值拷贝
特性
1.有返回值,支持连续赋值
2.检测是否自己给自己赋值
3.传参与返回值都使用引用,可以提高代码效率
4.若程序员没有写,则编译器会生成一个浅拷贝的程序
Date& operator=(const Date& d)
//第一个&是为了在连续赋值的时候提高效率
{
if (&d != this)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
}
const成员
const*
,const在*之前,修饰指针指向内容
*const
,const在*之后,修饰变量本身
const成员函数:const
修饰类成员函数,实际修饰该成员函数隐含的this
指针,表明在该成员函数中不能对类的任何成员进行修改
取地址及const取地址操作符重载
这两个默认成员函数一般不重新定义,编译器会自动生成
Date* operator&()
{
return this;
}
const Date* operator&() const
{
return this;
}
构造函数的另一种写法(初始化列表)
Date(int year = 1900, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
{}
注意事项
1.每个成员变量在初始化列表中只能出现一次,即初始化只能初始化一次
2.类中以下成员变量必须使用初始化列表
- [const类型变量]
- [引用类型变量 (&)]
- [类类型变量,且该类没有默认构造函数]
3.相比于上面的构造函数,建议使用初始化列表
4.成员变量在类中声明的顺序就是其在初始化列表中初始化的顺序,而与其在初始化列表中的顺序无关