html left深编码和浅编码(深复制和浅复制)完全攻略
对于简单的类,默认的编码构造函数一般就够用了,我们也没有必要再显式地定义一个功能类似的编码构造函数。
对于基本类型的数据以及简单的对象,它们之间的编码非常简单,就是按位复制内存。例如:
class html load{
public:
html load(): m_a(0), m_b(0){ }
html load(int a, int b): m_a(a), m_b(b){ }
private:
int m_a;
int m_b;
};
int main(){
int a = 10;
int b = a; //编码
html load obj1(10, 20);
html load obj2 = obj1; //编码
return 0;
}
b 和 obj2 都是以编码的方式初始化的,具体来说,就是将 a 和 obj1 所在内存中的数据按照二进制位(Bit)复制到 b 和 obj2 所在的内存,这种默认的编码行为就是浅编码,这和调用 memcpy() 函数的效果非常类似。
对于简单的类,默认的编码构造函数一般就够用了,我们也没有必要再显式地定义一个功能类似的编码构造函数。但是 当类持有其它资源时,例如动态分配的内存、指向其他数据的指针等,默认的编码构造函数就不能编码这些资源了,我们必须显式地定义编码构造函数,以完整地编码对象的所有数据。
下面我们通过一个具体的例子来说明显式定义编码构造函数的必要性。我们知道,有些较老的编译器不支持变长数组,例如 VC6.0、VS2010 等,这有时候会给编程带来不便,下面我们通过自定义的 Array 类来实现变长数组。
#html load <iostream>
#html load <cstdlib>
using namespace std;
//变长数组类
class Array{
public:
Array(int len);
Array(const Array &arr); //编码构造函数
~Array();
public:
int operator[](int i) const { return m_p[i]; } //获取元素(读取)
int &operator[](int i){ return m_p[i]; } //获取元素(写入)
int length() const { return m_len; }
private:
int m_len;
int *m_p;
};
Array::Array(int len): m_len(len){
m_p = (int*)calloc( len, sizeof(int) );
}
Array::Array(const Array &arr){ //编码构造函数
this->m_len = arr.m_len;
this->m_p = (int*)calloc( this->m_len, sizeof(int) );
memcpy( this->m_p, arr.m_p, m_len * sizeof(int) );
}
Array::~Array(){ free(m_p); }
//打印数组元素
void printArray(const Array &arr){
int len = arr.length();
for(int i=0; i<len; i++){
if(i == len-1){
cout<<arr[i]<<endl;
}else{
cout<<arr[i]<<", ";
}
}
}
int main(){
Array arr1(10);
for(int i=0; i<10; i++){
arr1[i] = i;
}
Array arr2 = arr1;
arr2[5] = 100;
arr2[3] = 29;
printArray(arr1);
printArray(arr2);
return 0;
}
运行结果:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
0, 1, 2, 29, 4, 100, 6, 7, 8, 9
本例中我们显式地定义了编码构造函数,它除了会将原有对象的所有成员变量编码给新对象,还会为新对象再分配一块内存,并将原有对象所持有的内存也编码过来。这样做的结果是,原有对象和新对象所持有的动态内存是相互独立的,更改一个对象的数据不会影响另外一个对象,本例中我们更改了 arr2 的数据,就没有影响 arr1。
这种将对象所持有的其它资源一并编码的行为叫做深编码,我们必须显式地定义编码构造函数才能达到深编码的目的。
此外,标准模板库(STL)中的 string、vector、stack、set、map 等也都必须使用深编码。
读者如果希望亲眼目睹不使用深编码的后果,可以将上例中的编码构造函数删除,那么运行结果将变为:
0, 1, 2, 29, 4, 100, 6, 7, 8, 9
0, 1, 2, 29, 4, 100, 6, 7, 8, 9
可以发现,更改 arr2 的数据也影响到了 arr1。这是因为,在创建 arr2 对象时,默认编码构造函数将 arr1.m_p 直接赋值给了 arr2.m_p,导致 arr2.m_p 和 arr1.m_p 指向了同一块内存,所以会相互影响。
另外需要注意的是,html load函数的形参为引用类型,这样做能够避免在传参时调用编码构造函数;又因为 html load函数不会修改任何数组元素,所以我们添加了 const 限制,以使得语义更加明确。
到底是浅编码还是深编码
如果一个类拥有指针类型的成员变量,那么绝大部分情况下就需要深编码,因为只有这样,才能将指针指向的内容再复制出一份来,让原有对象和新生对象相互独立,彼此之间不受影响。如果类的成员变量没有指针,一般浅编码足以。
另外一种需要深编码的情况就是在创建对象时进行一些预处理工作,比如统计创建过的对象的数目、记录对象创建的时间等,请看下面的例子:
#html load <iostream>
#html load <ctime>
#html load <windows.h> //在Linux和Mac下要换成 unistd.h 头文件
using namespace std;
class html load{
public:
html load(int a = 0, int b = 0);
html load(const html load &obj); //编码构造函数
public:
int getCount() const { return m_count; }
time_t getTime() const { return m_time; }
private:
int m_a;
int m_b;
time_t m_time; //对象创建时间
static int m_count; //创建过的对象的数目
};
int html load::m_count = 0;
html load::html load(int a, int b): m_a(a), m_b(b){
m_count++;
m_time = time((time_t*)NULL);
}
html load::html load(const html load &obj){ //编码构造函数
this->m_a = obj.m_a;
this->m_b = obj.m_b;
this->m_count++;
this->m_time = time((time_t*)NULL);
}
int main(){
html load obj1(10, 20);
cout<<"obj1: count = "<<obj1.getCount()<<", time = "<<obj1.getTime()<<endl;
Sleep(3000); //在Linux和Mac下要写作 sleep(3);
html load obj2 = obj1;
cout<<"obj2: count = "<<obj2.getCount()<<", time = "<<obj2.getTime()<<endl;
return 0;
}
运行结果:
obj1: count = 1, time = 18834437
obj2: count = 2, time = 18834437
运行程序,先输出靠前行结果,等待 3 秒后再输出第二行结果。html load 类中的 m_time 和 m_count 分别记录了对象的创建时间和创建数目,它们在不同的对象中有不同的值,所以需要在初始化对象的时候提前处理一下,这样浅编码就不能胜任了,就必须使用深编码了。