C拷贝构造函数汇总.docx
《C拷贝构造函数汇总.docx》由会员分享,可在线阅读,更多相关《C拷贝构造函数汇总.docx(25页珍藏版)》请在冰点文库上搜索。
C拷贝构造函数汇总
拷贝构造函数是C++最基础的概念之一,大家自认为对拷贝构造函数了解么?
请大家先回答一下三个问题:
1.以下函数哪个是拷贝构造函数,为什么?
1.X:
:
X(const X&;
2.X:
:
X(X;
3.X:
:
X(X&, int a=1;
4.X:
:
X(X&, int a=1, b=2;
2.一个类中可以存在多于一个的拷贝构造函数吗?
3.写出以下程序段的输出结果,并说明为什么?
如果你都能回答无误的话,那么你已经对拷贝构造函数有了相当的了解。
1.#include
2.#include
3.
4.struct X {
5. template
6. X( T& { std:
:
cout << "This is ctor." << std:
:
endl; }
7.
8. template
9. X& operator=( T& { std:
:
cout << "This is ctor." << std:
:
endl; }
10.};
11.
12.void main( {
13. X a(5;
14. X b(10.5;
15. X c = a;
16. c = b;
17.}
解答如下:
1.对于一个类X,如果一个构造函数的第一个参数是下列之一:
aX&
bconstX&
cvolatileX&
dconstvolatileX&
且没有其他参数或其他参数都有默认值,那么这个函数是拷贝构造函数.
1.X:
:
X(const X&; //是拷贝构造函数
2.X:
:
X(X&, int=1; //是拷贝构造函数
2.类中可以存在超过一个拷贝构造函数,
1.class X {
2.public:
3. X(const X&;
4. X(X&; // OK
5.};
注意,如果一个类中只存在一个参数为X&的拷贝构造函数,那么就不能使用constX或volatileX的对象实行拷贝初始化.
1.class X {
2.public:
3. X(;
4. X(X&;
5.};
6.
7.const X cx;
8.X x = cx; // error
如果一个类中没有定义拷贝构造函数,那么编译器会自动产生一个默认的拷贝构造函数.
这个默认的参数可能为X:
:
X(constX&或X:
:
X(X&,由编译器根据上下文决定选择哪一个.
默认拷贝构造函数的行为如下:
默认的拷贝构造函数执行的顺序与其他用户定义的构造函数相同,执行先父类后子类的构造.
拷贝构造函数对类中每一个数据成员执行成员拷贝(memberwiseCopy的动作.
a如果数据成员为某一个类的实例,那么调用此类的拷贝构造函数.
b如果数据成员是一个数组,对数组的每一个执行按位拷贝.
c如果数据成员是一个数量,如int,double,那么调用系统内建的赋值运算符对其进行赋值.
3. 拷贝构造函数不能由成员函数模版生成.
1.struct X {
2. template
3. X( const T& ; // NOT copy ctor, T can't be X
4.
5. template
6. operator=( const T& ; // NOT copy ass't, T can't be X
7.};
8.
原因很简单,成员函数模版并不改变语言的规则,而语言的规则说,如果程序需要一个拷贝构造函数而你没有声明它,那么编译器会为你自动生成一个.所以成员函数模版并不会阻止编译器生成拷贝构造函数,赋值运算符重载也遵循同样的规则.(参见EffectiveC++3edition,Item45
默认拷贝构造函数的行为如下:
默认的拷贝构造函数执行的顺序与其他用户定义的构造函数相同,执行先父类后子类的构造.
拷贝构造函数对类中每一个数据成员执行成员拷贝(memberwiseCopy的动作.
a如果数据成员为某一个类的实例,那么调用此类的拷贝构造函数.
b如果数据成员是一个数组,对数组的每一个执行按位拷贝.
c如果数据成员是一个数量,如int,double,那么调用系统内建的赋值运算符对其进行赋值.
1.深拷与浅拷
深拷贝和浅拷贝的定义可以简单理解成:
如果一个类拥有资源(堆,或者是其它系统资源,当这个类的对象发生复制过程的时候(复制指针所指向的值),这个过程就可以叫做深拷贝,反之对象存在资源但复制过程并未复制资源(只复制了指针所指的地址)的情况视为浅拷贝。
浅拷贝资源后在释放资源的时候会产生资源归属不清的情况导致程序运行出错,这点尤其需要注意!
原则上,应该为所有包含动态分配成员的类都提供拷贝构造函数。
浅:
usingnamespacestd;
//shallow&&deepcopy
//deepcopymakepointerpointtoanewplace!
classProduct
...{
public:
int*pointer;
Product(inti=0
...{
pointer=newint(i;
}
//changeclassvariable
voidchange(inti
...{
*pointer=i;
}
//deconstructor
~Product(
...{
deletepointer;
}
};
intmain(
...{
Productp1(2;
Productp2(p1;
p1.change(3;
cout<<*p2.pointer<
getchar(;
return0;
}
深:
usingnamespacestd;
//shallow&&deepcopy
//deepcopymakepointerpointtoanewplace!
classProduct
...{
public:
int*pointer;
Product(inti=0
...{
pointer=newint(i;
}
//changeclassvariable
voidchange(inti
...{
*pointer=i;
}
//copyingconstructor
Product(constProduct&p
...{
pointer=newint(*p.pointer;
}
//deconstructor
~Product(
...{
deletepointer;
}
};
intmain(
...{
Productp1(2;
Productp2(p1;
p1.change(3;
cout<<*p2.pointer<
getchar(;
return0;
}
2拷贝构造函数的另一种调用
当对象直接作为参数传给函数时,函数将建立对象的临时拷贝,这个拷贝过程也将调用拷贝构造函数。
例如:
#include
usingnamespacestd;
classDate...{
intn;
public:
Date(inti=0
...{
cout<<"载入构造函数"<n=i;
}
Date(constDate&d
...{
cout<<"载入拷贝构造函数"<n=d.n;
}
intGetMember(
...{
returnn;
}
};
voidDisplay(Dateobj//针对obj的操作实际上是针对复制后的临时拷贝进行的
...{
cout<}
intmain(
...{
Datea;
Dateb(99;
Display(a;//对象直接作为参数
Display(b;//对象直接作为参数
getchar(;
return0;
}
程序输出:
载入构造函数:
载入构造函数:
载入拷贝构造函数
0载入拷贝构造函数
99
还有一种情况,也是与临时对象有关的。
当函数中的局部对象被用作返回值,返回给函数调用时,也将建立此局部对象的一个临时拷贝,此时拷贝构造函数也将被调用。
——可是经测试发现情况有异。
代码如下:
#include
usingnamespacestd;
classDate...{
intn;
public:
Date(inti=0
...{
cout<<"载入构造函数"<n=i;
}
Date(constDate&d
...{
cout<<"载入拷贝构造函数"<n=d.n;
}
voidShow(
...{
cout<<"n="<}
};
DateGetClass(void//函数中的局部对象被用作返回值,按理说应该引用拷贝构造函数
...{
Datetemp(100;
returntemp;
}
intmain(
...{
Datea;
a.Show(;
a=GetClass(;//这里GetClass(函数中的局部对象被用作返回值
a.Show(;
Dateb=GetClass(;//这里GetClass(函数中的局部对象被用作返回值
b.Show(;
getchar(;
return0;
}
程序输出:
载入构造函数:
n=0
载入构造函数:
n=100
载入构造函数:
n=100
按理第2个和第3个应该输出'载入拷贝构造函数"才对,这个结果与预想的不一样,到底是哪里出问题了呢?
注:
后来有论坛上的朋友告诉我说这是因为编译器的不同而导致不同的输出。
有人得到这样的输出结果:
载入构造函数
n=0
载入构造函数
载入拷贝构造函数
n=100
载入构造函数
载入拷贝构造函数
n=100
还有人得到这样的输出结果:
载入构造函数
n=0
载入构造函数
载入拷贝构造函数
n=100
载入构造函数
载入拷贝构造函数
载入拷贝构造函数
n=100
(用的是vc++)
3.3无名对象
现在我们来说一下无名对象。
什么是无名对象?
利用无名对象初始化对象系统不会调用拷贝构造函数?
这是我们需要回答的两个问题。
首先我们来回答第一个问题。
很简单,如果在程序的main函数中有:
Internet ("中国";//Internet表示一个类
这样的一句语句就会产生一个无名对象。
无名对象会调用构造函数,但利用无名对象初始化对象时系统不会调用拷贝构造函数!
下面的代码是常见的利用无名对象初始化对象的例子。
#include
usingnamespacestd;
classDate...{
intn;
public:
Date(inti=0
...{
cout<<"载入构造函数"<n=i;
}
Date(constDate&d
...{
cout<<"载入拷贝构造函数"<n=d.n;
}
voidShow(
...{
cout<<"n="<}
};
intmain(
...{
Datea(100;
a.Show(;
Dateb=a;//"="在对象声明语句中,表示初始化,调用拷贝构造函数
b.Show(;
Datec;
c.Show(;
c=a;//"="在赋值语句中,表示赋值操作,调用赋值函数
c.Show(;
getchar(;
return0;
}
程序输出:
载入构造函数:
name的地址:
23ff40;name的字符串:
中国
cname的地址:
33778;cname的字符串:
中国
载入析构函数:
上面代码的运行结果有点“出人意料”,从思维逻辑上说,当无名对象创建了后,是应该调用自定义拷贝构造函数,或者是默认拷贝构造函数来完成复制过程的,但事实上系统并没有这么做,因为无名对象使用过后在整个程序中就失去了作用。
对于这种情况c++会把代码看成是:
Interneta("中国";省略了创建无名对象这一过程,所以说不会调用拷贝构造函数。
3.赋值符的重载
由于并非所有的对象都会使用拷贝构造函数和赋值函数,程序员可能对这两个函数有些轻视。
请先记住以下的警告,在阅读正文时就会多心:
本章开头讲过,如果不主动编写拷贝构造函数和赋值函数,编译器将以“位拷贝”的方式自动生成缺省的函数。
倘若类中含有指针变量,那么这两个缺省的函数就隐含了错误。
以类String的两个对象a,b为例,假设a.m_data的内容为“hello”,b.m_data的内容为“world”。
现将a赋给b,缺省赋值函数的“位拷贝”意味着执行b.m_data=a.m_data。
这将造成三个错误:
一是b.m_data原有的内存没被释放,造成内存泄露;二是b.m_data和a.m_data指向同一块内存,a或b任何一方变动都会影响另一方;三是在对象被析构时,m_data被释放了两次。
拷贝构造函数和赋值函数非常容易混淆,常导致错写、错用。
拷贝构造函数是在对象被创建时调用的,而赋值函数只能被已经存在了的对象调用。
以下程序中,第三个语句和第四个语句很相似,你分得清楚哪个调用了拷贝构造函数,哪个调用了赋值函数吗?
Stringa(“hello”;
Stringb(“world”;
Stringc=a;//调用了拷贝构造函数,最好写成c(a;
c=b;//调用了赋值函数
本例中第三个语句的风格较差,宜改写成Stringc(a以区别于第四个语句。
请看下面的代码:
(1)没有重载赋值函数
#include"stdafx.h"
usingnamespacestd;
//shallow&&deepcopy
//deepcopymakepointerpointtoanewplace!
classProduct
...{
public:
int*pointer;
Product(inti=0
...{
pointer=newint(i;
}
//changeclassvariable
voidchange(inti
...{
*pointer=i;
}
//copyingconstructor
Product(constProduct&p
...{
pointer=newint(*p.pointer;
}
//deconstructor
~Product(
...{
deletepointer;
}
};
intmain(
...{
Productp1(1;
Productp2(2;
Productp3(3;
p2=p3;
p3.change(4;
cout<<*p2.pointer<
getchar(;
return0;
}
//结果输出4
(2)重载赋值函数
#include"stdafx.h"
usingnamespacestd;
//shallow&&deepcopy
//deepcopymakepointerpointtoanewplace!
classProduct
...{
public:
int*pointer;
Product(inti=0
...{
pointer=newint(i;
}
//changeclassvariable
voidchange(inti
...{
*pointer=i;
}
//copyingconstructor
Product(constProduct&p
...{
pointer=newint(*p.pointer;
}
//重载=
Product&operator=(constProduct&p
...{
if(this!
=&p
pointer=newint(*p.pointer;
return*this;
}
//deconstructor
~Product(
...{
deletepointer;
}
};
intmain(
...{
Productp1(1;
Productp2(2;
Productp3(3;
p2=p3;
p3.change(4;
cout<<*p2.pointer<
getchar(;
return0;
}
//输出3
5.在拷贝构造函数中使用赋值函数
为了简化程序,我们通常在拷贝构造函数中使用赋值函数。
例如:
#include
usingnamespacestd;
classDate...{
intda,mo,yr;
public:
Date(intd=0,intm=0,inty=0
...{
cout<<"载入构造函数"<da=d;
mo=m;
yr=y;
}
Date(constDate&other;
Date&operator=(constDate&other;
voidShow(
...{
cout<}
};
Date:
:
Date(constDate&other//拷贝构造函数中使用赋值函数
...{
cout<<"载入拷贝构造函数"<*this=other;
}
Date&Date:
:
operator=(constDate&other
...{
cout<<"载入赋值函数"<if(this==&other
return*this;
da=other.da;
mo=other.mo;
yr=other.yr;
return*this;
}
intmain(
...{
Datea(1,3,6;
a.Show(;
Dateb=a;
b.Show(;
Datec;
c.Show(;
c=a;
c.Show(;
getchar(;
return0;
}
程序输出:
载入构造函数:
3-1-6
载入拷贝构造函数
载入赋值函数
3-1-6
载入构造函数:
0-0-0
载入赋值函数
3-1-6
请注意:
程序输出了两次“载入赋值函数”,这是因为我们在拷贝构造函数中使用了赋值函数,这样使程序变得简洁。
如果把拷贝构造函数改写为:
Date:
:
Date(constDate&other
{
cout<<"载入拷贝构造函数"<da=other.da;
mo=other.mo;
yr=other.yr;
}
则程序将输出:
载入构造函数:
3-1-6
载入拷贝构造函数
3-1-6
载入构造函数:
0-0-0
载入赋值函数
3-1-6
6.偷懒的办法处理拷贝构造函数和赋值函数
如果我们实在不想编写拷贝构造函数和赋值函数,又不允许别人使用编译器生成的缺省函数,怎么办?
偷懒的办法是:
只需将拷贝构造函数和赋值函数声明为私有函数,不用编写代码。
例如:
classA
{…
private:
A(constA&a;//私有的拷贝构造函数
A&operator=(constA&a;//私有的赋值函数
};
如果有人试图编写如下程序:
Ab(a;//调用了私有的拷贝构造函数
b=a;//调用了私有的赋值函数
编译器将指出错误,因为外界不可以操作A的私有函数。
(引自〈〈高质量c++编程指南〉〉)
本文来自CSDN博客,转载请标明出处:
【赛迪网讯】C++中的对象这一知识点可以引申出很多内容,而且它们涉及的内容都是比较重要和实用的。
这里将浅谈一下C++类对象的拷贝构造函数。
对于普通类型的对象来说,它们之间的复制是很简单的,例如:
inta=100;
intb=a;
而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量。
下面看一个类对象拷贝的简单例子。
运行程序,屏幕输出100。
从以上代码的运行结果可以看出,系统为对象B分配了内存并完成了与对象A的复制过程。
就类对象而言,相同类型的类对象是通过拷贝构造函数来完成整个复制过程的。
下面我们举例说明拷贝构造函数的工作过程。
CA(constCA&C就是我们自定义的拷贝构造函数。
可见,拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它的唯一的一个参数是本类型的一个引用变量,该参数是const类型,不可变的。
例如:
类X的拷贝构造函数的形式为X(X&x。
当用一个已初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,拷贝构造函数就会被自动调用。
也就是说,当类的对象需要拷贝时,拷贝构造函数将会被调用。
以下情况都会调用拷贝构造函数:
一个对象以值传递的方式传入函数体
一个对象以值传递的方式从函数返回
一个对象需要通过另外一个对象进行初始化。
如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝。
位拷贝又称浅拷贝,后面将进行说明。
自定义拷贝构造函数是一种良好的编程风格,它可以阻止编译器形成默认的拷贝构造函数,提高源码效率。
浅拷贝和深拷贝
在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。
这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。
这就出现了问题:
当B把内存释放了(如:
析构),这时A内的指针就是野指针了,出现运行错误。
深拷贝和浅拷贝可以简单理解为:
如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之