文章目录
- 引言
- 1.类的基本结构
- 2.构造函数和析构函数
- 3.基本成员函数
- 总结
引言
在C++编程中,字符串操作是非常常见且重要的任务。标准库中的std::string类提供了丰富且强大的功能,使得字符串处理变得相对简单。然而,对于学习C++的开发者来说,深入理解std::string的内部实现原理是非常有益的。通过亲手实现一个类似的String类,不仅可以帮助我们掌握面向对象编程的基本概念,还能增强我们对内存管理和字符串操作的理解。
在这篇博客中,我们将从零开始,逐步实现一个自定义的C++ String类。我们的目标是构建一个功能完整且高效的字符串类,同时尽可能地模仿std::string的行为。我们将讨论类的基本结构、构造函数和析构函数的实现、基本成员函数的设计、运算符重载、内存管理,以及如何编写测试代码来验证我们的实现。
通过这篇文章,您将学到如何在C++中进行动态内存分配和管理,如何实现深拷贝和移动语义,如何重载运算符以提升类的易用性,等等。无论您是刚刚入门的C++学习者,还是希望深入理解C++底层实现的开发者,这篇文章都将为您提供宝贵的知识和实践经验。
让我们一起来探索C++ String类的实现之旅吧!
1.类的基本结构
1.1定义类
#include #include using namespace std; namespace lyrics { class string { public: typedef char* iterator; typedef const char* const_iterator; //迭代器 iterator begin(); iterator end(); const_iterator begin()const; const_iterator end()const; //构造函数 string(const char* str = ""); string(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 = 0, size_t len = npos); //查找某个字符串或者字符 size_t find(char ch, size_t pos = 0); size_t find(const char* str, size_t pos = 0); //赋值拷贝 string& operator=(const string& s); //交换函数 void swap(string& s); //取子串 string substr(size_t pos = 0, size_t = npos); //比较函数运算符重载 bool operator=(const string& s)const; bool operator==(const string& s)const; //清理 void clear(); private: size_t _size; size_t _capacity; char* _str; const static size_t npos; }; //非成员函数,,重载流插入和流提取 istream& operator>>(istream& is, string& str); ostream& operator _str = new char[_size + 1]; _capacity = _size; strcpy(_str, str); } char* tmp = new char[s._capacity + 1]; strcpy(tmp, s._str); delete[] _str; _str = tmp; _size = s._size; _capacity = s._capacity; return *this; } return _str; } delete[] _str; _str = nullptr; _size = 0; _capacity = 0; } return _size; } assert(pos
4.0删除某段字符串
注意:在声明中len的缺省参数给的是npos,当我的长度大于pos对应的后面对应的长度的时候,这时候就有多少删多少,所以我们需要判断一下,第一个if判断的就是判断我们删除的长度是否已经超过了后面的长度,如果超过了就直接进入第一个if删除后面的所有,也就是把pos位置置为\0,然后将_size更新,如果不是的话可以直接将pos+len位置的子字符串拷贝到pos位置之后
//从pos位置删除len个字符 void string::erase(size_t pos, size_t len) { assert(pos = _size - pos) { _str[pos] = '\0'; _size = pos; } else { strcpy(_str + pos, _str + pos + len);//直接把后面的copy到前面 _size -= len; } }
4.1查找函数
- 查找单个字符
size_t string::find(char ch, size_t pos) { for (size_t i = pos;i
- 查找字符串
查找字符串的话可以直接用C语言的库函数进行查找
size_t string::find(const char* sub, size_t pos) { const char* str = strstr(_str + pos, sub); return str - _str; }
4.2深拷贝
//深拷贝 string::string(const string& s) { _str = new char[s._capacity + 1]; strcpy(_str, s._str); _size = s._size; _capacity = s._capacity; }
4.3交换函数
这里不用库里的交换函数因为库里的交换函数的效率太低了,我们可以简单看看库里交换函数的代码
这里可以看到库里的swap函数是直接拷贝构造一个零时的对象,然后进行两次赋值拷贝,这样做效率是极低的,因为是内置类型,两次赋值拷贝都会进行创建新空间,然后释放旧的空间,这样的成本是很大的,所以可以直接写一个swap对内置类型进行交换,直接交换两个指针的指向,还有size和capacity即可
void string::swap(string& s) { //内置类型交换代价更小 std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); }
4.4取子串
string string::substr(size_t pos, size_t len) { //检查pos是否合法 assert(pos _size - pos) { //直接取后面的子串 string sub(_str + pos);//从pos位置开始进行拷贝构造!!!! //返回子串 return sub; } else { //构造子串 string sub; //预开辟空间 sub.reserve(len); //循环拷贝 for (size_t i = 0;i
4.5比较函数operator的一系列重载
这里只需要重载两个即可,其他的只需要进行复用就够了,比较函数的重载可以直接调用C语言中的字符串比较函数
bool string::operator return strcmp(_str, s._str) (istream& is, string& str) { str.clear(); char ch = is.get(); while (ch != ' '&& ch != '\n') { str += ch; } return is; }
- 流提取
流提取也不用直接访问成员变量,流提取可以直接一个字符一个字符的访问,通过operator[]的重载访问,一个一个大打印
ostream& operator for (size_t i = 0;i
- 流提取
- 查找字符串
- 查找单个字符