六个默认成员函数
前言:
C++引入了类的概念,如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
一、构造函数
为了防止写类时,忘记初始化,引入了构造函数
说明:
构造函数的主要任务是初始化对象,并不是开空间创建对象。
特性:
无返回值,函数名和类名相同,构造函数可以重载
没有写构造函数时,编译器会自动生成一个构造函数
class Date
{
public:
//1.无参的构造函数
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
//2.有参的构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//3.全缺省的构造函数
Date(int year=1,int month=1,intday=1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
虽然上边是显示三种,但是还有一种我们没有定义,编译器默认生成的;在上边的三种中:
<1> 一、三虽然能构成函数重载,但是编译器依旧报错,因为在不传参情况时调用有歧义
<2> 一、二可以构成函数重载,且不报错
<3> 由此不难看出,第三种,能代替上边的两种,何乐而不为呢?
<4> 第四种,也就是编译器默认生成的那一个对 内置类型(如:int char double)不做处理(被初始化为乱码,不同的编译器结果不同),对自定义类型会调用它的构造函数
class Time
{
public:
Time(int hour = 1,int minute=1,int second=1)
{
_hour = hour;
_minute = minute;
_second = second;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
//这个t1在初始化时就会去调用 class Time中的构造函数Time
Time t1;
};
编译器会在创建对象时候自动调用
无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数
二、析构函数
说明:
析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。主要用于动态开辟的空间的释放 如:malloc realloc 等
特性:
析构函数名是在类名前加上字符 ~, 无参数无返回值类型, 析构函数不能重载
对象生命周期结束时,C++编译系统系统自动调用析构函数。
class Date
{
public:
~Date()
{
_year = 1;
_month = 1;
_day = 1;
}
private:
int _year;
int _month;
int _day;
};
对于类的成员是自定义类型的,析构函数不做处理,但是如果是自定义类型,就回去调用它的析构函数
我们不定义,编译器会默认生成一个,里边的操作同5
三、拷贝构造函数
说明:
用 该类 创建的对象 去给新对象赋值的过程 叫做拷贝构造(一个创建成功,一个未创建),切记不是赋值重载,赋值重载的前提是两个对象都创建完成
特性:
拷贝构造的书写形式:函数名与类名相同,没有返回值,参数为const +类+&+对象(已经创建的对象)
为什么要传引用?而不是直接类 类型的参数?
原因是因为在调用拷贝构造时,需要先传参,而C++在类 类型对象传参时会调用拷贝构造, 而拷贝构造再一次传参仍调用拷贝构造,这样会导致无穷递归下去
而如果传引用就不需要开辟新的对象,就免去了传参时去调用拷贝构造,在确保不会改变此对象时,最好加上const保护一下
由此不难看出,下次我们无论写函数传参还是函数返回值时,能用引用就用引用,这样程序运行效率更高更快(避免拷贝临时变量等操作)
拷贝构造也属于构造,若存在拷贝构造且不存在我们自己定义的构造,那么编译器就不会生成默认构造函数
你或许看出了,这没写拷贝构造,只写了一个构造,但是依旧完成了操作,所以拷贝构造不写,编译器会默认生成一份仅完成浅拷贝(逐字节拷贝,若是地址,也原样拷贝)
两个地址一样,销毁时就会导致一个内存被释放两次,第一次释放没事,考虑极端情况,若第一次刚刚释放完,就被分配到其他位置使用,那么此时该地址处的内容已经不属于你,不能第二次释放,此时就需要我们手动写一个拷贝函数(俗称深拷贝)
Stack(const Stack& st)
{
int* tmp = (int*)malloc(sizeof(int) * st.capacity);
if (tmp == nullptr)
{
return;
}
memcpy(tmp, st.a, sizeof(int) * st.top);
a = tmp;
top = st.top;
capacity = st.capacity;
}
四、赋值重载
前言:为了使代码规范,C++规定,可以使用 operator加上运算符 做函数名,和函数一样
说明:
内置类型可以使用等号运算符,那么如果我们想让自定义类型也使用等号运算符呢?这时候就需要我们把 = 的含义重载一下
特性:
参数类型:const T&,返回值类型:T&,函数名operator=
参数类型,上边提到过,便于提高运行效率;而返回值用引用其一效率高,其二可以接连赋值
class A
{
public:
A(int a1 = 1, int a2 = 1)
{
_a1 = a1;
_a2 = a2;
}
//1.operator 需要两个参数,在类里边,
//默认有一个this指针充当第一个参数
//2.有返回值的目的是为了能接连赋值,
//否则函数没有返回值 调用时就是 a3 = a1.operator=(a2)
//总不能把void 赋值给a3吧
A& operator=(const A& a)
//A operator=(const A& a)
{
_a1 = a._a1;
_a2 = a._a2;
return *this;
}
private:
int _a1;
int _a2;
};
int main()
{
A a1(6, 6);
A a2,a3;
a1.operator=(a2); //等价于 a2=a1 在c++中一般用后者
a3 = a2 = a1; //a3 = a1.operator=(a2)
return 0;
}
赋值重载函数,编译器会在我们没有写的情况下,默认生成一个赋值重载函数,仅仅完成浅拷贝,与拷贝构造一样
赋值运算符只能重载成类的成员函数,不能定义在类外
因为在调用时有两个函数(一个我们定义在类外部的,一个是类默认生成的),会产生调用冲突
编译器会灵活看待等号,对于内置类型,就不会去调用函数重载,对于自定义类型,则会去调用函数重载
五、取地址重载&const取地址重载
说明:
取地址重载可以获取对象的地址
const取地址重载获取const对象的地址
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()
{
return this ;
}
private :
int _year ;
int _month ;
int _day ;
};
特性:
不写时 编译器会自动生成
这两个函数比较单纯,取地址而已,故使用默认生成的就行
总结
1. 构造与拷贝构造,99%的情况下都需要自己手搓一个,因为我们不想要随机值初始化对象
2. 拷贝构造与赋值重载,如果是浅拷贝不涉及动态内存的可以用 不写,用默认的,如果涉及malloc 等需要我们写
3. 取地址重载与const取地址重载,不需要我们写,直接用就行