目录
首先我们先定义头文件String.h,和命名空间_string防止和库中的string发生名字冲突。
#include <iostream>
#include <cassert>
#include <string.h>
using namespace std;
namespace _string {
class string {
public:
static size_t npos;
protected:
char* _str;
size_t _size;
size_t _capacity;
}
size_t string::npos = -1;
}
其是我们的string类里面就是一个顺序表, 所以我们会定义一个数组_str,_size为实际字符所占的大小,_capacity为容量,如果达到了最大的容量我们就是_size == _capacity 我们就要进行扩容处理.
1.构造函数
string(const char* str = "")
:_str(new char[strlen(str) + 1])
,_size(strlen(str))
,_capacity(strlen(str))
{
memcpy(_str, str, _size + 1);
}
这里我们为什么要多开一个空间strlen(str) + 1呢?因为我们这里字符串后面会有'\0',我们要多开一个位置给'\0',但是为什么_size和_capacity为什么不用+1呢?因为我们的'\0' 不算大小,所以我们_size和_capacity不需要给多一个空间.
这里需要注意的是,我们开好空间的时候不要忘记去把str的内容拷贝的_str中,这里拷贝_size + 1是因为有'\0'也是需要拷贝的.
2.析构函数
~string() {
if (_str) {
delete[] _str;
_size = _capacity = 0;
}
}
这里析构函数我认为,如果_str为空才需要释放空间,不为空不需要判断,所以我们这里加上一个判断,不为空的时候才可以去释放空间。
3.扩容
1.reserve(扩容不初始化)
void reserve(size_t n) {
if (n > _capacity) {
char* tmp = new char[n + 1];
memcpy(tmp, _str, _size + 1);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
扩容的时候需要进行判断,如果n大于容量扩容才有意义,如果小于就没有意义了
2.resize(扩容加初始化)
void resize(size_t n , char ch = '\0') {
if (n < _size) {
_size = n;
_str[_size] = '\0';
}
else {
reserve(n);
while (_size != n) {
_str[_size++] = '\0';
}
_str[_size] = '\0';
}
}
resize如果给的n容量会继续缩小,所以当n < _size的时候我们直接把n位置变为'\0'就可以了
如果这里的n > _ size,就会出现两种情况,一种是 _capacity > n > _size 还有一种是 n > _capacity
因为我们写了扩容reserve,里面有判断,所以我们之间继续复用就可以了,最后再完成数据的拷贝就可以了.
4.push_back
void push_back(char ch) {
if (_size == _capacity) {
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size++] = ch;
_str[_size] = '\0';
}
5.append
void append(const char* str) {
size_t len = strlen(str);
if (_size + len > _capacity) {
reserve(_size + len);
}
memcpy(_str+_size,str,len + 1);
_size += len;
}
push_back和append都一样,使用时应该要检查容量是否足够,不够的时候再扩容,最后完成数据的拷贝就可以了.
6. += 运算符重载
1.+=一个字符
string& operator+=(char ch) {
push_back(ch);
return *this;
}
2.+=一个字符串
string& operator+=(const char* str) {
append(str);
return *this;
}
我们上面实现了push_back()和append()所以这里我们实现复用就可以了.
7 []运算符重载
//只读
const char operator[](size_t pos) const {
assert(pos < _size);
return _str[pos];
}
//读写
char operator[](size_t pos) {
assert(pos < _size);
return _str[pos];
}
我们在进行重载的时候应该注意,pos的范围不能超过_size的大小.
8.find
1.找一个字符
找一个字符返回当前位置
//默认从0位置开始
size_t find(char ch, size_t pos = 0) {
assert(pos < _size);
for (size_t i = pos; i < _size; i++) {
if (_str[i] == ch) {
return i;
}
}
return npos;
}
2.找一个字符串
找一个字符串返回第一个字符出现的位置.
size_t find(const char* str, size_t pos) {
assert(pos < _size);
char* ptr = strstr(_str, str);
if (ptr) {
return ptr - _str;
}
else {
return npos;
}
}
什么两个如果都没有找到的话,会返回npos,npos是string中静态变量,为无符号数的最大值.
9.insert
1.插入一个字符
void insert(size_t pos , char ch) {
assert(pos < _size);
if (_size == _capacity) {
reserve(_capacity == 0 ? 4 : _capacity*2);
}
//'\0'也要一起移动
size_t end = _size;
while (end >= pos && end != npos) {
_str[end + 1] = _str[end];
end--;
}
_str[pos] = ch;
_size++;
}
2.插入一个字符串
void insert(size_t pos, const char* str) {
size_t len = strlen(str);
if (_size + len > _capacity) {
reserve(_size + len);
}
//移动数据
size_t end = _size;
while (end >= pos && end != npos) {
_str[end + len] = _str[end];
end--;
}
//拷贝数据
for (size_t i = 0; i < len; i++) {
_str[pos + i] = str[i];
}
_size += len;
}
我们两个函数都需要注意的是移动数据的时候这里是end--,如果没有npos != end,pos的位置是0,会造成死循环的问题,这是为什么?
没有npos != end,如果pos的位置是0,那么循环的结束条件应该是end < 0,但是这里的end是无符号数,当end到达-1时相当于正数的最大值,不可能小于0. 所以我们这里应该要加上npos != end;
9.erase
void erase(size_t pos = 0 , size_t len = npos) {
// '\0'也要移动所以可以等于 _size;
assert(pos <= _size);
if (pos + len >= _size || len == npos) {
_str[_size] = '\0';
_str[pos] = '\0';
_size = pos;
}
else {
size_t end = pos + len;
while (end <= _size) {
_str[pos++] = _str[end++];
}
_size -= len;
}
}
从pos位置开始删除,长度为len的字符. 如果pos + len >= _size 或者说 len说民删除完后面的字符, 直接把pos换成'\0'就可以了.
如果pos + len < _size 就移动数据,为什么end <= _size ? 因为'\0'也要被移动.
10.substr
string substr(size_t pos = 0,size_t len = npos) {
assert(pos < _size);
//调整len的大小
if (pos + len >= _size || len == npos) {
len = _size - pos;
}
//预留好空间
string tmp;
tmp.reserve(len);
for (size_t i = 0; i < len; i++)
{
tmp += _str[pos + i];
}
tmp += '\0';
return tmp;
}
不要忘记最后一个位置要加上'\0'
11.运算符重载比较大小
1.<
我们要注意有三种特殊的情况
hello helloo true
hello hell false
hello hello false
我们给两个长度len1,len2从0开始遍历碰到第一个小于就返回true,大于返回false,要是等于就继续len1和len2都要加加.
如果出了循环,我们看上面三种情况,只有len2 < str._size && len1 == _size 才能返回true
其他都是false,所以我们直接返回len2 < str._size && len1 == _size就可以了
bool operator<(const string& str) const {
size_t len1 = 0, len2 = 0;
while (len1 < _size && len2 < str._size) {
if (_str[len1] < str._str[len2]) {
return true;
}
else if (_str[len1] > str._str[len2]) {
return false;
}
else {
len1++;
len2++;
}
}
return len1 == _size && len2 < str._size;
}
2.==
bool operator==(const string& str) const {
if (_size != str._size) {
return false;
}
else {
size_t len1 = 0, len2 = 0;
while (len1 < _size && len2 < str._size) {
if (_str[len1] < str._str[len2]) {
return false;
}
else if (_str[len1] > str._str[len2]) {
return false;
}
else {
len1++;
len2++;
}
}
return true;
}
}
如果两个字符串的长度不相等直接返回false就可以了,长度如果相等有不相等的两个字符也是直接返回false,如果能出循环直接返回true;
我们上面实现了< 和 = 我们直接复用就可以了
3.<=
bool operator<=(const string& str) const {
return *this < str || *this == str;
}
4.>
bool operator>(const string& str) const {
return !(*this <= str);
}
5.>=
bool operator>=(const string& str) const {
return !(*this < str);
}
6.!=
bool operator!=(const string& str) const {
return !(*this == str);
}
12.拷贝构造
类里面默认生成的拷贝构造为浅拷贝,如果我们直接用默认生成的赋值,会造成析构函数释放同一块空间两次的问题编译器会就行报错,所以我们需要实现深拷贝.
string(const string& str) {
//要多给一个位置给'\0'
char* _str = new char[str._capacity + 1];
_size = str._size;
_capacity = str._capacity;
//'\0' 也要进行拷贝所以拷贝_size + 1
memcpy(_str,str._str,str._size + 1);
}
13.赋值 =
和上面的拷贝构造一样,必须要实现深拷贝否则编译器会报错.
void swap(string& str) {
std::swap(_str, str._str);
std::swap(_size, str._size);
std::swap(_capacity, str._capacity);
}
//str为临时变量出了作用域就会销毁
//交换后不用就行处理
string& operator=(string str) {
swap(str);
return *this;
}
这里有很多人不明白赋值这里参数为什么不传引用?这里的string没有引用是一个临时变量,处理作用域就会销毁.所以我们把*this 的值和str的值进行交换不会有任何的问题
这里的<< 和 >> 我们一般不喜欢写在类里面,如果写在类里面,第一个参数默认是this指针,那么我们调用的时候就需要反过来写,不符合我们的习惯.
str >> cin
str << cout
14.<<
//按照_size的大小来进行打印
//不是碰到'\0'就停止
ostream& operator<<(ostream& _cout, const string& str) {
for (size_t i = 0; i < str.size(); i++)
{
_cout << str[i];
}
return _cout;
}
这里需要注意的是打印的是按照_size去打印,不是碰到'\0'就停下.
15.>>
//每次输入之前都要清空
istream& operator>>(istream& _cin, string& str) {
//清空
str.clear();
char ch;
//给一buff数组来减小拷贝来提高效率
int i = 0;
char buff[128] = { 0 };
//读取前置空格
ch = _cin.get();
while (ch == '\n' || ch == ' ')
ch = _cin.get();
while (ch != '\n' && ch != ' ') {
buff[i++] = ch;
if (i == 127) {
//最后一个位置给'\0'
buff[i] = '\0';
str += buff;
i = 0;
}
ch = _cin.get();
}
//如果i不等于0说明里面还有字符
if (i != 0) {
buff[i] = '\0';
str += buff;
}
return _cin;
}
16.迭代器
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;
}
完整代码实现
namespace _String {
class string {
friend ostream& operator<<(ostream& _cout, const string& str);
friend istream& operator>>(istream& _cin, string& str);
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 = "")
:_str(new char[strlen(str) + 1])
, _size(strlen(str))
, _capacity(strlen(str))
{
memcpy(_str, str, _size + 1);
}
~string() {
if (_str) {
delete[] _str;
_size = _capacity = 0;
}
}
string(const string& str) {
//要多给一个位置给'\0'
char* _str = new char[str._capacity + 1];
_size = str._size;
_capacity = str._capacity;
//'\0' 也要进行拷贝所以拷贝_size + 1
memcpy(_str,str._str,str._size + 1);
}
void swap(string& str) {
std::swap(_str, str._str);
std::swap(_size, str._size);
std::swap(_capacity, str._capacity);
}
//str为临时变量出了作用域就会销毁
//交换后不用就行处理
string& operator=(string str) {
swap(str);
return *this;
}
//只读
const char operator[](size_t pos) const {
assert(pos < _size);
return _str[pos];
}
//读写
char operator[](size_t pos) {
assert(pos < _size);
return _str[pos];
}
// hello helloo true
// hello hell false;
// hello hello false
bool operator<(const string& str) const {
size_t len1 = 0, len2 = 0;
while (len1 < _size && len2 < str._size) {
if (_str[len1] < str._str[len2]) {
return true;
}
else if (_str[len1] > str._str[len2]) {
return false;
}
else {
len1++;
len2++;
}
}
return len1 == _size && len2 < str._size;
}
bool operator==(const string& str) const {
if (_size != str._size) {
return false;
}
else {
size_t len1 = 0, len2 = 0;
while (len1 < _size && len2 < str._size) {
if (_str[len1] < str._str[len2]) {
return false;
}
else if (_str[len1] > str._str[len2]) {
return false;
}
else {
len1++;
len2++;
}
}
return true;
}
}
bool operator<=(const string& str) const {
return *this < str || *this == str;
}
bool operator>(const string& str) const {
return !(*this <= str);
}
bool operator>=(const string& str) const {
return !(*this < str);
}
bool operator!=(const string& str) const {
return !(*this == str);
}
void reserve(size_t n) {
if (n > _capacity) {
char* tmp = new char[n + 1];
memcpy(tmp, _str, _size + 1);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void resize(size_t n, char ch = '\0') {
if (n < _size) {
_str[_size] = '\0';
_size = n;
_str[_size] = '\0';
}
else {
//会自己判断不用加
reserve(n);
while (_size != n) {
_str[_size++] = '\0';
}
_str[_size] = '\0';
}
}
void push_back(char ch) {
if (_size == _capacity) {
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size++] = ch;
_str[_size] = '\0';
}
void insert(size_t pos , char ch) {
assert(pos <= _size);
if (_size == _capacity) {
reserve(_capacity == 0 ? 4 : _capacity*2);
}
//'\0'也要一起移动
size_t end = _size;
while (end >= pos && end != npos) {
_str[end + 1] = _str[end];
end--;
}
_str[pos] = ch;
_size++;
}
void insert(size_t pos, const char* str) {
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity) {
reserve(_size + len);
}
//移动数据
size_t end = _size;
while (end >= pos && end != npos) {
_str[end + len] = _str[end];
end--;
}
//拷贝数据
for (size_t i = 0; i < len; i++) {
_str[pos + i] = str[i];
}
_size += len;
}
void erase(size_t pos = 0 , size_t len = npos) {
// '\0'也要移动所以可以等于 _size;
assert(pos <= _size);
if (pos + len >= _size || len == npos) {
_str[_size] = '\0';
_str[pos] = '\0';
_size = pos;
}
else {
size_t end = pos + len;
while (end <= _size) {
_str[pos++] = _str[end++];
}
_size -= len;
}
}
//如果len == npos 说明从pos位置开始截完全部
string substr(size_t pos = 0,size_t len = npos) {
assert(pos < _size);
//调整len的大小
if (pos + len >= _size || len == npos) {
len = _size - pos;
}
//预留好空间
string tmp;
tmp.reserve(len);
for (size_t i = 0; i < len; i++)
{
tmp += _str[pos + i];
}
tmp += '\0';
return tmp;
}
//默认从0位置开始
size_t find(char ch, size_t pos = 0) {
assert(pos < _size);
for (size_t i = pos; i < _size; i++) {
if (_str[i] == ch) {
return i;
}
}
return npos;
}
//默认0位置开始
size_t find(const char* str, size_t pos = 0) {
assert(pos < _size);
char* ptr = strstr(_str, str);
if (ptr) {
return ptr - _str;
}
else {
return npos;
}
}
void append(const char* str) {
size_t len = strlen(str);
if (_size + len > _capacity) {
reserve(_size + len);
}
//'\0'也要就行拷贝
memcpy(_str + _size, str, len + 1);
_size += len;
}
string& operator+=(char ch) {
push_back(ch);
return *this;
}
string& operator+=(const char* str) {
append(str);
return *this;
}
const char* c_str() const {
return _str;
}
size_t size() const {
return _size;
}
void clear() {
_str[0] = '\0';
_size = 0;
}
static size_t npos;
protected:
char* _str;
size_t _size;
size_t _capacity;
};
size_t string::npos = -1;
//按照_size的大小来进行打印
//不是碰到'\0'就停止
ostream& operator<<(ostream& _cout, const string& str) {
for (size_t i = 0; i < str.size(); i++)
{
_cout << str[i];
}
return _cout;
}
//每次输入之前都要清空
istream& operator>>(istream& _cin, string& str) {
//清空
str.clear();
char ch;
//给一buff数组来减小拷贝来提高效率
int i = 0;
char buff[128] = { 0 };
//读取前置空格
ch = _cin.get();
while (ch == '\n' || ch == ' ')
ch = _cin.get();
while (ch != '\n' && ch != ' ') {
buff[i++] = ch;
if (i == 127) {
buff[i] = '\0';
str += buff;
i = 0;
}
ch = _cin.get();
}
//如果i不等于0说明里面还有字符
if (i != 0) {
buff[i] = '\0';
str += buff;
}
return _cin;
}
}
如果有错误,欢迎各位大佬指正.