作者:华清远见讲师
说道c++,大家第一印象就是面向对象这四个字。当我们把一个抽象的类描述完毕,该有的功能都有的时候,接下来要做的事情就是去把这个类实例化成对象,换成人话就是创建一个对象。这个对象的类型 就是用于实例化这个对象的基类的类型。举个栗子,在c语言中,我们想要定义一个整型变量,首先要写出它的基类型int,然后写出你想给出这个变量的名字int a=888;此时一个你想象中的整型变量就出来了。内存中就有一个大小为4个字节的空间,名字叫做a的家伙。它的值是888。然后我们就可以在这个变量的生存周期里面使用它了。
一个对象也可以这么理解,假如有类class T ,则我们可以利用该类去定义一个对象,T a,你看 基类型此时叫T ,变量名叫a。是不是和定义一个整型变量差不多?既然如此,那么是不是整型变量的赋值操作也同样可以用在对象的赋值呢?
首先来看下整型变量的赋值:
int a = 10;
int b = a;
好了,就是这么简单明了。把a变量所对应内存空间中的那个888常量复制到变量b所在的内存空间中。然后我们再累看下对象的赋值:
首先有个类 class Meizi,妹子类。嗯,没错,程序员找女朋友都很困难,所以只能靠C++去虚拟出来。描述这个妹子的方法和属性我就不多说了,大家的想象力反正都很丰富,各种size自己选择。这时,需要将这个具体的妹子创造出来,因此有了以下的代码
class Meizi
{
public:
Meizi(int m_weight, float m_high, int m_age);
~Meizi();
void show()
{
cout << "this girl " << weight << "kg," << high << "cm,age" << age << endl;
}
private:
int weight;
float high;
int age;
};
Meizi::Meizi(int m_weight, float m_high, int m_age)
{
weight = m_weight;
high = m_high;
age = m_age;
}
Meizi::~Meizi()
{
}
int main()
{
Meizi Aoi_sola(45,155.4,33);
Aoi_sola.show();
return 0;
}
输出为
可以看到一个对象中的结构比一个单纯的整型变量要复杂,存在有各种成员变量。为了实现将一个类实例化成为一个对象,我们都知道会用到一种叫做构造函数的东西,把该分配的资源给分配了。现在想要用这样一个颇为复杂的对象去给另一个对象赋值的话,能否像之前整型变量赋值一样呢?现在,就用这个刚刚出炉的妹子复制成另一个妹子,主程序改成这个样子
int main()
{
Meizi Aoi_sola(45,155.4,33);
Aoi_sola.show();
Meizi Yoshizawa_Akiho = Aoi_sola;
Yoshizawa_Akiho.show();
return 0;
}
结果是
居然成功了,我们用了一个妹子去创造出了另一个。这个里面我们将原先那个Aoi_sola对象的内容完完全全复制给了新对象Yoshizawa_Akiho,那么在实现的过程中,我们的c++编译器会调用一个看不见的东西,他叫做默认拷贝构造函数。这个函数为新妹子Yoshizawa_Akiho分配了内存空间并完成了与妹子Aoi_sola的复制过程,克隆人就这么出来了。
拷贝构造函数是怎么工作的?怎么就能分配内存且复制成员了呢?向下看
class Meizi
{
public:
Meizi(int m_weight, float m_high, int m_age);
Meizi(Meizi &M);
~Meizi();
void show()
{
cout << "this girl " << weight << "kg," << high << "cm,age" << age << endl;
}
private:
int weight;
float high;
int age;
};
Meizi::Meizi(int m_weight, float m_high, int m_age)
{
weight = m_weight;
high = m_high;
age = m_age;
}
Meizi::Meizi(Meizi &M)//<-----看这里看这里
{
weight = M.weight;
high = M.high;
age = M.age;
cout << "调用拷贝构造" << endl;
}
Meizi::~Meizi()
{
}
int main()
{
Meizi Aoi_sola(45,155.4,33);
Aoi_sola.show();
Meizi Yoshizawa_Akiho = Aoi_sola;
Yoshizawa_Akiho.show();
return 0;
}
然后我们运行下
看到了么,在去创建新对象Yoshizawa_Akiho的时候调用了我们自己定义的一个函数,这个函数和类名同名,没有返回值类型,参数是该类的一个引用。我们不用显式的调用它,因为在用一个已经初始化过了的自定义类类型对象去初始化一个新对象的时候拷贝构造函数会被自动的调用,当类的对象需要拷贝时,拷贝构造函数将会被调用。以下情况都会调用拷贝构造函数:
(1)一个对象以值传递的方式传入函数体
(2)一个对象以值传递的方式从函数返回
(3)一个对象需要通过另外一个对象进行初始化。
如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝。位拷贝又称浅拷贝。
浅浅的拷贝一下,就能将一个新对象(妹子)创建出来,方便又快捷。想想还有些小激动呢。
没错,接下来我要说可是,在某些状况下,类内成员变量需要动态开辟堆内存,如果实行浅拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。不信?我们来试试看,还是这两个妹子,Aoi_sola和Yoshizawa_Akiho,
class Meizi
{
public:
Meizi(int m_weight, float m_high, int m_age,char *home);
~Meizi();
void show()
{
cout << "this girl " << weight << "kg," << high << "cm,age" << age <<"home"<
}
private:
int weight;
float high;
int age;
char *home;
};
Meizi::Meizi(int m_weight, float m_high, int m_age,char *m_home)
{
weight = m_weight;
high = m_high;
age = m_age;
home = new char[20];
strcpy(home,m_home);
}
Meizi::~Meizi()
{
}
int main()
{
Meizi Aoi_sola(45,155.4,33,"Tokyo");
Aoi_sola.show();
Meizi Yoshizawa_Akiho = Aoi_sola;
Yoshizawa_Akiho.show();
return 0;
}
新添加了妹子的家乡成员变量 home,是一个字符串。之后我们采取默认拷贝构造函数来初始化新对象,结果:
错了。
原因在于构造函数中我们利用malloc动态开辟了一段堆空间,此时对象Aoi_sola的成员变量指针home指向了该空间,利用默认构造函数初始化新对象Yoshizawa_Akiho,这个时候你知道是复制操作,因此Yoshizawa_Akiho对象的成员变量home指针也指向的和原先对象相同的地方,在析构的时候,我们有个好习惯,手动分配的资源要手动释放,结果我们将两个对象析构的时候调用了两次析构函数也就是调用了两次delete,也就是将同一块内存释放了两次。结果当然出错了。针对这种情况,我们就需要深拷贝构造函数,多深呢?请看代码
class Meizi
{
public:
Meizi(int m_weight, float m_high, int m_age,char *home);
Meizi(Meizi &M) //这里就是我们自己定义的拷贝构造函数
{
weight = M.weight;
high = M.high;
age = M.age;
home = new char[20];
if (home != NULL)
{
strcpy(home,M.home);
}
cout << "copy instructor" << endl;
//深刻的拷贝构造了一下
}
~Meizi();
void show()
{
cout << "this girl " << weight << "kg," << high << "cm,age" << age << endl;
}
private:
int weight;
float high;
int age;
char *home;
};
Meizi::Meizi(int m_weight, float m_high, int m_age,char *m_home)
{
weight = m_weight;
high = m_high;
age = m_age;
home = new char[20];
strcpy(home,m_home);
}
Meizi::~Meizi()
{
delete home;
}
int main()
{
Meizi Aoi_sola(45,155.4,33,"Tokyo");
Aoi_sola.show();
Meizi Yoshizawa_Akiho = Aoi_sola;
Yoshizawa_Akiho.show();
return 0;
}
结果:
所以原先的对象去创建新对象的时候会调用我们自己定义的拷贝构造函数,在自定义拷贝构造函数中我们自己手动分配了内存空间。进而实现的拷贝构造,叫做深拷贝。
那么问题来了,这两种拷贝构造我应该怎么选呢?什么时候用浅拷贝什么时候用深拷贝?总的来说还是要根据类的实现来判断该用浅拷贝还是深拷贝。如果需要拷贝这个对象引用的对象,则是深拷贝,否则是浅拷贝。