1拷贝构造函数参数的特点
赋值构造函数要申请内存。就像一般的构造函数一样。
而赋值操作,是已经申请好了内存。只是赋值。
对于一个类X,如果一个构造函数的第一个参数是下列之一:
a) X&
b) const X&
c) volatile X&
d) const volatile X&
因此 X::X(X&, int=1); //是拷贝构造函数
并且类中可以存在超过一个拷贝构造函数。拷贝构造函数采用引用作参数原因:
1) 效率 。
2) 避免死循环。 当一个对象需要以值方式传递时,编译器会生成代码调用它的拷贝构造函数以生成一个复本,因此当使用拷贝构造函数时会造成死循环。
2默认拷贝构造函数
如果一个类中没有定义拷贝构造函数,那么编译器会自动产生一个默认的拷贝构造函数。这个默认的参数可能为X::X(const X&)或X::X(X&),由编译器根据上下文决定选择哪一个。默认拷贝构造函数的行为如下(递归的)(默认的构造函数X()、默认的拷贝赋值函数X& operator=(X& a) 被调用时行为同理):
首先调用父类拷贝构造函数。然后拷贝构造函数对类中每一个数据成员执行成员拷贝的动作:
a) 如果数据成员为某一个类的实例,那么调用此类的拷贝构造函数。
b) 如果数据成员是一个数组(或指针、引用),对数组的每一个执行按位拷贝。
c) 如果数据成员是一个数量,如int,double,那么调用系统内建的赋值运算符对其进行赋值。
注:如果父类或成员的类提供的拷贝构造函数为private,则不能通过编译。这一点对默认的构造函数和默认的拷贝赋值函数被调用时同样适用,因此最好自己定义这三个函数和析构函数。
3拷贝构造函数调用时机
以下情况都会调用拷贝构造函数:
1) 一个对象以值传递的方式传入函数体。
2) 一个对象以值传递的方式从函数返回。
3) 一个对象需要通过另外一个对象进行初始化。
4理解copy constructor和copy assignment的一个例子
class callwitch{
string name ;
static int objcount;
public :
callwitch(const string& na = "") : name(na) {
objcount ++;
print("callwitch(const string&)");
};
~callwitch(){
objcount --;
print("~callwitch()");
}
callwitch(const callwitch& obj):name(obj.name) {
name = "copy of " + name;
objcount ++;
print("copy constructor");
};
callwitch& operator=(callwitch& obj){
print("copy assignment");
}
void print(const string& msg = "")const {
if(msg.size() !=0)
cout << msg << endl ;
cout << '/t' << name << ": " << "objcount = " << objcount << endl;
}
};
int callwitch::objcount = 0 ;
callwitch f(callwitch obj)
{
cout << "returning from f()" << endl;
return obj ;
}
int main(int argc, char *argv[])
{
callwitch c1("c1");
callwitch c2 = c1 ; //copy constructor called。因为此时c2还未被初始化成为对象(即还未构造出来),无法调用copy assignment函数!
callwitch c4 ;
c4 = c1 ; //copy assignment called
c2.print("call f()");
callwitch c3 = f(c1);
cout << "call f(),no need return value" << endl;
f(c1);
system("PAUSE");
return EXIT_SUCCESS;
}
以下是运行结果:
callwitch(int)
c1: objcount = 1
copy constructor
copy of c1: objcount = 2
callwitch(int)
: objcount = 3
copy assignment
: objcount = 3
call f()
copy of c1: objcount = 3
copy constructor
copy of c1: objcount = 4
returning from f()
copy constructor
copy of copy of c1: objcount = 5
~callwitch()
copy of c1: objcount = 4
call f(),no need return value
copy constructor
copy of c1: objcount = 5
returning from f()
copy constructor
copy of copy of c1: objcount = 6
~callwitch()
copy of copy of c1: objcount = 5
~callwitch()
copy of c1: objcount = 4
5其它:
1) 最好自定义拷贝构造函数。如果使用编译器生成的拷贝构造函数,调用拷贝构造函数时实行位拷贝,当类内成员变量需要动态开辟堆内存时,执行classA obj1 ; classA obj2(obj1)。如果obj1中有一个成员变量指针已经申请了内存,那obj2中的那个成员变量也指向同一块内存。这就出现了问题:当obj1把内存释放后,obj2内的指针就是无效指针了,出现运行错误。
2) 最好自定义operator=。如果打算在一个内含reference成员、内含const成员时,必须自己定义copy assignment操作符,因为C++本身不允许引用改指不同的对象,也不允许更改const成员;如果base class将copy assignment操作声明为private,编译器同样拒绝为其derived class生成一个copy assignment操作符(effective C++ Item 6)。
1. 何时调用复制构造函数
复制构造函数用于将一个对象复制到新创建的对象中。也就是说,它用于初始化过程中,而不是常规的赋值过程中。类的复制构造函数原型通常如下:
class_name(const class_name&);
它接受一个指向类对象的常量引用作为参数。例如,String类的复制构造函数的原型如下:
String(const String&);
新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。这在很多情况下都可能发生,最常见的情况是将新对象显示地初始化为现有的对象。例如,假设motto是一个String对象,则下面4种声明都将调用复制构造函数:
String ditto(motto);
String metoo = motto;
String also = String(motto);
String *pString = new String(motto);
其中中间的2种声明可能会使用复制构造函数直接创建metto和also,也可能会使用复制构造函数生成一个临时对象,然后将临时对象的内容赋给metoo和also,这取决于具体的实现。最后一种声明使用motto初始化一个匿名对象,并将新对象的地址赋给pString指针。
2. 何时调用赋值构造函数
赋值构造函数是通过重载赋值操作符实现的,这种操作符的原型如下:
Class_name& Class_name::operator=(const Class_name&);
它接受并返回一个指向类对象的引用。例如,String 类的赋值操作符的原型如下:
String& String::operator=(const String&);
将已有的对象赋给另一个对象时,将使用重载的赋值操作符:
String headline1("test");
String knot;
knot = headline1;
初始化对象时,并不一定会使用赋值操作符:
String metoo = knot;
这里,metoo是一个新创建的对象,被初始化为knot的值,因此使用复制构造函数。不过,正如前面指出的,实现时也可能分两步来处理这条语句:使用复制构造函数创建一个临时对象,然后通过赋值将临时对象的值复制到新对象中。这就是说,初始化总是会调用复制构造函数,而使用=操作符时也可能调用赋值操作符
#include <iostream>
using namespace std;
class Test
{
public:
Test();
~Test();
Test(const Test& t);
Test& operator=(const Test& t);
private:
int t1;
};
Test::Test()
{
cout<<"调用构造函数"<<endl;
}
Test::~Test()
{
cout<<"调用析构函数"<<endl;
}
Test::Test(const Test& t)
{
cout<<"调用复制构造函数"<<endl;
}
Test& Test::operator =(const Test& t)
{
cout<<"调用赋值构造函数"<<endl;
t1 = t.t1;
return *this;
}
void main()
{
Test t1;
Test t2 = t1;
Test t3;
t3 = t1;
}
输出如下:
1拷贝构造函数参数的特点
赋值构造函数要申请内存。就像一般的构造函数一样。
而赋值操作,是已经申请好了内存。只是赋值。
对于一个类X,如果一个构造函数的第一个参数是下列之一:
a) X&
b) const X&
c) volatile X&
d) const volatile X&
因此 X::X(X&, int=1); //是拷贝构造函数
并且类中可以存在超过一个拷贝构造函数。拷贝构造函数采用引用作参数原因:
1) 效率 。
2) 避免死循环。 当一个对象需要以值方式传递时,编译器会生成代码调用它的拷贝构造函数以生成一个复本,因此当使用拷贝构造函数时会造成死循环。
2默认拷贝构造函数
如果一个类中没有定义拷贝构造函数,那么编译器会自动产生一个默认的拷贝构造函数。这个默认的参数可能为X::X(const X&)或X::X(X&),由编译器根据上下文决定选择哪一个。默认拷贝构造函数的行为如下(递归的)(默认的构造函数X()、默认的拷贝赋值函数X& operator=(X& a) 被调用时行为同理):
首先调用父类拷贝构造函数。然后拷贝构造函数对类中每一个数据成员执行成员拷贝的动作:
a) 如果数据成员为某一个类的实例,那么调用此类的拷贝构造函数。
b) 如果数据成员是一个数组(或指针、引用),对数组的每一个执行按位拷贝。
c) 如果数据成员是一个数量,如int,double,那么调用系统内建的赋值运算符对其进行赋值。
注:如果父类或成员的类提供的拷贝构造函数为private,则不能通过编译。这一点对默认的构造函数和默认的拷贝赋值函数被调用时同样适用,因此最好自己定义这三个函数和析构函数。
3拷贝构造函数调用时机
以下情况都会调用拷贝构造函数:
1) 一个对象以值传递的方式传入函数体。
2) 一个对象以值传递的方式从函数返回。
3) 一个对象需要通过另外一个对象进行初始化。
4理解copy constructor和copy assignment的一个例子
class callwitch{
string name ;
static int objcount;
public :
callwitch(const string& na = "") : name(na) {
objcount ++;
print("callwitch(const string&)");
};
~callwitch(){
objcount --;
print("~callwitch()");
}
callwitch(const callwitch& obj):name(obj.name) {
name = "copy of " + name;
objcount ++;
print("copy constructor");
};
callwitch& operator=(callwitch& obj){
print("copy assignment");
}
void print(const string& msg = "")const {
if(msg.size() !=0)
cout << msg << endl ;
cout << '/t' << name << ": " << "objcount = " << objcount << endl;
}
};
int callwitch::objcount = 0 ;
callwitch f(callwitch obj)
{
cout << "returning from f()" << endl;
return obj ;
}
int main(int argc, char *argv[])
{
callwitch c1("c1");
callwitch c2 = c1 ; //copy constructor called。因为此时c2还未被初始化成为对象(即还未构造出来),无法调用copy assignment函数!
callwitch c4 ;
c4 = c1 ; //copy assignment called
c2.print("call f()");
callwitch c3 = f(c1);
cout << "call f(),no need return value" << endl;
f(c1);
system("PAUSE");
return EXIT_SUCCESS;
}
以下是运行结果:
callwitch(int)
c1: objcount = 1
copy constructor
copy of c1: objcount = 2
callwitch(int)
: objcount = 3
copy assignment
: objcount = 3
call f()
copy of c1: objcount = 3
copy constructor
copy of c1: objcount = 4
returning from f()
copy constructor
copy of copy of c1: objcount = 5
~callwitch()
copy of c1: objcount = 4
call f(),no need return value
copy constructor
copy of c1: objcount = 5
returning from f()
copy constructor
copy of copy of c1: objcount = 6
~callwitch()
copy of copy of c1: objcount = 5
~callwitch()
copy of c1: objcount = 4
5其它:
1) 最好自定义拷贝构造函数。如果使用编译器生成的拷贝构造函数,调用拷贝构造函数时实行位拷贝,当类内成员变量需要动态开辟堆内存时,执行classA obj1 ; classA obj2(obj1)。如果obj1中有一个成员变量指针已经申请了内存,那obj2中的那个成员变量也指向同一块内存。这就出现了问题:当obj1把内存释放后,obj2内的指针就是无效指针了,出现运行错误。
2) 最好自定义operator=。如果打算在一个内含reference成员、内含const成员时,必须自己定义copy assignment操作符,因为C++本身不允许引用改指不同的对象,也不允许更改const成员;如果base class将copy assignment操作声明为private,编译器同样拒绝为其derived class生成一个copy assignment操作符(effective C++ Item 6)。
1. 何时调用复制构造函数
复制构造函数用于将一个对象复制到新创建的对象中。也就是说,它用于初始化过程中,而不是常规的赋值过程中。类的复制构造函数原型通常如下:
class_name(const class_name&);
它接受一个指向类对象的常量引用作为参数。例如,String类的复制构造函数的原型如下:
String(const String&);
新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。这在很多情况下都可能发生,最常见的情况是将新对象显示地初始化为现有的对象。例如,假设motto是一个String对象,则下面4种声明都将调用复制构造函数:
String ditto(motto);
String metoo = motto;
String also = String(motto);
String *pString = new String(motto);
其中中间的2种声明可能会使用复制构造函数直接创建metto和also,也可能会使用复制构造函数生成一个临时对象,然后将临时对象的内容赋给metoo和also,这取决于具体的实现。最后一种声明使用motto初始化一个匿名对象,并将新对象的地址赋给pString指针。
2. 何时调用赋值构造函数
赋值构造函数是通过重载赋值操作符实现的,这种操作符的原型如下:
Class_name& Class_name::operator=(const Class_name&);
它接受并返回一个指向类对象的引用。例如,String 类的赋值操作符的原型如下:
String& String::operator=(const String&);
将已有的对象赋给另一个对象时,将使用重载的赋值操作符:
String headline1("test");
String knot;
knot = headline1;
初始化对象时,并不一定会使用赋值操作符:
String metoo = knot;
这里,metoo是一个新创建的对象,被初始化为knot的值,因此使用复制构造函数。不过,正如前面指出的,实现时也可能分两步来处理这条语句:使用复制构造函数创建一个临时对象,然后通过赋值将临时对象的值复制到新对象中。这就是说,初始化总是会调用复制构造函数,而使用=操作符时也可能调用赋值操作符
#include <iostream>
using namespace std;
class Test
{
public:
Test();
~Test();
Test(const Test& t);
Test& operator=(const Test& t);
private:
int t1;
};
Test::Test()
{
cout<<"调用构造函数"<<endl;
}
Test::~Test()
{
cout<<"调用析构函数"<<endl;
}
Test::Test(const Test& t)
{
cout<<"调用复制构造函数"<<endl;
}
Test& Test::operator =(const Test& t)
{
cout<<"调用赋值构造函数"<<endl;
t1 = t.t1;
return *this;
}
void main()
{
Test t1;
Test t2 = t1;
Test t3;
t3 = t1;
}
输出如下:
分享到:
相关推荐
C++复制构造函数详解C++复制构造函数详解C++复制构造函数详解C++复制构造函数详解C++复制构造函数详解
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于: 通过使用另一个同类型的对象来初始化新创建的对象。 复制对象把它作为参数传递给...
C++复制(拷贝) 构造函数实验代码 每个类只有一个析构函数 和一个赋值函数 ,但可以有多个构造函数 (包含一个拷贝构造函数 ,其它的称为普通构造函数 )。对于任意一个类 A ,如果不想编写上述函数, C++ 编译器将...
初始化和赋值问题详解C++ 拷贝构造函数和赋值运算符详解C++中对构造函数和赋值运算符的复制和移动操作C++中复制构造函数和重载赋值操作符总结深入C++中构造函数、拷贝构造函数、赋值操作符、析构函数的调用过程总结...
1.什么是拷贝构造函数: 拷贝构造函数嘛,当然就是拷贝和构造了。(其实很多名字,只要静下心来想一想,就真的是顾名思义呀)拷贝又称复制,因此拷贝构造函数又称复制构造函数。百度百科上是这样说的:拷贝构造函数...
1、本文详细描述了C++语言拷贝构造函数的用法。 2、通过详细示例,让读者更直观地阅读,更清晰的理解。 3、示例代码可直接复制,编译后可直接运行。 4、根据示例以及运行结果,让读者加强记忆及理解。
本篇文章是对C++中构造函数、拷贝构造函数、赋值操作符、析构函数的调用过程进行了总结与分析,需要的朋友参考下
关于复制构造函数的简单介绍,可以看我以前写过的一篇文章C++复制控制之复制构造函数该文章中介绍了复制构造函数的定义、调用时机、也对编译器合成的复制构造函数行为做了简单说明。本文因需要会涉及到上文的一些...
C++ 拷贝构造函数 double*指针成员
这篇文章将对C++中复制构造函数和重载赋值操作符进行总结,包括以下内容: 1.复制构造函数和重载赋值操作符的定义; 2.复制构造函数和重载赋值操作符的调用时机; 3.复制构造函数和重载赋值操作符的实现要点; 4....
对C++的初学者来说很实用的拷贝构造函数学习框架
在C++中复制控制是一个比较重要的话题,主要包括复制构造函数、重载赋值操作符、析构函数这三部分,这三个函数是一致的,如果需要手动定义了其中了一个,那么另外的两个也需要定义,通常在存在指针或者前期相关操作...
深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝
友元函数和普通函数的定义一样;在类内必须将该普通函数声明为友元。 (2)友元函数不是成员函数。 不能通过对象来调用,而是直接调用;友元函数可以访问类的公有、受保护以及私有成员,但是必须通过对象、对象指针或者...
C++基础教程之指针拷贝详解 指针是编程人员的梦魇,对C语言的开发者是如此,对C++的开发者也是如此。特别是在C++中,如果不注意处理类中的指针,非常容易出问题。如果朋友们不相信可以看看下面的代码: class ...
4.4 含有构造函数和析构函数的stash 61 4.5 含有构造函数和析构函数的stack 63 4.6 集合初始化 65 4.7 缺省构造函数 67 4.8 小结 68 4.9 练习 68 第5章 函数重载与缺省参数 69 5.1 范围分解 69 5.1.1 用返回值重载 ...
4.拷贝构造函数是在用一个对象初始化另一个对象时被调用,系统缺省的拷贝构造函数的工作方法是( 把对象的每个数据成员的值都复制到新建立的对象中)。 5.用new申请某一个类的动态对象数组时,在该类中必须能够匹配到...
全书共分十八章,内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚...