注意在类中有指针变量成员时,初始化的时候,一定要显示的给他分配动态内存;
指针变量成员一定要初始化,一定要显示的有构造函数(即动态分配内存),以及不是基本数据类型时,一定要有重载赋值函数
关于重载赋值函数和拷贝构造函数:
是否可以将视频里向量扩容代码中的:
for (int i = 0; i < _size; i++) _elem[i] = oldElem[i];
替代为:
memcpy(_elem, oldElem, _size * sizeof(T));
先给出解答:
如果T是基本数据类型,则第二种memcpy是可行的
但如果T是自己构造的类类型,则如果有=号重载,则第一种会进行深拷贝,
而第二种相当于位拷贝,是浅拷贝,当delete []oldElem时,_elem也会被释放,从而变成野指针
从看视频中的习题,遇到了上面这个问题,发现构造函数以及深拷贝,浅拷贝这里还有很大的问题,于是我上网去寻找答案,
下面是关于搜到的答案的汇总:
-
先是解决深拷贝和浅拷贝的问题:
关于浅拷贝的定义:
同类对象之间可以通过赋值运算符=
互相赋值。如果没有经过重载,=
的作用就是把左边的对象的每个成员变量都变得和右边的对象相等,即执行逐个字节拷贝的工作,这种拷贝叫作“浅拷贝”
而这种拷贝对于基本数据类型是完全不用担心的,但是遇到指针或者是自己构造的类类型时,就会遇到麻烦!
因为有的时候,两个对象相等,从实际应用的含义上来讲,指的并不应该是两个对象的每个字节都相同,而是有其他解释,这时就需要对=
进行重载。
/*-------- Copy.h ----------*/
#include <string>
#include <iostream>
/*
注意在类中有动态分配内存的成员时,在析构函数处,一定要进行delete;
大多数情况下,执行动态内存分配的的类都在构造函数里用 new 分配内存,然后在析构函数里用 delete 释放内存。
最初写这个类的时候当然不难做,你会记得最后对在所有构造函数里分配了内存的所有成员使用 delete。
然而,这个类经过维护、升级后,情况就会变得困难了,因为对类的代码进行修改的程序员不一定就是最早写这个类的人。
而增加一个指针成员意味着几乎都要进行下面的工作:
对类增加一个指针成员需要进行以下几个工作:
1、在每个构造函数里对指针进行初始化。对于一些构造函数,如果没有内存要分配给指针的话,
指针要被初始化为 0(即空指针)。
2、删除现有的内存,通过赋值操作符分配给指针新的内存。
3、在析构函数里删除指针
*/
using namespace std;
class Copy
{
private:
string *m_value;
public:
//构造函数
Copy();
Copy(const string *value); //传值对象的构造函数
//=号拷贝构造
Copy &operator=(const string value);
string &get() { return *m_value; };
//析构
~Copy();
};
//默认构造函数,一定要显示的初始化
Copy::Copy()
{
m_value = new string();
cout << "默认初始化" << endl;
}
Copy::Copy(const string *value)
{
m_value = new string(*value); //给m_value初始化
cout << "传参初始化" << endl;
}
Copy &Copy::operator=(const string value)
{
//首先删除原先的内存,防止出现野指针
delete m_value;
m_value = new string(value);
cout << "=号拷贝构造完成" << endl;
return *this;
}
Copy::~Copy()
{
delete m_value;
m_value = NULL;
cout << "delete" << endl;
system("pause");
}
/*--------- cpp -------------*/
#include <string>
#include "copy.h"
using namespace std;
int main()
{
Copy A;
return 0;
}
此时运行结果是:
int main()
{
Copy A;
Copy B;
B = "haha";
return 0;
}
int main()
{
Copy A;
A = "henhen";
Copy B;
B = "haha";
A = B;
return 0;
}
当执行这样的操作时,就会因为没有给此类类型进行=号重载,从而导致了删除无效指针的问题;
当加上重载之后,此时进行的是深拷贝;
Copy &Copy::operator=( Copy &value)
{
//首先删除原先的内存,防止出现野指针
delete m_value;
m_value = new string(value.get());
cout << "=号拷贝类类型构造完成" << endl;
return *this;
}
但是如果要是进行构造函数初始化:
Copy A(B);
则会出现问题,因为此时没有对此构造函数进行重载,从而会导致浅拷贝;从而在析构的时候就会出现问题;
这里有一点需要注意:
拷贝构造函数必须以引用的方式传递参数。这是因为,在值传递的方式传递给一个函数的时候,会调用拷贝构造函数生成函数的实参。如果拷贝构造函数的参数仍然是以值的方式,就会无限递归调用下去,直到函数的栈溢出;
Copy::Copy(Copy &value)
{
m_value = new string(value.get());
}
在这里说明一下,为什么拷贝构造函数一定要传引用,而不能传值呢!
如果在类中加入这样一个函数
void Copy::myTestFunc(Copy ex)
{
}
int main()
{
string a="haha";
string a="HAHA";
Copy aaa(&a);
Copy bbb(&b);
bbb = aaa;
Copy ccc = aaa;
bbb.myTestFunc(aaa);
return 0;
}
- constructor with argument // Copy aaa(&a);
- constructor with argument // Copy bbb(&b);
- assignment operator // bbb = aaa;
- copy constructor // Copy ccc = aaa;
- copy constructor // bbb.myTestFunc(aaa);
对于第1,第2,第3个毫不奇怪,但对于第4个由于ccc是新增对象,还没有初始化,此时就会调用Copy( Copy &value)这个构造函数进行初始化;而不是赋值函数;
对于第5个,当传进来的是值时,此时会执行Copy ex = aaa;因为ex没有进行初始化,ex又会调用Copy( Copy &value)这个构造函数;
于是,总结一下:
看第4个例子 copy constructor // Copy ccc = aaa;
由于ccc没有初始化,从而会调用构造函数进行初始化,如果传进的参数是值的话,则对于构造函数,就会有个形参ex,此时
相当于Copy ex=aaa;而ex又没有初始化,从而又会调用构造函数,又有个形参ex_plus,又会执行Copy ex_plus=aaa;从而无限的递归调用下去!这就很严重了。
(此处转自http://blog.csdn.net/tunsanty/article/details/4264738)
所以在构造函数时,传类类型引用是件大事!
下面来讲讲拷贝构造函数的事:
基本上有三个场合要用到拷贝构造函数:
- 对象作为函数的参数,以值传递的方式传给函数
- 对象作为函数的返回值,以值传递的方式从函数返回调用处
- 使用一个对象去初始化一个新建的对象
即有拷贝构造函数的调用一定会有新对象生成。这里还要强调一下上面所说的,拷贝构造函数在传递参数的时候,必须传递引用。like A(B);
Copy::string disp() { return *m_value; };
void Copy::f(Copy p)
{
cout << "enter f \n";
cout << p.disp() << endl;
}
Copy Copy::f1()
{
cout << "enter f \n";
Copy p;
cout << "next is return object of Person\n";
return p;
}
int main()
{
Copy p1;
cout<<"拷贝构造函数调用在函数形参是对象且值传递\n";
f(p1); //①
cout<<"拷贝构造函数调用在函数返回值是对象且值传递\n";
f1(); //②
return 0;
}
在这里,我们看一下①式, 由于p1是值传递,因此会有一个函数的形参p,此时Copy p=p1,于是,在传值进去的时候,p会调用拷贝构造函数; 它的作用域就在此函数里面,所以当函数结束时,它就会被析构,但p1不会被析构,还可以用; 再看②式,在函数里面创建了临时对象p,当返回p时,由于返回值是值,不是引用,从而此时会创建临时对象假设是ex,相当于Copy ex=p;
此时会调用拷贝构造函数,然后p被析构,然后临时对象ex也会被析构;
拷贝构造函数和赋值运算符的行为比较相似,都是将一个对象的值复制给另一个对象;但是其结果却有些不同,拷贝构造函数使用传入对象的值生成一个新的对象的实例,而赋值运算符是将对象的值复制给一个已经存在的实例。这种区别从两者的名字也可以很轻易的分辨出来,拷贝构造函数也是一种构造函数,那么它的功能就是创建一个新的对象实例;赋值运算符是执行某种运算,将一个对象的值复制给另一个对象(已经存在的)。调用的是拷贝构造函数还是赋值运算符,主要是看是否有新的对象实例产生。如果产生了新的对象实例,那调用的就是拷贝构造函数;如果没有,那就是对已有的对象赋值,调用的是赋值运算符。
深拷贝与浅拷贝
深拷贝和浅拷贝主要是针对类中的指针和动态分配的空间来说的,因为对于指针只是简单的值复制并不能分割开两个对象的关联,任何一个对象对该指针的操作都会影响到另一个对象。这时候就需要提供自定义的深拷贝的拷贝构造函数,消除这种影响。通常的原则是:
- 含有指针类型的成员或者有动态分配内存的成员都应该提供自定义的拷贝构造函数
- 在提供拷贝构造函数的同时,还应该考虑实现自定义的赋值运算符
对于拷贝构造函数的实现要确保以下几点:
- 对于值类型的成员进行值复制
- 对于指针和动态分配的空间,在拷贝中应重新分配内存空间
- 对于基类,要调用基类合适的拷贝方法,完成基类的拷贝
- 拷贝构造函数和赋值运算符的行为比较相似,却产生不同的结果;拷贝构造函数使用已有的对象创建一个新的对象,赋值运算符是将一个对象的值复制给另一个已存在的对象。区分是调用拷贝构造函数还是赋值运算符,主要是否有新的对象产生。
- 关于深拷贝和浅拷贝。当类有指针成员或有动态分配空间,都应实现自定义的拷贝构造函数。提供了拷贝构造函数,最后也实现赋值运算符。
此处转载自http://www.cnblogs.com/gaochaochao/p/8370762.html