个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创C++string类模拟实现
收录于专栏【C++语法基础】
本专栏旨在分享学习C++的一点学习笔记,欢迎大家在评论区交流讨论💌
目录
前置说明:
这里需要模拟实现string的操作有:
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace my_string
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin();
iterator end();
const_iterator begin() const;
const_iterator end() const;
//string();
string(const char* str = "");
string(const string& s);
string& operator=(const string& s);
~string();
const char* c_str() const;
size_t size() const;
char& operator[](size_t pos);
const char& operator[](size_t pos) const;
void reserve(size_t n);
void push_back(char ch);
void append(const char* str);
string& operator+=(char ch);
string& operator+=(const char* str);
void insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
void erase(size_t pos = 0, size_t len = npos);
size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos = 0);
void swap(string& s);
string substr(size_t pos = 0, size_t len = npos);
bool operator<(const string& s) const;
bool operator>(const string& s) const;
bool operator<=(const string& s) const;
bool operator>=(const string& s) const;
bool operator==(const string& s) const;
bool operator!=(const string& s) const;
void clear();
private:
// char _buff[16];
char* _str;
size_t _size;
size_t _capacity;
const static size_t npos;
};
istream& operator>> (istream& is, string& str);
ostream& operator<< (ostream& os, const string& str);
}
1. string类对象的常见构造
1.1 构造函数
string::string(const char* str)
:_size(strlen(str))
{
_str = new char[_size + 1];
_capacity = _size;
strcpy(_str, str);
}
内存的大小是 _size + 1,多出的一字节用于存储字符串的终止空字符 \0。
1.2 拷贝构造
// s2(s1)
string::string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
注意:
这里的拷贝构造不能省略!!!
如果这里省略的话,编译器会调用默认的拷贝构造,最终导致的问题是:s1、s2共用同一块内 存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规.
1.3 析构函数
string::~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
1.4 赋值运算符
string& string::operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
2. string类的迭代器
2.1 begin
string::iterator string::begin()
{
return _str;
}
string::const_iterator string::begin() const
{
return _str;
}
注意:
这里实现了begin方法的两个版本: 一个是普通的成员函数,返回一个iterator,另一个是const成员函数,返回一个const_iterator.下面end同理.
这样实现的原因:
主要原因是为了支持不同的使用场景和对象状态。普通的 begin 方法返回一个可修改的迭代器,适用于非 const 对象,这样可以对字符串内容进行修改。另一方面,const 成员函数的 begin 方法返回一个只读的迭代器,适用于 const 对象,确保在遍历时不能修改字符串。这种设计保持了代码的一致性和灵活性。
2.2 end
string::iterator string::end()
{
return _str + _size;
}
string::const_iterator string::end() const
{
return _str + _size;
}
3. string类对象容量操作
3.1 size
size_t string::size() const
{
return _size;
}
3.2 reserve
void string::reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
当n <= capacity时,reserve是不会缩小空间的
3.3 clear
void string::clear()
{
_str[0] = '\0';
_size = 0;
}
4. string类对象的访问操作
4.1 operator[]
char& string::operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
5. string类对象的增删查改
5.1 operator+=
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
operator+=一个字符是通过push_back完成
operator+=一个字符串是通过append完成
5.2 append
void string::append(const char* str)
{
insert(_size, str);
}
5.3 push_back
void string::push_back(char ch)
{
insert(_size, ch);
}
5.4 insert
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
++_size;
}
这段代码插入字符 ch 到字符串的指定位置 pos。
如果当前容量已满,则扩展容量以确保有足够的空间。
通过移动字符,为插入的字符腾出位置。
更新 _size 以反映新插入的字符。
void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size + len;
while (end > pos + len - 1)
{
_str[end] = _str[end - len];
--end;
}
memcpy(_str + pos, str, len);
_size += len;
}
检查位置有效性: 确保插入位置不超过当前字符串长度。
计算插入字符串长度: 获取要插入字符串的长度。
检查并扩展容量: 如果需要,扩展 string 对象的容量。
移动字符: 从字符串的末尾开始,将字符向后移动以腾出插入位置。
复制插入字符串: 将要插入的字符串复制到指定位置。
更新长度: 增加字符串的长度以反映插入的内容。
5.5 erase
void string::erase(size_t pos, size_t len)
{
assert(pos < _size);
// len大于前面字符个数时,有多少删多少
if (len >= _size - pos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
如果 len 大于或等于从 pos 开始到字符串末尾的字符数(即 len >= _size - pos),删除 len 个字符会把从 pos 开始到字符串末尾的所有字符都删除。此时,将 \0(空字符)放在 pos 位置,以表示字符串的新结束位置,然后将 _size 更新为 pos,表示新的字符串长度。
如果 len 小于 pos 到字符串末尾的字符数,使用 strcpy 将从 pos + len 开始的字符复制到 pos 位置,从而覆盖删除的字符。然后,将 _size 减去 len,以反映删除后的字符串长度。
5.6 swap
void string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
6. string类对字符串进行操作
6.1 c_str
const char* string::c_str() const
{
return _str;
}
6.2 find
size_t string::find(char ch, size_t pos)
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
size_t string::find(const char* sub, size_t pos)
{
char* p = strstr(_str + pos, sub);
if (p == nullptr)
return npos;
return p - _str;
}
查找字符:
功能: 从字符串中查找字符 ch,从位置 pos 开始。
遍历 : 从 pos 开始遍历字符串,直到 _size(字符串末尾),检查每个字符是否等于 ch。
找到字符 : 如果找到了匹配的字符,返回其索引 i。
未找到 : 如果遍历结束都未找到字符,返回 npos,表示没有找到。
查找字符串:
功能: 从字符串中查找子字符串 sub,从位置 pos 开始。
使用 strstr : strstr 函数用于在 _str + pos 位置后的子字符串中查找 sub。strstr 返回指向子字符串首次出现的位置的指针。
计算位置 : 通过计算 p - _str,将找到的子字符串位置转换为相对于字符串开始的位置(即从 0 开始的索引)。
返回 : 返回子字符串的起始位置。如果 sub 未找到,则 strstr 返回 NULL
6.3 substr
string string::substr(size_t pos, size_t len)
{
// len大于后面剩余字符,有多少取多少
if (len > _size - pos)
{
string sub(_str + pos);
return sub;
}
else
{
string sub;
sub.reserve(len);
for (size_t i = 0; i < len; i++)
{
sub += _str[pos + i];
}
return sub;
}
}
功能: 从原字符串中提取一个子字符串。
处理超长情况 : 如果请求的长度 len 超过了从 pos 开始到字符串末尾的剩余字符数,则创建一个包含从 pos 到末尾的所有字符的子字符串。
处理正常情况 : 如果 len 不超过剩余字符数,则逐个字符地构造子字符串,并使用 reserve 提高效率。
效率 : 通过使用 reserve 和逐个字符追加,代码在正常情况下效率较高,但如果 len 超过剩余字符数,则会直接利用 string 的构造函数创建子字符串,这样可能在某些实现中效率更高。
7. string类中的常量值
7.1 npos
const size_t string::npos = -1;
8. string类中的运算符重载
bool string::operator<(const string& s) const
{
return strcmp(_str, s._str) < 0;
}
bool string::operator>(const string& s) const
{
return !(*this <= s);
}
bool string::operator<=(const string& s) const
{
return *this < s || *this == s;
}
bool string::operator>=(const string& s) const
{
return !(*this < s);
}
bool string::operator==(const string& s) const
{
return strcmp(_str, s._str) == 0;
}
bool string::operator!=(const string& s) const
{
return !(*this == s);
}
9. string类中的输入输出流
istream& operator>> (istream& is, string& str)
{
str.clear();
char ch = is.get();
while (ch != ' ' && ch != '\n')
{
str += ch;
ch = is.get();
}
return is;
}
ostream& operator<< (ostream& os, const string& str)
{
for (size_t i = 0; i < str.size(); i++)
{
os << str[i];
}
return os;
}