c++拷贝构造函数(深浅拷贝)+运算符重载
1拷贝构造函数
1.1定义
只有一个形参,且该形参是对本类类型对象的引用(一般用const 修饰),在用已经存在的类类型对象穿件新对象是由编译器自动调用。(是一种特殊构造,即初始化一个一模一样的新对象)
1.2特征
- 拷贝构造函数是一种特殊成员函数,是构造函数的一个重载形式
- 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用
class Date
{
public:
Date(int year = 2004, int month = 10, int day = 1)//全缺省构造函数
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)//拷贝构造函数,这里必须是引用
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
- 若为显式定义,编译器会生成默认的拷贝构造函数。默认拷贝构造函数对象按内存存储按字节序完成拷贝,这种方式成为浅拷贝或者值拷贝
简要介绍一下深拷贝和浅拷贝
浅拷贝/值拷贝:按字节拷贝,在复制对象时,仅复制对象的成员变量的值。若对象包含指针成员,那么浅拷贝只会复制指针的值,也就是复制后的对象和原对象的指针会指向同一块内存地址。这就意味着两个对象会共享同一块动态分配的内存。当其中一个对象被销毁时,它所指向的内存也会被释放,而另一个对象的指针就会变成悬空指针,这可能会引发程序崩溃或其他未定义行为。
深拷贝:在复制对象时,不仅会复制对象的成员变量的值,还会为指针成员分配新的内存空间,并将原对象指针所指向的内存中的数据复制到新分配的内存中。这样,复制后的对象和原对象就会拥有各自独立的内存空间,彼此之间不会相互影响。(开一样的空间,拷贝一份一模一样的东西)
像Date,My Queue等不需要显示拷贝构造,因为他是浅拷贝的类
像Stack需要显示拷贝构造,因为他是深拷贝的类
class Time
{
public:
Time()
{
_hour = 1;
_minute = 1;
_second = 1;
}
Time(const Time& t)
{
_hour = t._hour;
_minute = t._minute;
_second = t._second;
cout << "Time::Time(const Time&)" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
//内置类型
int _year;
int _month;
int _day;//编译器在拷贝时,完成字节序的值拷贝
//自定义类型
Time _t;//编译器在拷贝时,调用其构造函数完成拷贝
};
int main()
{
Date d1;
//这里是用已经创建好的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
//但是Date没有显式的拷贝构造函数,因此编译器会生成默认的拷贝构造函数
Date d2(d1);
Date d3 = d1;//这个和上一行作用一样
return 0;
}
小结:
1.当类中包含动态分配的资源(像动态数组、动态对象等)时,为了防止多个对象共享同一块内存而引发问题(如悬空指针、重复释放内存),需要实现深拷贝。这就意味着在非默认拷贝构造函数里,要为新对象分配新的内存空间,再把原对象的数据复制到新的内存中。
2.如果没有管理资源,一般情况下不需要写拷贝构造函数,直接用默认的拷贝构造,如Date类
3.如果都是自定义类型成员,内置类型成员没有指向资源,也类似默认生成的拷贝构造函数就可以,如My Queue类
4.一般情况下,不需要显式析构函数的,就不需要写拷贝构造
2赋值运算符重载
2.1运算符重载
本质:运算符重载是具有特殊函数名的函数,也具有返回值类型,函数名字以及参数列表
目的:增强代码的可读性,
函数名字:关键字 operator后面接需要重载的运算符符号
函数原型:返回值类型 operator操作符(参数列表)
返回值类型主要取决于操作符到底干什么事,比如操作符是比较大小,返回值类型就是bool类型,如果是自增,则返回值就是原类(eg:Date类)
注意:
- 重载操作符必须有一个类类型参数
- 用于内置类型的操作符,其含义不可以改变,例如:+,-,*,.....
- .* :: sizeof ?: . 以上5个运算符不能重载(笔试常考)
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
如上图画波浪线的报错可知:如果我们重载运算符为全局,那么则无法访问私有成员,解决方法有以下三种:1.提供这些成员get和set方法;2.友元;3.重载成员函数(C++中常用这种)
2.2赋值运算符重载
1.赋值运算符重载格式
- 参数类型:const& T(传递引用可以提高传参效率)
- 返回值类型:T&,(返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值)
- 检测是否自己给自己赋值(存疑)
- 返回*this:要符合连续赋值的含义
class Date {
public:
Date(int year = 2004, int month = 10, int day = 20)//全缺省构造函数
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)//拷贝构造函数
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& operator=(const Date& d)
{
//这里的this指针指代函数运行时调用该函数的对象
if (this != &d)//这一段代码是为了防止出现d1=d1;即自己给自己赋值的情况
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
区别拷贝构造和复制拷贝
int main()
{
Date d1(2024, 04, 20);
Date d2(d1);
Date d3 = d1;//d2,d3都是拷贝构造,意思是创建两个与d1一样的实例化对象
//注意,拷贝构造的本质是构造,构造同类型的对象,并且使之初始化为与d1一摸一样的对象
//一个已经存在的对象,拷贝给另一个要创建的初始化的对象
Date d4(2024, 05, 21);
d1 = d4;//注意,这里不是拷贝构造,因为d1和d4都是已经存在的对象,
//这里的本质是把一个已经存在的对象,拷贝给另一个已经存在的对象
//d4相当于d,d1相当于this(见上个代码块)
return 0;
}
2.赋值运算符只能重载成类的成员函数(不论形参的类型是什么),不能重载成全局函数