string的模拟实现
1.简单实现string
我们先实现一个简单的string,只考虑资源管理深浅拷贝问题
暂且不考虑增删查改,增删查改版本在下面目录会有源代码
那么实现string先把基本的写了些,比如构造析构函数等等
因为库里面有个string所以这里有二种做法,一种是命名空间就是我下面那种,另一种把我们写的类把小写string改成大写String这样来区分来
代码
#pragma once
namespace str
{
// 先实现一个简单的string,只考虑资源管理深浅拷贝问题
// 暂且不考虑增删查改
class string
{
public:
string(const char* str)
:_str(new char[strlen(str) + 1])//这个加1是留给'\0'的
{
strcpy(_str, str);
}
~string()
{
if (_str)
{
delete[] _str;
}
}
private:
char* _str;
};
}
这上面有个小问题,就是在主函数记得包我们写的这个头文件,要不然strlen它会找不到,为什么包含了这个就会找到呢?
因为主函数里面保含了#include这些库,执行文件的时候会预处理,预处理里面有头文件展开,而前面的文件展开了,那么就有定义strlen了
没保含头文件的执行结果
图片
包含的
除了这个还有一个问题,如果没有加
#define _CRT_SECURE_NO_WARNINGS 这一串有的编译器是编译不过的,比如我的vs2022就会有下面图片的报错
这个报错其实是认为这个函数不安全所以报错,
而#define _CRT_SECURE_NO_WARNINGS是屏蔽这个报错
写完了析构和构造,那么我们就要实现string的基本用法了,比如用string创建一个字符串再打印出来,我们发现直接打印是打印不出来的
代码
#pragma once
namespace str
{
// 先实现一个简单的string,只考虑资源管理深浅拷贝问题
// 暂且不考虑增删查改
class string
{
public:
string(const char* str)
:_str(new char[strlen(str) + 1])//这个加1是留给'\0'的
{
strcpy(_str, str);
}
~string()
{
if (_str)
{
delete[] _str;
}
}
const char* c_str() const
{
return _str;
}
private:
char* _str;
};
}
打印使用方式
void test1()
{
str::string s1("hello world");
cout << s1.c_str() << endl;
}
那我们现在再做其他功能,简单修改字符串和打印每个字符用空格分割
代码
char& operator[](size_t pos)
{
assert(pos < strlen(_str));
return _str[pos];
}
size_t size()
{
return strlen(_str);
}
void test1()
{
str::string s1("hello world");
cout << s1.c_str() << endl;
s1[0] = 'x';
cout << s1.c_str() << endl;
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i] << " ";
}
}
打印结果
接下来我们复制字符串
代码
void test2()
{
str::string s1("hello world");
str::string s2(s1);
cout << s1.c_str() << endl;
cout << s2.c_str() << endl;
}
运行结果
可以看到打印结果出来了,但是报错了这是怎么回事?
我们来调试一下
可以看到s1和s2指向的是同一个位置,而这里有问题的是析构了二次,还有就是其中一个改变其他也会改变,而会造成这样的有个专属的名字叫浅拷贝
那么这时候我们就要用深拷贝了
深拷贝的意思是有一样大的空间一样的值,但是用的不是同一个空间
代码
//s2(s1) s1是s s2是this 指针
string(const string& s)
:_str(new char[strlen(s._str) + 1])//这个加1是留给'\0'的
{
strcpy(_str, s._str);
}
这样就不会浅拷贝的问题了
接下来来实现赋值
这里实现赋值一样有和之前一样的问题析构二次,还有就是内存问题就是内存太大或者不够越界,还有可能自己和自己赋值,自己和自己赋值就直接返回自己就好了
写法
先释放要被赋值的空间,拷贝赋值的空间大小给被赋值的空间,再把内容拷贝过去
代码
//s1=s3
string& operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
}
return *this;
}
不过这种代码有个问题就是,如果拷贝失败了怎么办?
因为它是先释放了再拷贝的
更好的版本
//s1=s3
string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[strlen(s._str) + 1];
strcpy(tmp,s._str);
delete[] _str;
_str = tmp;
}
return *this;
}
使用场景代码
void test3()
{
str::string s1("hello wprld");
str::string s2(s1);
str::string s3("111111");
s1 = s3;
cout << s1.c_str() << endl;
}
2.考虑增删查改的string模拟实现
因为这里是要考虑增删查改的了,而插入肯定不能一个一个插入,所以
成员要加上size,capacity之类的
因为有些部分比较简单所以就直接放上代码(代码会有注释的)如果不知道里面的函数有什么用可以去翻翻我上期写的string讲解也可以在评论区问我,或者不知道函数怎么实现的可以在评论区问我
string模拟实现代码
#pragma once
#include<assert.h>
namespace str
{
// 先实现一个简单的string,只考虑资源管理深浅拷贝问题
// 暂且不考虑增删查改
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin() const
{
return _str;
}
iterator end() const
{
return _str + _size;
}
iterator begin()
{
return _str;
}
iterator end()
{
return _str+_size;
}
string(const char* str="")//""这个就是默认'\0'
:_size(strlen(str))
,_capacity(_size)
{
_str = new char[_capacity +