拷贝构造函数
1.作用
\quad
用一个对象去初始化赋值给另外一个新的对象的时候就会自动调用拷贝构造函数(常规的赋值不会调用拷贝构造函数)
Cat c1;
Cat c2;
c2=c1; 常规的赋值不会调用拷贝构造函数
具体写法:
int a=15;
int b=a; //定义变量b,立马用a(已经有的变量)去 初始化赋值
int a=15;
int b; //定义变量b,没有初始化
b=a; //给b赋值,不叫做初始化赋值 --常规赋值
同样的道理(拷贝构造函数调用的情况总结)
Animal a(6,60.5);
写法一:
Animal b=a; //调用拷贝构造函数
写法二:
Animal b(a); //调用拷贝构造函数
写法三:
Animal b=Animal(a); //调用拷贝构造函数
写法四:
Animal *b=new Animal(a); //调用拷贝构造函数
写法五:
如果一个函数返回值是类对象,当保存返回值的时候,也是调用拷贝构造函数(取决于编译器的具体实现)
gcc默认会优化返回值调用拷贝构造函数,若要看到现象,在编译时取消优化
g++ xx.cpp -fno-elide-constructors
实参传递值给形参(都是类对象),调用拷贝构造函数
示例代码1:拷贝构造函数的调用
#include <iostream>
using namespace std;
/*
只要用一个对象去初始化赋值给另外一个新对象--》就调用拷贝构造
*/
class Animal
{
public:
// 定义动物的构造函数
Animal()
{
cout<<"动物的构造函数,当前对象是:"<<this<<endl;
}
// 自定义拷贝构造函数
Animal(const Animal &other)
{
cout<<"动物的拷贝构造函数,参数other地址:"<<&other<<" 当前对象地址:"<<this<<endl;
}
private:
};
int main(int argc, char const *argv[])
{
Animal a1; // 调用构造函数
Animal a2 = a1; // 调用拷贝构造
Animal a3; //调用构造函数
Animal a4; //调用构造函数
a3 = a4; //调用赋值运算
return 0;
}
/*
执行结果:
动物的构造函数,当前对象是:0x7ffc0d773204
动物的拷贝构造函数,参数other地址:0x7ffc0d773204 当前对象地址:0x7ffc0d773205
动物的构造函数,当前对象是:0x7ffc0d773206
动物的构造函数,当前对象是:0x7ffc0d773207
*/
示例代码2:系统默认的拷贝构造做的事情
#include <iostream>
using namespace std;
/*
通过现象反推:系统默认的拷贝构造函数究竟帮我们做了什么
*/
class Cat
{
public:
//定义猫类的构造函数
Cat(string _name,int _age)
{
name=_name;
age=_age;
cout<<"猫的构造函数"<<endl;
}
void show()
{
cout<<"name : "<<name<<" age is: "<<age<<endl;
}
private:
int age;
string name;
};
int main(int argc,char **argv)
{
Cat c1("来福",5);
//调用系统默认提供的拷贝构造
/*
//this就是c2 other就是c1
Cat(const Cat &other)
{
this->name=other.name;
this->age=other.age;
}
*/
Cat c2=c1; //无法直观的感受到调用了拷贝构造函数
//Cat c2(c1); //调用拷贝构造函数
//Cat c2=Cat(c1); //调用拷贝构造函数
//验证结果
c2.show();
return 0;
}
/*
执行结果:
猫的构造函数
name : 来福 age is: 5
*/
示例代码3:写法1-4
#include <iostream>
#include <cstring>
using namespace std;
/*
什么时候调用拷贝构造?
一句话:右边的对象初始化赋值给左边的新对象
*/
class Cat
{
public:
//定义猫类的构造函数
Cat()
{
cout<<"猫的构造函数,当前对象是: "<<this<<endl;
}
//自己写个拷贝构造
Cat(const Cat &other)
{
cout<<"猫的拷贝构造函数被调用了"<<endl;
}
private:
};
Cat fun()
{
cout<<"fun函数返回猫的对象"<<endl;
Cat temp;
return temp;
}
int main(int argc,char **argv)
{
Cat c1; //调用构造函数
//写法1:
Cat c2=c1; //c2调用拷贝构造
//写法2:
Cat c3(c1); //c3调用拷贝构造
//写法3:
Cat c4=Cat(c1); //c4调用拷贝构造
//写法4:
Cat *c5=new Cat(c1); //c5调用拷贝构造
//写法5:取决于编译器实现,有些编译系统是可以调用拷贝构造的
//Cat ret=fun();
//delete c5;
return 0;
}
示例代码4:写法5
#include <iostream>
#include <cstring>
using namespace std;
/*
什么时候调用拷贝构造?
一句话:右边的对象初始化赋值给左边的新对象
*/
class Cat
{
public:
//定义猫类的构造函数
Cat()
{
cout<<"猫的构造函数,当前对象是: "<<this<<endl;
}
//自己写个拷贝构造
Cat(const Cat &other)
{
cout<<"猫的拷贝构造函数被调用了"<<endl;
}
private:
};
Cat fun()
{
cout<<"fun函数返回猫的对象"<<endl;
Cat temp;
return temp;
}
void otherfun(Cat obj1) //Cat obj1=c1;
{
cout<<"otherfun函数被调用"<<endl;
}
int main(int argc,char **argv)
{
Cat c1; //调用构造函数
//函数返回值:返回类对象,此时调用拷贝构造函数
//g++编译默认会优化掉返回值返回类对象,调用拷贝构造函数
//g++ xxx.cpp -fno-elide-constructors 这个选项告知编译器不要优化为普通构造函数
Cat ret=fun(); // 拷贝构造调用2次 (第一次是temp给到一个匿名变量,第二次是匿名变量给到ret时)
cout<<endl;
//函数参数如果是类对象,你传递实参也是调用拷贝构造函数
otherfun(c1);
return 0;
}
/*
执行结果:
猫的构造函数,当前对象是: 0x7fff18a259e5
fun函数返回猫的对象
猫的构造函数,当前对象是: 0x7fff18a259b7
猫的拷贝构造函数被调用了
猫的拷贝构造函数被调用了
猫的拷贝构造函数被调用了
otherfun函数被调用
*/
示例代码5:C++编译器默认给类提供了4中隐含的方法
#include <iostream>
#include <cstring>
using namespace std;
/*
总结:C++编译器默认给类提供了哪些隐含的方法
隐含的方法:程序员可以直接用的方法
第一个隐含方法:默认构造函数
第二个隐含方法:默认析构
第三个隐含方法;默认拷贝构造
第四个隐含方法:默认的赋值运算
*/
class Cat
{
public:
//定义猫类的构造函数
Cat(int _age)
{
age=_age;
cout<<"猫的构造函数"<<endl;
}
void show()
{
cout<<"age is: "<<age<<endl;
}
private:
int age;
};
int main(int argc,char **argv)
{
Cat c1(10);
Cat c2(5);
c2=c1;
c2.show();
return 0;
}
2.语法规则
类名(const 类名 &)
{
代码
}
特点:
- 没有重载形式
- 一定是用对象初始化赋值给另一个对象才调用
(不是常规赋值,是初始化赋值)
- 如果程序员没有定义拷贝构造函数,系统会自动生成一个拷贝构造函数
如果程序员自定义拷贝构造函数,那么系统就不会再自动生成默认的拷贝构造函数
Animal(const Animal &other)
{
other中的数据赋值给this
}
示例代码:
#include <iostream>
#include <cstring>
using namespace std;
/*
通过现象反推:系统默认的拷贝构造函数究竟帮我们做了什么
*/
class Cat
{
public:
//定义猫类的构造函数
Cat(string _name,int _age)
{
name=_name;
age=_age;
cout<<"猫的构造函数"<<endl;
}
void show()
{
cout<<"name : "<<name<<" age is: "<<age<<endl;
}
private:
int age;
string name;
};
int main(int argc,char **argv)
{
Cat c1("来福",5);
//调用系统默认提供的拷贝构造
/*
//this就是c2 other就是c1
Cat(const Cat &other)
{
this->name=other.name;
this->age=other.age;
}
*/
Cat c2=c1; //无法直观的感受到调用了拷贝构造函数
//Cat c2(c1); //调用拷贝构造函数
//Cat c2=Cat(c1); //调用拷贝构造函数
//验证结果
c2.show();
return 0;
}
/*
执行结果:
猫的构造函数
name : 来福 age is: 5
*/
3.深拷贝和浅拷贝
3.1.浅拷贝(指针赋值时有bug)
\quad
指的就是使用系统默认的拷贝构造函数
,构造出来的新对象跟原有的对象共用同一块堆空间,一旦某个对象修改了堆空间中的数据,其它对象全部跟着修改,这种现象称之为浅拷贝。
#include <iostream>
#include <cstring>
using namespace std;
/*
默认的拷贝构造函数存在bug:引入什么是浅拷贝
1.看现象
我修改c1的姓名,c2也跟着被修改
2.bug产生的前题条件
属性当中用到了指针,申请了堆空间
*/
class Cat
{
public:
//定义猫类的构造函数
Cat(const char *_name,int _age)
{
//name申请堆空间
name=new char[20];
strcpy(name,_name);
age=_age;
cout<<"猫的构造函数"<<endl;
}
//修改猫的属性
void setAttr(const char *newname,int newage)
{
strcpy(name,newname);
age=newage;
}
void show()
{
cout<<"name : "<<name<<" age is: "<<age<<endl;
cout<<"name 地址: "<<(int *)name<<endl;
cout<<"age 地址: "<<&age<<endl;
}
private:
int age;
char *name;
};
int main(int argc,char **argv)
{
Cat c1("来福",5);
//调用系统默认提供的拷贝构造
/*
//this就是c2 other就是c1
Cat(const Cat &other)
{
//此时name是指针,不是string的对象
this->name=other.name; //两个name指向同一个地址
this->age=other.age;
}
*/
Cat c2=c1;
//验证结果
c1.show();
c2.show();
cout<<"==============修改之后=============="<<endl;
//打算修改c1的属性--》按道理来说,跟c2无关,但由于c2这个对象的name指针指向的也是c1对象的name指针,所以c2对象name也会被修改
c1.setAttr("莱宝",6);
c1.show();
c2.show();
return 0;
}
/*
执行结果:
猫的构造函数
name : 来福 age is: 5
name 地址: 0x555fa44b6eb0
age 地址: 0x7ffffbb95fc0
name : 来福 age is: 5
name 地址: 0x555fa44b6eb0
age 地址: 0x7ffffbb95fd0
==============修改之后==============
name : 莱宝 age is: 6
name 地址: 0x555fa44b6eb0
age 地址: 0x7ffffbb95fc0
name : 莱宝 age is: 5
name 地址: 0x555fa44b6eb0
age 地址: 0x7ffffbb95fd0
*/
3.2.深拷贝
\quad 程序员自己动手写个拷贝构造函数,解决浅拷贝的bug,这个自定义的拷贝构造函数实现就是深拷贝。
#include <iostream>
#include <cstring>
using namespace std;
/*
自己动手解决浅拷贝的问题--》实现了深拷贝
*/
class Cat
{
public:
//定义猫类的构造函数
Cat(const char *_name,int _age)
{
//name申请堆空间
name=new char[20];
strcpy(name,_name);
age=_age;
cout<<"猫的构造函数"<<endl;
}
//自己写个拷贝构造函数
Cat(const Cat &other)
{
cout<<"我自己写的拷贝构造函数被调用,实现深拷贝"<<endl;
//给当前对象的name单独申请堆空间
this->name=new char[20];
strcpy(this->name,other.name);
this->age=other.age;
}
~Cat()
{
delete []name;
}
//修改猫的属性
void setAttr(const char *newname,int newage)
{
strcpy(name,newname);
age=newage;
}
void show()
{
cout<<"name : "<<name<<" age is: "<<age<<endl;
//cout<<"name 地址: "<<(int *)name<<endl;
//cout<<"age 地址: "<<&age<<endl;
}
private:
int age;
char *name;
};
int main(int argc,char **argv)
{
Cat c1("来福",5);
//调用我自定义的拷贝构造函数
Cat c2=c1;
//验证结果
c1.show();
c2.show();
cout<<"==============修改之后=============="<<endl;
//修改c1的属性--此时实现的是深拷贝,新对象申请了新的堆空间
c1.setAttr("莱宝",6);
c1.show();
c2.show();
return 0;
}
/*
执行结果:
猫的构造函数
我自己写的拷贝构造函数被调用,实现深拷贝
name : 来福 age is: 5
name : 来福 age is: 5
==============修改之后==============
name : 莱宝 age is: 6
name : 来福 age is: 5
*/