String类
String类的构造函数、拷贝构造函数、赋值函数和析构函数的在面试和笔试中是经常见到的,如果能准确写出来,那就具备c++基本功的一大半了。
在这个类中包括了指针类成员变量_data,当类中包括指针类成员变量时,一定要重载其拷贝构造函数、赋值函数和析构函数,这既是对C++程序员的基本要求,也是《Effective C++》中特别强调的条款。
深拷贝和浅拷贝的区别
(1)简单的来说就是,在有指针的情况下,浅拷贝只是增加了一个指针指向已经存在的内存,而深拷贝就是增加一个指针并且申请一个新的内存,使这个增加的指针指向这个新的内存,采用深拷贝的情况下,释放内存的时候就不会出现在浅拷贝时重复释放同一内存的错误!
(2)浅拷贝 只拷贝指针,深拷贝就是拷贝他的值,重新生成的对像。就像是浅拷贝就是你的影子,深拷贝是你的克隆人,你没了影子也就没了,但是克隆人还活着。
以下为深拷贝的正确写法
#include<iostream>
using namespace std;
//String类——管理字符串——深拷贝—简洁版(高级版)
class String
{
public:
String(const char* pStr = "")//构造函数
{
if (NULL == pStr)
{
_pStr = "";
}
_pStr = new char[strlen(pStr) + 1];
strcpy(_pStr, pStr);
cout << "String()构造 " << endl;
}
String(const String& s) //拷贝构造函数
:_pStr(NULL)
{
String strTmp(s._pStr);
swap(_pStr, strTmp._pStr);
cout << "String()拷贝构造 " << endl;
}
1、第一种形式——更好
//String& operator=(const String& s)//赋值操作符重载
//{
// if (this != &s)
// {
// cout << "String() = 赋值 " << endl;
// /*String strTmp(s);*/ //直接调拷贝构造
// String strTmp(s._pStr); //直接调构造函数
// swap(_pStr, strTmp._pStr);
// }
// return *this;
//}
2、第二种形式
//String& operator=(const String& s)//赋值操作符重载 ——s就是外部实参的别名
//{
// String strTmp(s);
// swap(_pStr, strTmp._pStr);
// cout << "String() = 赋值 " << endl;
// return *this;
//}
//3、第三种方式
String& operator=(const String& s)//赋值操作符重载
{
if (this != &s)
{
cout << "String() = 赋值 " << endl;
char* pStr = new char[strlen(s. _pStr) + 1];
strcpy(pStr, s._pStr);
delete _pStr; //释放原空间
_pStr = pStr; //释放新空间
}
return *this;
}
4、第四种方式
//String& operator=(String s)
//{
// swap(_pStr, s._pStr);
// cout << "String() = 赋值 " << endl;
// return *this;
//}
~String() //析构函数
{
if (_pStr)
{
cout << "~String() " << endl;
delete[] _pStr;
}
}
private:
char* _pStr;
};
void test()
{
String s1("hello");
String s2(s1);//测试拷贝构造函数
String s3;
s3 = s2; //测试赋值运算符重载
}
int main()
{
test();
return 0;
}
更确切的说,上面的代码的打印只是为了更好的进行测试
测试结果 :
对结果进行分析
String s1("hello"); //——————————————构造函数s1
String s2(s1);//测试拷贝构造函数 ————构造一个临时对象,然后拷贝构造s2
String s3; // ——————————————构造函数s3
s3 = s2; //测试赋值运算符重载————————————s1给s3赋值
最后出了函数作用域就调用析构函数进行销毁,先构造的对象后销毁
在以后的面试和笔试中用这种深拷贝是完全OK的
那么在看了高级版的之后,再看一下普通版的吧,支语区别就自己看吧
//String类——管理字符串——深拷贝—普通版
class String
{
public:
String(const char* pStr = "")//构造函数
{
if (NULL == pStr)
{
_pStr = new char[1];
*_pStr = '\0';
}
else
{
_pStr = new char[strlen(pStr) + 1];
strcpy(_pStr, pStr);
}
cout << "String()构造 " << endl;
}
String(const String& s) //拷贝构造函数
:_pStr(new char[strlen(s._pStr) + 1])
{
cout << "String()拷贝构造 " << endl;
strcpy(_pStr, s._pStr);
}
String& operator=(const String& s)//赋值操作符重载
{
if (this != &s)
{
cout << "String& =()赋值 " << endl;
//———1、这种更好 如果开辟失败,对原来的对象不会造成影响
char* pTmp = new char[strlen(s._pStr) + 1];//申请新空间
strcpy(pTmp, s._pStr);//拷贝元素
delete[] _pStr;//释放旧空间
_pStr = pTmp;//使用新空间
//————2、一开始释放当前空间,如果开辟失败,以前的空间也不存在了
/*delete[] _pStr;
_pStr = new char[strlen(s._pStr) + 1];
strcpy(_pStr, s._pStr);*/
}
return *this;
}
~String() //析构函数
{
if (_pStr)
{
cout << "~String()析构 " << endl;
delete[] _pStr;
_pStr = NULL;
}
}
private:
char* _pStr;
};
void Test()
{
String s1("hello");
String s2(s1);
String s3;
s3 = s1;
}
int main()
{
Test();
return 0;
}
调试结果如下
对以上结果图进行分析
String s1("hello"); //——————构造函数s1
String s2(s1);//————————直接拷贝构造s2(在前面拷贝构造函数中没有利用临时对象)
String s3;//————————构造函数s3
s3 = s1;//————————将s1赋值给s3
出了函数之后调用析构函数,一共创建了3个对象,进行3次析构
至于浅拷贝在这里就不说了,在面试和笔试中只要掌握正确的就可以啦