【C++】string的模拟实现
创始人
2024-01-26 12:36:59
0


目录

一、std::swap和std::string::swap的区别

二、string的默认构造函数

1、构造函数

2、拷贝构造

3、赋值运算符重载

4、析构函数

三、string中的小接口

四、遍历接口的实现

1、对operator[]进行重载

2、迭代器

五、reserve和resize

六、插入删除查找相关接口

1、push_back、append、+=

2、insert和earse

3、find

七、流插入和流提取

八、模拟实现的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 
#include 
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);//}//写法2string(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;//}//写法2string& 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(){}
}


《 符合学习规律的超详细linux实战快速入门》

相关内容

热门资讯

女生创业有什么好项目有哪些 女... 女生创业者越来越多,那么对于初次创业的女生来说选好一个项目是非常重要的,下面是百分网小编整理的女生适...
适合女性的创业项目排行榜 10...   儿童早期教育行业超过半数的中国城市家庭,孩子每月花费占家庭总收入的20%以上,44%的家庭每月用...
银行创业贷款有什么条件 银行创... 一、银行创业贷款条件1、身份及营业场所证明贷款申请人必须具备合法有效的身份证明和在贷款行所在地合法居...
大学生创业银行贷款需要什么条件...   大学生创业贷款优惠政策  1大学毕业生在毕业后两年内自主创业,到创业实体所在地的工商部门办理营业...
5大适合大最适合大学生创业的项... 大学生创业能做什么?没有社会经验,经济基础差,可是思维活跃,学习能力强,这样的情况下,能做什么创业呢...
大学生创业计划项目有哪些 附... 创业已经不单单是社会人会做的事情了,现在的在校大学生,挥着毕业的大学生都会想着参与创业,为自己的成功...
2020创业选项目 2020创... 创业还是比较难的,首先要选好了创业的方向,并且要为之去不懈的努力,同时要有一定的抗压能力和坚韧不拔的...
2020年,创业者可以选择哪些... 危机其实是一个资源重新分配的过程,如果你不掌握资源,被动等待分配,最后仅有的那一点也将被拿走。这场疫...
就业创业牡丹项目包装 就业创业... 油用牡丹挂果了,李晓鹏很开心在西安当快递员的“90后”李晓鹏回铜川老家创业,种植油用牡丹。目前,陕西...
女性创业农村创业项目 女性创业... 女性在农村怎样创业,有哪些可以推荐的创业项目。以下是学习啦小编分享给大家的关于女性农村创业项目,一起...