模拟实现string(传统写法与现代写法)
目录
- 1. 简易string(无增删查改)
- 1.1 深浅拷贝
- 2. string的传统写法
- 2.1 四个默认成员函数
- 3. string的现代写法
- 3.1 构造函数与析构函数
- 3.2 拷贝构造
- 3.3 赋值重载
- 3.4 std中的swap与string中的swap
- 4. 修改
- 5. 增加
- 6. 删除
- 7. 查找
- 8. 重载的全局函数
- 最终代码
1. 简易string(无增删查改)
开始必须重新定义一个命名空间,把我们自定义的string放进命名空间里。
假如不考虑增删查改,我们先可以做一个简单的string,几乎下意识的就写出了这样的构造函数
string(char* str):_str(str){}
这是非常不合理的,假如外面传入的是一个常量字符串。
string s("1234");
假如要修改常量字符串的话,会直接中断。
那我们就开一个和他同样大小的空间,再把字符拷贝进去。使用new开辟后面还可以动态增长。
namespace zjn
{class string{public:string(char* str):_str(new char[strlen( str)+1]){strcpy(_str, str);}~string(){delete[] _str;}private:char* _str;};
}
有时我们也会定义无参的string。那么这样写可以吗,我们配合一个接口c_str进行验证
string():_str(nullptr){;}
const char* c_str()
{return _str;
}
int main()
{zjn::string s1("1234");zjn::string s2;cout << s1.c_str() << endl;cout << s2.c_str ()<< endl;return 0;}
cout是自动识别类型,是因为他重载了各种类型的函数,打印的原理无非就是遇到‘\0’停止,可是在s2由于我们构造的时候使用nullptr构造的,所以cout会解引用进行访问,就会崩溃。(析构函数时delet[]所包含的free(NULL)并不会报错)
所以正确的做法应该是
string():_str(new char[1]){_str[0] = '\0';}
其实只要写成默认构造函数就好了。这样就不用写两个构造函数
string(char* str=""):_str(new char[strlen( str)+1]){strcpy(_str, str);}
1.1 深浅拷贝
在学习拷贝构造的时候,我们讲到一个stack对象的拷贝,自动生成的构造函数只是浅拷贝,两个指针指向的是同一块区域,所以执行析构函数的时候会将同一块资源释放两次。
所以默认生成的满足不了需求,必须自己写一个拷贝构造函数来实现深拷贝。
#include<iostream>
#include<assert.h>
#pragma warning(disable:4996)
using namespace std;namespace zjn
{class string{public:string(char* str=""):_str(new char[strlen( str)+1]){strcpy(_str, str);}~string(){delete[] _str;_str = nullptr;}const char* c_str(){return _str;}string(const string& s):_str(new char[strlen(s._str)+1]){strcpy(_str,s._str);}char& operator[](size_t i){assert(i < _size);return _str[i];}//释放原空间,然后深拷贝string& operator=(const string& s){if (this!=&s){delete[] _str;//不能直接strcpy因为空间可能不够_str = new char[strlen(s._str) + 1];strcpy(_str, s._str);}return *this;}private:char* _str;};
}
int main()
{zjn::string s1("1234");zjn::string s2;zjn::string s3(s1);s3[1] = 'x';cout << s1.c_str() << endl;cout << s2.c_str ()<< endl;return 0;
}
2. string的传统写法
一个真正的string是要支持增删查改的,所以必须要有size和capcacity。
size和capacity是内置类型几乎不需要注意什么地方
2.1 四个默认成员函数
/*string(char* str):_str(new char[strlen(str)+1]),_size(strlen( str)), _capacity(_size){}*/string(char* str=""){_str=new char[strlen(str)+1];_size = strlen(str);_capacity = _size;strcpy(_str, str);}string(const string& s){_size = s._size;_capacity = s._capacity;_str = new char[strlen(s._str) + 1];strcpy(_str, s._str);}~string(){delete[] _str;_str = nullptr;}string& operator=(const string& s){if (this != &s){delete[] str;_str = new char[strlen(s._str) + 1];_capacity = s._capacity;_size = s._size;}return *this;}
- 默认构造函数:当默认构造函数选择为为参数列表声明时,最好不要写有依赖关系的,因为实际初始化的顺序只和声明的顺序有关。所以我们直接选择写在函数体内
- 拷贝构造函数:需要深拷贝实现,即重新开一段空间,把数据拷贝过来
- 重载赋值函数:需要注意自己给自己赋值的情况,释放原空间进行深拷贝,假如是自己给自己赋值的话,原空间释放,指针指向新开的一段空间,strlen时遇到’\0’停止,那段空间是随机值。
3. string的现代写法
3.1 构造函数与析构函数
string(const char* str=""){_size = strlen(str);_capacity = _size;_str = new char[_size + 1];strcpy(_str, str);}
~string(){delete[] _str;_str = nullptr;_capacity = _size=0;}
- 构造函数与析构函数没有什么差别。
3.2 拷贝构造
一开始是这么写的
string(const string& s){.//局部对象拥有相同的str,用于和this._str进行交换string temp(s._str);std::swap(_str,temp._str);std::swap(_size,temp._size);std::swap(_capacity,temp._capacity);}
很明显是有错误的,因为_str没有初始化是一个野指针。当与temp._str进行交换时,_str指向temp._str那块空间。temp._str指向那块随机空间,当出了函数体,调用析构函数,清理资源_str,free掉temp._str,就会报错,即free了一个野指针。
string(const string& s):_str(nullptr),_size(0),_capacity(0){string temp(s._str);std::swap(_str,temp._str);std::swap(_size,temp._size);std::swap(_capacity,temp._capacity);}
那么我们将它初始化成nullptr就好了。
3.3 赋值重载
string& operator=(const string& s){if (this != &s){string temp(s._str);std::swap(_str,temp._str);std::swap(_size,temp._size);std::swap(_capacity,temp._capacity);}return *this;}
交换了之后,出了函数体,调用析构,刚好也会清理掉不要的"worldx"那块资源。
其实还有一种更简洁的写法
//参数不能加const因为我们会修改s,不能传引用因为我们修改了s,传引用外面的也会修改string& operator=( string s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);return *this;}
s就代替了之前的temp对象,且s也是一个局部对象,也会调用析构函数。
可以看到存在了大量的swap代码复用,我们可以将swap写成一个函数。然后对所有代码进行修改。
string(const char* str=""){_size = strlen(str);_capacity = _size;_str = new char[_size + 1];strcpy(_str, str);}// std中的swap并不好void swap(string& s){std::swap(_str, s._str);std::swap(_size,s._size);std::swap(_capacity, s._capacity);}//拷贝构造//string s1(s2);string(const string& s):_str(nullptr), _capacity(0), _size(0){string temp(s._str);swap(temp);}~string(){delete[] _str;_str = nullptr;_capacity = _size=0;}string& operator=( string s){swap(s);return *this;}
这就是一个有资源管理的string的现代写法。
3.4 std中的swap与string中的swap
那么为什么不直接调用库中的swap交换两个对象,而是用库中的swap一个个交换属性,最后将他们封装成一个专门的swap呢?
swap(s1,s2);
s1.swap(s2);
这两种差异是特别大的。
std中的swap是一个由函数模板推演而来的模板函数。其中经历了一次拷贝构造,两次赋值。代价太大,效率会大打折扣。
4. 修改
//[]重载char& operator[](size_t i){assert(i < _size);return _str[i];}//[]重载(const对象调用)const char& operator[](size_t i)const{assert(i < _size);return _str[i];}//迭代器typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str+_size;}//迭代器(const对象)typedef const char* const_iterator;const_iterator begin()const{return _str;}const_iterator end()const{return _str + _size;}//范围forfor (auto& e : s){e += 1;}
- 重载[]:需要注意,i需要小于size,size作为下表的话是’\0’的位置。
- string 的迭代器实际就是一个指针。typedef为iterator是为了和其它接口保持一致,假如你是一个链表就不能继续使用指针了,因为指针指向那个节点,解引用之后是一个结构体,并不是节点的值。所以在链表中迭代器实际是这样定义的。
struct ListIterator{operator*();operator++();Node* node;};
重载他的*和++来达到目的。
- 范围for:会自动转换为迭代器,也就是说自动调用begin(),与end()。
5. 增加
void resize(size_t n, char ch='\0'){//_size变小,capacity不变,直接在n处放'\0'if (n<_size){_str[n] ='\0';_size = n;}else{if (n>_capacity){reserve(n);}//1 2 3 4 for (size_t i = _size; i < n; i++){_str[i] = ch;}_str[n] = '\0';_size = n;}}//增加就可能会引起扩容void reserve(int n){if (n > _capacity){//需要temp不然直接delete的话字符串的内容会丢失char* temp = new char[n + 1];strcpy(temp, _str);delete[] _str;_str = temp;_capacity = n;}}//字符尾插string& push_back(char ch){if (_size == _capacity){reserve(2 * _capacity);}_str[_size] = ch;_size++;_str[_size] = '\0';return *this;}//字符串拼接void append(const char* str){int len = strlen(str);if (len + _size> _capacity){reserve(len + _size);}//忘记strcpy,参数忘记+_sizestrcpy(_str+_size, str);_size += len;}//字符+=string& operator+=(char ch){push_back(ch);return *this;}//字符串+=string& operator+=(const char* str){append(str);return *this;}//string对象+=string& operator+=(const string& s){append(s._str);return *this;}//插入字符string& insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){size_t newcapacity = _capacity == 0 ? 8 : 2 * _capacity;reserve(newcapacity);}/* 逻辑没有问题,但是当pos等于0时,要把第一位往前挪,end--就会变成-1,才能由于类型为size_t他就会变为整形最大值。假如把end变为int类型也避免不了,因为pos为size_t,身为int类型的end会类型提升为size_t,依旧是整形最大值。那把end变成int类型,再把pos显示的转为int(pos不要直接改成int与库不相符),这样是可以的。size_t end = _size;while(end>=pos){_str[end + 1] = _str[end];end--;}*///虽然_size是'\0',但是_size+1不会越界因为假如空间不够前面会扩容,数组是有这么大空间的,只不过没有存字符。//这样当pos等于0时,end为1字符已经全部挪走,end--为0,此时不会再进入循环,也就不会变成-1了。size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];end--;}_str[pos] = ch;_size++;return *this;}//插入字符串string& insert(size_t pos, const char* str){assert(pos <= _size);int len = strlen(str);if (_size + strlen(str) > _capacity){reserve(_size+len);}int end = _size + len;//画图分析//避免类型提升while (end >= (int)pos+len){_str[end] = _str[end - len];end--;}strncpy(_str + pos, str, len);_size += len;return *this;}
- reserve:只要增加就可能会有扩容的场景。
- push_back:追加一个字符
- append:拼接一个字符串。
- +=:分别有字符,字符串,string三种函数
- insert:push_back,append,+=可以复用这个插入函数,但是要注意类型提升问题。
6. 删除
//删除,和库类似,我们给一个npos缺省值void erase(size_t pos, size_t len = npos){//从pos起,删除len个字符。assert(pos < _size);//len为npos的时候会溢出//if (pos+len>_size)////假如要求删的超过字符串,a b c d,要求从c删除100个,就把d置成'\0'就行了。if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{//直接拷过去,然后覆盖,'\0'也会拷贝过去strcpy(_str + pos, _str + pos + len);_size -= len;}}
- erase,注意溢出,直接使用strcpy覆盖。
7. 查找
//查找字符,缺省默认从第一个位置开始size_t find(char ch, size_t pos = 0){for (size_t i = 0; i < _size; i++){if (ch == _str[i]){return i;}}return npos;}//查找子串size_t find(const char* sub, size_t pos = 0){//在_str+pos这个字符串里找subconst char* ret = strstr(_str + pos, sub);if (nullptr == ret){return npos;}else{return ret - _str;}}
- find: 一个查找字符,一个查找子串,缺省值都是从第一个位置开始查找
8. 重载的全局函数
//大于重载bool operator>(const string& s1, const string& s2){size_t l1=0, l2 = 0;while (l1 < s1.size() && l2 < s2.size()){if (s1[l1] == s1[l2]){l1++;l2++;}else{if (s1[l1]>s2[l2]){return true;}else{return false;}}}//假如有一个长一个短if (l1 < s1.size()){return true;}else if (l2 < s2.size()){return false;}else{return false;}}//==重载bool operator==(const string& s1, const string& s2){size_t l1 = 0, l2 = 0;while (l1 < s1.size() && l2 < s2.size()){if (s1[l1] == s1[l2]){l1++;l2++;}else{return false;}}//假如有一个长一个短if (l1 < s1.size()){return false;}else if (l2 < s2.size()){return false;}else{return true;}}//输出std::ostream& operator<<(std::ostream& out, const string& s){for (size_t i = 0; i < s.size(); i++){out << s[i] ;}return out;}//输入std::istream& operator>>(std::istream& in, string& s){char ch;while (1){//这样不行,默认空格为下一个,不能123这样输,只能1 2 3//in >> ch;in.get(ch);if ( ch==' '||ch=='\n'){break;}else{s += ch;}}return in;}
最终代码
#include<iostream>
#include<assert.h>#pragma warning(disable:4996)namespace zjn
{class string{public://传统写法构造//string(const char* str = "")//{// _size = strlen(str);// _capacity = _size;// _str = new char[_capacity + 1];// strcpy(_str, str);//}析构//~string()//{// delete[] _str;// _str = nullptr;// _capacity = _size = 0;//}拷贝构造//string(const string& s)//{// _str = new char[strlen(s._str) + 1];// strcpy(_str, s._str);// _capacity = s._capacity;// _size = s._size;//}赋值重载//string& operator=(const string& s)//{// if (this != &s)// {// delete[] _str;// _str = new char[strlen(s._str) + 1];// strcpy(_str, s._str);// _size = s._size;// _capacity = s._capacity;// }// return *this;//}//构造string(const char* str = ""){_size = strlen(str);_capacity = _size;_str = new char[_size + 1];strcpy(_str, str);}//std中的swap并不好void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}//拷贝构造//string s1(s2);string(const string& s):_str(nullptr), _capacity(0), _size(0){string temp(s._str);swap(temp);}//析构~string(){delete[] _str;_str = nullptr;_capacity = _size = 0;}//赋值string& operator=(string s){swap(s);return *this;}size_t size()const{return _size;}//重载[]//返回值忘记引用了char& operator[](size_t i){assert(i < _size);return _str[i];}const char& operator[](size_t i)const{assert(i < _size);return _str[i];}typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}typedef const char* const_iterator;const_iterator begin()const{return _str;}const_iterator end()const{return _str + _size;}//增//扩容+初始化。//当前n<capacity的话,不改变capacity,只改变size。n>capacity,改变size改变capacity。void resize(size_t n, char ch='\0'){//_size变小,capacity不变,直接在n处放'\0'if (n<_size){_str[n] ='\0';_size = n;}else{if (n>_capacity){reserve(n);}//1 2 3 4 for (size_t i = _size; i < n; i++){_str[i] = ch;}_str[n] = '\0';_size = n;}}//扩容void reserve(size_t n){if (n > _capacity){//需要temp不然直接delete的话字符串的内容会丢失char* temp = new char[n + 1];strcpy(temp, _str);delete[] _str;_str = temp;_capacity = n;}}//字符尾插void push_back(char ch){/*if (_size == _capacity){reserve(2 * _capacity);}_str[_size] = ch;_size++;_str[_size] = '\0';*/insert(_size, ch);}void append(const char* str){//int len = strlen(str);//if (len + _size> _capacity)//{// reserve(len + _size);//}忘记strcpy,参数忘记+_size//strcpy(_str+_size, str);//_size += len;insert(_size, str);}//字符+=string& operator+=(char ch){push_back(ch);return *this;}//字符串+=,参数忘记conststring& operator+=(const char* str){append(str);return *this;}//string对象+=string& operator+=(const string& s){append(s._str);return *this;}//插入字符void insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){//假如capacity为0一开始开8个对象类型(char)空间size_t newcapacity = _capacity == 0 ? 8 : 2 * _capacity;reserve(newcapacity);}/* 逻辑没有问题,但是当pos等于0时,要把第一位往前挪,end--就会变成-1,才能由于类型为size_t他就会变为整形最大值。假如把end变为int类型也避免不了,因为pos为size_t,身为int类型的end会类型提升为size_t,依旧是整形最大值。那把end变成int类型,再把pos显示的转为int(pos不要直接改成int与库不相符),这样是可以的。size_t end = _size;while(end>=pos){_str[end + 1] = _str[end];end--;}*///虽然_size是'\0',但是_size+1不会越界因为假如空间不够前面会扩容,数组是有这么大空间的,只不过没有存字符。//这样当pos等于0时,end为1字符已经全部挪走,end--为0,此时不会再进入循环,也就不会变成-1了。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);int len = strlen(str);if (_size + strlen(str) > _capacity){reserve(_size + len);}int end = _size + len;//画图分析//避免整形提升while (end >= (int)pos + len){_str[end] = _str[end - len];end--;}strncpy(_str + pos, str, len);_size += len;}//删除,和库类似,我们给一个npos缺省值void erase(size_t pos, size_t len = npos){//从pos起,删除len个字符。assert(pos < _size);//len为npos的时候会溢出//if (pos+len>_size)////假如要求删的超过字符串,a b c d,要求从c删除100个,就把d置成'\0'就行了。if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{//直接拷过去,然后覆盖,'\0'也会拷贝过去strcpy(_str + pos, _str + pos + len);_size -= len;}}//查找字符,缺省默认从第一个位置开始size_t find(char ch, size_t pos = 0){for (size_t i = 0; i < _size; i++){if (ch == _str[i]){return i;}}return npos;}//查找子串size_t find(const char* sub, size_t pos = 0){//在_str+pos这个字符串里找subconst char* ret = strstr(_str + pos, sub);if (nullptr == ret){return npos;}else{return ret - _str;}}private:char* _str;size_t _capacity;size_t _size;//声明static const size_t npos;};//定义初始化const size_t string::npos = -1;//大于重载bool operator>(const string& s1, const string& s2){size_t l1=0, l2 = 0;while (l1 < s1.size() && l2 < s2.size()){if (s1[l1] == s1[l2]){l1++;l2++;}else{if (s1[l1]>s2[l2]){return true;}else{return false;}}}//假如有一个长一个短if (l1 < s1.size()){return true;}else if (l2 < s2.size()){return false;}else{return false;}}//==重载bool operator==(const string& s1, const string& s2){size_t l1 = 0, l2 = 0;while (l1 < s1.size() && l2 < s2.size()){if (s1[l1] == s1[l2]){l1++;l2++;}else{return false;}}//假如有一个长一个短if (l1 < s1.size()){return false;}else if (l2 < s2.size()){return false;}else{return true;}}//大于等于bool operator>=(const string& s1, const string& s2){return s1 > s2 || s1 == s2;}//小于bool operator<(const string& s1, const string& s2){return !(s1>=s2);}//小于等于bool operator<=(const string& s1, const string& s2){return !(s1>s2);}//输出std::ostream& operator<<(std::ostream& out, const string& s){for (size_t i = 0; i < s.size(); i++){out << s[i] ;}return out;}//输入std::istream& operator>>(std::istream& in, string& s){//防止对一个原本就有数据的对象输入s.resize(0);char ch;while (1){//这样不行,默认空格为下一个,不能123这样输,只能1 2 3//in >> ch;in.get(ch);if ( ch==' '||ch=='\n'){break;}else{s += ch;}}return in;}}
模拟实现string(传统写法与现代写法)相关推荐
- android kotlin中的when语句写法与for写法
示例代码: fun getScore(name: String) = if (name == "Tom") {85 } else if (name == "AA" ...
- 浅拷贝+引用计数--写时拷贝---模拟实现string容器
引用计数 深拷贝 多个对象共享同一份资源时,最后能够保证该资源只被释放一次 应该由哪个对象释放资源? 由最后一个使用该资源的对象去释放 怎么知道一个对象是最后一个使用该资源的对象? 给一个计数,记录使 ...
- JSX列表渲染(五)——4种写法:基本写法 遍历写法 map遍历写法(常用-加工处理数组的每一项数据,最后形成一个新的数组) 列表遍历可以直接写到表达式中
JSX列表渲染--4种写法:基本写法 & 遍历写法 & map遍历写法(常用-加工处理数组的每一项数据,最后形成一个新的数组) & 列表遍历可以直接写到表达式中 渲染多个元素 ...
- 【C++修行之路】STL——模拟实现string类
文章目录 前言 类框架 构造与析构 迭代器 操作符重载 []: =: > == >= < <= !=: reverse与resize reverse resize push_b ...
- ajax的通用写法,ajax的jquery写法和原生写法
一.ajax的简介 Ajax被认为是(Asynchronous(异步) JavaScript And Xml的缩写).现在,允许浏览器与服务器通信而无须刷新当前页面的技术都被叫做Ajax. 同步是指: ...
- 背景图片等比缩放的写法background-size简写法
1.背景图片或图标也可像img一样给其宽高就能指定其缩放大小了. 比如一个实际宽高36*28的图标,要缩小一半引用进来的写法就是: background:rgba(0, 0, 0, 0) url(&q ...
- 设计模式:单例模式的写法(基础写法和线程安全写法)
单例模式的写法非常多.先给出一种最基础的写法: (A种写法): package singleton;public class SingletonInstance {private static Sin ...
- .ajax get 写法,原生Ajax写法(GET)
ajax的GET提交方式的原生代码: var xhr = null; if(window.XMLHttpRequest){ xhr = new XMLHttpRequest(); }else if(w ...
- 单片机小白学步系列(四) 模拟电路、传统数字电路与单片机
大家都用过计算器,有没有想过它是怎么实现的呢?这里我不详述计算器的原理,而只对思路进行简单介绍.等我们学会了单片机,也可以亲手制作一个计算器.通过电路进行数学计算,应该怎么做呢?为了便于理解,下面我举 ...
最新文章
- 面向对象进阶2 组合
- 宏病毒的研究与实例分析06——终结篇 进击的MACRO
- JDBC的入门案列以及JDBC的对事务的管理
- python 查看当前目录_Python学习第156课--ls的运用、环境变量以及PATH
- 我们找了3家制造企业,问问他们是怎么破解供应链难题的
- Prometheus 原理和实践,含docker部署Prometheus、node Exporters、Alertmanager、Push Gateway、grafana
- linux shutdown 命令
- Windows中安装Electron说明
- linux centos Intel® Centrino® Wireless-N 1000 无线网卡的安装
- ERROR:STATUS_BROM_CMD_SEND_DA_FALL(0xC0060003)
- 还在用PPT做组织架构图?公司都在用的架构图软件是什么?
- 合工大苍穹战队视觉组培训Day8——视觉,目标识别
- 使用element走马灯 + video-player实现图片和视频混合轮播
- Manjaro Linux KDE 安装后的配置
- 叶子结点和分支节点_结点数和叶子结点数有什么区别
- Epson XP-225 驱动
- 电脑用的时间长了,如何给电脑提速?
- python 中range(10)什么意思_请问在Python中for i in range(10,0,-1)是什么意思
- 微信公众号开发之如何将本机IP映射成外网域名
- 算法工程师八股文——序言
热门文章
- train-images-idx3-ubyte.gz train-labels-idx1-ubyte.gz 下载
- 机器视觉之工业摄像机知识点(一)
- 大恒水星相机开发记录
- android3D摄像机
- 单片机四路抢答器课程C语言设计,四路抢答器单片机课程设计.doc
- 小猫爪:嵌入式小知识06-KEIL scf分散加载文件解析-链接代码至RAM
- 武林外传经典108句
- 数学不好学java好学吗_数字不好能学java吗?
- 第十三周翻译:Stairway to SQL Server Replication: Level 1 - Introduction to SQL Server Replication
- Android视频技术探索之旅:美团外卖商家端的实践