C++ String的模拟实现
一.基本框架
1.成员变量
string类的成员变量分别是存储字符串的一段空间_str,表示字符串的有效字符个数_size和表示存储有效字符空间的_capacity。
private:
char *_str;
size_t _size;// 有效字符的个数
size_t _capacity;// 存储有效字符的空间
还有一个string类的特殊成员,npos表示size_t的最大值,一般表示表示string的结束位子。
private:
char* _str;
size_t _size;
size_t _capacity;
public:
const static size_t npos;
};
const size_t string::npos = -1;?
-1也可以表示size_t类型的最大值。
注意:
- 对于【npos】这个参数,首先我们知道对于静态成员变量,它的规则是在类外定义,类里面声明,定义时不加static关键字;
- 但如果静态成员变量有const修饰,这时它可以在类内直接进行定义,这样的特性只针对于整型,对于其他类型则是不适用的;
- npos就是const static修饰的成员变量,可以直接在类内进行定义。
?2.构造函数
?1.2.1 全缺省构造函数
? 首先,string类的有参构造函数其实可以设计为全缺省函数,缺省值设置为空串,当没有传入参数时使用缺省值,将_str设置为空串,这样就可以不需要定义无参构造函数了。其次,如果传入了参数,就将_size和_capacity设置为形参的长度,然后开一段大小和形参相同的空间给_str,最后将值拷贝过去即可。
??
//构造函数
string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
{
//cout << "构造函数" << endl;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
?
1.2.2 拷贝构造函数?
?string的拷贝构造函数,关于string的拷贝构造函数,我的建议是用传入的string类型中的_str去构造一个临时变量,然后交换临时变量和当前类内部的成员变量的内容。
??
void swap(string& s) {
std::swap(s._str, _str);
std::swap(s._size, _size);
std::swap(s._capacity, _capacity);
}
//拷贝构造
string(const string& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
//cout << "拷贝构造函数" << endl;
string tmp(s._str);
swap(tmp);
}
1.2.3 移动构造
??
? ?没啥好说的,直接交换右值即可。
//移动构造
string(string&& s) noexcept{
//cout << "移动构造函数" << endl;
swap(s);
}
3 赋值重载
1.3.1 默认重载
默认重载的赋值运算符功能很简单,就是将原有对象的所有成员变量一一赋值给新对象,并且返回新对象,这和默认拷贝构造函数的功能类似。
? 因此其代码和拷贝构造函数代码差不多,只不过不用初始化,因为在构造时已经初始化完毕。
??
//拷贝赋值
string& operator=(const string& s) {
//cout << "拷贝赋值函数" << endl;
string tmp(s);
swap(tmp);
return *this;
}
1.3.2 移动重载
? 直接交换资源就好。
//移动赋值
string& operator=(string&& s) noexcept {
//cout << "移动赋值函数" << endl;
swap(s);
return *this;
}
4.析构函数
?把_str空间释放,置空,并且把_size和_capacity 置0?
//析构函数
~string() {
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
二 容量函数
2.1 size()
顾名思义返回字符串的长度(以字符数为单位)
size_t size() const {
return _size;
}
2.2 capacity()
返回当前为 string 分配的存储空间的大小,以字符表示。
size_t capacity() const {
return _capacity;
}size_t capacity() const {
return _capacity;
}
2.3 reserve()
注意是有效字符,不包含标识字符,而在具体实现的时候,我们在底层多开一个空间给\0。
这里我们的做法是,如果n >_capacity,那么我们就开始扩容, 先 创建一个新数组,把其大小扩容为n+1,留一个空间给 \0? ,然后将原本的 str中的内容拷贝进新数组,然后delete str开的空间,最后将str指向新空间,更新_capacity的内容。
代码如下:
void reserve(size_t n) {
if (n > _capacity) {
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
2.4 resize()
其实它的情况大体上可以分为插入数据和删除数据两种情况。
1.对于插入数据:直接调用【reserve】提前预留好空间,然后搞一个for循环将字符ch尾插到数组里面去,最后再在数组末尾插入一个\0标识字符;
2.对于删除数据:如果 n 小于当前字符串长度,则当前值将缩短为 n ,删除第 n 个字符以后的字符,然后重置一下_size的大小为n即可。
代码如下:
void resize(size_t n, char ch = '\0') {
if (_size > n) {
_str[n] = '\0';
_size = n;
}
else{
reserve(n);
while (_size < n) {
_str[_size] = ch;
++_size;
}
_str[n] = '\0';
}
}
2.5 clear()
??顾名思义就是清除字符串,擦除string的内容,该内容变为空字符串(长度为?0?个字符)。
void clear(){
_str[0] = '\0';
_size = 0;
}
三 元素访问
3.1??operator[]
元素访问操作相对来说用的最多的就是operator[] .
? 对它进行调用时可能进行的是写操作,也可能进行读操作,所以为了适应const和非const对象,operator[]应该实现两个版本的函数,并且这个函数处理越界访问的态度就是assert直接断言,而at对于越界访问的态度是抛异常。
char& operator[](size_t pos) {
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const {
assert(pos < _size);
return _str[pos];
}
3.2 find()
??查找字符串中的第一个匹配项,?在string中搜索由其参数指定的序列的第一个匹配项。
?直接从pos位置遍历即可。
size_t find(char ch, size_t pos = 0) {
for (size_t i = pos; i < _size; i++) {
if (_str[i] == ch){
return i;
}
}
//找不到返回npos
return npos;
}
size_t find(const char* sub, size_t pos = 0) {
const char* p = strstr(sub + pos, sub);
if (p) {
return p - _str;
}
else {
return npos;
}
}
四 修改?
?4.1 push_back()
在字符串尾部插入单个数据。
首先,当我们的_size ==_capacity 的时候,我们要准备扩容,但是这里我们该如何扩容呢?
我的建议是,如果是初次扩容的时候,建议扩为四个字节,但以后我们每次都扩容为二倍,然后再_size的位置插入一个数据即可,注意补'\0',
? 在后续完成了insert的时候,也可以复用insert函数。
?代码如下:
void push_back(char ch) {
if (_size == _capacity) {
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
4.2 append()
追加到字符串,?通过在当前值的末尾附加其他字符来扩展。
- 我们可以直接调用strcpy接口来进行字符串的尾插,但是需要注意一点,那就是【string】类的字符串函数是不会进行自动扩容的,所以我们需要判断一下是否需要进行扩容,在空间预留好的情况下进行字符串的尾插即可实现。
- 但注意,这里我们不能扩容两倍,假如我们扩容两倍后的空间,依旧不够追加的字符串的大小呢?我们这里建议直接扩容为 待插入的字符串长度+_size
- 其次,如果已经实现了【insert】函数的情况下。我们可以直接复用【insert】函数也可实现对应的操作。
代码实现:
void append(const char* str) {
size_t len = strlen(str);
if (_size + len > _capacity) {
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
4.3?operator+=
追加到字符串,通过在当前值的末尾附加其他字符来扩展
在这里我们只实现添加字符和字符串的操作。
我们可以直接复用【push_back】和 【append] 的操作来实现。
代码如下:
string& operator+=(char ch) {
push_back(ch);
return *this;
}
string& operator+=(const char* str) {
append(str);
return *this;
}
4.4 insert()
插入到字符串中,在?pos(或?p)指示的字符之前将其他字符插入
?4.4.1 指定位置插入字符
- 首先
assert
先断言,确保插入位置合法 - 如果插入的总长度大于当前容量,就进行扩容,将容量扩展到?0或者2倍
- 接着,从字符串的末尾开始,依次往后移,给插入字符留下足够的空间
- 最后,使用一个循环将指定数量的字符
ch
插入到指定位置pos
之后。
// insert(0, 'x')
void insert(size_t pos, char ch){
assert(pos <= _size);
if (_size == _capacity) {
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t end = _size + 1;
while (end > pos) {
_str[end] = _str[end - 1];
end--;
}
_str[pos] = ch;
_size++;
}
4.4.2?指定位置插入字符串
- ?首先使用assert断言来确保插入位置pos不超过当前字符串的长度_size
- 如果插入后的总长度_size+len超过当前容量_capacity,就扩容到_size+len的长度
- 接着,将字符串的末尾开始,依次往后移动字符,为插入字符串成留出足够的位置。
- 然后使用一个循环将字符串中字符赋值到pos之后的指定位置
- 最后,更新字符串的长度_size
void insert(size_t pos, const char* str) {
assert(pos <= _size);
size_t len = strlen(str);
if (len + _size > _capacity) {
/*reserve(_capacity == 0 ? 4 : _capacity * 2);*/
reserve(_size + len);
}
size_t end = _size;
while (end >= pos&&end!=npos) {
_str[end + len] = _str[end];
--end;
}
strncpy(_str + pos, str, len);
_size += len;
}
注意:这里在头部插入时有可能end会等于-1 ,插入字符串时需要判断字符串长度,才能进行元素的移动和元素的写入。
4.5 erase() 函数?
意思很简单,就是从字符串中删除字符
对于删除,思路很简单,分为两种情况下的删除:
1.如果当前位置加上要删除的长度大于字符串的长度,即【 pos + len >= _size】,此时的意思即为删除pos之后的所有元素;
2.除了上述情况,就是在字符串内正常删除操作。我们只需利用strcpy来进行,将pos+len之后的字符串直接覆盖到pos位置,这样实际上就完成了删除的工作。
代码如下:
?
void erase(size_t pos, size_t len = npos) {
assert(pos < _size);
size_t end = pos+len;
if (len == npos || len + pos > _size) {
_str[pos] = '\0';
_size = pos;
}
else {
size_t begin = pos + len;
while (begin <= _size)
{
_str[begin - len] = _str[begin];
++begin;
}
_size -= len;
}
}
五 字符串操作
? 5.1 c_str()
获取等效的 C 字符串,返回指向一个数组的指针,该数组包含以 null 结尾的字符序列(即 C 字符串),表示string对象的当前值
const char* c_str() const {
return _str;
}
?5.2 比较操作
直接上代码吧。
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 || *this == s;
}
bool operator>(const string& s) const {
return !(*this <= s);
}
bool operator>=(const string& s) const {
return !(*this < s);
}
bool operator!=(const string& s) const {
return !(*this == s);
}
5.3 substr
从字符串中提取子字符串
- 首先,使用断言assert验证起始位置pos是否小于字符串的长度,避免越界。
- 如果len为npos或者pos+len超过了字符串的长度,n被设置为从pos到字符串末尾的长度
- 接下来,创建一个临时的字符串对象tmp,并使用reserve()为该字符串分配足够的容量以容纳字符串
- 然后,使用一个循环,依次将pos到pos+n的字符添加到tmp字符串中
- 最后,返回存储子字符串tmp字符串对象
//截取string中的一段
string substr(size_t pos, size_t len = npos) {
string s;
size_t end = pos + len;
if (len == npos || pos + len > _size) {
len = _size - pos;
end = _size;
}
s.reserve(len);
for (size_t i = pos; i < len; i++) {
s += _str[i];
}
return s;
}
六 非成员函数重载
6.1 operator<<
?将字符串插入流,?将符合?str?值的字符序列插入到 os?中。
- 函数的参数是一个输出流对象和一个常量字符串引用
- 遍历每一个字符,将字符依次输出到给定的流对象中
- 最后,函数返回输出流对象的引用
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
out << ch;
return out;
}
6.2?operator>>
从流中提取字符串,?从输入流中提取字符串,将序列存储在 str 中,该序列被覆盖(替换 str?的先前值)
- 函数的参数是一个输入流对象和一个字符串引用
- 首先清空字符串,然后读取输入流中的字符
- 在处理输入之前,函数会检查并跳过缓存区前面的空格或者换行符
- 然后,将字符一个接一个添加到字符缓冲区中,直到遇到换行符或者空格为止
- 如果字符缓冲区已满,将缓冲区的内容追加到字符串中,并清空缓冲区
- 最后,函数将最后一个字符缓冲区的内容追加到字符串中(如果缓冲区不为空)。函数返回输入流对象的引用。
istream& operator>>(istream& in, string& s)
{
s.clear();
char buff[129]{};
size_t i = 0;
char ch;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 128)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
七 完整代码+ 测试代码
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstring>
#include<assert.h>
using namespace std;
namespace My {
class string
{
public:
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;
}
//构造函数
string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
{
//cout << "构造函数" << endl;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
void swap(string& s) {
std::swap(s._str, _str);
std::swap(s._size, _size);
std::swap(s._capacity, _capacity);
}
//拷贝构造
string(const string& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
//cout << "拷贝构造函数" << endl;
string tmp(s._str);
swap(tmp);
}
//拷贝赋值
string& operator=(const string& s) {
//cout << "拷贝赋值函数" << endl;
string tmp(s);
swap(tmp);
return *this;
}
//移动构造
string(string&& s) noexcept{
//cout << "移动构造函数" << endl;
swap(s);
}
//移动赋值
string& operator=(string&& s) noexcept {
//cout << "移动赋值函数" << endl;
swap(s);
return *this;
}
//析构函数
~string() {
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
char& operator[](size_t pos) {
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const {
assert(pos < _size);
return _str[pos];
}
size_t capacity() const {
return _capacity;
}
size_t size() const {
return _size;
}
const char* c_str() const {
return _str;
}
void reserve(size_t n) {
if (n > _capacity) {
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void resize(size_t n, char ch = '\0') {
if (_size > n) {
_str[n] = '\0';
_size = n;
}
else{
reserve(n);
while (_size < n) {
_str[_size] = ch;
++_size;
}
_str[n] = '\0';
}
}
size_t find(char ch, size_t pos = 0) {
for (size_t i = pos; i < _size; i++) {
if (_str[i] == ch){
return i;
}
}
//找不到返回npos
return npos;
}
size_t find(const char* sub, size_t pos = 0) {
const char* p = strstr(sub + pos, sub);
if (p) {
return p - _str;
}
else {
return npos;
}
}
//截取string中的一段
string substr(size_t pos, size_t len = npos) {
string s;
size_t end = pos + len;
if (len == npos || pos + len > _size) {
len = _size - pos;
end = _size;
}
s.reserve(len);
for (size_t i = pos; i < len; i++) {
s += _str[i];
}
return s;
}
void push_back(char ch) {
if (_size == _capacity) {
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
void append(const char* str) {
size_t len = strlen(str);
if (_size + len > _capacity) {
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
string& operator+=(char ch) {
push_back(ch);
return *this;
}
string& operator+=(const char* str) {
append(str);
return *this;
}
// insert(0, 'x')
void insert(size_t pos, char ch){
assert(pos <= _size);
if (_size == _capacity) {
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t end = _size + 1;
while (end > pos) {
_str[end] = _str[end - 1];
end--;
}
_str[pos] = ch;
_size++;
}
void insert(size_t pos, const char* str) {
assert(pos <= _size);
size_t len = strlen(str);
if (len + _size > _capacity) {
/*reserve(_capacity == 0 ? 4 : _capacity * 2);*/
reserve(_size + len);
}
size_t end = _size;
while (end >= pos&&end!=npos) {
_str[end + len] = _str[end];
--end;
}
strncpy(_str + pos, str, len);
_size += len;
}
void erase(size_t pos, size_t len = npos) {
assert(pos < _size);
size_t end = pos+len;
if (len == npos || len + pos > _size) {
_str[pos] = '\0';
_size = pos;
}
else {
size_t begin = pos + len;
while (begin <= _size)
{
_str[begin - len] = _str[begin];
++begin;
}
_size -= len;
}
}
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 || *this == s;
}
bool operator>(const string& s) const {
return !(*this <= s);
}
bool operator>=(const string& s) const {
return !(*this < s);
}
bool operator!=(const string& s) const {
return !(*this == s);
}
void clear(){
_str[0] = '\0';
_size = 0;
}
private:
char* _str;
size_t _size;
size_t _capacity;
public:
const static size_t npos;
};
const size_t string::npos = -1;
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
out << ch;
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
char buff[129]{};
size_t i = 0;
char ch;
ch = in.get();
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 128)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
void test_string1()
{
string s1("hello world");
cout << s1.c_str() << endl;
string s2;
cout << s2.c_str() << endl;
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i] << " ";
}
cout << endl;
string::iterator it = s1.begin();
while (it != s1.end())
{
(*it)++;
cout << *it << " ";
++it;
}
cout << endl;
for (auto& ch : s1)
{
ch++;
cout << ch << " ";
}
cout << endl;
cout << s1.c_str() << endl;
}
void test_string2()
{
string s1("hello world");
cout << s1.c_str() << endl;
s1.push_back(' ');
s1.append("hello bit hello bit");
cout << s1.c_str() << endl;
s1 += '#';
s1 += "*********************";
cout << s1.c_str() << endl;
string s2;
s2 += '#';
s2 += "*********************";
cout << s2.c_str() << endl;
}
void test_string3()
{
string s1("hello world");
cout << s1.c_str() << endl;
s1.insert(5, '%');
cout << s1.c_str() << endl;
s1.insert(s1.size(), '%');
cout << s1.c_str() << endl;
s1.insert(0, '%');
cout << s1.c_str() << endl;
}
void test_string4()
{
string s1("hello world");
string s2("hello world");
cout << (s1 >= s2) << endl;
s1[0] = 'z';
cout << (s1 < s2) << endl;
cout << s1 << endl;
cin >> s1;
cout << s1 << endl;
/*char ch1, ch2;
cin >> ch1 >> ch2;*/
}
void test_string5()
{
string s1("hello world");
s1.insert(5, "abc");
cout << s1 << endl;
s1.insert(0, "xxx");
cout << s1 << endl;
s1.erase(0, 3);
cout << s1 << endl;
s1.erase(5, 100);
cout << s1 << endl;
s1.erase(2);
cout << s1 << endl;
}
void test_string6()
{
string s1("hello world");
cout << s1 << endl;
s1.resize(5);
cout << s1 << endl;
s1.resize(25, 'x');
cout << s1 << endl;
}
void test_string7()
{
string s1("test.cpp.tar.zip");
//size_t i = s1.find('.');
//size_t i = s1.rfind('.');
//string s2 = s1.substr(i);
//cout << s2 << endl;
string s3("https://legacy.cplusplus.com/reference/string/string/rfind/");
//string s3("ftp://www.baidu.com/?tn=65081411_1_oem_dg");
// 协议
// 域名
// 资源名
string sub1, sub2, sub3;
size_t i1 = s3.find(':');
if (i1 != string::npos)
sub1 = s3.substr(0, i1);
else
cout << "没有找到i1" << endl;
size_t i2 = s3.find('/', i1 + 3);
if (i2 != string::npos)
sub2 = s3.substr(i1 + 3, i2 - (i1 + 3));
else
cout << "没有找到i2" << endl;
sub3 = s3.substr(i2 + 1);
cout << sub1 << endl;
cout << sub2 << endl;
cout << sub3 << endl;
}
void test_string8()
{
string s1("hello world");
string s2 = s1;
cout << s1 << endl;
cout << s2 << endl;
string s3("xxxxxxxxxxxxxxxxxxx");
s2 = s3;
cout << s2 << endl;
cout << s3 << endl;
}
void test_string9()
{
string s1("hello world");
cin >> s1;
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
}
}
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!