C++模拟实现string的示例代码
作者:蒋灵瑜的笔记本
一、std::swap和std::string::swap的区别
如果用std::swap交换两个string对象,将会发生1次构造和2次赋值,也就是三次深拷贝;而使用std::string::swap仅交换成员,代价较小。
二、string的默认构造函数
1、构造函数
string(const char* s = "") { _size = strlen(s);//_size和_capacity均不包含'\0' _capacity = _size; _arr = new char[_size + 1]; memcpy(_arr, s, _size + 1); }
构造函数用缺省值,能够满足空串的构造。
这里设计_size和_capacity均不包含'\0'。_arr的空间多new一个,用于储存'\0'。
再将形参的内存拷贝至_arr中,即可完成构造。
2、拷贝构造
写法1:老老实实的根据string对象的私有变量进行拷贝构造。
string(const string& s) { _size = s._size;//_size和_capacity均不包含'\0' _capacity = s._capacity; _arr = new char[_capacity + 1]; memcpy(_arr, s._arr, _capacity + 1); }
写法2:通过构造一个临时对象,将这个临时对象的私有变量全部和*this的私有变量交换。
注意拷贝构造需要先将_arr初始化为nullptr,防止后续tmp拿到随机地址。(tmp销毁将调用析构函数,对一块随机地址的空间进行析构程序将会崩溃)
void swap(string& s) { std::swap(_arr, s._arr); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } string(const string& s) :_arr(nullptr)//防止交换后tmp._arr为随机值,析构出错 { string tmp(s.c_str());//构造 swap(tmp); }
3、赋值运算符重载
写法1:同样的老实人写法。这种写法要防止自己给自己赋值!
string& operator=(const string& s) { if (this != &s)//防止自己给自己赋值 { _size = s._size; _capacity = s._capacity; char* tmp = new char[_capacity + 1]; delete[] _arr; _arr = tmp; memcpy(_arr, s._arr, _capacity + 1); } return *this; }
写法2:通过构造临时变量tmp,完成赋值。这种写法无需担心自己给自己赋值的情况,并且_arr无需初始化为nullptr。
void swap(string& s) { std::swap(_arr, s._arr); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } string& operator=(const string& s) { string tmp(s.c_str());//构造 swap(tmp); return *this; }
4、析构函数
~string() { _size = _capacity = 0; delete[] _arr; _arr = nullptr; }
三、string中的小接口
//string的size()接口 size_t size()const//右const修饰*this,这样const和非const对象均可调用 { return _size; } //string的c_str()接口 const char* c_str()const { return _arr; } //string的capacity()接口 size_t capacity()const { return _capacity; } //string的clear()接口 void clear() { _arr[0] = '\0'; _size = 0; } //string的判空 bool empty()const { return _size == 0 ? false : true; }
如果函数形参不发生改变的,无脑加const修饰。
只有指针和引用会有const权限问题。
四、遍历接口的实现
1、对operator[]进行重载
char& operator[](size_t pos)//普通对象,可读可写 { assert(pos < _size); return _arr[pos]; } const char& operator[](size_t pos)const//const对象,仅读 { assert(pos < _size); return _arr[pos]; }
让字符串进行下标式的访问,需要重载两个operator[]函数,正常对象去调可读可写,const对象调用只读。
2、迭代器
typedef char* iterator; iterator begin() { return _arr; } iterator end()//end指向字符串的'\0' { return _arr + _size; }
string的迭代器是字符指针,写完迭代器就可以用迭代器实现访问、修改了。
范围for的底层也是一个迭代器,但是范围for底层只认begin()和end(),如果和自己实现的迭代器接口名称对不上,那么范围for将无法使用。
五、reserve和resize
//sring的reserve接口, 如果预开空间小于现有空间,将不会改变容量。 void reserve(size_t n = 0) { if (n + 1 > _capacity) { char* tmp = new char[n + 1]; memset(tmp, '\0', n + 1); memcpy(tmp, _arr, _size); delete[] _arr; _arr = tmp; _capacity = n; } } //sring的resize接口 void resize(size_t n, char c) { //判断n的大小 if (n > _capacity) { reserve(n); memset(_arr + _size, c, n - _size); _size = n; } else { _arr[n] = '\0'; _size = n; } }
reserve是扩容,可以用于预开空间,防止频繁的空间申请。申请一块n+1大小的空间,将该空间全部初始化'\0',再将_arr中的数据拷贝至tmp中,释放_arr,_arr指向tmp。
在resize中需要考虑_size扩容和缩容的问题。
六、插入删除查找相关接口
1、push_back、append、+=
string& push_back(const char c) { //判断容量 if (_size == _capacity) { size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;//防止出现空串的情况 reserve(newCapacity); } _arr[_size++] = c; return *this; } string& append(const char* s) { //判断容量 size_t len = strlen(s); if (_size + len > _capacity) { reserve(_size + len); } strcpy(_arr + _size, s); _size += len; return *this; } string& operator+=(const char c) { push_back(c); return *this; } string& operator+=(const char* s) { append(s); return *this; }
写push_back要考虑到原对象为空串的情况(即_capacity为0)。
+=可以复用push_back和append。
2、insert和earse
string& insert(size_t pos, char c) { assert(pos < _size); //判断容量 if (_size == _capacity) { reserve(_capacity + 1); } //挪动数据 for (size_t i = _size; i > pos; --i) { _arr[i] = _arr[i - 1]; } _arr[pos] = c; ++_size; return *this; } string& insert(size_t pos, const char* s) { size_t len = strlen(s); //判断容量 if (len + _size > _capacity) { reserve(len + _size); } //挪动数据 for (size_t i = _size + len; i > pos + len - 1; --i) { _arr[i] = _arr[i - len]; } memcpy(_arr + pos, s, len); _size += len; return *this; } string& earse(size_t pos, size_t len = npos) { assert(pos < _size); //先判断删到底的情况 if (len == npos || pos + len >= _size) { _arr[pos] = '\0'; _size = pos; } else { memcpy(_arr + pos, _arr + pos + len, _size - pos - len); _size -= len; } return *this; }
insert接口在挪动数据时,从最后一个元素的后一个(后len个)位置开始覆盖,可以保证不出现size_t 类型越界的情况。
earse接口,需要分类讨论字符串是否删到底。
注意,这个pos是const static成员,C++语法中,只有指针和整型的const static成员是可以在类中进行初始化的。
3、find
size_t find(const char c, size_t pos = 0)const { assert(pos < _size); for (size_t i = pos; i < _size; ++i) { if (_arr[i] == c) { return i; } } return npos; } size_t find(const char* s, size_t pos = 0)const { assert(pos < _size); const char* p = strstr(_arr, s); if (p != nullptr) { return _arr - p; } return npos; }
从指定位置找字符或字符串,找到了,返回第一个匹配字符/子串的下标。
七、流插入和流提取
//流插入和流提取的重载时为了自定义类型的输入输出 inline ostream& operator<<(ostream& out, const string& s)//这里访问的到私有,所以可以不用写成友元函数 { for (size_t i = 0; i < s.size(); ++i)//流插入按照_size打印,c_str找到'\0'结束打印 { //比如我在字符串中间插入一个'\0',打印结果不一样 out << s[i]; } return out; } inline istream& operator>>(istream& in, string& s) { s.clear();//用之前先清空s //in >> c;//流提取不会识别空格和换行 char c = in.get(); char buff[128] = { '\0' };//防止频繁扩容 size_t i = 0; while (c != ' ' && c != '\n') { if (i == 127) { s += buff; i = 0; } buff[i++] = c; c = in.get(); } if (i > 0) { buff[i] = '\0'; s += buff; } return in; }
因为string提供了访问私有的接口,所以流插入和流提取可以不用重载成string类的友元函数。
对于流提取,如果频繁的尾插,会造成频繁扩容。而且C++的扩容和C语言的扩容不一样,C++使用new不能原地扩容,只能异地扩容,异地扩容就会导致新空间的开辟、数据的拷贝、旧空间释放。为了防止频繁扩容,我们可以创建一个可以存储128字节的数组,在这个数组中操作,这个数组满了就尾插至对象s中。
为什么不能用getline,而是要一个字符一个字符尾插呢?因为流提取遇到空格和'\n'会结束提取,剩余数据暂存缓冲区,如果是getline的话,遇到空格是不会停止读取的。
八、模拟实现的string整体代码
#pragma once #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <assert.h> using std::cout; using std::cin; using std::endl; using std::ostream; using std::istream; namespace jly { class string { public: void swap(string& s) { std::swap(_arr, s._arr); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } //构造函数 string(const char* s = "") { _size = strlen(s);//_size和_capacity均不包含'\0' _capacity = _size; _arr = new char[_size + 1]; memcpy(_arr, s, _size + 1); } //拷贝构造 //写法1 //string(const string& s) //{ // _size = s._size;//_size和_capacity均不包含'\0' // _capacity = s._capacity; // _arr = new char[_capacity + 1]; // memcpy(_arr, s._arr, _capacity + 1); //} //写法2 string(const string& s) :_arr(nullptr)//防止交换后tmp._arr为随机值,析构出错 { string tmp(s.c_str());//构造 swap(tmp); } //赋值运算符重载 //写法1 //string& operator=(const string& s) //{ // if (this != &s)//防止自己给自己赋值 // { // _size = s._size; // _capacity = s._capacity; // char* tmp = new char[_capacity + 1]; // delete[] _arr; // _arr = tmp; // memcpy(_arr, s._arr, _capacity + 1); // } // return *this; //} //写法2 string& operator=(const string& s) { string tmp(s.c_str());//构造 swap(tmp); return *this; } //析构函数 ~string() { _size = _capacity = 0; delete[] _arr; _arr = nullptr; } //string的size()接口 size_t size()const//右const修饰*this,这样const和非const对象均可调用 { return _size; } //string的c_str()接口 const char* c_str()const { return _arr; } //string的capacity()接口 size_t capacity()const { return _capacity; } //string的clear()接口 void clear() { _arr[0] = '\0'; _size = 0; } //string的判空 bool empty()const { return _size == 0 ? false : true; } //对operator[]进行重载 char& operator[](size_t pos)//普通对象,可读可写 { assert(pos < _size); return _arr[pos]; } const char& operator[](size_t pos)const//const对象,仅读 { assert(pos < _size); return _arr[pos]; } //迭代器 typedef char* iterator; iterator begin()const { return _arr; } iterator end()const//end指向字符串的'\0' { return _arr + _size ; } //string的reserve接口,如果预开空间小于现有空间,将不会改变容量。 void reserve(size_t n=0) { if (n + 1 > _capacity) { char* tmp = new char[n + 1]; memset(tmp, '\0', n + 1); memcpy(tmp, _arr, _size); delete[] _arr; _arr = tmp; _capacity = n; } } //string的resize接口 void resize(size_t n, char c='\0') { //判断n的大小 if (n > _capacity) { reserve(n); memset(_arr + _size,c,n-_size); _size = n; } else { _arr[n] = '\0'; _size = n; } } //插入删除查找相关接口 string& push_back(const char c) { //判断容量 if (_size == _capacity) { size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;//防止出现空串的情况 reserve(newCapacity); } _arr[_size++] = c; return *this; } string& append(const char* s) { //判断容量 size_t len = strlen(s); if (_size + len > _capacity) { reserve(_size + len); } strcpy(_arr+_size,s); _size += len; return *this; } string& operator+=(const char c) { push_back(c); return *this; } string& operator+=(const char* s) { append(s); return *this; } string& insert(size_t pos, char c) { assert(pos < _size); //判断容量 if (_size == _capacity) { reserve(_capacity + 1); } //挪动数据 for (size_t i = _size; i > pos; --i) { _arr[i] = _arr[i - 1]; } _arr[pos] = c; ++_size; return *this; } string& insert(size_t pos, const char* s) { size_t len = strlen(s); //判断容量 if (len + _size > _capacity) { reserve(len + _size); } //挪动数据 for (size_t i = _size + len; i > pos + len - 1; --i) { _arr[i] = _arr[i - len]; } memcpy(_arr + pos, s, len); _size += len; return *this; } string& earse(size_t pos, size_t len = npos) { assert(pos<_size); //先判断删到底的情况 if (len == npos || pos + len >= _size) { _arr[pos] = '\0'; _size = pos; } else { memcpy(_arr + pos, _arr + pos + len,_size-pos-len); _size -= len; } return *this; } size_t find(const char c, size_t pos = 0)const { assert(pos < _size); for (size_t i = pos; i < _size; ++i) { if (_arr[i] == c) { return i; } } return npos; } size_t find(const char* s, size_t pos = 0)const { assert(pos < _size); const char* p = strstr(_arr, s); if (p != nullptr) { return _arr - p; } return npos; } private: char* _arr; size_t _size; size_t _capacity; const static size_t npos = -1;//只有const static整型、指针成员变量可以在类中定义,其他类型不行 }; //流插入和流提取的重载时为了自定义类型的输入输出 inline ostream& operator<<(ostream& out, const string& s)//这里访问得到私有,所以可以不用写成友元函数 { for (size_t i = 0; i < s.size(); ++i)//流插入按照_size打印,c_str找到'\0'结束打印 { //比如我在字符串中间插入一个'\0',打印结果不一样 out << s[i]; } return out; } inline istream& operator>>(istream& in, string& s) { s.clear();//用之前先清空s //in >> c;//流提取不会识别空格和换行 char c=in.get(); char buff[128] = { '\0' };//防止频繁扩容 size_t i = 0; while (c != ' ' && c != '\n') { if (i == 127) { s += buff; i = 0; } buff[i++] = c; c = in.get(); } if (i > 0) { buff[i] = '\0'; s += buff; } return in; } //测试函数 void test1() { } }
以上就是C++模拟实现string的示例代码的详细内容,更多关于C++实现string的资料请关注脚本之家其它相关文章!