目录
kmp详解可移步至:KMP算法详解(一眼看穿next[ ]数组)
准备
为了自己创建的string类不和库当中的发生冲突,可以自己创建一个命名空间
namespace test
{
}
string需要三个参数,一个存放字符串(char* _str),一个存放大小(size_t _size),一个存放容量(size_t _capacity)
//自定义一个命名空间,防止与stl中的string同
namespace test
{
class string
{
public:
//构造函数
string()
{
}
//析构函数
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
构造函数
构造函数需要做的就是对成员进行初始化,由于字符串是常量,类型为const char*,所以接收的时候也需要用const char*来接收,但这里不能简单的直接将str给_str,因为str是存放在常量区的,如果直接将_str=str,会将str在常量区的地址也拷贝过去,会导致之后无法修改string的内容,所以需要给_str分配一个空间,然后再将str的值全部拷贝过去,在分配空间的时候要多分配一个空间给'\0'
string(const char* str)
:_size(strlen(str))
,_capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
为了方便测试函数是否正确,提供一个c_str函数来获取对象中的字符串
c_str
官方解释:
函数类型是const char*因为这只是一个提取对象内容的函数,为了防止权限放大,因为某些原因导致指针被修改就麻烦大了
返回一个指针包括'\0'的字符串
const char* c_str() const
{
//添加const防止修改指针的内容
return _str;
}
测试一下构造函数,传一个hello world
通过监视窗口可以看到是拷贝成功了的
运行结果:
再提供两个接口来获取size和capacity
size
官方解释:
首先返回类型为size_t,大小肯定不会为负数,用const修饰
返回的是字符串的实际长度,不包括'\0',也不是容量的大小
size_t size() const
{
return _size;
}
capacity
官方解释:
和size差不多,返回的容量不包括'\0'
size_t capacity() const
{
return _capacity;
}
运行结果:
运行结果是正确的,但这里存在两个构造函数,第一种是默认的
string()
{
}
第二种就是我们自主实现的,其实这里可以简化一下,将两种合并为一种,在传的字符串为空的时候就会调用第一种构造函数,空的话字符串内容就是'\0',_capacity=0,_size=0,可以直接将第二种构造函数的参数给个缺省值,如果不填就默认是""或者"\0",这两种是等效的,第一种也是直接填一个'\0'上去,所以构造函数也可以写成:
string(const char* str="")
:_size(strlen(str))
,_capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
或者:
string(const char* str="\0")
:_size(strlen(str))
,_capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}
析构函数
成员变量中_str是动态开辟出来的,所以需要对内存进行delete释放,释放后再将其指向nullptr空
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
拷贝构造
思路1:
对_str重新分配空间,然后利用strcpy函数来讲str中的字符串全部拷贝到_str中
string(const string& str)
:_size(str._size)
,_capacity(str._capacity)
{
_str = new char[_capacity + 1];
strcpy(_str, str._str);
}
测试:
运行结果:
思路2:
创建一个临时变量tmp来拷贝str,再通过swap函数来交换,因为tmp是临时变量,出了函数作用域就会调用析构函数来销毁tmp,所以在tmp和str交换之后,因为str的原地址需要被销毁,这里就不需要手动销毁了,我们将str的地址给到了tmp,出了函数作用域之后调用析构函数就将其销毁了。
string(const string& str)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(str._str);
swap(tmp);
}
swap
全局的那个swap对于内置类型交换很快,但对于自定义类型会比较慢了,它会掉用三次构造函数,所以可以自己实现一个swap函数
void swap(string& str)
{
string tmp(str._str);
std::swap(_str, tmp._str);
std::swap(_size, tmp._size);
std::swap(_capacity, tmp._capacity);
}
operator[ ]
对操作符[ ]进行重载,可以通过[ ]来获取字符
官方解释:
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
迭代器iterator的实现
iterator实质上就是一个类似指针的东西但又不是指针,但是在string类中可以将其当作指针,所以对iterator进行重命名即可