类的默认成员函数
空类会自动生成6个默认成员函数, 默认成员函数就是⽤⼾没有显式实现(我们没有写出来的),编译器会⾃动⽣成的成员函数称为默认成员函数。⼀个类,我 们不写的情况下编译器会默认⽣成6个默认成员函数,需要注意的是这6个中最重要的是前4个,最后两个取地址重载不重要
构造函数:主要完成初始化工作
析构函数:完成清理工作,内存释放等。
拷贝构造:使用同类对象初始化创建对象。
赋值重载:把一个对象赋值给另一个对象。
构造函数
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象(我们常使⽤的局部对象是栈帧创建时,空间就开好了),⽽是对象实例化时初始化对象。构造函数的本质是要替代我们以前Stack和Date类中写的Init函数的功能,构造函数⾃动调⽤的特点就完美的替代的了Init。
特性
1.函数名与类名相同。
2.无返回值。(不需要写void)
3.对象实例化编译器自动调用,避免忘记初始化产生的错误。
4.构造函数可以重载,一般情况下写一个全缺省函数就行了。
5. 如果类中没有显式定义构造函数,则C++编译器会⾃动⽣成⼀个⽆参的默认构造函数,⼀旦⽤⼾显 式定义编译器将不再⽣成。
6. ⽆参构造函数、全缺省构造函数、我们不写构造时编译器默认⽣成的构造函数,都叫做默认构造函 数。但是这三个函数有且只有⼀个存在,不能同时存在。⽆参构造函数和全缺省构造函数虽然构成 函数重载,但是调⽤时会存在歧义。要注意很多同学会认为默认构造函数是编译器默认⽣成那个叫默认构造,实际上⽆参构造函数、全缺省构造函数也是默认构造,总结⼀下就是不传实参就可以调⽤的构造就叫默认构造。
7. 我们不写,编译器默认⽣成的构造,对内置类型成员变量的初始化没有要求,也就是说是是否初始 化是不确定的,看编译器。对于⾃定义类型成员变量,要求调⽤这个成员变量的默认构造函数初始 化。如果这个成员变量,没有默认构造函数,那么就会报错,我们要初始化这个成员变量,需要⽤初始化列表才能解决。
注意:当自己没有写构造函数的时候,编译器自动生成的构造函数对于内置类型的成员变量,没有规定要不要处理,看编译器行为,vs中就没有处理是随机值。对于自定义类型成员才会调用。
说明:C++把类型分成内置类型(基本类型)和⾃定义类型。内置类型就是语⾔提供的原⽣数据类型, 如:int/char/double/指针等,⾃定义类型就是我们使⽤class/struct等关键字⾃⼰定义的类型。
#include<iostream>
using namespace std;
class A
{
public:
A()//.⽆参构造函数
{
_a = 0;
cout << "A()" << endl;
}
private:
int _a;
};
class Date1
{
public:
// 我们没写,有没有构造函数?有->编译器自动生成
// 内置类型/基本类型 int/char/double.../指针
// 自定义类型 class/struct...
// 编译器自动生成构造函数,对于内置类型成员变量,没有规定要不要做处理!(有些编译器会处理)
// 对于自定义类型成员变量才会调用他的无参构造
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
A _aa;
};
class Date2
{
public:
Date2(int year = 2024, int month = 9, int day = 13)//全缺省构造函数
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
A _aa;
};
int main()
{
Date1 d1;
d1.Print();
Date2 d2;
d2.Print();//没有调用构造函数,但是编译器会自动调用
return 0;
}
可以看到Date1中没有写构造函数,编译器自动调用将_year,_month,_day初始化成了随机数,而Date2中创建对象时没有调用构造函数,但是编译器自动调用了我们写的构造函数将_year,_month,_day初始化成了2024-9-13,_a被初始化成了0。
析构函数
与构造函数的作用相反,析构函数不是完成对对象本⾝的销毁,⽐如局部对象是存在栈帧的, 函数结束栈帧销毁,他就释放了,不需要我们管,C++规定对象在销毁时会⾃动调⽤析构函数,完成对象中资源的清理释放⼯作。
特点
1.析构函数名是类名前加上字符~。
2.无参数也没有返回值,不需要void。
3.一个类只能有一个析构函数,不能重载。若未显式定义,系统会⾃动⽣成默认的析构函数。
4.对象生命周期结束时,系统会自动调用。
5.跟构造函数类似,我们不写编译器⾃动⽣成的析构函数对内置类型成员不做处理,⾃定类型成员会 调⽤他的析构函数。
6.如果类中没有申请资源时,析构函数可以不写,直接使⽤编译器⽣成的默认析构函数,如Date;如果默认⽣成的析构就可以⽤,也就不需要显⽰写析构;但是有资源申请时,⼀定要⾃⼰写析构,否则会造成资源泄漏,如Stack。
7.⼀个局部域的多个对象,C++规定后定义的先析构。
实践总结:
1.有资源显示清理,就需要写析构。
2.有两种场景不需要写。
a.没有资源需要清理。
b.内置成员类型没有资源需要清理,剩下都是自定义类型成员。
上面这个Stack中就有一个析构函数,在对象生命周期结束时,系统会自动调用。
#include<iostream>
using namespace std;
class a
{
public:
a()//.⽆参构造函数
{
_a = 0;
cout << "a()" << endl;
}
private:
int _a;
};
class date1
{
public:
void print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
a _aa;
};
int main()
{
date1 d1;
d1.print();
return 0;
}
这个代码中data1和a都没有需要清理的资源,所以不需要显示析构函数。
拷贝函数
特点
1. 拷⻉构造函数是构造函数的⼀个重载。
2. 拷⻉构造函数的只有⼀个参数且必须是类类型对象的引⽤,使⽤传值⽅式编译器直接报错,因为语法逻辑上会引发⽆穷递归调⽤。
3. C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,所以这⾥⾃定义类型传值传参和传值返回都会调⽤拷⻉构造完成。
4. 若未显式定义拷⻉构造,编译器会⽣成⾃动⽣成拷⻉构造函数。⾃动⽣成的拷⻉构造对内置类型成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉),对⾃定义类型成员变量会调⽤他的拷⻉构造。
#include<iostream>
using namespace std;
class date1
{
public:
date1(int year = 2024, int month = 9, int day = 12)
{
_year = year;
_month = month;
_day = day;
}
date1(date1& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year=2024; // 年
int _month=9; // 月
int _day=13; // 日
};
int main()
{
date1 d1;
d1.print();
date1 d2(d1);
d2.print();
return 0;
}
这里使用的是浅拷贝,未显示的拷贝构造函数也能完成。