文章目录
成员变量
string的原理和C语言的字符串数组类似,所以我们可以把string当成一个顺序表去实现,那么成员变量就得有一块可动态开辟的空间,要有记录数据长度的变量,还要有记录空间容量大小的变量
private:
char* _str;
size_t _size;
size_t _capacity;
这里要强调一个点,因为string是一个字符串,所以要以’\0’结尾,我这里的_size是指字符串的有效字符个数并不包含’\0’所以在后面开辟空间的时候一定要记住给’\0’留一个空间。
构造、拷贝构造、析构
既然是模拟实现一个类,那这个类的最基本的构造函数是必不可少的了。根据官方提供的几种方法里,我就挑几个比较常见的出来实现。
首先不管是哪一种构造方法都必须先开辟好空间,要注意如果根据我这样size指的是有效字符长度一定要记住开辟空间要开比size多一个。拷贝构造的原理和默认构造都是一样的,开辟好空间后直接使用strcpy拷贝赋值就好。
mystring(const char* str = "") {
//如果传过来的是一个空串,那strlen就会是0
//不过要考虑'\0',所以开辟的空间要+1
//构造完之后有效长度就得等于strlen的长度
_size = strlen(str);
_capacity = strlen(str);
_str = new char[_capacity + 1];
strcpy(_str, str);
}
mystring(const mystring& s) {
_str = new char[s._capacity + 1];
_capacity = s._capacity;
_size = s._size;
strcpy(_str, s._str);
}
除了这两个常见的,还有一种直接使用 = 去进行赋值初始化的,所以要实现这个 = 就得使用到运算符重载了。其中的原理都是一样的,但是要注意这时候我们要有返回值才能接受得到,使用引用返回效率会更优
mystring& operator=(const mystring& s) {
//判断两个是否是同一个地址值
if (this != &s) {
//利用一个临时的变量先存储
char* str = new char[s._capacity + 1];
strcpy(str, s._str);
//注意如果不先把之前的空间释放可能会浪费空间
delete[] _str;
//将临时的变量赋值
_str = str;
//这两个成员变量每一次都要记得随时更新
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
因为我们是开辟了空间的,所以这种类使用完之后记得释放空间,防止内存泄漏
~mystring() {
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
迭代器的begin()、end()
因为迭代器的实现有很多种方式,这里我就采用指针的方式去实现。begin()是获取首元素的地址,end()是获取尾元素的地址也就是’\0’的地址。字符的地址是char* 类型,所以可以先将其命名成迭代器的名字
//迭代器第一种方式,指针
typedef char* iterator;
//获取begin
iterator begin() const {
return _str;
}
//获取end
iterator end() const {
//尾元素就只需要加上有效元素个数就行了
return _str + _size;
}
实现 [] 可读可写
如果想要像数组一样去随机访问,那么这个 [] 运算符就得去重载它。获取的是一个char类型字符,所以只需要返回成员变量里的数组指定的元素即可。官方库里有const版本,也是一样的,加上即可
//[]实现可读可写 用引用返回
char& operator[](size_t pos) {
assert(pos < _size);
return _str[pos];
}
char& operator[](size_t pos) const {
assert(pos < _size);
return _str[pos];
}
reserve实现扩容
因为在类里面还会有插入字符或字符串的功能,既然涉及插入就避免不了空间不够的情况。因为C++使用new没有像C语言里面的realloc一样可以追加,那我们就只能重新开辟一块空间赋值后,再把原空间释放最后赋值就行。
//实现扩容
void reserve(size_t n) {
//开辟新空间
char* str = new char[n + 1];
strcpy(str, _str);
//释放原有空间
delete[] _str;
_str = str;
//注意容量记得改变
_capacity = n;
}
插入字符或字符串
单单尾插一个字符或字符串很容易实现,只需要确保空间足够拷贝进去最后补上’\0’即可。扩容的话,我这里设计的是插入字符空间不够就扩容2倍,字符串不够就按数扩容
//实现push_back插入字符
void push_buck(char ch) {
//判断是否需要扩容
if (_size == _capacity) {
int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity * 2);
}
_str[_size++] = ch;
//记住要补上'\0'
_str[_size] = '\0';
}
//实现插入一个字符串
void append(const char* s) {
//判断是否需要扩容
if (_size + strlen(s) > _capacity) {
reserve(_capacity + strlen(s));
}
//可以选择一个字符的放
/*while (*s != '\0') {
_str[_size++] = *s;
s++;
}
_str[_size] = '\0';*/
//也可以选择库函数直接拷贝
strcpy(_str + _size, s);
_size += strlen(s);
}
但是一般情况我们想要插入字符或者字符串都不会直接去调用这两个函数,而是直接使用运算符 += ,为了方便我们就可以将运算符+=重载一下。重载这个运算符我们就可以使用调用这两个函数实现即可
mystring& operator+=(char ch) {
push_buck(ch);
return *this;
}
mystring& operator+=(const char* str){
append(str);
return *this;
}
mystring& operator+=(const mystring& s) {
append(s.c_str());
return *this;
}
上边的那个c_str() 这个函数就是返回C语言形式的字符串,实现的方法更简单了,返回我们的成员变量指针即可
//c_str:返回C形式的字符串
const char* c_str() const{
return _str;
}
clear、resize、swap
清空所有的内容这个思路非常简单,只需要将首元素改为’\0’即可,又或者可以将所有的元素都改为’\0’,总之内容都不会再显示。清空之后也要记住将_size改为0,因为都没有有效字符了
//实现清空内容
void clear() {
/*for (int i = 0; i < _size; i++) {
_str[i] = '\0';
}*/
_str[0] = '\0';
_size = 0;
}
调整字符串大小也就是说,如果我们想把它变小,就直接加上个’\0’,想变长,变长的部分补上我们想要的字符就好
//实现调整字符串大小
void resize(size_t n, char c = '\0') {
if (n < _size) {
_str[n + 1] = '\0';
_size = n;
}
else {
if (_capacity < n) {
reserve(n);
}
while (_size != n) {
_str[_size++] = c;
}
_str[_size] = '\0';
}
}
实现交换内容,要考虑的是两个容器的容量,为了确保都能装的下,那就将它们那个比较小的就扩容到比较大的长度那么多的容量
//-----实现交换容器的内容
void swap(mystring& s) {
//确保两个容器的容量都能装得下
if(_size>s.size())
s.reserve(_size);
else
reserve(s.size());
mystring tmp = *this;
*this = s;
s = tmp;
}
比较大小
调用库函数strcmp 比较两个字符串大小,返回bool值即可
bool operator<(const mystring& s) {
if (strcmp(this->c_str(), s.c_str()) < 0)
return true;
else
return false;
}
bool operator<=(const mystring& s) {
if (strcmp(this->c_str(), s.c_str()) <= 0)
return true;
else
return false;
}
bool operator>(const mystring& s) {
if (strcmp(this->c_str(), s.c_str()) > 0)
return true;
else
return false;
}
bool operator>=(const mystring& s) {
if (strcmp(this->c_str(), s.c_str()) >= 0)
return true;
else
return false;
}
bool operator==(const mystring& s) {
if (strcmp(this->c_str(), s.c_str()) == 0)
return true;
else
return false;
}
bool operator!=(const mystring& s) {
if (strcmp(this->c_str(), s.c_str()) != 0)
return true;
else
return false;
}
find
返回字符或者字符串首次出现的位置。找字符的话很简单,只需要遍历即可。注意find还有一个参数,pos。也就是指定从pos位置开始往后找,那我们遍历就要从pos开始遍历。如果找不到返回npos即可,npos也就是无符号整型的最大值
// 返回c在string中第一次出现的位置
size_t find(char c, size_t pos = 0) const {
assert(pos < _size);
for (int i = pos; i < _size; i++) {
if (_str[i] == c)
return i;
}
return string::npos;
}
要找字符串的话,也就死找子串,库函数里面的strstr可以实现这个功能,如果记不得的话也可以通过创建临时变量,再遍历去找
// 返回子串s在string中第一次出现的位置
size_t find(const char* s, size_t pos = 0) const {
assert(pos < _size);
/*int i = strlen(s);
mystring str = "";
str.reserve(i);
for (int j = 0; j < _size; j++) {
for (int k = j; k < i + j; k++) {
str.push_buck(_str[k]);
}
if (str == s)
return j;
str.clear();
}*/
const char* p = strstr(_str + pos, s);
if (p != nullptr)
return p - _str;
return string::npos;
}
insert、erase
insert 在指定的位置插入字符或字符串。那这就得涉及到挪动数据了。首先的确定容量是否合适,不合适的话先扩容。然后要将pos之后的包括pos位置的数据都往后挪动
// 在pos位置上插入字符c/字符串str,并返回该字符的位置
mystring& insert(size_t pos, char c) {
assert(pos <= _size);
if (_size >= _capacity) {
int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
for (size_t i = _size; i > pos; i--) {
_str[i] = _str[i - 1];
}
_str[pos] = c;
_str[++_size] = '\0';
return *this;
}
插入字符串也就是可以用strcpy了,指定位置赋值即可
mystring& insert(size_t pos, const char* str) {
assert(pos < _size);
if (_size + strlen(str) > _capacity)
reserve(_size + strlen(str));
for (int i = _size; i > pos - 1; i--) {
_str[i + strlen(str)] = _str[i];
}
strncpy(_str + pos, str, strlen(str));
_size += strlen(str);
return *this;
}
erase 删除指定位置指定长度的元素。如果不指定长度默认为npos,也就是pos位置之后包括pos全部删除。如果有指定长度并且指定长度之后还有元素,那就要对这些元素进行挪动,覆盖到要删除的位置上来。
// 删除pos位置上的元素,并返回该元素的下一个位置
mystring& erase(size_t pos, size_t len = string::npos) {
assert(pos < _size);
if (len > _size - pos || len== npos) {
for (int i = pos; i < _size; i++)
_str[i] = '\0';
_size = pos;
}
else {
for (int i = pos + len - 1; i < _size; i++) {
_str[i - len] = _str[i];
}
_size -= len;
_str[_size] = '\0';
}
return *this;
}
流输入和流输出
想要查看string类的数据我们可以通过将string类的对象通过c_str函数转换为C语言形式的字符串,再通过<< 可以直接输出。但是我们也可以通过重载流输出运算符实现直接输出类对象的数据。很简单,只需要在函数内将每个位置的数据逐一输出即可
ostream& operator<<(ostream& out, const mystring& s) {
for (int i = 0; i < s.size(); i++)
out << s[i];
return out;
}
同样流输入我们也可以重载,要注意流输入是有限制条件的,空格和回车是不能输入的,所以要加上判断条件,每个位置逐一输入即可。但是有一个问题,因为我上面实现的单个字符插入是每次开辟两倍的空间,所以如果是逐一输入的话就会出现开辟的空间过大的情况。此时我们可以通过数组的办法,先开辟好一个静态的空间,将字符都放在这个数组里,如果数组满了我们再插入进对象中,之后继续从下标0位置开始将字符放入数组。这样就可以很好的节省空间
istream& operator>>(istream& in, mystring& s) {
s.clear();
char butt[128] = { '\0' };
size_t i = 0;
char c = in.get();
while (c != ' ' && c != '\n') {
if (i == 127) {
s += butt;
i = 0;
}
butt[i++] = c;
c = in.get();
}
if (i > 0) {
butt[i] = '\0';
s += butt;
}
return in;
}
这里首先有一个clear函数是应该,如果不先把对象里的内容清空,那么在我们对一个已存在的对象进行输入时,它会直接尾插进对象里
由于cin不会提取到空格,所以有需要的话可以去查一下getline的用法。
总代码
#include<iostream>
#include <cassert>
using namespace std;
namespace myspace {
class mystring {
public:
mystring(const char* str = "") {
//如果传过来的是一个空串,那strlen就会是0
//不过要考虑'\0',所以开辟的空间要+1
//构造完之后有效长度就得等于strlen的长度
_size = strlen(str);
_capacity = strlen(str);
_str = new char[_capacity + 1];
strcpy(_str, str);
}
mystring(const mystring& s) {
_str = new char[s._capacity + 1];
_capacity = s._capacity;
_size = s._size;
strcpy(_str, s._str);
}
mystring& operator=(const mystring& s) {
//判断两个是否是同一个地址值
if (this != &s) {
//利用一个临时的变量先存储
char* str = new char[s._capacity + 1];
strcpy(str, s._str);
//注意如果不先把之前的空间释放可能会浪费空间
delete[] _str;
//将临时的变量赋值
_str = str;
//这两个成员变量每一次都要记得随时更新
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
~mystring() {
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
///
//迭代器第一种方式,指针
typedef char* iterator;
//获取begin
iterator begin() const {
return _str;
}
//获取end
iterator end() const {
//尾元素就只需要加上有效元素个数就行了
return _str + _size;
}
//
//c_str:返回C形式的字符串
const char* c_str() const{
return _str;
}
//返回长度
size_t size() const {
return _size;
}
//返回容量
size_t capacity() const {
//因为总容量是包括'\0'的所以要加1
return _capacity + 1;
}
//判断是否为空
bool empty() const {
return _size == 0;
}
//[]实现可读可写 用引用返回
char& operator[](size_t pos) {
assert(pos < _size);
return _str[pos];
}
char& operator[](size_t pos) const {
assert(pos < _size);
return _str[pos];
}
//实现扩容
void reserve(size_t n) {
//开辟新空间
char* str = new char[n + 1];
strcpy(str, _str);
//释放原有空间
delete[] _str;
_str = str;
//注意容量记得改变
_capacity = n;
}
//实现push_back插入字符
void push_buck(char ch) {
//判断是否需要扩容
if (_size == _capacity) {
int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity * 2);
}
_str[_size++] = ch;
//记住要补上'\0'
_str[_size] = '\0';
}
//实现插入一个字符串
void append(const char* s) {
//判断是否需要扩容
if (_size + strlen(s) > _capacity) {
reserve(_capacity + strlen(s));
}
//可以选择一个字符的放
/*while (*s != '\0') {
_str[_size++] = *s;
s++;
}
_str[_size] = '\0';*/
//也可以选择库函数直接拷贝
strcpy(_str + _size, s);
_size += strlen(s);
}
//实现+=
mystring& operator+=(char ch) {
push_buck(ch);
return *this;
}
mystring& operator+=(const char* str){
append(str);
return *this;
}
mystring& operator+=(const mystring& s) {
append(s.c_str());
return *this;
}
//实现清空内容
void clear() {
/*for (int i = 0; i < _size; i++) {
_str[i] = '\0';
}*/
_str[0] = '\0';
_size = 0;
}
//实现调整字符串大小
void resize(size_t n, char c = '\0') {
if (n < _size) {
_str[n + 1] = '\0';
_size = n;
}
else {
if (_capacity < n) {
reserve(n);
}
while (_size != n) {
_str[_size++] = c;
}
_str[_size] = '\0';
}
}
//-----实现交换容器的内容
void swap(mystring& s) {
//确保两个容器的容量都能装得下
if(_size>s.size())
s.reserve(_size);
else
reserve(s.size());
mystring tmp = *this;
*this = s;
s = tmp;
}
bool operator<(const mystring& s) {
if (strcmp(this->c_str(), s.c_str()) < 0)
return true;
else
return false;
}
bool operator<=(const mystring& s) {
if (strcmp(this->c_str(), s.c_str()) <= 0)
return true;
else
return false;
}
bool operator>(const mystring& s) {
if (strcmp(this->c_str(), s.c_str()) > 0)
return true;
else
return false;
}
bool operator>=(const mystring& s) {
if (strcmp(this->c_str(), s.c_str()) >= 0)
return true;
else
return false;
}
bool operator==(const mystring& s) {
if (strcmp(this->c_str(), s.c_str()) == 0)
return true;
else
return false;
}
bool operator!=(const mystring& s) {
if (strcmp(this->c_str(), s.c_str()) != 0)
return true;
else
return false;
}
// 返回c在string中第一次出现的位置
size_t find(char c, size_t pos = 0) const {
assert(pos < _size);
for (int i = pos; i < _size; i++) {
if (_str[i] == c)
return i;
}
return string::npos;
}
// 返回子串s在string中第一次出现的位置
size_t find(const char* s, size_t pos = 0) const {
assert(pos < _size);
/*int i = strlen(s);
mystring str = "";
str.reserve(i);
for (int j = 0; j < _size; j++) {
for (int k = j; k < i + j; k++) {
str.push_buck(_str[k]);
}
if (str == s)
return j;
str.clear();
}*/
const char* p = strstr(_str + pos, s);
if (p != nullptr)
return p - _str;
return string::npos;
}
// 在pos位置上插入字符c/字符串str,并返回该字符的位置
mystring& insert(size_t pos, char c) {
assert(pos <= _size);
if (_size >= _capacity) {
int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
for (size_t i = _size; i > pos; i--) {
_str[i] = _str[i - 1];
}
_str[pos] = c;
_str[++_size] = '\0';
return *this;
}
mystring& insert(size_t pos, const char* str) {
assert(pos < _size);
if (_size + strlen(str) > _capacity)
reserve(_size + strlen(str));
for (int i = _size; i > pos - 1; i--) {
_str[i + strlen(str)] = _str[i];
}
strncpy(_str + pos, str, strlen(str));
_size += strlen(str);
return *this;
}
// 删除pos位置上的元素,并返回该元素的下一个位置
mystring& erase(size_t pos, size_t len = string::npos) {
assert(pos < _size);
if (len > _size - pos || len == string::npos) {
for (int i = pos; i < _size; i++)
_str[i] = '\0';
_size = pos;
}
else {
for (int i = pos + len - 1; i < _size; i++) {
_str[i - len] = _str[i];
}
_size -= len;
_str[_size] = '\0';
}
return *this;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
ostream& operator<<(ostream& out, const mystring& s) {
for (int i = 0; i < s.size(); i++)
out << s[i];
return out;
}
istream& operator>>(istream& in, mystring& s) {
s.clear();
char butt[128] = { '\0' };
size_t i = 0;
char c = in.get();
while (c != ' ' && c != '\n') {
if (i == 127) {
s += butt;
i = 0;
}
butt[i++] = c;
c = in.get();
}
if (i > 0) {
butt[i] = '\0';
s += butt;
}
return in;
}
}
总结
每次学习完一个容器后,通过模拟实现会让自己有更深的理解
string类的功能很多,先记住常见的函数方法
有需求的时候再去多查查文档
多敲代码多学习✌️