如何把网站建设好,哪个网站做宣传比较好,在哪里购买虚拟空间建设网站,做旅游网站的项目背景#x1f4d6;定义 拷贝构造函数是构造函数的一个重载#xff0c;它的本质还是构造函数#xff0c;那就意味着#xff0c;只有在创建对象的时候#xff0c;编译器才会自动调用它#xff0c;那他和普通的构造函数有什么区别呢#xff1f;
拷贝构造函数#xff0c;是创建…
定义 拷贝构造函数是构造函数的一个重载它的本质还是构造函数那就意味着只有在创建对象的时候编译器才会自动调用它那他和普通的构造函数有什么区别呢
拷贝构造函数是创建对象的时候用一个已存在的对象去初始化待创建的对象。简单来说就是在我们创建对象的时候希望创建出来的对象和一个已存在的对象一模一样此时就应该用拷贝构造函数而不是普通的构造函数。拷贝构造函数有一点类似于克隆技术。
Data d1(2023, 7, 20);//定义一个日期类对象d1
Data d2(d1);//会去调用拷贝构造函数int a 10;
int b a;//不会调用拷贝构造上面代码首先定义了一个日期类对象d1接着想创建第二个日期类对象d2并且希望d2和d1一模一样也就是用d1去克隆出d2d2相当于是d1的一份拷贝。所以在创建d2对象的时候参数列表直接传递了d1。
小Tips拷贝构造函数是针对自定义类型的自定义类型的对象在拷贝的时候C规定必须要调用拷贝构造函数。内置类型不涉及拷贝构造函数如上用a去创建b是由编译器直接把a所表示的空间中的内容直接拷贝到b所表示的空间并不涉及拷贝构造函数。
拷贝构造函数的错误写法 有了上面的分析可能很多朋友会觉得那我直接在类里面再写一个构造函数把它的形参设置成日期类对象不就行了嘛于是便得到了下面的代码
Data(Data d)//错误的拷贝构造
{_year d._year;_month d._month;_day d._day;
}是不是觉得很简单创建d2对象的时候实参把d1传过来然后用d接收最后再把d的所有值赋值给this指针当前this指针就指向d2这一切堪称完美但是我想告诉你这种写法是大错特错的。
为什么是错的 问题出现在传参就是实参d1传递给形参d的时候上面代码中的形参d既不是指针也不是引用说明是值传递值传递就意味着形参d是实参d1的一份拷贝注意是拷贝就是说形参d要和实参d1一模一样怎么才能让d和d1一摸一样调用拷贝构造函数呀。
形参d在接收实参d1的时候又要去调用拷贝构造来创建d这次调用拷贝构造又会有一个形参d这个形参d又需要调用拷贝构造才能创建相信到这里小伙伴们已经看出问题所在了———无穷递归形参在接收的时候会无穷无尽的去调用拷贝构造函数就像套娃一样。 为了避免出现这种无穷递归编译器会自行检查如果拷贝构造函数的形参是值传递编译时会直接报错。 必须是引用 为了打破上面的魔咒拷贝构造函数的形参只能有一个并且必须是类类型对象的引用。下面才是正确的拷贝构造函数
Data(Data d)//正确的拷贝构造
{_year d._year;_month d._month;_day d._day;
}
Data d1(2023, 7, 20);//定义一个日期类对象d1
Data d2(d1);//此时创建d2的时候传递d1调用拷贝构造函数形参d是一个日期类的引用因为引用时区别名意味着d是d1的一个别名此时就不会再去无穷无尽的调用拷贝构造啦。
建议加const 因为存在用一个const对象去初始化创建一个新对象这种场景所以建议在拷贝构造函数的形参前面加上const此时普通的对象能用const对象也能用。
Data(const Data d)//正确的拷贝构造
{_year d._year;_month d._month;_day d._day;
}
const Data d1(2023, 7, 20);//定义一个日期类对象d1
Data d2(d1);编译器生成的拷贝构造干了什么 上一节提到拷贝构造是一种默认成员函数我们不写编译器会自动生成。编译器生成的默认拷贝构造函数对内置类型按照字节方式直接拷贝也叫值拷贝或浅拷贝对自定义类型是调用其拷贝构造函数完成拷贝。
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 1970;int _month 1;int _day 1;// 自定义类型Time _t;
};
int main()
{Date d1;// 用已经存在的d1拷贝构造d2此处会调用Date类的拷贝构造函数// 但Date类并没有显式定义拷贝构造函数则编译器会给Date类生成一个默认的拷贝构造函数Date d2(d1);return 0;
}什么是浅拷贝 上面提到编译器生成的拷贝构造函数会对内置类型完成浅拷贝浅拷贝就是以字节的方式把一个字节里的内容直接拷贝到另一个字节中。
拷贝构造函数可以不写嘛 通过上面的分析可以得出编译器自己生成的构造函数对内置类型和自定义类型都做了处理。那是不是意味着我们就可以不写拷贝构造函数了呢答案是否定的对于日期类我们确实可以不写用编译器自己生成的但是对于一些需要深拷贝的对象构造函数是非写不可的。栈就是一个典型的需要我们自己写构造函数的例子
typedef int DataType;
class Stack
{
public:Stack(size_t capacity 10){_array (DataType*)malloc(capacity * sizeof(DataType));if (nullptr _array){perror(malloc申请空间失败);return;}_size 0;_capacity capacity;}void Push(const DataType data){// CheckCapacity();_array[_size] data;_size;}~Stack(){if (_array){free(_array);_array nullptr;_capacity 0;_size 0;}}
private:DataType *_array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);
return 0;
}上面定义了一个栈类Stack我们没有写它的拷贝构造函数编译器会自动生成一个默认的拷贝构造函数栈中的成员变量都是内置类型默认的拷贝构造函数会对这三个成员变量都完成值拷贝浅拷贝。 此时浅拷贝的问题在于对象s1和对象s2中的_array存的是同一块空间的地址他俩指向了同一块空间当程序退出往s1或s2中的任意一个对象push值另一个也会跟着改变。s1和s2要销毁s2先销毁s2销毁时调用析构函数已经将0X11223344这块空间释放了但是s1并不知道到s1销毁的时候会将0X11223344这块空间再释放一次一块内存空间多次释放最终就会导致程序崩溃。
深拷贝 通过上面的分析可以看出简单的浅拷贝不能满足栈的需求因此对于栈我们需要自己写一个拷贝构造函数来实现深拷贝深拷贝就是去堆上重新申请一块空间把s1中_array指向的空间中的内容拷贝到新申请的空间再让s2中的_array指向该空间。
//自己写的拷贝构造函数实现深拷贝
Stack(const Stack st)
{DataType* tmp (DataType*)malloc(sizeof(DataType) * st._capacity);if (nullptr tmp){perror(malloc申请空间失败);return;}memcpy(tmp, st._array, sizeof(DataType) * st._size);_array tmp;_size st._size;_capacity st._capacity;
}总结 类中如果没有涉及资源申请时拷贝构造函数写不写都可以一旦涉及到资源申请时拷贝构造函数是一定要写的否则就是浅拷贝最终析构的时候就会释放多次造成程序崩溃。
拷贝构造函数典型的调用场景
使用已存在对象创建新对象。函数参数类型为类类型对象。函数返回值为类类型对象。
class Data
{
public:Data(int year 1, int month 1, int day 1){cout 调用构造函数 this endl;cout endl;_year year;_month month;_day day;}Data(const Data d){cout 调用拷贝构造 this endl;cout endl;_year d._year;_month d._month;_day d._day;}~Data(){cout ~Data() this endl;cout endl;}
private:int _year;int _month;int _day;//可以不用写析构因为全是自定义类型并且没有动态申请的空间这三个成员变量会随着对象生命周期的结束而自动销毁
};
Data Text(Data x)
{Data tmp;return tmp;
}int main()
{Data d1(2023, 4, 29);Text(d1);return 0;
}总结 自定义类型在传参的时候形参最好用引用来接收这样可以避免调用拷贝构造函数尤其是深拷贝的时候会大大的提高效率函数返回时如果返回的对象在函数栈帧销毁后还在最好也用引用返回。 结语 今天的分享到这里就结束啦如果觉得文章还不错的话可以三连支持一下您的支持就是春人前进的动力