C++中,类有六个默认函数,他们分别是“默认构造函数”,“析构函数”,“拷贝赋值运算符”,“拷贝构造函数”,“移动构造函数”,“移动赋值运算符”。如果开发者不显式地定义以上函数,编译器会自动生成,但是有一些时候编译器生成的往往不能满足我们的需求,因此需要开发者自己实现。那么今天介绍常用的前四种函数。
一、构造函数
构造函数类似于我们的初始化函数,可用于初始化对象的参数或者成员参数,它的注意事项如下:
1.构造函数无返回值(函数名前什么都不写)。
2.构造函数可以构成重载,一般只写一个。
3.构造函数的函数名就是类名。
4.构造函数在创建对象的时候默认调用。
它的一般实现如下:
class Date
{
public:
//创建构造函数
Date(int year = 2024, int month = 7, int day = 13)
{
cout << "调用了构造函数" << endl;
_year = year;
_month = month;
_day = day;
}
private:
//创建成员变量
int _year;
int _month;
int _day;
};
int main()
{
Date Mydate;
return 0;
}
运行结果如下:
二、析构函数
析构函数用于对象的生命周期结束时资源的释放,一般来讲,有动态内存管理的类一般都需要定义析构函数,以防止因开发者忘记调用销毁函数带来的内存泄漏。它的注意事项如下:
1.析构函数的函数名是“~”+类名。
2.一个类只能有一个析构函数。
3.自定义类型的成员会自动调用它的析构函数。
4.析构函数在对象的生命周期结束时自动调用。
5.如果在同一个域内定义了多个对象,那么析构函数遵循栈的先入后出的规则,最后定义的对象先析构。
析构函数的一般实现如下:
class Date
{
public:
//创建构造函数
Date(int year = 2024, int month = 7, int day = 13)
{
cout << "调用了构造函数" << endl;
_year = year;
_month = month;
_day = day;
}
//创建析构函数
~Date()
{
cout << "调用了析构函数" << endl;
}
private:
//创建成员变量
int _year;
int _month;
int _day;
};
int main()
{
Date Mydate;
return 0;
}
运行结果:
三、拷贝构造函数
拷贝构造函数主要是用一个已经创建好的对象去初始化另一个正在创建的对象。它的注意事项如下:
1.拷贝构造是构造函数的一个重载,它的参数是一个对象的引用(不是传值值,是传引用)。
2.C++规定自定义类型的对象的拷贝行为(传值传参,传值返回)必须调用拷贝构造。
它的一般实现如下:
class Date
{
public:
//创建构造函数
Date(int year = 2024, int month = 7, int day = 13)
{
cout << "调用了构造函数" << endl;
_year = year;
_month = month;
_day = day;
}
//创建拷贝构造函数
Date(const Date& date)
{
//cout << "调用了拷贝构造函数" << endl;
_year = date._year;
_month = date._month;
_day = date._day;
}
//创建析构函数
~Date()
{
cout << "调用了析构函数" << endl;
}
void Print()
{
cout << _year <<"-"<<_month <<"-"<< _day << endl;
}
private:
//创建成员变量
int _year;
int _month;
int _day;
};
int main()
{
Date Mydate1;
Date Mydate2(Mydate1);//或者Date Mydate2 = Mydate1;
return 0;
}
运行结果如下:
值得注意的是,C++在拷贝构造的参数检查十分严格,它的参数不是传值而是产引用的原因是:传值会引发无穷递归。
上边第二点明确了:C++规定自定义类型的对象的拷贝行为(传值传参,传值返回)必须调用拷贝构造。
如果是传值传参:那么在初始化 Mydate2 的时候,会优先调用拷贝构造,但是由于拷贝构造也需要传参,那么就会在调用拷贝构造的时候调用这个拷贝构造的拷贝构造......
这样导致了当前调用的拷贝构造会调用属于他自己的拷贝构造,这样就形成了无限循环,最终可能导致程序崩溃。
如果是传引用传参:那么在传递参数的时候就不会调用拷贝构造自然也就没有了无穷递归。
因为C++编译器会在定义拷贝构造的时候进行检查,所以不必担心拷贝构造的参数没有写成引用传参,但作为初学者应牢记这个重点。
四、赋值运算符重载
赋值运算符重载主要用于两个已经定义好的对象的相互复制。它使用关键字“operator”+“需要重载的符号”,这里是“operator=”,它也属于函数,所以一般声明如下:
class Date
{
public:
Date(int year = 2024, int month = 7, int day = 13)
{
//cout << "调用了构造函数" << endl;
_year = year;
_month = month;
_day = day;
}
//创建拷贝构造函数
Date(const Date& date)
{
//cout << "调用了拷贝构造函数" << endl;
_year = date._year;
_month = date._month;
_day = date._day;
}
//创建析构函数
~Date()
{
//cout << "调用了析构函数" << endl;
}
void Print()
{
cout << _year <<"-"<<_month <<"-"<< _day << endl;
}
// = 运算符重载
Date& operator=(const Date& date);
private:
//创建成员变量
int _year;
int _month;
int _day;
};
在类中的函数默认第一个参数是this指针(编译过后),双目运算符的左右参数顺序就是函数的参数顺序。由于改变的只是第一个参数,为了较少拷贝带来的开销,所以这里的参数使用传引用,并且加以const修饰。在某些场景下(比如连等 a = b = c)需要接收返回值,并减少拷贝的开销,所以函数的返回值是传引用返回。
这里日期类的定义可以是:
Date& Date::operator=(const Date& date)
{
_year = date._year;
_month = date._month;
_day = date._day;
return *this;
}
int main()
{
//创建Mydate1对象并初始化
Date Mydate1(2024,6,10);
//打印Mydate1年月日
Mydate1.Print();
//创建Mydate对象并初始化
Date Mydate2(2024,7,30);
//打印Mydate2年月日
Mydate2.Print();
//将Mydate2 赋值给 Mydate1
Mydate1 = Mydate2;
//打印Mydate2年月日
Mydate2.Print();
return 0;
}
运行结果如下:
除“ .* sizeof . ? : :: ”不能重载之外,其他运算符都能重载,并且重载之后不能更改原本符号的含义。
另外“赋值运算符在类中不显式实现时,编译器会生成一份默认的,此时用户在类外再将赋值运算符重载为全局的,就和编译器生成的默认赋值运算符冲突了,故赋值运算符只能重载成成员函数”