自学内容网 自学内容网

c++_stl_string模拟实现

标准模板库 STL(Standard Template Library),是 C++ 标准库的一部分,不需要单独安装,只需要#include 头文件。

C++ 对模板(Template)支持得很好,STL 就是借助模板把常用的数据结构及其算法都实现了一遍,并且做到了数据结构和算法的分离

而string就是其中之一,string可以对字符串进行处理。它里面的函数也是相当之丰富的。在本文中我选择了几种相对常用的进行了模拟实现。

首先我们需要将我们建好的my_string放入我们自己的命名空间中,以便于之后的测试。

实现思路

变量创建:

创建指针_str用来指向开辟的空间,_size表示字符串的长度,_capacity表示开辟空间的容量。

private:
char* _str;
size_t _size;
size_t _capacity;

构造函数与析构函数:

构造函数

在这里有两种方式可以实现构造:

(1)

string()
    :_str(new char[1])
    , _size(0)
    ,_capacity(0)
{
_str[0] = '\0';
}

这种方法固然可以对我们的创建的变量进行构造但是无法在创建变量是就将其填入字符,需要在创建完成后自行调用函数来进行填入。所以我们可以使用函数重载来进行构造,在创建函数时就传入字符串。

(2)

string(const char* str = "")
:_size(strlen(str))
{
_capacity = _size == 0 ? 3 : _size;
_str = new char[_capacity + 1];
strcpy(_str, str);

}

这样就可以在创建变量时填入字符

void test()
{
    string str("hello world");
    
}

析构函数:

在进行析构函数的时候需要注意我们在new的使用,在string中构造函数使用的是new[],所以在写析构函数的时候应该delete[],然后将size以及capacity置零即可。

~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}

正向迭代器

在string中不会解释迭代器的底层原理,只是会对其进行简单的实现以及调用。

在string中迭代器可以帮助开发者进行遍历。

而且为了防止调用者传入const变量我们需要重载。

typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}

iterator end()
{
return _str + _size;
}

const_iterator begin() const
{
return _str;
}

const_iterator end() const
{
return _str + _size;
}

实现逻辑并不复杂。

使用迭代器进行遍历:

void test10()
{
string str("hello world");
string::iterator it = str.begin();
while(it != str.end())
{
cout << *it << " ";
it++;
}
cout << endl;

}

说到迭代的话我们也可以使用cpp的另一个语法:范围for。

具体实现如下:

string s1("hallo world");
v1.push_back(a);
v1.push_back(b);
v1.push_back(c);
v1.push_back(d);
v1.push_back(e);
for(auto ch:v1)
{
cout << ch << " ";
}
cout << endl;

重载:

在我们自己实现的string中cpp一些操作符无法达到我们的目的所以我们需要对其进行重载。例如[],比较运算符,+=,-=,==等运算符。

[]的重载:

通过重载[]运算符,可以使类的对象可以像数组一样进行索引操作。在返回值的设定中我们应该返回需要的位置的引用。

char* operator[](size_t pos)
{
assert(pos < _size);
return&_str[pos];
}
const char* operator[](size_t pos)const
{
assert(pos < _size);
return&_str[pos];
}

比较运算符的重载:

choangza我们比较字符串可以使用strcmp,在返回值的时候使用strcmp的返回值即可。

bool operator>(const string& s)const
{
return strcmp(_str, s._str) > 0;
}
bool operator<(const string& s)const
{
return strcmp(_str, s._str) < 0;
}

bool operator>=(const string& s)const
{
return !(strcmp(_str, s._str) < 0);
}
bool operator==(const string& s)const
{
return strcmp(_str, s._str) == 0;
}
bool operator<=(const string& s)const
{
return !(*this > s);
}

重载+=:

在使用string时,我们可以用push_back在字符串背后追加字符,但是频繁调用push_back会使代码变得很长,所以我们可以通过重载+=运算符来达到追加字符的目的。

在实现的过程中我们可以分两种情况来实现,一种是追加字符,一种是追加字符串。

在实现追加字符是我们可以直接使用push_back来追加。

在实现追加字符串时我们可以使用append函数来达到追加字符串的目的。append函数在本文的后面会进行实现。

string& operator+=(char s)
{
push_back(s);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}

重载流运算符:

在C++中,重载流运算符(<< 和 >>)必须作为全局函数进行重载,而不能直接放在类中。

