本文只简述string类模拟实现的重点,其余不再过多赘述
一、模拟实现string类的构造函数
本文主要实现下图两个构造函数,即string()和string(const string& str)
而关于string的底层,其实就是数组,在物理逻辑上是连续的空间:
//string.h文件
namespace mxj
{class string{public://不带参的构造函数,编译器默认生成的已经满足使用需求//无参的构造,就是字符串'\0',所以在string的模拟实现里,带参的构造函数包含了无参构造函数//string();//带参构造函数string(const char* str = "");//给缺省值"",//这样我们的mxj::string s1就不会报错,不然得写成mxj::string s1()private:char* _str;size_t _size;size_t _capacity;};
}
带参构造函数如下,以strlen为基础,计算开辟新空间的大小,通过开辟新的内存空间并将str字符串长度的下一个位置赋值'\0'
这样也很巧妙的解决了无参构造函数。
//string.cpp文件
namespace mxj
{string::string(const char* str):_str(new char[strlen(str) + 1])//这里为什么要加1?因为strlen不会计算\0, _size(strlen(str)), _capacity(strlen(str)){strcpy(_str, str);}
}
二、模拟实现string类的拷贝构造和赋值拷贝
拷贝构造和赋值拷贝可以通过交换函数来实现,拷贝构造和赋值拷贝都是深拷贝!!!
/ s2(s1),拷贝构造string::string(const string& s){_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}// s3=s2=s1=s,赋值拷贝,string& string::operator=(const string& s){if (this != &s){//先把原来的空间释放了,如果s1=s,s1原本的空间非常大,s的空间非常小,就非常容易造成空间浪费delete[] _str;//为什么要+1,因为还有\0,_size和_capacity都是不计算\0的_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}return *this;}
当然,上面有关拷贝构造和赋值拷贝虽然直观,但是很啰嗦 ,通过swap函数我们也能实现
两者均不改变当前对象的资源,都是通过临时对象进行资源的交换
//string.cpp
namespace mxj
{string::string(const string& s){string tmp(s._str);//将s中的资源构造对象tmpswap(tmp);}string& string::operator=(string s){swap(s);//临时对象s和*this进行交换资源return *this;}void swap(string& s1, string& s2){s1.swap(s2);//通过库函数中的swap来实现}void string::swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}
}
三、模拟实现string类的析构函数
析构函数需要清理类对象的资源
string.cpp
string::~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}
四、赋值运算符重载[ ]
赋值运算符重载[ ],使得string类有通过下标来访问字符串数据的功能
char& operator[](size_t i){return _str[i];}
五、获取字符串数据
string类底层是数组,接口直接返回字符串即可
const char* c_str() const{return _str;}
六、获取当前对象元素个数
size_t size() const{return _size;}
七、清理当前string类对象的数据置空
字符串的终止符号为'\0',所以将字符串第一个元素置为字符'\0',字符串有效个数置零即可
void clear(){_str[0] = '\0';_size = 0;}
八、普通迭代器
迭代器是指针或者是像指针一样的东西,但在string类中,迭代器的底层是通过指针实现的
using iterator = char*;//using类似于typedef的作用
iterator begin()
{return _str;
}iterator end()
{return _str + _size;
}
九、const修饰的迭代器
using const_iterator = const char*;
const_iterator begin()const
{return _str;
}const_iterator end()const
{return _str + _size;
}
十、string类一些常用接口的实现
void string::reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];//保留n个位置实际上要保留n+1个,要给'\0'留位置strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;//_capacity是不计算'\0'的}}void string::push_back(char ch){if (_size == _capacity) {reserve(_capacity = 0 ? 4 : 2 * _capacity);}_str[_size] = ch;_size++;}void string::append(const char* str){size_t len = strlen(str);if (len + _size > _capacity){size_t newcapacity = 2 * _capacity;//如果扩了两倍还是不满足,那就按串的大小来扩if (len + _size >newcapacity){newcapacity = len + _size;}reserve(newcapacity);}strcpy(_str+_size, str);_size += len;}string& string::operator+=(char ch){push_back(ch);return *this;}string& string::operator+=(const char* str){append(str);return *this;}void string::insert(size_t pos, char ch){//判断pos位置是否合法assert(pos <= _size);//判断是否需要扩容if (_size == _capacity) {reserve(_capacity == 0 ? 4 : 2 * _capacity);}//pos+1位置往后挪size_t end = _size;while (end > pos) {_str[end] = _str[end-1];--end;}_str[pos] = ch;_size++;}void string::insert(size_t pos, const char* str){size_t len = strlen(str);//判断是否需要扩容if (_size + len > _capacity) {size_t newCapacity = 2 * _capacity;if (newCapacity < _size + len){newCapacity = _size + len;}reserve(newCapacity);}//数据往后挪:str的长度为len,pos位置每一个字符都需要向后挪len个,size_t end = _size+len;while (end > pos + len-1) {_str[end] = _str[end-len];--end;}//插入for (size_t i = 0; i < pos; i++){_str[pos+i] = str[i];}//长度更新_size += len;}//删除void string::erase(size_t pos, size_t len){assert(pos < _size);if (len > _size - pos) {_str[pos] = '\0';_size = pos;}//挪数据else {size_t end = pos + len;while (end > pos) {_str[end-len] = _str[end];++end;}_size -= len;}}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* str, size_t pos){assert(pos < _size);const char* ptr = strstr(pos+_str,str);if (ptr == nullptr){return npos;}else{return ptr - _str;}}string string::substr(size_t pos, size_t len){assert(pos < _size);if (len > (_size - pos)) {len = _size - pos;}mxj::string sub;sub.reserve(len);for (size_t i = pos; i < len; i++){sub += _str[pos + i];//尾插}return sub;}//通过复用bool operator== (const string& lhs, const string& rhs){return strcmp(lhs.c_str(), rhs.c_str()) == 0;}bool operator!= (const string& lhs, const string& rhs){return !(lhs == rhs);}bool operator> (const string& lhs, const string& rhs){return strcmp(lhs.c_str(), rhs.c_str()) > 0;}bool operator< (const string& lhs, const string& rhs){return !(lhs >=rhs);}bool operator>= (const string& lhs, const string& rhs){return (lhs > rhs)||(lhs==rhs);}bool operator<= (const string& lhs, const string& rhs){return !(lhs > rhs);}ostream& operator<<(ostream& os, const string& str){for (size_t i = 0; i < str.size(); i++){os << str[i];}return os;}istream& operator>>(istream& is, string& str){str.clear();char ch;//is >> ch;ch = is.get();while (ch != ' ' && ch != '\n')//如果想实现getline的效果while( ch != '\n'){str += ch;ch = is.get();}return is;}