从零开始学c++———模拟实现string类(常用接口的实现)
Posted sjp11
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从零开始学c++———模拟实现string类(常用接口的实现)相关的知识,希望对你有一定的参考价值。
string
1.前言
之前学到了string类常用接口的后,我很好奇string类在底层是怎样实现的,在由之前学习到c++有关的之后,所以我打算模拟实现一下string类常用的接口,以便加深我对之前的知识的理解,和更好的去使用string类,比如在哪些场景使用哪些接口较为合适。接下来我会用c++来模拟实现各个接口。
2.string类常用接口实现
string类就是存储字符的顺序表,它的实现与与顺序表的实现是非常相似,主要接口为增删查改。实现如下
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
#include<assert.h>
using namespace std;
//定义一个属于自己的域
namespace sjp
{
class string
{
private:
char* _str;
size_t _size;//字符串中的有效的字符个数
size_t _capacity;//字符串中的最大容量
static const size_t npos;//npos是string类对象中的一个静态成员变量,为整数的最大值
public:
//构造函数
string(const char* str ="")
{
_size = strlen(str);//计算出初始化字符串的大小
_capacity = _size;
_str = new char[_capacity+1];//开辟空间多开辟一块空间,这块空间留给\\0
strcpy(_str, str);
}
//交换函数,可以与另一个sting对象交换数据
void swap(string& s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
//析构函数,清理资源
~string()
{
delete _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
//拷贝构造,对象创建时,可以将另一个string对象的数据拷贝给要创建的对象
string(const string& s)
{
sjp::string tmp(s._str);
swap(tmp);//函数调用结束时tmp会被析构函数给清理掉
}
//迭代器,string的迭代器的底层就是指针
typedef char* iterator;
iterator begin()
{
return this->_str;//返回字符串首元素的地址
}
iterator end()
{
return this->_str + _size;//返回字符串最后一个地址
}
// 返回字符的个数
size_t size()
{
return _size;
}
//预留字符串的空间,只改变_capacity,不改变初始化空间
void reserve(size_t capacity)
{
//如果预留的空间大于原本的空间,直接开辟一块capacity的空间,并把数据拷贝给这块空间
if (capacity > _capacity)
{
char* str= new char[capacity];
strcpy(str, _str);
_str = str;
_capacity = capacity;
}
//如果capacity小于_size,则_capacity不做改变,_size改变即可
else if(capacity<_size)
{
_size = capacity;
_str[_size] = '\\0';
}
}
//预留空间,并且初始化这块空间,既改变capacity和size
void resize(size_t capacity,char ch='\\0')
{
if (capacity < _size)
{
_size = capacity;
_str[_size] = '\\0';
}
else
{
if (capacity > _capacity)
{
reserve(capacity);
}
memset(_str + _size, ch, (capacity-_size));//memset是创建一块空间,并初始化这块空间
_str[_size] = '\\0';
_size = capacity;
}
}
//尾插一个字符
void push_back(char ch)
{
if (_size == _capacity)//判断字符串的空间是否满了
{
size_t newcapacity = _capacity == 0 ? 5 : _capacity * 2;
reserve(newcapacity);
_capacity = newcapacity;
}
_str[_size] = ch;
_size += 1;
_str[_size] = '\\0';//最后记得加上\\0
}
//尾插一个字符串
void append(const char* str)
{
size_t len = strlen(str);//尾插的字符串的个数
if (len + _size > _capacity)//原来的字符串的个数和要插入的字符的个数大于字符串的空间
{
reserve(len + _size);//增容
}
strcpy(_str + _size, str);
_size += len;
}
//操作符+=重载,尾插一个字符串
string& operator+=(const char* str)
{
append(str);
return *this;
}
//操作符+=重载,尾插一个string类对象
string& operator+=(const string& s)
{
append(s._str);
return *this;
}
//操作符+=重载,尾插一个字符
string& operator+=(const char ch)
{
push_back(ch);
return *this;
}
//操作符[]重载,访问字符串中的某个字符,既可读也可以写,const的string不能调用它
char& operator[](size_t i)
{
return _str[i];
}
//操作符[]重载,访问字符串中的某个字符,只能读不能写,const调用它
const char& operator[](size_t i)const
{
return _str[i];
}
//在某个位置插入一个字符
string& insert(size_t pos,char ch)
{
assert(pos < _size);
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 5 : _capacity * 2;
reserve(newcapacity);
_capacity = newcapacity;
}
//不能这样实现,pos为0时,end减到-1时,由于end和pos是无符号,所以end会变成最大整数,一直无限循环
/*size_t end = _size - 1;
while (end >= pos)
{
_str[end - 1] = _str[end];
end--;
}
*/
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* s)
{
assert(pos < _size);
size_t len = strlen(s);
if (len + _size > _capacity)
{
reserve(len + _size);
}
char* end = _str+_size;
while (end >=_str+pos)
{
*(end + len) = *end;
end--;
}
size_t cur = pos;
strncpy(_str + pos, s, len);
_size += len;
return *this;
}
//在某个位置去掉多少个字符
string& erase(size_t pos,size_t n=npos)
{
assert(pos < _size);
size_t leftlen = _size - pos;//leftlen是尾上
if (leftlen <= n)
{
_size = pos;
_str[_size] = '\\0';//注意
}
else
{
strcpy(_str + pos, _str + pos + n);
_size -= n;
}
return *this;
}
//从pos位置开始查找查找一个字母
size_t find(const char ch,size_t pos=0)
{
assert(pos < _size);
for (int i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
//找不到返回npos
return npos;
}
//从某个位置开始,查找一个字符串,如果找到,返回该字符串的位置,找不到返回npos
size_t find(const char* ch, size_t pos = 0)
{
assert(pos < _size);
//strstr(s1,s2)的功能是判断s2是否为s1的字串,并且返回s2在s1中的地址,找不到返回nullptr
const char* ret = strstr(_str+pos, ch);
/*char* cur = _str;
while (cur < _str+_size)
{
if (cur == tmp)
{
return cur - _str;
}
cur++;
}*/
if (ret)
{
return ret - _str;
}
else
{
return npos;
}
return npos;
}
//访问对象中的字符串
char* s_str()const
{
return _str;
}
//判断字符串是否为空
bool empty()const
{
return _size == 0;
}
//清空字符串
void clear()
{
_size = 0;
_str[_size] = '\\0';
}
};
const size_t string::npos = -1;
//下面这几个为比较两个string对象的大小的操作符重载
bool operator==(const sjp::string& s1, const sjp::string& s2)
{
return strcmp(s1.s_str(), s2.s_str()) == 0;
}
bool operator>(const sjp::string& s1, const sjp::string& s2)
{
return strcmp(s1.s_str(), s2.s_str()) > 0;
}
bool operator<(const sjp::string& s1, const sjp::string& s2)
{
return !(s1 == s2) || !(s1 > s2);
}
bool operator>=(const sjp::string& s1, const sjp::string& s2)
{
return !(s1 < s2);
}
bool operator<=(const sjp::string& s1, const sjp::string& s2)
{
return !(s1 > s2);
}
bool operator!=(const sjp::string& s1, const sjp::string& s2)
{
return !(s1 == s2);
}
//操作符<<重载,使cout<<能够输出string对象
ostream& operator<<(ostream& out, string& s)
{
for (auto ch: s)//范围for的使用
{
out << ch;
}
return out;
}
//操作符>>重载使cin>>能够输入string对象
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch = in.get();//in.get()能够获取空格和回车字符
while (ch != ' ' && ch != '\\n')//如果ch为空格或回车,将跳出输入
{
s += ch;
ch = in.get();
}
return in;
}
}
3.总结
1.strstr(char* s1,char* s2)判断s2是否为s1的字串,如果是,则返回该字串在s1中的地址位置,如果不是返回空。
strcpy(char* s1,char* s2)将s2中的字符拷贝给s1,包括\\0;
memset(void* str, ch, size) 从str的位置开始,开辟size个字节的大小,并将开辟的空间初始为ch
2.写istream和iostream的重载时,记得返回的值是istream&或iostream&(注意是引用),参数也是istream&或iostream&
3.写构造函数需要多开辟一个空间给\\0,
4.如果没使用strcpy,删除或则增加需要记得在结尾_str[_size]=‘\\0’.
模拟实现string常用接口后,我们可以知道string的迭代器的底层是指针,修改数据尽量少用insert,erase,因为它们的使用需要挪动数据,时间复杂度为O(n^2),效率太低了,reserve与resize的区别。
点个赞呗~!!!!
以上是关于从零开始学c++———模拟实现string类(常用接口的实现)的主要内容,如果未能解决你的问题,请参考以下文章
C/C++编程笔记:C++单例模式详细解析!从零开始学懂单例