C++中一般创建对象,拷贝或赋值的方式有构造函数,拷贝构造函数,赋值函数这三种方法。下面就详细比较下三者之间的区别以及它们的具体实现
1.构造函数
构造函数是一种特殊的类成员函数,是当创建一个类的对象时,它被调用来对类的数据成员进行初始化和分配内存。(构造函数的命名必须和类名完全相同)
首先说一下一个C++的空类,编译器会加入哪些默认的成员函数
·默认构造函数和拷贝构造函数
·析构函数
·赋值函数(赋值运算符)
·取值函数
**即使程序没定义任何成员,编译器也会插入以上的函数!
构造函数可以被重载,可以多个,可以带参数;
析构函数只有一个,不能被重载,不带参数
而默认构造函数没有参数,它什么也不做。当没有重载无参构造函数时,
A a就是通过默认构造函数来创建一个对象
2.拷贝构造函数
拷贝构造函数是C++独有的,它是一种特殊的构造函数,用基于同一类的一个对象构造和初始化另一个对象。
当没有重载拷贝构造函数时,通过默认拷贝构造函数来创建一个对象
A a;
A b(a);
A b=a; 都是拷贝构造函数来创建对象b
强调:这里b对象是不存在的,是用a 对象来构造和初始化b的!!
先说下什么时候拷贝构造函数会被调用:
在C++中,3种对象需要复制,此时拷贝构造函数会被调用
1)一个对象以值传递的方式传入函数体
2)一个对象以值传递的方式从函数返回
3)一个对象需要通过另一个对象进行初始化
系统提供的默认拷贝构造函数工作方式是内存拷贝,也就是浅拷贝。这种方法如果对象中用到了需要手动释放的对象(如指针),则会出现问题。
下面说说深拷贝与浅拷贝:
浅拷贝:如果复制的对象中引用了一个外部内容(例如分配在堆上的数据),那么在复制这个对象的时候,让新旧两个对象指向同一个外部内容,就是浅拷贝。(指针虽然复制了,但所指向的空间内容并没有复制,而是由两个对象共用,两个对象不独立,删除空间存在)浅拷贝复制了一份对象,只复制了对象的本身
深拷贝:如果在复制这个对象的时候为新对象制作了外部对象的独立复制,就是深拷贝。深拷贝,把空间也拷贝了一份
默认的浅拷贝的情况:
class cperson { public : int *a;//当a是整型变量时就不会发生浅拷贝问题 public : cperson(const cperson &aa) { this->a=aa.a; } ~cperson() { delete a; a=NULL; } };
cperson ps(ps1);//会出现错误,程序会崩溃
原因就是浅拷贝,只复制了对象本身的内容,指针成员所指向的空间是没有拷贝的,会出现两个对象使用一个空间从而导致在释放空间的时候一个空间被释放两次
解决的方法:
1.使用深拷贝,如下例子:
class cperson
{
public :
int *a;
public :
cperson(const cperson &aa)
{
this->a=new int (*(aa.a));//深拷贝
}
~cperson()
{
delete a;
a=NULL;
}
};
2.不让它执行拷贝构造,即对象的函数传参时不要用值传递,要用引用如下面的例子
void qq(cperson ps1)
{
}
cperson ps;
qq(ps);//发生了浅拷贝,程序错误
用引用代替后
void qq(cperson &ps1)
{
}
cperson ps;
qq(ps);//程序正确
特别要注意的是下面这种情况:
cperson qq(cperson &ps1) {
cperson ps2; return ps2; }
原因是为了保证能把ps2的值拿出去,ps2是临时对象,要想把它传出去就把它的值复制出去,此时又发生浅拷贝了。可以用返回一个引用解决。
cperson &qq(cperson &ps1)
{
cperson ps2;
return ps2;
}
但实际上尽量不要返回局部对象。
3.赋值函数
当一个类的对象向该类的另一个对象赋值时,就会用到该类的赋值函数。
当没有重载赋值函数(赋值运算符)时,通过默认赋值函数来进行赋值操作
A a;
A b;
b=a;
强调:这里a,b对象是已经存在的,是用a 对象来赋值给b的!!
赋值运算的重载声明如下:
A& operator = (const A& other),我们先来看一下下面的例子
class cperson
{
public :int *a;
public :
cperson()
{
a=new int(100);
}
cperson & operator=(const cperson&ps)
{
return *this;
}
~cperson()
{
delete a;
a=NULL;
};
cperson ps;
cperson ps1;
ps1=ps;
该程序会崩溃,原因是对象中含有指针,当用一个对象给另一个对象赋值的时候,又出现了两个指针使用同一块地址空间的问题,那么如何解决呢?
我们可以通过先删除原来指针的空间,再为被赋值的对象中的指针重新分配空间,如下述代码:
class cperson
{
public :int *a;
public :
cperson()
{
a=new int(100);
}
cperson & operator=(const cperson&ps)
{
delete this->a;
this->a=new int (*(ps.a));
return *this;
}
~cperson()
{
delete a;
a=NULL;
};
cperson ps;
cperson ps1;
ps1=ps;
通常大家会对拷贝构造函数和赋值函数混淆,这儿仔细比较两者的区别:
1)拷贝构造函数是一个对象初始化一块内存区域,这块内存就是新对象的内存区,而赋值函数是对于一个已经被初始化的对象来进行赋值操作。
2)一般来说在数据成员包含指针对象的时候,需要考虑两种不同的处理需求:一种是复制指针对象,另一种是引用指针对象。拷贝构造函数大多数情况下是复制,而赋值函数是引用对象
3)实现不一样。拷贝构造函数首先是一个构造函数,它调用时候是通过参数的对象初始化产生一个对象。
赋值函数则是把一个新的对象赋值给一个原有的对象,所以如果原来的对象中有内存分配要先把内存释放掉,而且还要检察一下两个对象是不是同一个对象,如果是,不做任何操作,直接返回。
!!!如果不想写拷贝构造函数和赋值函数,又不允许别人使用编译器生成的缺省函数,最简单的办法是将拷贝构造函数和赋值函数声明为私有函数,不用编写代码。:
所以如果类定义中有指针或引用变量或对象,为了避免潜在错误,最好重载拷贝构造函数和赋值函数。
一句话记住三者:对象不存在,且没用别的对象来初始化,就是调用了构造函数;
对象不存在,且用别的对象来初始化,就是拷贝构造函数(上面说了三种用它的情况!)
对象存在,用别的对象来给它赋值,就是赋值函数。