C++之类和对象:构造函数,析构函数,拷贝构造,赋值运算符重载
前提:如果一个类是空类,C++中空类中真的什么都没有吗,不是的,编译器会自动生成6个默认成员函数。默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
默认成员函数:构造函数,析构函数,拷贝构造函数,赋值运算符重载,取地址重载,const取地址重载。
构造函数(完成初始化):
我们在设计类的时候,在C语言中必须有一个初始化函数才好定义变量。在C++中类如果我们没有写这个初始化函数,那么编译器就会默认生成一个默认构造函数。这个默认构造函数对内置类型不处理,自定义类型调用它的默认构造函数。
构造函数的调用:
class Data
{
public:Data(int year,int month,int day){_year = year;_month = month;_day = day;}
private://内置类型int _year = 2025;int _month = 4;int _day = 28;//自定义类型//Time _t;
};int main()
{Data day(2025,4,28);//调用带参的构造函数return 0;
}
如果是无参的构造函数:Data day; 即可,不要括号。
构造函数的特性:
1.函数名与类名相同
2.无返回值
3.对象(也可以称为自定义类型变量)实例化时编译器自动调用对应的构造函数。
4.构造函数也可以重载。
5.如果类中没有显式定义构造函数,编译器才会生成,一旦显式定义就不会生成
6.编译器生成的默认构造函数对内置类型不处理,自定义类型调用它的默认构造函数。
7.无参的构造函数,全缺省的构造函数,编译器自动生成的构造函数都是默认构造函数。
注意提醒:编译器生成的默认构造函数对于自定义类型会自动调用它的默认构造函数,而如果我们自己写了一个无参的构造函数就按照自己写的内容来,不会自动调用。
还有一个容易混淆的点:编译器自动生成的默认构造函数会自动调用自定义类型的默认构造函数,如果这个自定义类型的构造函数如果不是默认构造函数就会报错。
//会报错的代码:
class Time
{
public://不是默认构造函数Time(int hour,int minute,int second){cout << "Time" << endl;_hour = hour;_minute = minute;_second = second;}
private:int _hour;int _minute;int _second;
};class Data
{
private://内置类型int _year;int _month;int _day;//自定义类型Time _t;
};int main()
{Data day;return 0;
}
上述代码 Data 类中就有 Time 自定义类型,但是构造函数不是默认构造函数所以报错了。
//正确的代码:
class Time
{
public://全缺省函数Time(int hour=0,int minute=0,int second=0){cout << "Time" << endl;_hour = hour;_minute = minute;_second = second;}
private:int _hour;int _minute;int _second;
};class Data
{
private://内置类型int _year;int _month;int _day;//自定义类型Time _t;
};int main()
{Data day;return 0;
}
所以我们尽量使用全缺省构造函数。
还有对于编译器自动生成的默认构造函数,针对内置类型不做处理,C++11有了内置类型成员在类中声明时可以给默认值 就是在声明时给缺省值:
析构函数(完成清理工作):
析构函数是在对象销毁时完成对象中资源的清理工作,对象在销毁时会自动调用析构函数。
析构函数不是销毁对象本身,对象本身是由编译器销毁的。
析构函数的特性:
1.析构函数名是类名前面加字符~。
2.无参数也无返回值。
3.一个类只有一个析构函数,如果没有显式定义,编译器会自动生成默认的析构函数,注意:析构函数不能重载。
4.对象生命周期结束时,系统会自动调用析构函数。
5.编译器生成的默认析构函数,对内置类型不做处理,对自定义类型调用它的析构函数。
6.如果类中有申请资源,例如:动态申请空间之类的需要手动销毁,必须要写析构函数手动销毁,否则会造成资源泄漏。如果没有资源申请则可以默认析构函数。
#include<iostream>
using namespace std;class Time
{
public:Time(int hour=0,int minute=0,int second=0){cout << "Time" << endl;_hour = hour;_minute = minute;_second = second;}~Time(){cout << "Time析构函数" << endl;}
private:int _hour;int _minute;int _second;
};class Data
{
public:Data(int year,int month,int day){cout << "Data" << endl;_year = year;_month = month;_day = day;}~Data(){cout << "Data析构函数" << endl;}
private:int _year = 2025;int _month = 4;int _day = 28;
};int main()
{Data day(2025,4,28);Time TY;return 0;
}
上述代码让我们明白系统调用析构函数的顺序:局部对象(后定义先析构)-> 局部静态对象 ->
全局对象(后定义先析构):
拷贝构造函数(同类对象初始化创建对象):
拷贝构造函数:只有单个形参,形参是本类类型对象的引用(一般用const修饰),用已存在的自定义类型对象创建新对象时由编译器自动调用。
拷贝构造的特性:
1.拷贝构造函数是构造函数的一个重载形式。
2.拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器会报错,会引发无尽递归调用。
3.如果类中没有显式定义拷贝构造,编译器会生成默认拷贝构造函数,默认拷贝构造对内置类型按内存存储按字节序完成拷贝,是值拷贝,或者叫浅拷贝。自定义类型调用它的拷贝构造。
4.编译器的默认拷贝构造,只会完成浅拷贝,一旦类中有资源申请,拷贝构造函数必须要写,例如:有动态内存管理,那么指向动态内存的指针,浅拷贝只会拷贝地址,不会生成相同的动态内存,会导致很多问题。
5.拷贝构造典型调用场景:使用已存在对象创建新对象 / 函数参数类型为类类型对象 / 函数返回值类型为类类型对象。
class Data
{
public:Data(int year = 2025,int month = 4,int day = 29){cout << "Data" << endl;_year = year;_month = month;_day = day;}Data(const Data& d){cout << "Data拷贝构造" << endl;_year = d._year;_month = d._month;_day = d._day;}~Data(){cout << "Data析构函数" << endl;}
private://内置类型int _year;int _month;int _day;
};int main()
{Data day1;Data day2(day1);return 0;
}
当函数传值传参时或者返回值时调用拷贝构造:
class Data
{
public:Data(int year = 2025,int month = 4,int day = 29){cout << "Data" << endl;_year = year;_month = month;_day = day;}Data(const Data& d){cout << "Data拷贝构造" << endl;_year = d._year;_month = d._month;_day = d._day;}~Data(){cout << "Data析构函数" << endl;}
private://内置类型int _year;int _month;int _day;
};Data Test(Data d)
{Data tmp(d);return tmp;
}int main()
{Data day1;Test(day1);return 0;
}
实际过程中编译器为了效率会减少一些拷贝构造优化拷贝构造。为了提高效率,一般对象传参尽量使用引用类型,返回值看情况。
赋值运算符重载(把一个对象赋值给另一个对象):
运算符重载:
C++引入了运算符重载,运算符重载是具有特殊函数名的函数。
注意:1.不能通过连接其他符号来创建新操作符:operator@
2.重载操作符必须有一个类类型参数,不能去重载运算符改变内置类型的行为。
3. .* :: sizeof ?: . 以上五个运算符不能被重载。
class Data
{
public:Data(int year = 2025,int month = 4,int day = 29){cout << "Data" << endl;_year = year;_month = month;_day = day;}Data(const Data& d){cout << "Data拷贝构造" << endl;_year = d._year;_month = d._month;_day = d._day;}//这里左操作数是this指针,指向调用函数的对象bool operator==(const Data& d2){return _year == d2._year && _month == d2._month && _day == d2._day;}~Data(){cout << "Data析构函数" << endl;}
private://内置类型int _year;int _month;int _day;
};int main()
{Data day1;Data day2(day1);cout << (day1 == day2) << endl;return 0;
}
赋值运算符重载:
1.赋值运算符重载格式:1参数类型 const T& ,传递引用提高效率。2返回值类型 T& 支持连续赋值且提高效率。3检查是否给自己赋值。4返回 *this,要符合连续赋值的含义。
2.赋值运算符只能重载成类的成员函数不能重载成全局函数。因为类中没有显式实现,编译器会生成默认赋值运算符重载,如果在全局中也有一个,就冲突了。
3.编译器生成的默认运算符重载,对内置类型浅拷贝,对自定义类型调用它的赋值运算符重载。
还是需要注意类中是否有资源申请,有的话必须要实现。
class Data
{
public:Data(int year = 2025,int month = 4,int day = 29){cout << "Data" << endl;_year = year;_month = month;_day = day;}Data(const Data& d){cout << "Data拷贝构造" << endl;_year = d._year;_month = d._month;_day = d._day;}Data& operator= (const Data& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}bool operator== (const Data& d2){return _year == d2._year && _month == d2._month && _day == d2._day;}~Data(){cout << "Data析构函数" << endl;}
private://内置类型int _year;int _month;int _day;
};int main()
{Data day1(1,2,3);Data day2;day2 = day1;return 0;
}
和拷贝构造的区别:拷贝构造是一个存在的对象创建一个新对象并且赋值,赋值运算符重载是两个存在的对象,一个给另一个赋值。