默认成员函数
构造函数
构造函数设置为缺省参数,若不传入参数,则默认构造为空字符串。字符串的初始大小和容量均设置为传入C字符串的长度(不包括’\0’)
//默认构造函数-全缺省
string(const char * str ="")//常量字符串以\0为结束标志
{
_size = strlen(str);
_capacity = strlen(str);//capacity存储有效字符,\0不是有效字符
_str = new char [strlen(str) + 1];//strlen 计算字符不包含\0,但是需要多开一个空间给\0
strcpy(_str, str);
}
拷贝构造函数
在模拟实现拷贝构造函数前,我们应该首先了解深浅拷贝:
浅拷贝:拷贝出来的目标对象的指针和源对象的指针指向的内存空间是同一块空间。其中一个对象的改动会对另一个对象造成影响。
深拷贝:深拷贝是指源对象与拷贝对象互相独立。其中任何一个对象的改动不会对另外一个对象造成影响。
深拷贝写法一:
string(const string& s)
:_str(new char[strlen(s._str) + 1]) //_str申请一块刚好可以容纳s._str的空间
, _size(0)
, _capacity(0)
{
strcpy(_str, s._str); //将s._str拷贝一份到_str
_size = s._size; //_size赋值
_capacity = s._capacity; //_capacity赋值
}
先开辟一块足以容纳源对象字符串的空间,然后将源对象的字符串拷贝过去,接着把源对象的其他成员变量也赋值过去即可。因为拷贝对象的_str与源对象的_str指向的并不是同一块空间,所以拷贝出来的对象与源对象是互相独立的。
深拷贝写法二(推荐):
//第二种写法
string(const string& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
string tmp(s._str); //调用构造函数,构造出一个C字符串为s._str的对象
swap(tmp); //交换这两个对象
}
拷贝对象的_str与源对象的_str指向的也不是同一块空间,是互相独立的。
赋值运算符重载函数
与拷贝构造函数类似,赋值运算符重载函数的模拟实现也涉及深浅拷贝问题,我们同样需要采用深拷贝。下面也提供深拷贝的几种写法:
//第一种写法
//s1=s3
string& operator=(const string& s)
// string& operator=( string *this ,const string& s)
{
if (this!= &s)//不是自己给自己赋值
{
//开辟一块和s3一样大的空间tmp,将s3的内容拷贝到tmp中,释放s1,s1的指针指向tmp
char* tmp = new char[s._capacity + 1];//+1 是给\0的
memcpy(tmp, s._str, s._size + 1);
delete[] this->_str;
this->_str = tmp;
this->_size = s._size;
this->_capacity = s._capacity;
}
return *this;
}
这种写法是通过采用“值传递”接收右值的方法,让编译器自动调用拷贝构造函数,然后我们再将拷贝出来的对象与左值进行交换即可。
//第2种写法(推荐)
//s1=s3
string& operator=(string tmp) //编译器接收右值的时候自动调用拷贝构造函数
//string& operator=( string *this ,string tmp)
{
//this->swap(tmp)
swap(tmp);//s1和tmp交换
return *this;
}
但是第二种写法无法避免自己给自己赋值,就算是自己给自己赋值这些操作也会进行,虽然操作之后对象中_str指向的字符串的内容不变,但是字符串存储的地址发生了改变,为了避免这种操作我们可以采用下面这种写法:
//第三种写法
//s1=s3
string& operator=(const string& s)
// string& operator=( string *this ,const string& s)
{
if (this != &s)//不是自己给自己赋值
{
string tmp(s);
//this->swap(tmp);//this就是s1
swap(tmp);//s1和tmp交换,tmp指向s1 ,并且出了作用域,tmp就销毁了
}
return *this;
}
析构函数
string类的析构函数需要我们进行编写,因为每个string对象中的成员_str都指向堆区的一块空间,当对象销毁时堆区对应的空间并不会自动销毁,为了避免内存泄漏,我们需要使用delete手动释放堆区的空间
~string()
{
delete []_str;
_str = nullptr;
_size = _capacity = 0;
}
begin
begin函数的作用就是返回字符串中第一个字符的地址
iterator begin()
{
return _str; //返回字符串中第一个字符的地址
}
const_iterator begin()const
{
return _str; //返回字符串中第一个字符的const地址
}
end
end函数的作用就是返回字符串中最后一个字符的后一个字符的地址(即’\0’的地址)
iterator end()
{
return _str + _size; //返回字符串中最后一个字符的后一个字符的地址
}
const_iterator end()const
{
return _str + _size; //返回字符串中最后一个字符的后一个字符的const地址
}
用迭代器遍历string的代码,其实就是用指针在遍历字符串
string s("hello world!!!");
string::iterator it = s.begin();
//auto it = s.begin();
while (it != s.end())
{
cout << *it << " ";
it++;
}
cout << endl;
范围for与迭代器,代码编译的时候,编译器会自动将范围for替换为迭代器的形式,也就是说范围for的底层就是迭代器,我们已经实现了string类的迭代器,自然也能用范围for对string进行遍历
string s("hello world!!!");
//编译器范围for将其替换为迭代器形式
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
size
size函数用于获取字符串当前的有效长度(不包括’\0’)
//大小
size_t size()const
{
return _size; //返回字符串当前的有效长度
}
capacity
capacity函数用于获取字符串当前的容量
//容量
size_t capacity()const
{
return _capacity; //返回字符串当前的容量
}
reserve
1、当n大于对象当前的capacity时,将capacity扩大到n或大于n。
2、当n小于对象当前的capacity时,什么也不做。
void reserve(size_t n)//扩容
{
if (n > _capacity)
{
char* tmp = new char [n + 1];//+1 是给\0开辟的空间
strcpy(tmp, _str);//将对象原本的C字符串拷贝过来(包括'\0')
delete[] _str;//释放对象原本的空间
_str = tmp;//将新开辟的空间交给_str
_capacity = n;
}
}
erase
//版本一
//void erase(size_t pos, size_t len = npos)
//{
// assert(pos <= _size);
// //全部删完
// size_t n = _size - pos;//pos位置后面的字符
// if (len > n)
// {
// _str[pos] = '\0';//pos位置放\0
// _size = pos;
// }
// //删除一部分,n不完全删完
// else
// {
// //需要删除的部分数据 的后面几个覆盖需要删除的数据
// strcpy(_str + pos, _str + pos + len); //用需要保留的有效字符覆盖需要删除的有效字符
// _size -= len; //size更新
// }
//}
//版本二
void erase(size_t pos, size_t len = npos)
{
assert(pos <= _size);
//全部删完
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
//删除一部分,n不完全删完
else
{
//需要删除的数据的 后几个数据 往前覆盖
size_t end = pos + len;
while (end <= _size)
{
_str[pos++] = _str[end++];
}
_size -= len;
}
}
size_t find(char ch, size_t pos = 0)
{
assert(pos < _size);
//遍历string
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)
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, str);
if (ptr)//ptr!= nullptr
{
return ptr - _str;
}
return npos;
}
resize
1、当n大于当前的size时,将size扩大到n,扩大的字符为ch,若ch未给出,则默认为’\0’。
2、当n小于当前的size时,将size缩小到n。
//改变大小
void resize(size_t n, char ch = '\0')
{
if (n <= _size) //n<_size
{
_size = n; //将size调整为n
_str[_size] = '\0'; //在size个字符后放上'\0'
}
else //n>_size
{
if (n > _capacity) //判断是否需要扩容
{
reserve(n); //扩容
}
//n>_size && n<_capacity
for (size_t i = _size; i < n; i++) //将size扩大到n,扩大的字符为ch
{
_str[i] = ch;
}
_size = n; //size更新
_str[_size] = '\0'; //字符串后面放上'\0'
}
}
push_back
push_back函数就是在当前字符串尾插一个字符,尾插之前需要判断是否需要增容,若需要,则调用reserve函数进行增容,然后再尾插字符,注意尾插完字符后需要在该字符的后方设置上’\0’
void push_back(char ch )
{
//2倍扩容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';//字符串后面放上'\0'
}
append
append函数在当前字符串的后面追加一个字符串,追加前需要判断_size + len 和_capacity之间的关系 ,判断是否需要增容,然后再将待追加的字符串追加到对象的后方,因为待尾插的字符串后方自身带有’\0’,所以我们无需再在后方设置’\0’
void append(const char* str)
{
//至少扩容到_size+len
size_t len = strlen(str);
if (_size + len > _capacity)//_size+len<=_capacity都不需要扩容
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
operator+=
//+=运算符重载
string& operator+=(char ch)
//string & operator+= (string *this , const char * str)
{
push_back(ch); //尾插字符串
return *this; //返回左值(支持连续+=)
}
string & operator+= (const char ch)
//string & operator( string * this ,const char ch)
{
push_back(ch);
return *this;
}
insert
insert函数的作用是在字符串的任意位置插入字符或是字符串。
insert函数用于插入字符时,首先需要判断pos的合法性,若不合法则无法进行操作,紧接着还需判断当前对象能否容纳插入字符后的字符串,若不能则还需调用reserve函数进行扩容。插入字符的过程也是比较简单的,先将pos位置及其后面的字符统一向后挪动一位,给待插入的字符留出位置,然后将字符插入字符串即可。
void insert(size_t pos, size_t n, char ch)//插入字符
{
assert(pos <= _size);
//扩容
if (n + _size > _capacity)
{
//至少扩容到_size+n
reserve(_size+n);
}
挪动数据 版本一
//int end = _size;
//while (end>=(int)pos) //int 和 size_t 在比较中涉及到算术转换,int需要转换为unsigned int
//{
// _str[end + n] = _str[end];
// end--;
//}
//挪动数据 版本二
size_t end = _size;
while (end >= pos && end!=npos ) //end是size_t ,end会等于-1 ,加了end!=npos,end就不会等于-1了
{
_str[end + n] = _str[end];
end--;
}
//插入数据
for (size_t i = 0; i < n; i++)
{
_str[pos+i] = ch;
}
_size += n;
}
void insert(size_t pos, const char* str)//插入字符串
{
assert(pos <= _size);
//扩容
size_t len = strlen(str);
if (_size + len >= _capacity)
{
reserve(_size + len);
}
//挪动数据
int end = _size;
while (end>=(int)pos)
{
_str[end + len] = _str[end];
--end;
}
//插入数据
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}
swap
swap函数用于交换两个对象的数据,直接调用库里的swap模板函数将对象的各个成员变量进行交换即可。但我们若是想在这里调用库里的swap模板函数,需要在swap函数之前加上“std::”,告诉编译器在c++标准库寻找swap函数,否则编译器编译时会认为你调用的是正在实现的swap函数(就近原则)。
//交换两个对象的数据
void swap(string& s)
{
//调用库里的swap
::swap(_str, s._str); //交换两个对象的C字符串
::swap(_size, s._size); //交换两个对象的大小
::swap(_capacity, s._capacity); //交换两个对象的容量
}
substr
在str中从pos位置开始,截取n个字符,然后将其返回
string substr(size_t pos = 0, size_t len = npos)
{
assert(pos < _size);
size_t n = len;
//子串的长度大于源字符串,源字符串有多大,子串就有多大
if (len == npos || pos + len > _size)
{
n = _size - pos;
}
//子串的长度小于源字符串,将源字符串和子串匹配的内容拷贝到tmp中
string tmp;
tmp.reserve(n);
for (size_t i = pos; i < pos + n; ++i)
{
tmp += _str[i];
}
return tmp;
}
c_str
c_str函数用于获取C形式的字符串,实现时直接返回对象的成员变量_str即可。
c_str将string对象转换为C类型字符串,以便与C语言的函数或者需要以C风格字符串作为参数的函数进行交互。
//返回C类型的字符串
const char* c_str() const //将string转换为C类型的字符串,以便与C语言代码进行交互
{
return _str;
}
void Test_string10()
{
cxq::string s1("hello world");
s1 += '\0';
s1 += "!!!!!!";//hello world\0!!!!!!\0
cout << s1.c_str() << endl;//c形式的字符串遇到\0就终止,打印的结果就是hello world\0
cout << s1 << endl;//打印_size个数的字符,而不是遇到\0就终止
}
如果用 cout << s1.c_str() << endl,c形式的字符串遇到第一个\0就终止,打印的结果就是hello world\0
但是用cout << s1 << endl,打印的是s1中的_size个数的字符,而不是遇到\0就终止
总结:
c的字符数组以\0为终止算长度
string不看\0,以_size为终止算长度
operator[ ]
[ ]运算符的重载是为了让string对象能像C字符串一样,通过[ ] +下标的方式获取字符串对应位置的字符
char & operator[](size_t pos) //可读可写
// char & operator[]( char *this ,size_t pos )
{
assert(pos <_size );
return _str[pos];//出了作用域,对象还在
}
const char& operator[](size_t pos) const //只能读
// char & operator[]( char *this ,size_t pos )
{
assert(pos < _size);
return _str[pos];//出了作用域,对象还在
}
find
find在字符串中查找一个字符或是字符串,即从字符串开头开始向后查找
//正向查找第一个匹配的字符
size_t find(char ch, size_t pos = 0)
{
assert(pos < _size);
//遍历string
for (size_t i = 0; i < _size; ++i)
{
if (_str[i] == ch)
{
return i;
}
}
//没有找到就返回npos
return npos;//npos是string类的一个静态成员变量,其值为整型最大值
}
//正向查找第一个匹配的字符串
size_t find(const char* str, size_t pos = 0)
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, str);//strstr函数若是找到了目标字符串会返回字符串的起始位置,若是没有找到会返回一个空指针
if (ptr )//ptr!= nullptr
{
return ptr-_str;//计算目标字符串的起始位置和对象C字符串的起始位置的差值,进而得到目标字符串起始位置的下标
}
return npos;
}
clear
clear函数用于将对象中存储的字符串置空,实现时直接将对象的_size置空,然后在字符串后面放上’\0’即可
//清空字符串
void clear()
{
_size = 0; //size置空
_str[_size] = '\0'; //字符串后面放上'\0'
}
getline
getline函数用于读取一行含有空格的字符串。实现时于>>运算符的重载基本相同,只是当读取到’\n’的时候才停止读取字符
//读取一行含有空格的字符串
istream& getline(istream& in, string& s)
{
s.clear();
//分割多个string用空格或者换行,getline可以读取空格
char ch =in.get();//读取第一个字符
while (ch == '\n')
{
s += ch;将读取到的字符尾插到字符串后面
ch = in.get(); //继续读取字符
}
}
>>运算符的重载
重载>>运算符是为了让string对象能够像内置类型一样使用>>运算符直接输入。
输入前我们需要先将对象的C字符串置空,然后从标准输入流读取字符,直到读取到’ ‘或是’\n’便停止读取。
istream& operator>> (istream& in, string& s)//流提取
{
//get函数,无论什么字符都能从流中读取,包括换行和空格
char ch = in.get();//从输入流中读取一个字符,并将其赋值给变量ch
流提取多个string用空格或者换行分割
// 处理前缓冲区前面的空格或者换行
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
<<运算符的重载
重载<<运算符是为了让string对象能够像内置类型一样使用<<运算符直接输出打印
ostream& operator<< (ostream& out, const string& str) //流插入
{
/* for (size_t i = 0; i < str.size(); ++i)
{
out << str[i];
}*/
for (auto ch : str)
{
out << ch;
}
return out;
}
说明:
为了防止库冲突,写在自己定义的命名空间内
整个模拟实现的代码都是写在.h文件下的, 不可以.h放声明, .cpp放定义(不可以声明和定义分离), 因为模板不支持分离编译文章来源:https://www.toymoban.com/news/detail-606913.html
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once //防止头文件被多次包含
#include<assert.h>
#include<iostream>
#include<string>
namespace cxq
{
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()
// :_size(0)
// ,_capacity(0)
// ,_str(new char [1]) // 如果是_str(nullptr)这种写法,会导致nullptr解引用
//{
// _str[0] = '\0';//开一个空间放\0,
//}
//默认构造函数-全缺省
string(const char* str = "")//常量字符串以\0为结束标志
{
_size = strlen(str);
_capacity = strlen(str);//capacity存储有效字符,\0不是有效字符
_str = new char[strlen(str) + 1];//strlen 计算字符不包含\0,但是需要多开一个空间给\0
/* strcpy(_str, str);*/
memcpy(_str, str, _size + 1);
}
//string(const char * str )
// :_size(strlen(str))
// ,_capacity(strlen(str))//capacity存储有效字符,\0不是有效字符
// , _str(new char [_capacity +1] ) //strlen 计算字符不包含\0,但是需要多开一个空间给\0
//{
// strcpy(_str, str);
//}
//拷贝构造
string(const string& s)
{
_str= new char[s._capacity + 1]; //+1 给\0开辟空间
//拷贝
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
//第一种写法
//string& operator=(const string& s)
string& operator=( string *this ,const string& s)
// //s1=s3
//
//{
//
// if (this!= &s)//不是自己给自己赋值
// {
// //开辟一块和s3一样大的空间tmp,将s3的内容拷贝到tmp中,释放s1,s1的指针指向tmp
// char* tmp = new char[s._capacity + 1];//+1 是给\0的
// memcpy(tmp, s._str, s._size + 1);
// delete[] this->_str;
// this->_str = tmp;
// this->_size = s._size;
// this->_capacity = s._capacity;
// }
// return *this;
//}
第二种写法
//string& operator=(const string& s)
// // string& operator=( string *this ,const string& s)
// //s1=s3
//{
// if (this != &s)//不是自己给自己赋值
// {
// string tmp(s);
// //this->swap(tmp);//s1和tmp交换,tmp指向s1 ,并且出了作用域,tmp就销毁了
// swap(tmp);//s1和tmp交换,tmp指向s1 ,并且出了作用域,tmp就销毁了
// }
// return *this;
//}
//第三种写法(推荐)
string& operator=(string tmp) //s1=s3
//string& operator=( string *this ,string tmp)
{
//this->swap(tmp)
swap(tmp);//s1和tmp交换
return *this;
}
~string()
{
delete[]_str;
_str = nullptr;
_size = _capacity = 0;
}
const char* c_str() const //将string转换为C风格的字符串,以便与C语言代码进行交互
{
return _str;
}
size_t size()const
{
return _size;
}
char& operator[](size_t pos)
// char & operator[]( char *this ,size_t pos )
{
assert(pos < _size);
return _str[pos];//出了作用域,对象还在
}
const char& operator[](size_t pos) const
// char & operator[]( char *this ,size_t pos )
{
assert(pos < _size);
return _str[pos];//出了作用域,对象还在
}
void reserve(size_t n)//扩容
{
if (n > _capacity)
{
cout << "reserve()->" << n << endl;
char* tmp = new char[n + 1];//+1 是给\0开辟的空间
memcpy(tmp, _str, _size + 1);
delete[] _str;
_str = tmp;//无法理解
_capacity = n;
}
}
void resize(size_t n, char ch = '\0')
{
//n<= _size
if (n <= _size)
{
_size = n;
_str[_size] = '\0';
}
//n>_size
else
{
//判断是否需要扩容
if (n > _capacity)
{
reserve(n);
}
//n>_size && n<_capacity,不需要扩容
//插入数据
for (size_t i = _size; i < n; ++i)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
}
void push_back(char ch)
{
//2倍扩容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
void append(const char* str)
{
//至少扩容到_size+len
size_t len = strlen(str);
if (_size + len > _capacity)//_size+len<=_capacity都不需要扩容
{
reserve(_size + len);
}
/*strcpy(_str + _size, str);*/
memcpy(_str + _size, str, len + 1);
_size += len;
}
string& operator+= (const char* str)
//string & operator+= (string *this , const char * str)
{
append(str);
return *this;
}
string& operator+= (const char ch)
//string & operator( string * this ,const char ch)
{
push_back(ch);
return *this;
}
void insert(size_t pos, size_t n, char ch)//插入字符
{
assert(pos <= _size);
//扩容
if (n + _size > _capacity)
{
//至少扩容到_size+n
reserve(_size + n);
}
挪动数据 版本一
//int end = _size;
//while (end>=(int)pos) //int 和 size_t 在比较中涉及到算术转换,int需要转换为unsigned int
//{
// _str[end + n] = _str[end];
// end--;
//}
//挪动数据 版本二
size_t end = _size;
while (end >= pos && end != npos) //end是size_t ,end会等于-1 ,加了end!=npos,end就不会等于-1了
{
_str[end + n] = _str[end];
end--;
}
//插入数据
for (size_t i = 0; i < n; i++)
{
_str[pos + i] = ch;
}
_size += n;
}
void insert(size_t pos, const char* str)//插入字符串
{
assert(pos <= _size);
//扩容
size_t len = strlen(str);
if (_size + len >= _capacity)
{
reserve(_size + len);
}
//挪动数据
int end = _size;
while (end >= (int)pos)
{
_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, size_t len = npos)
//{
// assert(pos <= _size);
// //全部删完
// size_t n = _size - pos;//pos位置后面的字符
// if (len > n)
// {
// _str[pos] = '\0';//pos位置放\0
// _size = pos;
// }
// //删除一部分,n不完全删完
// else
// {
// //需要删除的部分数据 的后面几个覆盖需要删除的数据
// strcpy(_str + pos, _str + pos + len); //用需要保留的有效字符覆盖需要删除的有效字符
// _size -= len; //size更新
// }
//}
//版本二
void erase(size_t pos, size_t len = npos)
{
assert(pos <= _size);
//全部删完
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
//删除一部分,n不完全删完
else
{
//需要删除的数据的 后几个数据 往前覆盖
size_t end = pos + len;
while (end <= _size)
{
_str[pos++] = _str[end++];
}
_size -= len;
}
}
size_t find(char ch, size_t pos = 0)
{
assert(pos < _size);
//遍历string
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)
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, str);
if (ptr)//ptr!= nullptr
{
return ptr - _str;
}
return npos;
}
string substr(size_t pos = 0, size_t len = npos)
//在str中从pos位置开始,截取n个字符,然后将其返回
{
assert(pos < _size);
size_t n = len;
//子串的长度大于源字符串,源字符串有多大,子串就有多大
if (len == npos || pos + len > _size)
{
n = _size - pos;
}
//子串的长度小于源字符串,将源字符串和子串匹配的内容拷贝到tmp中
string tmp;
tmp.reserve(n);
for (size_t i = pos; i < pos + n; ++i)
{
tmp += _str[i];//拷贝数据
}
return tmp;
}
void swap(string & s )
//void swap( void * this ,string& s)
{
//调用库里的swap
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//bool operator<(const string& s)
// //bool operator<( string * this ,const string& s)
//{
// size_t i1 = 0;
// size_t i2 = 0;
// while (i1 < _size &&i2 < s._size ) //i1和i2不能超过对应数组的size
// {
// if (_str[i1]> s._str[i2] )
// {
// return false;
// }
// else if (_str[i1] < s._str[i2])
// {
// return true;
// }
// else//相等继续走
// {
// i1++;
// i2++;
// }
//
// }
//
// if (i1 == _size && i2 != s._size) //i1走完第一个数组了,i2没有走完
// {
// // "hello" "helloxx" true
// return true;
// }
// else
// {
// // "hello" "hello" false
// // "helloxx" "hello" false
// return false;
// }
// // 如果不考虑代码可读性,可以直接写成return i1 == _size && i2 != s._size
//}
//第二种版本
bool operator<(const string& s) const
{
//分三种情况
// "hello" "hello" false
// "helloxx" "hello" false
// "hello" "helloxx" true
int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
return ret == 0 ? _size : s._size;
}
bool operator==(const string& s) const
// bool operator==( string const *this , const string& s) const
{
//两个数组的大小相等
return _size == s._size && memcpy(_str, s._str, _size) ==0;
}
bool operator<=(const string& s) const
//bool operator<=(string const * this , const string& s)
{
return *this < s || *this ==s ;
}
bool operator>(const string& s) const
//bool operator>( string const * this const string& s)
{
return !(*this <= s);
}
bool operator>=(const string& s) const
//bool operator>=(string const * this ,const string& s)
{
return !(*this < s);
}
bool operator!=(const string& s) const
//bool operator!=( string const *this , const string& s)
{
return !(*this == s);
}
void clear()
{
_size = 0;
_str[_size] = '\0';
}
//读取一行含有空格的字符串
istream& getline(istream& in, string& s)
{
s.clear();
//分割多个string用空格或者换行,getline可以读取空格
char ch =in.get();//读取第一个字符
while (ch == '\n')
{
s += ch;//将读取到的字符尾插到字符串后面
ch = in.get(); //继续读取字符
}
return in;
}
private:
size_t _size;
size_t _capacity;
char* _str;
public:
static size_t npos;//声明
};
size_t string::npos = -1;//静态成员变量必须在类外面定义
ostream& operator<< (ostream& out, const string& str) //流插入
{
/* for (size_t i = 0; i < str.size(); ++i)
{
out << str[i];
}*/
for (auto ch : str)
{
out << ch;
}
return out;
}
//istream& operator>> (istream& in, string& s)//流提取
//{
// s.clear();//清空上一次的字符
// //get函数,无论什么字符都能从流中读取,包括换行和空格
// char ch = in.get();//从输入流中读取一个字符,并将其赋值给变量ch
// //流提取多个string用空格或者换行分割
// // 处理前缓冲区前面的空格或者换行
// while (ch != ' ' && ch != '\n')
// {
// s += ch;
// ch = in.get();
// }
// return in;
//}
istream& operator>> (istream& in, string& s)//流提取
{
s.clear();//清空上一次的字符
//补充;get函数,无论什么字符都能从流中读取,包括换行和空格
char ch = in.get();//从输入流中读取一个字符,并将其赋值给变量ch
char buff[128];
int i = 0;
//补充:流提取多个string用空格或者换行分割
while (ch != ' ' && ch != '\n') // 处理前缓冲区前面的空格或者换行
{
//将字符存到buff数组中,再将buff存到s中
buff[i++] = ch;
if (i == 127)
{
buff[i] = '\0';//最后一个位置放\0
s += buff;//将buff存到s对象中
i = 0;//重置
}
ch = in.get();//读取下一个ch
}
//不够128个字符,开辟的空间有剩余
if (i != 0)
{
buff[i] = '\0';//在有效字符后加\0
//再将buff存到s中
s += buff;
}
return in;
}
};
完整代码
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once //防止头文件被多次包含
#include<assert.h>
#include<iostream>
#include<string>
namespace cxq
{
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()
// :_size(0)
// ,_capacity(0)
// ,_str(new char [1]) // 如果是_str(nullptr)这种写法,会导致nullptr解引用
//{
// _str[0] = '\0';//开一个空间放\0,
//}
//默认构造函数-全缺省
string(const char * str ="")//常量字符串以\0为结束标志
{
_size = strlen(str);
_capacity = strlen(str);//capacity存储有效字符,\0不是有效字符
_str = new char [strlen(str) + 1];//strlen 计算字符不包含\0,但是需要多开一个空间给\0
strcpy(_str, str);
}
//string(const char * str )
// :_size(strlen(str))
// ,_capacity(strlen(str))//capacity存储有效字符,\0不是有效字符
// , _str(new char [_capacity +1] ) //strlen 计算字符不包含\0,但是需要多开一个空间给\0
//{
// strcpy(_str, str);
//}
//
~string()
{
delete []_str;
_str = nullptr;
_size = _capacity = 0;
}
const char* c_str() const //将string转换为C风格的字符串,以便与C语言代码进行交互
{
return _str;
}
size_t size()const
{
return _size;
}
char & operator[](size_t pos)
// char & operator[]( char *this ,size_t pos )
{
assert(pos <_size );
return _str[pos];//出了作用域,对象还在
}
const char& operator[](size_t pos) const
// char & operator[]( char *this ,size_t pos )
{
assert(pos < _size);
return _str[pos];//出了作用域,对象还在
}
void reserve(size_t n)//扩容
{
if (n > _capacity)
{
char* tmp = new char [n + 1];//+1 是给\0开辟的空间
strcpy(tmp, _str);
delete[] _str;
_str = tmp;//无法理解
_capacity = n;
}
}
void resize(size_t n, char ch = '\0')
{
//n<= _size
if (n <= _size)
{
_size = n;
_str[_size] = '\0';
}
//n>_size
else
{
//判断是否需要扩容
if (n>_capacity)
{
reserve(n);
}
//n>_size && n<_capacity,不需要扩容
//插入数据
for (size_t i = _size; i < n; ++i)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
}
void push_back(char ch )
{
//2倍扩容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
void append(const char* str)
{
//至少扩容到_size+len
size_t len = strlen(str);
if (_size + len > _capacity)//_size+len<=_capacity都不需要扩容
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
string & operator+= (const char * str )
//string & operator+= (string *this , const char * str)
{
append(str);
return *this;
}
string & operator+= (const char ch)
//string & operator( string * this ,const char ch)
{
push_back(ch);
return *this;
}
void insert(size_t pos, size_t n, char ch)//插入字符
{
assert(pos <= _size);
//扩容
if (n + _size > _capacity)
{
//至少扩容到_size+n
reserve(_size+n);
}
挪动数据 版本一
//int end = _size;
//while (end>=(int)pos) //int 和 size_t 在比较中涉及到算术转换,int需要转换为unsigned int
//{
// _str[end + n] = _str[end];
// end--;
//}
//挪动数据 版本二
size_t end = _size;
while (end >= pos && end!=npos ) //end是size_t ,end会等于-1 ,加了end!=npos,end就不会等于-1了
{
_str[end + n] = _str[end];
end--;
}
//插入数据
for (size_t i = 0; i < n; i++)
{
_str[pos+i] = ch;
}
_size += n;
}
void insert(size_t pos, const char* str)//插入字符串
{
assert(pos <= _size);
//扩容
size_t len = strlen(str);
if (_size + len >= _capacity)
{
reserve(_size + len);
}
//挪动数据
int end = _size;
while (end>=(int)pos)
{
_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, size_t len = npos)
//{
// assert(pos <= _size);
// //全部删完
// size_t n = _size - pos;//pos位置后面的字符
// if (len > n)
// {
// _str[pos] = '\0';//pos位置放\0
// _size = pos;
// }
// //删除一部分,n不完全删完
// else
// {
// //需要删除的部分数据 的后面几个覆盖需要删除的数据
// strcpy(_str + pos, _str + pos + len); //用需要保留的有效字符覆盖需要删除的有效字符
// _size -= len; //size更新
// }
//}
//版本二
void erase(size_t pos, size_t len = npos)
{
assert( pos <= _size);
//全部删完
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
//删除一部分,n不完全删完
else
{
//需要删除的数据的 后几个数据 往前覆盖
size_t end = pos + len;
while(end<=_size)
{
_str[pos++] = _str[end++];
}
_size -= len;
}
}
size_t find(char ch, size_t pos = 0)
{
assert(pos < _size);
//遍历string
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)
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, str);
if (ptr )//ptr!= nullptr
{
return ptr-_str;
}
return npos;
}
string substr(size_t pos = 0, size_t len = npos)
{
assert(pos <_size);
size_t n = len;
//子串的长度大于源字符串,源字符串有多大,子串就有多大
if (len == npos || pos+len > _size)
{
n = _size - pos;
}
//子串的长度小于源字符串,将源字符串和子串匹配的内容拷贝到tmp中
string tmp;
tmp.reserve(n);
for (size_t i = pos; i <pos+n; ++i)
{
tmp += _str[i];
}
return tmp;
}
private:
size_t _size;
size_t _capacity;
char* _str;
public:
static size_t npos;//声明
};
size_t string::npos = -1;//静态成员变量必须在类外面定义
ostream& operator<< (ostream& out, const string & str)
{
/* for (size_t i = 0; i < str.size(); ++i)
{
out << str[i];
}*/
for (auto ch : str)
{
out << ch;
}
return out;
}
};
如果你觉得这篇文章对你有帮助,不妨动动手指给点赞收藏加转发,给鄃鳕一个大大的关注
你们的每一次支持都将转化为我前进的动力!!!文章来源地址https://www.toymoban.com/news/detail-606913.html
到了这里,关于STL中的string类的模拟实现【C++】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!