16.C++STL 3(string类的模拟,深浅拷贝问题)
⭐本篇重点:string类的模拟,自己实现一个简单的string类
⭐本篇代码:c++学习/05.string类的学习 · 橘子真甜/c++-learning-of-yzc - 码云 - 开源中国 (gitee.com)
目录
一. 经典string类的模拟
1.1 深浅拷贝问题
1.2 使用深拷贝完成经典string类的模拟
a size函数
b 拷贝构造函数
c 赋值运算符重载
d operator[]重载
二. 现代写法的string类模拟
2.1 拷贝构造函数
2.2 赋值运算符重载
三. 下篇文章:STL中vector的使用
一. 经典string类的模拟
实现一个简单的string类,主要是实现string类的构造函数,析构函数,拷贝构造函数,赋值运算符重载。这个过程需要我们首先理解深浅拷贝的问题。
1.1 深浅拷贝问题
浅拷贝问题:如果我们初始化一个对象的时候,只是简单的将另一个对象的值赋值给这个对象。比如我们在堆上申请的空间。当我们销毁这两个对象的时候,由于它们指向同一个空间,这个空间就会被销毁两次。程序就会崩溃。
测试代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
using namespace std;
namespace yzc
{
class string
{
public:
string(const char* str = "")
:_str(new char[strlen(str) + 1])
{
cout << "调用构造函数" << endl;
strcpy(_str, str);
}
//拷贝构造
string(const string& str)
{
*this = str;
}
~string()
{
cout << "调用析构函数" << endl;
delete[] _str;
}
void print()
{
cout << _str << endl;
}
private:
char* _str;
};
}
int main()
{
yzc::string s1 = "123456";
yzc::string s2 = "abcdef";
s1.print();
s2.print();
return 0;
}
没有调用拷贝构造函数,可以正常输出。
运行结果如下:
如果我们使用拷贝构造函数
int main()
{
yzc::string s1 = "123456";
yzc::string s2(s1);
return 0;
}
分析代码可知,由于浅拷贝问题。输出一次调用构造函数和两次析构函数后程序崩溃
只有使用深拷贝,我们在拷贝构造函数里面重新申请一份空间,然后重新全部复制才行。
1.2 使用深拷贝完成经典string类的模拟
a size函数
为了完成深拷贝,我们需要定义一个size函数用于求_str的长度。加上const,因为函数内部并不会更改任何值
int size()const
{
return strlen(_str);
}
b 拷贝构造函数
与构造函数类似,我们重新开辟一份空间,然后使用strcpy进行拷贝
//拷贝构造,使用深拷贝完成拷贝
string(const string& s)
:_str(new char[s.size()])
{
strcpy(_str, s._str);
}
测试代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string>
using namespace std;
namespace yzc
{
class string
{
public:
string(const char* str = "")
:_str(new char[strlen(str) + 1]) //由于C语言字符串后面都带'\0',需要加上1
{
strcpy(_str, str);
}
拷贝构造
//string(const string& str)
//{
// *this = str;
//}
//拷贝构造,使用深拷贝完成拷贝
string(const string& s)
:_str(new char[s.size() + 1]) //一定要注意要加1,否则会越界访问
{
strcpy(_str, s._str);
}
~string()
{
delete[] _str;
_str = nullptr;
}
int size()const
{
return strlen(_str);
}
void print()
{
cout << _str << endl;
}
private:
char* _str;
};
}
int main()
{
yzc::string s1 = "123456";
yzc::string s2(s1);
s1.print();
s2.print();
return 0;
}
测试结果
c 赋值运算符重载
与拷贝构造函数一样,要注意深浅拷贝的问题
//赋值运算符重载
string& operator=(const string& s)
{
if (this != &s)//地址不同才进行赋值
{
delete[] _str; //释放原地址空间,防止内存泄漏
//定义中间变量tmp用于拷贝
char* tmp = new char[s.size() + 1]; //注意要 + 1
strcpy(tmp, s._str);
_str = tmp;
}
return *this; //返回当前对象,为了支持 a = b = c
}
测试:主函数代码如下
int main()
{
yzc::string s1 = "123456";
yzc::string s2 = "abcdef";
yzc::string s3 = s1;
yzc::string s4;
s4 = s2;
s3.print();
s4.print();
return 0;
}
测试结果:
d operator[]重载
我们还能重载[]这个操作符,方便我们遍历整个字符串
直接输入i,返回_str[i]即可(注意const)
char& operator[](size_t i)
{
assert(i < size());
return _str[i];
}
const char& operator[](size_t i)const
{
assert(i < size());
return _str[i];
}
测试代码:
int main()
{
yzc::string s1 = "159753468244";
for (int i = 0; i < s1.size(); i++)
{
cout << s1[i] << " ";
}
cout << endl;
return 0;
}
运行结果:
二. 现代写法的string类模拟
2.1 拷贝构造函数
为了提高代码的复用,我们在拷贝构造函数中使用构造函数去构造一个对象,然后交换当前对象和这个对象。
代码如下:
//现代版写法
string(const string& s)
:_str(nullptr)
{
string strTmp(s._str); //使用构造函数将s._str构造一个tmp
swap(_str, strTmp._str);//交换_str和tmp
}
交换后,strTmp由于是局部变量就直接被销毁了! 而我们创建的对象被保留
2.2 赋值运算符重载
我们使用传值方法传参,交换_str后我们成功创建的对象。且不会影响传入的对象
//现代版写法
string& operator=(string s) //不使用引用传参,而是传值
{
swap(_str, s._str);
return *this;
}
测试代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <assert.h>
using namespace std;
namespace yzc
{
class string
{
public:
string(const char* str = "")
:_str(new char[strlen(str) + 1]) //由于C语言字符串后面都带'\0',需要加上1
{
strcpy(_str, str);
}
拷贝构造,使用深拷贝完成拷贝
//string(const string& s)
// :_str(new char[s.size() + 1]) //注意要加1
//{
// strcpy(_str, s._str);
//}
//现代版写法
string(const string& s)
:_str(nullptr)
{
string strTmp(s._str); //使用构造函数将s._str构造一个tmp
swap(_str, strTmp._str);//交换_str和tmp
}
赋值运算符重载
//string& operator=(const string& s)
//{
// if (this != &s)//地址不同才进行赋值
// {
// delete[] _str; //释放原地址空间,防止内存泄漏
// //定义中间变量tmp用于拷贝
// char* tmp = new char[s.size() + 1];
// strcpy(tmp, s._str);
// _str = tmp;
//
// }
// return *this; //返回当前对象,为了支持 a = b = c
//}
//现代版写法
string& operator=(string s) //不使用引用传参,而是传值
{
swap(_str, s._str);
return *this;
}
~string()
{
delete[] _str;
_str = nullptr;
}
size_t size()const
{
return strlen(_str);
}
void print()
{
cout << _str << endl;
}
private:
char* _str;
};
}
int main()
{
yzc::string s1 = "123456";
yzc::string s2(s1);
yzc::string s3 = s1;
s1.print();
s2.print();
s3.print();
return 0;
}
运行结果: