一、string实际的底层原理
string类型底层实现通常使用动态数组,因为它允许在运行时动态分配内存空间。这使得字符串的长度可以根据需要进行调整,而不需要提前分配固定大小的空间,并且可以避免缓冲区溢出和内存泄漏等问题。 string类型还提供了各种字符串操作函数,例如拼接、查找、替换、大小写转换等。
底层实际的储存结构:
class string { private: char* _str; size_t _capacity; size_t _size; };
二、string的模拟实现
基本成员函数
构造函数
通过设置缺省参数以及初始化列表来默认构造string对象
string(const char* str = "") :_size(strlen(str)) ,_capacity(_size) { _str = new char[_capacity + 1]; strcpy(_str, str); }
在模拟实现string的构造函数时,很多人包括作者我一开始都会想着将他分为有参以及无参两部分,但是这种做法是会产生一定的问题的,如下为作者写出的错误的代码:
class string { public: string()//无参构造函数 //初始化列表 :_str(nullptr) , _size(0) , _capacity(0) { } string(char* str)//带参构造函数->char a[] = "abc";string kk(a) :_str(str), _size(strlen(str)), _capacity(strlen(str)) { } string(const char* str)//带参构造函数->string kk("abc") :_str(str), _size(strlen(str)), _capacity(strlen(str)) { } private: char* _str; size_t _capacity; size_t _size; };
对于以上代码,若调用如上的带参构造函数中string(const char* str)就会报错,因为str传给_str属于权限放大,现在有两种解决方法,一种是将它改为 string(char* str),但是这样就不能直接传递字符串,需要先生成字符串,还有一种方法是将_str改为const char*类型,但是此时就不能修改_str指向的内容,后续的一些操作会出现一些错误。
拷贝构造函数
由于我们使用了开辟了空间来用于储存数据,因此我们需要进行深拷贝,防止对于另外一个对象的影响。
传统写法:
开辟对应的空间,然后再给相应的值。
// 传统写法 // s2(s1) //string(const string& s) //{ // _str = new char[s._capacity+1]; // strcpy(_str, s._str); // _size = s._size; // _capacity = s._capacity; //}
较为推荐的写法:
通过让构造函数帮我们创建一个构造相应的字符的对象,然后我们再交换两个对象的数据即可得。
void swap(string& s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } string(const string& s) :_str(nullptr) ,_size(0) ,_capacity(0) { string tmp(s._str); swap(tmp); }
析构函数
避免内存泄漏需要自己写一个析构函数用以销毁new在堆区所创建的空间
~string() { delete[]_str; _str = nullptr; _size = _capacity = 0; }
重载赋值运算符
同上面的拷贝构造函数一样,我们对此也是需要深拷贝的
传统写法:
开辟对应的空间,然后再给相应的值。
//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; //}
较为推荐的写法:
通过让构造函数帮我们创建一个构造相应的字符的对象,然后我们再交换两个对象的数据即可得。
string& operator=(const string& s) { if (this != &s) { string tmp(s); //this->swap(tmp); swap(tmp); } return *this; }
再简化一点:通过在传值时进行拷贝构造,然后交换数据。
string& operator=(string tmp) { swap(tmp); return *this; }
迭代器
迭代器的概念
C++迭代器是一种用于遍历容器中元素的对象,它提供了一种统一的接口,使得不同类型的容器都可以使用相同的方式进行遍历。迭代器可以指向容器中的某个元素,并用于访问该元素的值或者进行修改。迭代器通常是一个类,它重载了指针操作符(*和->),以及自增(++)和自减(--)运算符,使得迭代器可以像指针一样进行移动。
string的迭代器实际实现原理:
string的迭代器实际上是一个指向string底层存储空间中char类型数据的指针,不同的迭代器类型标志着不同的访问权限。例如,一个string::iterator对象可以用来读写字符,而一个const string::iterator对象只能用来读取字符。
于是我们定义出以下的迭代器:
typedef char* iterator; typedef const char* const_iterator;
begin()
用于返回第一个字符的地址。
iterator begin() { return _str; } const_iterator begin()const { return _str; }
end()
用于返回后一个字符的后一个字符的地址的地址。其实就是‘\0’的地址
iterator begin() { return _str; } const_iterator begin()const { return _str; }
空间管理
size_t size()const//大小 { return _size; } size_t capacity()const//空间 { return _capacity; } bool empty()const//空? { return _size == 0; } 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 c = '\0')//重设大小 { if (n <= _size) { _str[n] = '\0'; _size = n; } else { reserve(n); while (_size < n) { _str[_size] = c ; ++_size; } _str[_size] = '\0'; } }
修改相关
push_back()
根据原型可知此函数是在当前字符串尾插一个字符,其中需要额外注意容量。
void push_back(char c) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } _str[_size] = c; ++_size; _str[_size] = '\0'; }
append ()
根据原型可知此函数有很多的函数重载,但是我们最常用的就是尾插一段字符串,对此就不过多进行阐述。也需要额外注意容量。
void append(const char* str) { size_t len = strlen(str); if (_size + len > _capacity) { reserve(_size + len); } strcpy(_str + _size, str);//拷贝到原字符串后 _size += len; }
重载+=运算符
此处复用了以上的push_back和append函数,这也是根据C++中函数管用的复用习惯来做出的选择。
string& operator+=(char c) { push_back(c); } string& operator+=(const char* str) { append(str); return *this; }
c_str ()
返回指向数组的指针,该数组包含以 null 结尾的字符序列(即 C 字符串),该字符表示字符串对象的当前值。简而言之就是将string对象转换为C语言风格类型的字符串。
const char* c_str()const { return _str; }
clear()
void clear() { _str[0] = '\0'; _size = 0; }
swap()
void swap(string& s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); }
重载[ ](最爱的运算符!!!)
这里其实就是按照数组的相关操作即可,但是要注意越界的判断!
char& operator[](size_t index) { assert(index < _size); return _str[index]; } const char& operator[](size_t index)const { assert(index < _size); return _str[index]; }
关系运算符的重载
关系运算符的重载的关键就是根据所需进行相应的数据变化以及做出值的返回。
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); }
基本操作
查找find()
由原型知,分为按照字符查找以及字符串查找还有常量字符串查找,对此,此次实现字符以及字符串查找。
对于字符查找:
1. size_t find(char c, size_t pos = 0) const { for (size_t i = pos; i < _size; i++) { if (_str[i] == c) { return i; } } return npos; }
对于字符串查找:
前置知识substr()
他的主要作用就是在str中从pos位置开始,截取n个字符,然后将其返回。其中的npos实际上是一个全局的静态变量,实际的赋值为-1,都是由于类型为size_t,因此实际上为最大值!
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 < end; i++) { s += _str[i]; } return s; } // 返回子串s在string中第一次出现的位置 size_t find(const char* s, size_t pos = 0) const { const char* p = strstr(_str + pos, s); if (p) { return p - _str; } else { return npos; } }
插入insert()
根据所插入的空间大小挪字符
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 (_size + len > _capacity) { reserve(_size + len); } // 挪动数据 int end = _size; while (end >= (int)pos) { _str[end + len] = _str[end]; --end; } strncpy(_str + pos, str, len); _size += len; }
删除 erase()
删除指定位置的字符,可根据调节删除的数量,默认是指定位置以后全删。
void erase(size_t pos, size_t len = npos) { assert(pos < _size); if (len == npos || pos + len >= _size) { _str[pos] = '\0'; _size = pos; } else { size_t begin = pos + len; while (begin <= _size) { _str[begin - len] = _str[begin]; ++begin; } _size -= len; } }
流插入流提取
重载<<和>>是为了能够像内置一样调用,输入打印。
ostream& operator<<(ostream& out, const string& s) { /*for (size_t i = 0; i < s.size(); i++) { out << s[i]; }*/ 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; } //s += ch; ch = in.get(); } if (i != 0) { buff[i] = '\0'; s += buff; } return in; }
总体代码
#pragma once #define _CRT_SECURE_NO_WARNINGS 01 #include<iostream> #include<assert.h> using namespace std; namespace lt { class string { public: typedef char* iterator; typedef const char* const_iterator; friend ostream& operator<<(ostream& out, const string& s); friend istream& operator>>(istream& in, string& s); iterator begin() { return _str; } iterator end() { return _str + _size; } const_iterator begin() const { return _str; } const_iterator end() const { return _str + _size; } /*string() :_str(new char[1]{'\0'}) ,_size(0) ,_capacity(0) {}*/ string(const char* str = "") :_size(strlen(str)) , _capacity(_size) { _str = new char[_capacity + 1]; strcpy(_str, str); } // 传统写法 // s2(s1) //string(const string& s) //{ // _str = new char[s._capacity+1]; // strcpy(_str, s._str); // _size = s._size; // _capacity = s._capacity; //} s2 = s3 //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; //} void swap(string& s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } // s2(s1) string(const string& s) :_str(nullptr) , _size(0) , _capacity(0) { string tmp(s._str); swap(tmp); } // s2 = s3 //string& operator=(const string& s) //{ // if (this != &s) // { // string tmp(s); // //this->swap(tmp); // swap(tmp); // } // return *this; //} // s2 = s3 string& operator=(string tmp) { swap(tmp); return *this; } ~string() { delete[] _str; _str = nullptr; _size = _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 (n <= _size) { _str[n] = '\0'; _size = n; } else { reserve(n); while (_size < n) { _str[_size] = ch; ++_size; } _str[_size] = '\0'; } } size_t find(char ch, size_t pos = 0) { for (size_t i = pos; i < _size; i++) { if (_str[i] == ch) { return i; } } return npos; } size_t find(const char* sub, size_t pos = 0) { const char* p = strstr(_str + pos, sub); if (p) { return p - _str; } else { return npos; } } 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 < end; 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; } 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 (_size + len > _capacity) { reserve(_size + len); } // 挪动数据 int end = _size; while (end >= (int)pos) { _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); if (len == npos || pos + len >= _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; //const static size_t npos = -1; // 特例 //const static double npos = 1.1; // 不支持 public: const static size_t npos; }; const size_t string::npos = -1; ostream& operator<<(ostream& out, const string& s) { /*for (size_t i = 0; i < s.size(); i++) { out << s[i]; }*/ for (auto ch : s) out << ch; return out; } istream& operator>>(istream& in, string& s) { s.clear(); //s.reserve(128); 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; } //s += ch; ch = in.get(); } if (i != 0) { buff[i] = '\0'; s += buff; } return in; } }
感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o!