一、定义变量
内置类型定义变量
(1)int a=10;int b=10;
(2)int a=10,b=a;
我们要讨论的就是第二种定义方式,在自定义数据类型中,我们把这种定义方式叫做拷贝构造。
自定义数据类型定义变量
详细定义方式我们就不展开讲了,可以查看我相关文章。主要是就以下情况来介绍。
我么定义以下类,作为演示的自定义数据类型。
class person{
public:
person (){}
~person(){}
private:
string name;
int age;
};
类比int a=10,b=a;,我们可以定义person p1;perosn p2=p1;在自定义数据类型中我多提一句
person p1;person p2(p1)
和 person p1;perosn p2=p1;
和person p1, person p2=person p1;
和person p1,p2=p1;四者是等价的。
都是调用的拷贝构造。
拷贝构造内部实现(浅拷贝)
对于拷贝构造,就算你的类中没有拷贝构造,编译器也会提供一个拷贝构造来完成变量的定义。如下图:
class person{
public:
//默认构造 编译器自动提供默认构造
person (){
cout <<"默认构造"<<endl;
}
//有参构造 编译器不会提供 当用户提供有参构造函数 编译器不再提供默认构造
person(string name, int age){
this->age=age;
this->name=name;
cout <<"有参构造"<<endl;
}
//拷贝构造 用户不提供拷贝构造 编译器会默认提供浅拷贝
person(person &p){
//系统提供的浅拷贝
this->age=p.age;
this->name=p.name;
}
void showinfo(){
cout<<"年龄:"<<this->age<<"姓名:"<<this->name<<endl;
}
//析构函数
~person(){
}
private:
string name;
int age;
};
深拷贝剖析
当类成员属性有一个为指针变量时,系统拷贝构造只会浅拷贝。
class person{
public:
//默认构造 编译器自动提供默认构造
person (){
id=new int (-1);
cout <<"默认构造"<<endl;
}
//有参构造 编译器不会提供 当用户提供有参构造函数 编译器不再提供默认构造
person(string name, int age,int id){
this->age=age;
this->name=name;
this->id=new int(id);
cout <<"有参构造"<<endl;
}
//拷贝构造 用户不提供拷贝构造 编译器会默认提供浅拷贝
person(person &p){
//系统提供的浅拷贝
this->age=p.age;
this->name=p.name;
this->id=p.id;
}
void showinfo(){
cout<<"年龄:"<<this->age<<"姓名:"<<this->name<<"ID号:"<<this->id<<endl;
}
//析构函数
~person(){
if(this->id!=nullptr){
delete this->id;
this->id=nullptr;
}
}
private:
string name;
int age;
int * id;
};
编译器所提供的默认拷贝构造是一个浅拷贝,所以带来的问题:当有属性指针指向堆区情况时,同一块空间将会被多次释放。就会造成程序的异常中止。
解决办法(整体源代码)
#include <iostream>
using namespace std;
class person{
public:
//默认构造 编译器自动提供默认构造
person (){
id=new int (-1);
cout <<"默认构造"<<endl;
}
//有参构造 编译器不会提供 当用户提供有参构造函数 编译器不再提供默认构造
person(string name, int age,int id){
this->age=age;
this->name=name;
this->id=new int(id);
cout <<"有参构造"<<endl;
}
//拷贝构造 用户不提供拷贝构造 编译器会默认提供浅拷贝
person(person &p){
//系统提供的浅拷贝
this->age=p.age;
this->name=p.name;
//this->i=p.i;
//当有一变量为指针变量 在堆区开辟空间,使用上述浅拷贝就会造成两个指针变量指向同一堆区空间
//在类对象被摧毁时会调用析构,而调用析构时必须把本类属性在堆区开辟的空间释放。如果使用浅拷贝。
//一共创建两个对象,摧毁第一个对象是正常调用析构函数,当摧毁第二个对象时,它成员指针属性指向
//的内存已经被上一个对象在调用析构函数时释放掉了。再释放就是非法访问会造成程序异常终止。
//不懂可以看下图
//自己需要深拷贝解决浅拷贝问题
this->id=new int(*p.id);//再申请一个空间
}
void showinfo(){
cout<<"年龄:"<<this->age<<"姓名:"<<this->name<<"ID号:"<<this->id<<endl;
}
//析构函数
~person(){
if(this->id!=nullptr){
delete this->id;
this->id=nullptr;
}
}
private:
string name;
int age;
int * id;
};
int main()
{
person p1("张三",16,1008);
person p2(p1);
p1.showinfo();
p2.showinfo();
return 0;
}
拷贝构造的调用时机
1.用另外一个对象为本(正在生成的)对象进行初始化时,自动调用拷贝构造。(定义变量时)
2.当函数参数为类类型时,将自动调用拷贝构造。(内置数据类型值传递给函数传参)
3.当函数返回值为类类型时,将自动调用拷贝构造。(内置数据类型值函数值方式返回)
所以说:为了避免无意义拷贝过程,在函数传参时,或函数返回时,推荐使用引用。