目录
前言
一、初始准备
二、迭代器
三、构造函数
四、赋值运算符重载
五、插入字符及字符串
六、查找字符及字符串
七、删除字符或字符串
八、重载关系运算符
九、重载+=运算符
十、析构函数
十一、重载输入输出运算符
十二、全部代码
前言
由于string类的接口函数很多,所以这里只实现常用的接口函数
部分很简单的接口函数,都放在代码段里了
一、初始准备
引用头文件
文章图片
引用string的头文件,是为了后面写接口函数,需要调用字符串函数,比如strstr,strcpy等等
引用assert的头文件,采用断言,从而方便方便调试
命名空间
文章图片
采用命名空间,则是为了避免与库里的变量,函数名重名
成员变量
文章图片
string类的实现,与顺序表类似,都是采用动态数组的形式存储数据,所以成员变量都一样,只是多了一个静态成员变量,因为查找字符或字符串时,可能会找不到,所以其用来作返回值
注意:这里的_size和_capacity都不包括'\0'
成员变量的交换
文章图片
C++标准库中有一个swap函数,但是效率不是很高,所以在String类中也有了一个swap函数,来交换两个对象的成员变量,在某些接口函数中可以使用,来提高效率
增加容量
文章图片
如果插入字符或者字符串时,空间不够了,就需要增容,因为采用C++的方式来开辟空间,不会像之前C语言直接用realloc增容,所以这里采用临时指针变量开辟空间,将原来空间的数据拷贝至新空间,指针指向新空间,销毁原来的空间,改变容量大小即可
注意:多开辟一个空间是为了存放'\0'
二、迭代器
文章图片
string类中的迭代器是通过指针实现的,找到起始地址和'\0'的地址即可实现对字符串的遍历
三、构造函数
默认构造函数
文章图片
采用初始化表的形式,同时设置一个'\0'的缺省值,加一个const,则是为了接收常量字符串
浅拷贝
文章图片
文章图片
像这种s2指向s1指向的空间,即s1与s2指向同一块空间的情况,就是浅拷贝,在调用析构函数时,同一块空间会被析构释放两次,造成程序崩溃,编译器默认的拷贝构造函数就是浅拷贝
深拷贝
文章图片
如上图,两个指针分别指向两块独立的空间,就是深拷贝,需要我们自己来实现
传统版拷贝构造函数
文章图片
先在初始化列表中,给_size和_capacity初始化,然后在函数体中给被拷贝的对象的存储数据的成员开辟空间,再进行拷贝数据
现代版拷贝构造函数
文章图片
文章图片
为了避免野指针问题,先给待拷贝的对象的存储数据的成员置为nullptr,然后创建临时对象,通过调用它的默认构造函数,把字符串拷贝过去,最后两个对象的成员进行交换,函数结束时,临时对象会通过析构函数被销毁,现代版写法,在某些时候极大地提高效率
四、赋值运算符重载
传统写法
文章图片
开辟新空间,将原来空间的数据拷贝过去,销毁原来空间,将原来空间的指针指向新空间,然后将其余成员变量赋值给被赋值的对象的成员变量
现代写法
文章图片
文章图片
先进行传值拷贝,所以就有了形参以及开辟的空间,然后改变形参的成员指针变量与被赋值的对象的成员指针变量的指针指向,现代写法,对效率也有很大提升
五、插入字符及字符串
尾插字符
文章图片
首先得判断是否还有空间,没有就增容,然后开辟的空间大小一般是原来的二倍,但也有可能开始就没有有效字符,即_capacity可能和_size都为0,所以需要判断一下;然后插入字符,有效字符个数+1,最后一个字节的空间插入'\0'即可
尾插字符串
文章图片
首先得判断插入的字符串的字符个数+原来字符串的字符个数(不包括'\0')与容量的大小,从而判断空间够不够,不够就增容,然后从原来字符串的‘\0’位置开始拷贝字符串,最后有效字符个数=原来的字符个数+新添加的字符个数
随机插入字符
??????
文章图片
文章图片
断言判断一下,插入的位置不能在\0在之后
首先判断容量大小,不够就增容,然后将pos指向的字符以及其后的字符全部向后挪一个位置,然后在pos指向的位置插入字符,有效字符个数+1,然后引用返回字符串
随机插入字符串
文章图片
文章图片
断言判断一下,第一个字符插入的位置不能在\0在之后
首先判断容量大小,不够就增容,然后将pos指向的字符以及其后的字符全部向后挪len个位置,然后在pos指向的位置开始插入字符串,有效字符个数+len,然后引用返回字符串(len是待插入的字符串有效字符个数)
六、查找字符及字符串
查找字符
文章图片
从第一个字符开始遍历,到最后一个有效字符,找得到就返回其下标,找不到返回-1
查找字符串
文章图片
借助strstr函数,即字符串查找函数,找到了就返回找到的字符串的第一个字符下标,找不到就返回-1
七、删除字符或字符串
文章图片
断言一下,保证删除的字符都在’\0‘之前
文章图片
首先给一个npos的缺省值,如果删除的长度为npos,即在传参时没有给长度,或从删除的第一个字符的下标+ 待删除的字符个数比有效字符个数大于或等于总字符个数时,则将pos指向的字符及其后面的字符全部删除,再将pos指向的位置添加’\0‘,有效字符个数就为pos的大小
文章图片
或者将后面的字符往前挪len个位置(len为待删除的字符个数)
八、重载关系运算符
<(小于)
文章图片
比较两个字符串,从第一个字符开始比较,如果s1小于s2,就返回true,大于就false,等于就比较下一个字符,如果前面都相等,只是一个长一点,s2的字符还没走完就true,否则就false
==(等于)
文章图片
取出两个字符串的第一个字符的起始地址,然后用strcmp比较,等于0就true,否则就false
其余的关系运算符复用这两个重载的运算符即可
九、重载+=运算符
文章图片
直接调用前面的尾插字符或字符串函数即可
十、析构函数
文章图片
与顺序表类似,将开辟的空间释放,成员变量置为nullptr或0
十一、重载输入输出运算符
重载输出运算符
文章图片
将每一个字符都遍历一次输出即可
注意:不能直接写为out << c_str(),因为如果字符串中间有一个'\0',就会截止,就达不到想达到的效果了
重载输入运算符
文章图片
先清空有效数据,输入字符串,再从缓冲区中+=一个一个字符,遇到空格或换行符,则读取截止
注意:重载的输入输出运算符,其中的两个参数不能颠倒,除非在使用时也颠倒,否则会报错
十二、全部代码
//全部代码
#pragma once
#include
#include
#include
using namespace std;
namespace Henry
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
//迭代器
iterator begin()
{
return _str;
}iterator end()
{
return _str + _size;
}const_iterator begin() const
{
return _str;
}const_iterator end() const
{
return _str + _size;
}//默认构造
string(const char* str = "") : _size(strlen(str)), _capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, str);
}//拷贝构造
//传统写法
/*string(const string& s) : _size(s._size), _capacity(s._capacity)
{
_str = new char[_capacity + 1];
strcpy(_str, s._str);
}*///现代写法
string(const string& s):_str(nullptr),_size(0),_capacity(0)
{
string tmp(s._str);
swap(tmp);
}void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}//赋值重载
//传统写法
string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}//现代写法
string& operator=(string s)
{
swap(s);
return *this;
}//增容
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
_capacity = n;
delete[] _str;
_str = tmp;
}
}void resize(size_t n, char ch = '\0')
{
if (n <= _size)
{
_size = n;
_str[_size] = '\0';
}
else
{
if (n > _capacity)
{
reserve(n);
}
memset(_str + _size, ch, n - _size);
_size = n;
_str[_size] = '\0';
}
}//尾部添加字符
void push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}//尾部添加字符串
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}//+=重载
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}//随机插入字符
string& Insert(char ch,size_t pos)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = ch;
_size++;
return *this;
}//随机插入字符串
string& Insert(const char* str, size_t pos)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size + len;
_size += len;
while (end > pos + len)
{
_str[end] = _str[end - len];
end--;
}
strncpy(_str + pos, str, len);
return *this;
}//查找字符
size_t find(char ch)
{
for (size_t i = 0;
i < _size;
i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
//找不到
}//查找字符串
size_t find(const char* str,size_t pos = 0)
{
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}//删除字符
string& erase(size_t pos,size_t len = npos)
{
assert(pos < _size);
if(len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}return *this;
}size_t size() const
{
return _size;
}//重载[]
char& operator[](size_t i)
{
return _str[i];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}void clear()
{
_str[0] = '\0';
_size = 0;
}const char* c_str() const
{
return _str;
}//析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
private:
char* _str;
size_t _size;
size_t _capacity;
public:
static const size_t npos = -1;
};
//判断大小
bool operator<(const string& s1,const string& s2)
{
int i = 0;
int j = 0;
while (i < s1.size() && j < s2.size())
{
if (s1[i] < s2[j])
{
return true;
}
else if (s1[i] > s2[j])
{
return false;
}
else
{
++i;
++j;
}
}
return j < s2.size() ? true : false;
} bool operator==(const string& s1,const string& s2)
{return strcmp(s1.c_str(), s2.c_str()) == 0;
} bool operator>=(const string& s1,const string& s2)
{
return !(s1 < s2);
} bool operator<=(string& s1, string& s2)
{
return s1 < s2 || s1 == s2;
} bool operator!=(string& s1, string& s2)
{
return !(s1 == s2);
} bool operator>(string& s1, string& s2)
{
return !(s1 <= s2);
} ostream& operator<<(ostream& out,const string& s)
{
for (size_t i = 0;
i < s.size();
i++)
{
out << s[i];
}
return out;
} istream& operator>>(istream& in,string& s)
{
s.clear();
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
}
【c++|模拟实现string类】
推荐阅读
- Go语言|Go语言编程笔记1(Hello World)
- RBE104TC 分析
- python|来聊聊SourceMap
- JavaWeb|浅谈JVM
- Go语言|【Golang】做算法题可能会用到的知识
- 简单搜索|Smallest Difference POJ-2718(全排列)
- 玩转JAVA系列|【JavaSE】集合框架及背后的数据结构
- 进阶C语言|动态内存管理
- 进阶C语言|程序环境和预处理