这是因为流运算符必须具有特定的格式,即左操作数为流对象,右操作数为要输出或输入的数据。当流运算符作为成员函数时,左操作数将成为调用该函数的对象,而右操作数则无法直接传递给函数。因此,无法直接在类中重载流运算符。

为了实现类的输出和输入功能,我们可以在类中定义友元函数,并在该函数内部调用类的成员函数来完成相关操作。这样可以间接实现对流运算符的重载。

但是我们在重载流提取时,控制的循环条件为等于空格或者换行时才会跳出循环。但是我们cin会觉得我们在输入带空格的长字符,所以我们在读取str的时候并不能像重载<<那样直接调用<<操作符,我们应该使用get函数来读取。

istream& operator>>(istream& in, string& str)//在输入时打了空格并不会在控制台打印字符
{
char ch;
in >> ch;
while(ch != ' ' && ch != '\n')
{
str += ch;
in >> ch;
}
return in;
}
void test11()
{
string str;
cin >> str;
string::iterator it = str.begin();
while (it != str.end())
{
cout << *it << " ";
it++;
}
cout << endl;
}

下面的123是敲了回车之后再次输入的并不是下面的遍历输出的。

ostream& operator<<(ostream& out,const string& str)//c_str遇到‘\0’会停止,cout不会
{
for(auto ch : str)
{
out << ch;
}
return out;
}
istream& operator>>(istream& in, string& str)
{
str.clear();//该函数在下文中会实现,主要功能是清空原先对象存储的字符。

char ch = in.get();
char buff[128];
size_t i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 127)
{
buff[127] = '\0';
str += buff;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
buff[i] = '\0';
str += buff;
}
return in;
}

c_str的实现:

c_str的作用是拿到一个指向那段字符串数组的指针。我们直接返回_str即可

const char* c_str()
{
return _str;
}

size的实现:

该函数是想要得知数组中元素的个数。返回_size即可。

const char* c_str()
{
return _str;
}

reserve:

void reserve(size_t n = 0)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}

push_back:

尾插操作时记得检查空间是否足够,如果不够记得扩容。

void push_back(char s)
{
if (_size + 1 > _capacity)
reserve(_capacity * 2);
_str[_size] = s;
_size++;
_str[_size + 1] = '\0';

}

append:

append函数所支持的是在原先字符串后追加字符串。原先的字符串里的内容需要保留,我们可以在原字符串的最后一个字符后使用strcpy即可。

void append(const char* str)
{
size_t len = strlen(str);
if(_size+len>_capacity)
{
reserve(_capacity + len);
}
strcpy(_str + _size, str);
_size = _size + len;

}

capacity:

返回开辟空间的大小。

size_t capacity()const
{
return _capacity;
}

find:

查找某一元素在字符串中的位置并返回下标。

size_t find(char ch,size_t pos = 0)
{
assert(pos > _size);
for(size_t i = pos;i<_size;i++)
{
if(_str[i] == ch)
{
return i;
}

}
return npos;
}

insert:

在某个位置插入。

string& insert(size_t pos,const char* str)
{
assert(pos < _capacity);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_capacity+len);
}
//挪动数据
size_t end = _size + len;
while(end>pos+len-1)
{
_str[end] = _str[end-len];
end--;
}

//拷贝数据
strncpy(_str + pos, str, len);
_size += len;
return *this;
}

erase:

在某个位置删除len个字符。如果len没有传入参数那么全部删除。在这里需要有缺省值。

void erase(size_t pos,size_t len = npos)//在某个位置删除len个字符
{
if(len == npos || pos+len>_size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}





const size_t string::npos = -1;

resize:

void resize(size_t n,char ch)
{
if(n<_size)
{
_str[n] = '\0';
_size = n;
}
else if(n>_size)
{
size_t  num = n;
reserve(n);
while(num>_size)
{
num--;
_str[num] = ch;
}
_size = n;
_str[_size] = '\0';


}
}

clear:

直接将size置为0并且在str数组中将‘\0’放置到第一个位置。

void clear()
{
_str[0] = '\0';
_size = 0;
}

swap:

直接调用库函数里的swap函数即可。

void swap(string& str)
{
std::swap(_str, str._str);
std::swap(_size, str._size);
std::swap(_capacity, str._capacity);

}


原文地址:https://blog.csdn.net/m0_64334842/article/details/132816202

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!