C++模拟实现string的示例代码

这篇文章主要为大家详细介绍了C++模拟实现string的相关资料,文中的示例代码讲解详细,对我们学习C++有一定的帮助,需要的可以参考一下

一、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 >(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); //} //写法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 >(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的资料请关注0133技术站其它相关文章!

以上就是C++模拟实现string的示例代码的详细内容,更多请关注0133技术站其它相关文章!

赞(0) 打赏
未经允许不得转载:0133技术站首页 » C语言