拷贝构造
注意:拷贝构造不会申请空间
本质:构造函数(与其他构造函数是重载关系)
1.形式
Cstu(const Cstu &a)//参数是类的常引用
{
}
2.原理:
用已有对象给新对象初始化的方式(只有这几种方式才会调用拷贝构造)
新建一个对象,并将其初始化为同类现有对象(用一个对象给新的对象初始化)
3.拷贝构造的调用情况
(1)把对象作为参数进行初始化时会调用
//下面代码是将对象stu1通过传参赋值给a
class Cstu
{
public:
Cstu()
{
}
//拷贝构造
Cstu(const Cstu &a)//参数是类名的引用
{
}
};
//主函数调用
//创建新的对象 用stu1作为参数传递
Cstu stu2(stu1);
(2)通过对象赋值进行初始化时会调用
//街上
//主函数调用
Cstu stu2 = stu1;//赋值传递
(3)通过临时对象赋值(比(2)多了一步创建一个临时对象)时会调用
//同上
//主函数调用
Cstu stu2 = Cstu(stu1);//通过临时对象进行赋值
(4)利用指针对象赋值初始化时会调用
//主函数调用
Cstu*p = new Cstu(stu1); //利用指针对象赋值
delete p;
过程:已有对象遍历所有成员除拷贝构造,新对象只遍历拷贝构造,把已有对象进行拷贝。
(5)当程序生成对象副本时:
作为参数(初始化的原因)
a.形参是局部变量或者局部对象,是局部的,作用域是函数内部
b.解析过程
函数传stu这个对象的参数,传给新对象a,即是用stu给a初始化了 ,所以会调用拷贝构造
c.形参在调用的时候才会分配空间,而不是在编译时
class Cstu
{
public:
Cstu(const Cstu &a)//参数是类名的引用
{
cout << "拷贝构造" << endl;
}
};
void fun(Cstu a)
{
}
int main()
{
Cstu stu1;
fun(stu1);
system("pause");
return 0;
}
作为返回值(临时对象或临时变量的原因)
class Cstu
{
public:
Cstu(const Cstu &a)//参数是类引用
{
cout << "拷贝构造" << endl;
}
};
Cstu fun()
{
Cstu a;
return a; //若返回一个值,则返回的是临时对象或临时变量
}
解析:
上面的函数返回的是临时对象,假如返回的是整形12,那么返回的就是int (12);
同理,假如返回的是对象,然后就用a给临时对象初始化,所以会调用拷贝构造
特殊情况(赋值不会调用):
而赋值是不会经过拷贝构造的。
//主函数调用
Cstu stu1;
Cstu stu2;
stu2 = stu1;
4.默认拷贝构造(又叫浅拷贝)
a.基于的特性是同一个类的多个对象,对象内存排布是一样的,对象地址可能不同
b.与拷贝构造的区别
默认的拷贝构造什么都不执行,是一个NULL,拷贝构造是执行内容的
总代码如下://结果为abc abc
#define _CRT_SECURE_NO_DEPRECATE
#include<iostream>
using namespace std;
class Cstu
{
public:
char str[4];
int b ;
Cstu()
{
str[0] = 'c';
b = 12;
strcpy(&str[0],"abc");
}
Cstu(const Cstu& a)
{
this->b = a.b; //将拷贝的过程写在表面上
strcpy(this->str ,a.str );
}
};
int main()
{
Cstu stu1;
cout << stu1.str <<' ';
Cstu stu2 = stu1;
cout << stu2.str << endl;
system("pause");
return 0;
}
c.功能
默认拷贝构造函数,会逐个复制非静态成员(成员赋值拷贝为浅复制/浅拷贝,复制的是成员的值)值,仅是值的变化,与strcpy
类似
//strcpy(目标字符串首地址,源字符串); 一般会报错,要求使用下面的
//strcpy(目标字符串首地址,字节数,源字节数);安全性更高
class Cstu
{
public:
char a[4];
int b = 0;
Cstu()
{
a[0] = 'c';
b = 12;
}
void print()
{
strcpy(&a[0],"abc");
for (int i = 0; i < 4; i++)
{
cout << a[i] << ' ' ;
}
}
};
//主函数调用
Cstu stu;
stu.print();
Cstu stu1 = stu; //会发现直接执行print()函数,不进Cstu()
//说明进了默认的拷贝构造函数
stu1.print();
深拷贝(有指针成员用)
写在前面:只针对(1)(2)(3)(4)上面4种情况拷贝时的方法
指针变量不是全部复制,若定义int*p = &a;
指针仅仅记录的是首地址,当它拷贝的时候,只是会把它装的地址拷贝过去
数组类型和指针类型都是变量,类型不同。
数组类型:假如int a[4]
,则a的类型是 int[ ]
指针类型:假如int*p
,则p的类型是int*
浅拷贝在关于指针成员存在的重复释放空间的问题,演示如下
使用指针成员:在构造函数分配空间 ,在析构函数里释放空间
#include<iostream>
using namespace std;
class Cstu
{
public:
int *a;
Cstu()
{
a = new int[2];//指针成员在构造函数申请空间
a[0] = 1;
a[1] = 2;
}
~Cstu()
{
delete [2] a;
}
};
int main()
{
{
Cstu stu;
cout << stu.a[0] << ' ' << stu.a[1] << endl;
Cstu stu1 = stu;
cout << stu1.a[0] << ' ' << stu1.a[1] << endl;
} //加上花括号,析构函数在此调用
system("pause");
return 0;
}
错误解析:析构函数每次结束时都会调用
上面代码在stu1=stu
走完拷贝构造后,再走析构函数,会使同一块空间重复释放,第二次释放时,此时空间已归系统所有,而指针便成了野指针,所以就会造成系统崩溃。
深拷贝过程:
Cstu(const Cstu& b)
{
//传递内容,首先要申请空间,让b有自己的空间
this->a = new int [2];
}
原来的对象为stu,新对象为stu1
stu有一段空间,stu1进到拷贝没有空间就指向stu的空间,导致他俩指向了同一块空间,对同一块空间进行多次释放,就崩溃了
深拷贝就是不让stu1指向stu的空间了,让它自己有一块空间,那就是给stu1在拷贝构造里申请空间,把stu里的全部内容复制过来(这里不单单是一个值的传递,一定是把内容也传递过来),复制到stu1申请的新空间来,这样stu1就有自己的空间了,在调用两次析构时,stu释放的是它的空间,stu1释放他的新空间
有指针成员可以用拷贝构造的代码如下:
#include<iostream>
using namespace std;
class Cstu
{
public:
int *a;
Cstu()
{
a = new int[2];//指针成员在构造函数申请空间
a[0] = 1;
a[1] = 2;
}
Cstu(const Cstu& b)
{
//传递需要释放空间的内容,首先要申请空间
this->a = new int [2];
this->a[0] = b.a[0];//给新空间的数组赋值
this->a[1] = b.a[1];
}
~Cstu()
{
delete[2] a; //指针成员在析构函数释放空间
}
};
int main()
{
{
Cstu stu;
cout << stu.a[0] << ' ' << stu.a[1] << endl;
Cstu stu1 = stu;
cout << stu1.a[0] << ' ' << stu1.a[1] << endl;
} //加上花括号,析构函数在此调用
system("pause");
return 0;
}
如上赋值的元素多的话,就可以用内存赋值函数了(将一块空间的内容赋值到另一块空间)
memcpy(目标空间,源空间,空间大小字节数)
Cstu(const Cstu& b)
{
this->a = new int [2];
memcpy(this->a ,b.a ,8);
}
当对象作为函数参数或返回值时的解决办法
作为函参时:
可以利用传引用或指针,直接指向。中间没产生任何对象赋值初始化的过程,就不会去调用拷贝构造
//类外声明定义
Cstu& westrong(Cstu&a) //传引用,避免拷贝构造
{
return a;
}
//主函数调用
Cstu stu;
Cstu b = westrong(stu);
作为返回值时:
也可以利用引用或指针,就不产生临时对象了,传递的就是a的本身了。不调用拷贝构造,效率也提高了。
//传指针时 类外定义
Cstu*west(Cstu*a)
{
return a;
}
//主函数调用
west(&stu);
Cstu* c = west(&stu);
cout << c->a [1];