在C++中,如果我们定义一个类,没有定义任何的构造函数,那么编译器会为我们提供两个特殊的构造函数:无参构造函数 和 拷贝构造函数
无参构造函数: 其实就是一个没有参数、函数体为空的构造函数
拷贝构造函数: 函数参数为 const class_name&,拷贝构造函数只是简单的进行成员变量的赋值
下边来看下边两个类test1和test2
test1中我们没有定义任何构造函数,实则编译器会默认提供一个无参构造函数和一个拷贝构造函数,这时候test1类中其实是有一个无参构造函数test1(){}和一个拷贝构造函数test1(const test1& obj){}
class test1
{
};
class test2
{
public:
test2(){}
test2(const test2& obj){}
};
对于无参构造函数,比较简单,下边我们重点来看看拷贝构造函数,上边说了,编译器提供的默认拷贝构造函数,能简单的进行对象成员的赋值,那么我们以下边的代码来验证验证:
#include <iostream>
#include <string>
using namespace std;
class test1
{
private:
int m_i;
int m_j;
public:
void setI(int i)
{
m_i = i;
}
void setJ(int j)
{
m_j = j;
}
int getI()
{
return m_i;
}
int getJ()
{
return m_j;
}
};
int main()
{
test1 t1;
t1.setI(12);
t1.setJ(34);
test1 t2 = t1; //这里会调用拷贝构造函数,由于我们没有定义任何构造函数,所以使用编译器提供的默认拷贝构造函数
cout << "t1.getI() = " << t1.getI() << endl;
cout << "t1.getJ() = " << t1.getJ() << endl;
cout << "t2.getI() = " << t2.getI() << endl;
cout << "t2.getJ() = " << t2.getJ() << endl;
system("pause");
}
编译输出:
从代码中我们看到,对于test1类,我们没有定义任何构造函数,所以编译器会为我们提供两个默认的构造函数,一个无参构造函数,一个拷贝构造函数,在执行到test1 t2 = t1 这条语句时,会调用拷贝构造函数,将t1的内容复制一份给t2,跟我们C语言中的一个变量赋值给另一个变量道理是一样的,这里由于我们没有定义拷贝构造函数,所以调用的是编译器提供的默认拷贝构造函数,进行成员m_i与m_j的值得赋值,从而使得t2中的成员变量与t1中的成员变量一致。
相信你对C++的拷贝构造函数已经有了一个初步的认识,但是在C++中,拷贝构造函数还分为深拷贝和浅拷贝
深拷贝:对象的逻辑状态相同
浅拷贝:对象的物理状态相同(即只是对对象成员进行简单的复制)
编译器为我们提供的只是浅拷贝
在讨论深拷贝前,我们先来看下边的一段代码:
#include <iostream>
#include <string>
using namespace std;
class test1
{
private:
int m_i;
int m_j;
int *m_p; //增加一个指针成员变量
public:
test1()
{
m_p = new int(100);
}
void setI(int i)
{
m_i = i;
}
void setJ(int j)
{
m_j = j;
}
int getI()
{
return m_i;
}
int getJ()
{
return m_j;
}
int* getP()
{
return m_p;
}
void freeP()
{
delete m_p;
}
};
int main()
{
test1 t1;
t1.setI(12);
t1.setJ(34);
test1 t2 = t1;
cout << "t1.getI() = " << t1.getI() << endl;
cout << "t1.getJ() = " << t1.getJ() << endl;
cout << "t1.getP() = " << t1.getP() << endl << endl;
cout << "t2.getI() = " << t2.getI() << endl;
cout << "t2.getJ() = " << t2.getJ() << endl;
cout << "t2.getP() = " << t2.getP() << endl;
t1.freeP(); //释放t1中的指针m_p
t2.freeP(); //释放t2中的指针m_p
system("pause");
}
编译运行一下:
可以看到,程序其实已经崩溃了,分析一下就知道是执行t1.freeP()和t2.freeP()这两个函数奔溃的,细心的朋友会发现,t1.getP()跟t2.getP()返回的值是一样的,也就是说t1和t2对象的成员指针变量m_p是一样,t1和t2执行freeP()函数时,导致同一个地址释放了两次,所以程序会奔溃,上边代码执行默认的拷贝构造函数后,其实对象的指针成员变量就如下图所示,指向的是同一片内存空间。
为了解决程序奔溃问题,我们首先来分析下上边的代码,前边我们说过,我们如果没有定义构造函数,那么编译器会默认提供一个拷贝构造函数,而提供的拷贝构造函数只是浅拷贝,也就是只能简单的进行对象成员的复制,所以在执行test1 t2 = t1时,只是简单的把t1中的m_p变量的值赋值给 t2中m_p变量,并没有分配额外的空间,这就导致了同一个内存地址会释放两次的原因。
有了以上的分析,我们就可以重写拷贝构造函数来解决这个问题,改写的代码如下,其中我们重新实现了拷贝构造函数:
#include <iostream>
#include <string>
using namespace std;
class test1
{
private:
int m_i;
int m_j;
int *m_p; //增加一个指针成员变量
public:
test1()
{
m_p = new int(100);
}
test1(const test1& obj) //重新实现拷贝构造函数
{
m_i = obj.m_i;
m_j = obj.m_j;
m_p = new int; //新申请内存空间
*m_p = *(obj.m_p); //将obj的m_p指向的内存空间的值赋值给m_p指向的新申请内存空间
}
void setI(int i)
{
m_i = i;
}
void setJ(int j)
{
m_j = j;
}
int getI()
{
return m_i;
}
int getJ()
{
return m_j;
}
int* getP()
{
return m_p;
}
int getPValue()
{
return *m_p;
}
void freeP()
{
delete m_p;
}
};
int main()
{
test1 t1;
t1.setI(12);
t1.setJ(34);
test1 t2 = t1;
cout << "t1.getI() = " << t1.getI() << endl;
cout << "t1.getJ() = " << t1.getJ() << endl;
cout << "t1.getP() = " << t1.getP() << endl;
cout << "t1.getPValue() = " << t1.getPValue() << endl << endl;
cout << "t2.getI() = " << t2.getI() << endl;
cout << "t2.getJ() = " << t2.getJ() << endl;
cout << "t2.getP() = " << t2.getP() << endl;
cout << "t2.getPValue() = " << t2.getPValue() << endl;
t1.freeP(); //释放t1中的指针m_p
t2.freeP(); //释放t2中的指针m_p
system("pause");
}
编译运行一下:
可以看到,t1和t2中的m_p指针变量的值已经不想同了,此时两个对象释放的就不是同一个内存空间了;同时它们的空间的值是相同的,都为100,这就是我们想要的,这就是我们所说的深拷贝。
C++中需要深拷贝的情况一般是有成员指代了系统资源,如动态使用内存空间、打开外部文件、使用网络端口等等,就需要深拷贝。
在C++中默认的潜规则是,如果自己要实现拷贝构造函数,必须要实现成深拷贝。
最后总结一下:
-C++编译器会默认提供构造函数
-无参构造函数用于定义对象的默认初始化状态
-拷贝构造函数在创建对象时拷贝对象的状态
-对象的拷贝有浅拷贝(物理状态相同)和深拷贝(逻辑状态相同)
-自己定义的拷贝构造函数,必须要实现深拷贝