文章目录
1.拷贝构造函数基础
1.1.构造和析构函数调用次数
#include <iostream>
using namespace std;
class A{
int data;
public:
A(int d=0):data(d){
cout<<"A(int=0)"<<endl;
}
void show(){
cout<<"data="<<data<<endl;
}
~A(){
cout<<"~A()"<<endl;
}
//声明友员,表示对其授权,允许其访问A类中的所有成员。友员不是A的成员函数
friend A add( A a1, A a2);
};
A add( A a1, A a2){
//访问A中的成员,访问方式:“对象.成员”
int sum =a1.data+a2.data;
return A(sum);
}
int main()
{
A obj1(40);
A obj2(50);
add(obj1,obj2).show();
}
执行结果:
root@host:/home/LinuxShare/004.c++/day11# g++ 01.cpp
root@host:/home/LinuxShare/004.c++/day11# ./a.out
A(int=0)
A(int=0)
A(int=0)
data=90
~A()
~A()
~A()
~A()
~A()
结果分析:
从输出信息分析,析构调用5次属于正常,但为何只调用3此构造函数呢???
实际编译过程:创建obj1对象时,传递的是int型数据,会调用构造函数A(int d=0);同理创建obj2时,传递的也是int型数据,同样调用构造函数A(int d=0);add函数的return返回类型是A(sum),也是调用构造函数A(int d=0)。但add函数,是使用obj1初始化a1,即a1(obj1),并不会调用构造函数A(int d=0),因为传递参数的类型不是int型,理论上应该调用A(A)形式的构造函数,但我们在代码中没有写,所以看不到输出信息。实际运行中,系统会自动产生一个构造函数A(const A& obj),
1.2.新增拷贝构造函数测试构造函数调用次数
#include <iostream>
using namespace std;
class A{
int data;
public:
A(int d=0):data(d){
cout<<"A(int=0)"<<endl;
}
void show(){
cout<<"data="<<data<<endl;
}
~A(){
cout<<"~A()"<<endl;
}
//新增构造函数
A(const A& obj){
cout<<"A(const A&)"<<endl;
}
friend A add( A a1, A a2);
};
A add( A a1, A a2){
int sum =a1.data+a2.data;
return A(sum);
}
int main()
{
A obj1(40);
A obj2(50);
add(obj1,obj2).show();
}
执行结果:
root@host:/home/LinuxShare/004.c++/day11# g++ 02.cpp
root@host:/home/LinuxShare/004.c++/day11# ./a.out
A(int=0)
A(int=0)
A(const A&)
A(const A&)
A(int=0)
data=1028279616
~A()
~A()
~A()
~A()
~A()
2.拷贝构造函数
A(const A& obj)称作拷贝构造函数。除有个名字叫拷贝构造函数外,与普通构造函数相比毫无特殊之处,所有构造函数的调用均由传递的参数类型决定。
在类中没写构造函数时,系统自动产生两个构造函数,一个是无参构造函数,一个是拷贝构造函数。所以在没写上述两种构造函数时,可以被调用。
默认的拷贝构造函数,把旧对象中的内容逐个字节复制到新对象中。
2.1.传递的参数类型决定调用哪个构造函数例1
#include <iostream>
using namespace std;
class A{
int data;
public:
//添加不同类型的构造函数
A():data(100){
cout<<"A()"<<endl;
}
A(int d):data(d){
cout<<"A(int)"<<endl;
}
A(bool b):data(b?123:456){
cout<<"A(bool)"<<endl;
}
A(char c):data((int)c){
cout<<"A(char)"<<endl;
}
//添加拷贝构造函数
A(const A& o):data(o.data){
cout<<"A(const A&)"<<endl;
}
//添加析构函数
virtual ~A(){
cout<<"~A()"<<endl;
}
void show(){
cout<<"data="<<data<<endl;
}
};
int main()
{
A a1;
A a2(200);
A a3(false);
A a4('V');
A a5(a2);
}
执行结果:
root@host:/home/LinuxShare/004.c++/day11# g++ 03.cpp
root@host:/home/LinuxShare/004.c++/day11# ./a.out
A()
A(int)
A(bool)
A(char)
A(const A&)
~A()
~A()
~A()
~A()
~A()
结果分析:所有构造函数的调用均由传递的参数类型决定,拷贝构造函数也不例外。
2.2.传递的参数类型决定调用哪个构造函数例2
#include <iostream>
using namespace std;
class A{
int data;
public:
//添加不同类型的构造函数
A():data(100){
cout<<"A()"<<"---"<<this<<endl;
}
A(int d):data(d){
cout<<"A(int)"<<"---"<<this<<endl;
}
A(bool b):data(b?123:456){
cout<<"A(bool)"<<"---"<<this<<endl;
}
A(char c):data((int)c){
cout<<"A(char)"<<"---"<<this<<endl;
}
//添加拷贝构造函数
A(const A& o):data(o.data){
cout<<"A(const A&)"<<"---"<<this<<endl;
}
//添加析构函数
virtual ~A(){
cout<<"~A()"<<"---"<<this<<endl;
}
void show(){
cout<<"data="<<data<<endl;
}
};
void show(A obj){
obj.show();
}
int main()
{
A a1;
show(a1);
show(200);
}
执行结果:
root@host:/home/LinuxShare/004.c++/day11# g++ 04.cpp
root@host:/home/LinuxShare/004.c++/day11# ./a.out
A()---0x7ffe080b20a0
A(const A&)---0x7ffe080b20b0
data=100
~A()---0x7ffe080b20b0
A(int)---0x7ffe080b20b0
data=200
~A()---0x7ffe080b20b0
~A()---0x7ffe080b20a0
自我理解:show函数,传递参数为200时,并不会因为show(A obj)而调用拷贝构造函数,而是根据调用时传递的参数类型选择调用哪个构造函数,上例中传递int型参数200,故调用构造函数A(int d):data(d)。相当于A(200),将200强制转换成A类型,所以调用的是构造函数A(int d):data(d)。
2.3.拷贝构造函数去掉&
是否可行
#include <iostream>
using namespace std;
class A{
int data;
public:
//添加不同类型的构造函数
A():data(100){
cout<<"A()"<<endl;
}
A(int d):data(d){
cout<<"A(int)"<<endl;
}
A(bool b):data(b?123:456){
cout<<"A(bool)"<<endl;
}
A(char c):data((int)c){
cout<<"A(char)"<<endl;
}
//添加拷贝构造函数
//A(const A& o):data(o.data){
//修改拷贝构造函数如下
A(A o):data(o.data){
cout<<"A(const A&)"<<endl;
}
//添加析构函数
virtual ~A(){
cout<<"~A()"<<endl;
}
void show()const{
cout<<"data="<<data<<endl;
}
};
void show(A obj){
obj.show();
}
void func(const A& obj){
obj.show();
}
int main()
{
A a1;
show(a1);
}
编译报错:
root@host:/home/LinuxShare/004.c++/day11# g++ 05.cpp
05.cpp:23:10: error: invalid constructor; you probably meant ‘A (const A&)’
A(A o):data(o.data){
^
结果分析:
拷贝构造函数去掉引用时,在show(a1)调用构造函数A(A o)时,会新建一个A类型的对象,而新建一个A类型的对象又会调用A(A o),从而无限循环地调用下去。
PS:形参是一个新变量,在调用时创建,由实参来初始化。
3.默认拷贝构造函数和自定义拷贝构造函数的区别
默认的拷贝构造函数,把旧对象中的内容逐个字节复制到新对象中。而自定义构造函数,旧对象的内容只是传递到了自定义拷贝构造函数的形参,而不会自动复制到新对象中,如果需要对新对象赋值,需要在初始化列表中写,或者在函数体中写语句实现。
自定义拷贝构造函数后,就不再有默认了拷贝构造函数。要在自定义的拷贝构造函数中处理所有需要处理的数据。
3.1.默认拷贝构造函数
旧对象中的内容逐个字节复制到新对象中。
#include <iostream>
using namespace std;
class A{
int data;
public:
//添加无参构造函数
A():data(100){
cout<<"A()"<<endl;
}
void show()const{
cout<<"data="<<data<<endl;
}
//添加析构函数
virtual ~A(){
cout<<"~A()"<<endl;
}
};
int main()
{
A a1;
a1.show();
cout<<"======"<<endl;
A a5(a1);
a5.show();
}
执行结果:
root@host:/home/LinuxShare/004.c++/day11# g++ 06.cpp
root@host:/home/LinuxShare/004.c++/day11# ./a.out
A()
data=100
======
data=100
~A()
~A()
3.2.自定义拷贝构造函数
#include <iostream>
using namespace std;
class A
{
int data;
public: //添加无参构造函数
A():data(100)
{
cout<<"A()"<<endl;
}
//添加自定义拷贝构造函数
A(const A& o)
{
cout<<"A(const A&)"<<endl;
}
void show()const
{
cout<<"data="<<data<<endl;
}
//添加析构函数
virtual ~A()
{
cout<<"~A()"<<endl;
}
};
int main()
{
A a1; a1.show();
cout<<"======"<<endl;
A a5(a1);
a5.show();
}
执行结果:
A()
data=100
======
A(const A&)
data=-1217415840
~A()
~A()
结果分析:
a1中的data并没有自动赋值给a5中的data。
4.类成员有指针,调用拷贝构造函数的情形
4.1.基础代码,不调用拷贝构造函数
#include <iostream>
using namespace std;
class Array
{
char *p;//数组指针
int len;//数组长度
public:
//构造函数
Array(int n):len(n),p(new char[n]){}
//获取数组长度
int size()
{
return len;
}
//填充数组内容
void fill(char start,int skip)
{
for(int i=0;i<len;i++)
{
p[i]=start+i*skip;
}
}
//输出数组内容
void show()
{
for(int i=0;i<len;i++)
{
cout<<p[i];
}
cout<<endl;
}
//析构函数
~Array()
{
delete[] p;
}
};
int main()
{
Array a1(10);
a1.fill('a',2);
a1.show();
}
执行结果:
root@host:/home/LinuxShare/004.c++/day11# g++ 08.cpp
root@host:/home/LinuxShare/004.c++/day11# ./a.out
acegikmoqs
4.2.增加新对象,并用旧对象对其初始化
#include <iostream>
using namespace std;
class Array
{
char *p;//数组指针
int len;//数组长度
public:
//构造函数
Array(int n):len(n),p(new char[n]){}
//获取数组长度
int size()
{
return len;
}
//填充数组内容
void fill(char start,int skip)
{
for(int i=0;i<len;i++)
{
p[i]=start+i*skip;
}
}
//输出数组内容
void show()
{
for(int i=0;i<len;i++)
{
cout<<p[i];
}
cout<<endl;
}
//析构函数
~Array()
{
delete[] p;
}
};
int main()
{
Array a1(10);
a1.fill('a',2);
a1.show();
//新增对象a2
Array a2(a1);
a2.fill('A',2);
a2.show();
}
执行结果:
./a.out
acegikmoqs
ACEGIKMOQS
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x09a97008 ***
======= Backtrace: =========
/lib/i386-linux-gnu/i686/cmov/libc.so.6(+0x6ff41)[0xb74cff41]
/lib/i386-linux-gnu/i686/cmov/libc.so.6(+0x717a8)[0xb74d17a8]
/lib/i386-linux-gnu/i686/cmov/libc.so.6(cfree+0x6d)[0xb74d48ed]
/usr/lib/i386-linux-gnu/libstdc++.so.6(_ZdlPv+0x1f)[0xb76594bf]
./a.out[0x80488e2]
/lib/i386-linux-gnu/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0xb7476e46]
./a.out[0x8048781]
======= Memory map: ========
08048000-08049000 r-xp 00000000 00:12 496 /home/gongxiang/c++day21/day11/a.out
08049000-0804a000 rw-p 00000000 00:12 496 /home/gongxiang/c++day21/day11/a.out
09a97000-09ab8000 rw-p 00000000 00:00 0 [heap]
b7300000-b7321000 rw-p 00000000 00:00 0
b7321000-b7400000 ---p 00000000 00:00 0
b745e000-b7460000 rw-p 00000000 00:00 0
b7460000-b75c1000 r-xp 00000000 08:01 927283 /lib/i386-linux-gnu/i686/cmov/libc-2.13.so
b75c1000-b75c2000 ---p 00161000 08:01 927283 /lib/i386-linux-gnu/i686/cmov/libc-2.13.so
b75c2000-b75c4000 r--p 00161000 08:01 927283 /lib/i386-linux-gnu/i686/cmov/libc-2.13.so
b75c4000-b75c5000 rw-p 00163000 08:01 927283 /lib/i386-linux-gnu/i686/cmov/libc-2.13.so
b75c5000-b75c8000 rw-p 00000000 00:00 0
b75c8000-b75e4000 r-xp 00000000 08:01 894084 /lib/i386-linux-gnu/libgcc_s.so.1
b75e4000-b75e5000 rw-p 0001b000 08:01 894084 /lib/i386-linux-gnu/libgcc_s.so.1
b75e5000-b7609000 r-xp 00000000 08:01 927454 /lib/i386-linux-gnu/i686/cmov/libm-2.13.so
b7609000-b760a000 r--p 00023000 08:01 927454 /lib/i386-linux-gnu/i686/cmov/libm-2.13.so
b760a000-b760b000 rw-p 00024000 08:01 927454 /lib/i386-linux-gnu/i686/cmov/libm-2.13.so
b760b000-b760c000 rw-p 00000000 00:00 0
b760c000-b76ec000 r-xp 00000000 08:01 887484 /usr/lib/i386-linux-gnu/libstdc++.so.6.0.17
b76ec000-b76f0000 r--p 000e0000 08:01 887484 /usr/lib/i386-linux-gnu/libstdc++.so.6.0.17
b76f0000-b76f1000 rw-p 000e4000 08:01 887484 /usr/lib/i386-linux-gnu/libstdc++.so.6.0.17
b76f1000-b76f8000 rw-p 00000000 00:00 0
b76fd000-b7700000 rw-p 00000000 00:00 0
b7700000-b7701000 r-xp 00000000 00:00 0 [vdso]
b7701000-b771d000 r-xp 00000000 08:01 894040 /lib/i386-linux-gnu/ld-2.13.so
b771d000-b771e000 r--p 0001b000 08:01 894040 /lib/i386-linux-gnu/ld-2.13.so
b771e000-b771f000 rw-p 0001c000 08:01 894040 /lib/i386-linux-gnu/ld-2.13.so
bfa7d000-bfa9e000 rw-p 00000000 00:00 0 [stack]
已放弃 (core dumped)
结果分析:
现象解释:由于默认的拷贝构造函数将旧对象的指针赋值给新对象指针,旧对象调用析构函数时,将指针p指向的空间释放,新对象调用析构函数时,要释放同样的地址空间,报错。
4.3.自定义拷贝构造函数,防止重复是否地址空间
#include <iostream>
using namespace std;
class Array
{
char *p;//数组指针
int len;//数组长度
public:
//构造函数
Array(int n):len(n),p(new char[n]){}
//自定义拷贝构造函数
Array(const Array& o):len(o.len)
{
p = new char[len];
for(int i=0;i<len;i++)
{
p[i]=o.p[i];
}
}
//获取数组长度
int size()
{
return len;
}
//填充数组内容
void fill(char start,int skip)
{
for(int i=0;i<len;i++)
{
p[i]=start+i*skip;
}
}
//输出数组内容
void show()
{
for(int i=0;i<len;i++)
{
cout<<p[i];
}
cout<<endl;
}
//析构函数
~Array()
{
cout<<"~A()-1"<<endl;
delete[] p;
cout<<"~A()-2"<<endl;
}
};
int main()
{
Array a1(10);
a1.fill('a',2);
a1.show();
//新增对象a2
Array a2(a1);
a2.fill('A',2);
a2.show();
a1.show();
}
执行结果:
root@host:/home/LinuxShare/004.c++/day11# g++ 10.cpp
root@host:/home/LinuxShare/004.c++/day11# ./a.out
acegikmoqs
ACEGIKMOQS
acegikmoqs
~A()-1
~A()-2
~A()-1
~A()-2
结果解释:
旧对象没有被改变,并且新对象调用析构函数正常释放空间。