文章目录
- 一、String的基本结构
- 二、String的构造函数
- 1.string(const char* str)
- 2.string()
- 3.string(const char* str = " ")
- 4.string(const string& s)
- 二、String的析构函数
- 三、获取String中的字符串
- 四、获取有效元素个数
- 五、operator[]运算符重载
- 六、增删查改之增
- 1.reserve()
- 2.pushback(char ch)
- 3.append(const char* str)
- 4.operator+=
- 5.insert
- 七、增删查改之删
- 1.erase
- 2.clear
- 八、增删查改之查
- 九、获取子串
- 十、resize
- 十一、流插入
- 1.流插入
- 2. s.c_str()与cout<<s的区别
- 十二、修改由于字符函数导致的bug
- 1.修改构造函数
- 1>string(const char* str = "")
- 2>string(const string& s)
- 2.对reserve的修改
- 3.append的修改
- 4.对于迭代器的修改
- 十三、流提取
- 十四、比较大小
- 1.operator<
- 2.operator==(const string& s)
- 3.其他比较
- 十五、赋值运算符重载
- 1.传统写法
- 2.现代写法
- 十六、拷贝构造的现代写法
- 十七、string模拟实现完整代码
一、String的基本结构
string在库里面实现的是比较冗余的,我们这里就实现一个最基本的string,该string采用类似顺序表的方式来进行实现,总体来说比较简单。
我们使用三个私有成员变量,一个指向string内容的指针,一个记录当前有效字符个数的变量size,一个记录当前容量的变量capacity,如下所示
char* _str;
size_t _size;
size_t _capacity;
二、String的构造函数
对于string的构造函数,在库里面有很多个写法。里面的显得过于冗余,我们采用比较简单的方式来实现。
1.string(const char* str)
如下代码所示,在这个构造函数中,我们使用了初始化列表来为_str开空间,_size和_capacity赋初值,然后再使用strcpy函数即可将字符串的内容放入数据中。
这里值得注意的是我们的size是记录的有效字符个数,他是不包括\0字符的。所以我们给_size赋初值的时候要切记注意不要加1。同样的,对于_capacity这个变量而言,它代表的的容量也是不能考虑\0字符的,所以它也是相比实际容量要少一个的,而在开容量的时候,一定要多开一个这个是用来存放字符\0的
//给一个字符串去构造一个string
string(const char* str)
:_str(new char[strlen(str) + 1])
, _size(strlen(str))
, _capacity(strlen(str))
{
strcpy(_str, str);
}
2.string()
这是一个无参的默认构造函数,由于我们一开始不知道要方多少个字符,不妨我们就一开始给16个空间,0个有效字符,15个容量即可。初始化的时候,我们直接初始化一些\0字符即可
//无参的默认构造函数
string()
:_str(new char[16])
,_size(0)
,_capacity(15)
{
memcpy(_str, "\0\0\0\0", 4);
}
3.string(const char* str = " ")
这个构造函数实际上是不能与前面的重合的。因为他是一个默认构造函数。不过是带了缺省参数的缘故。它的思路与前面的都是一致的
//以上合二为一的构造函数写法
string(const char* str = "")
:_str(new char[strlen(str) + 1])
, _size(strlen(str))
, _capacity(strlen(str))
{
strcpy(_str, str);
}
4.string(const string& s)
显然这是一个拷贝构造函数,我们的串中涉及到了开空间的问题,必须得需要深拷贝,所以我们需要自己写一个拷贝构造,而不能依赖于编译器自己生成的拷贝构造函数
//拷贝构造函数
string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
二、String的析构函数
这个函数就比较简单了,我们直接去delete掉字符串,然后置空,清空数据即可
//析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
三、获取String中的字符串
这个接口我们也是比较熟悉的,它可以直接获取string中的字符串,注意它需要加上const进行修饰,因为我们是不会破坏对象里面的数据的,所以需要后置const,并且我们返回这个字符串的时候,它也是肯定不会被修改的,所以也需要加上const
//获取string中的字符串
const char* c_str() const
{
return _str;
}
四、获取有效元素个数
这个接口的意思也比较直白,就是获取_size的值,直接返回即可
//获取当前有效元素个数
size_t size() const
{
return _size;
}
五、operator[]运算符重载
这个运算符重载,我们需要实现两个,一个是针对普通对象的,一个是针对于const的对象的。我们注意到[]这个的特性,它既可以作为右值(读取),又可以作为左值(修改),我们想要去修改读或者修改某一处的值,那么我们应该是得知道该处的地址的,我们可以直接用引用,将该处空间的别名返回,这样一来,即可避免更大的开销,因为传引用返回可以节省开销,又可直接根据该处的别名去访问该处。从而达到读写的目的。
对于const修饰的对象而言,由于它不可能被修改,只能读写。但为了节省开销,我们还是采用传引用返回,这样我们就得采用const来进行一次修饰,缩小权限。使之只能读,不能写
//读写接口
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
//只读接口
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
六、增删查改之增
1.reserve()
我们首先需要知道的就是这个函数了,这个函数的功能就是扩容,但凡涉及到增的时候,必然就需要进行扩容了。它的具体实现如下
这个函数它接收一个参数n,也就是需要扩到多大,注意到最好不要进行缩容,所以我们为了简洁,默认使他只能扩容。我们先扩充n+1个空间,这多出来的一个是为\0字符做出打算的
扩容的具体逻辑是这样的,首先先开辟n+1个空间,然后将原来的字符内容交给这个新空间,最好在修改容量,和释放之前的空间。
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
_capacity = n;
delete[] _str;
_str = tmp;
}
}
2.pushback(char ch)
这段代码是这样的,我们首先检查容量是否足够,如果不足够,那么我们就扩容,然后就是类似顺序表的操作进行插入数据。不要忘记最后添加一个\0字符
void pushback(char ch)
{
if (_size == _capacity)
{
int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size++] = ch;
_str[_size] = '\0';
}
3.append(const char* str)
这个函数的逻辑和上一个是差不多的,我们需要做的就是注意扩容多大空间,我们是扩容到len+_size个大小即可。最后直接字符串拷贝即可
void append(const char* str)
{
int len = strlen(str);
if (len + _size > _capacity)
{
int newcapacity = (len+_size) ;
reserve(newcapacity);
}
//while (len--)
//{
// _str[_size++] = *str++;
//}
//_str[_size] = '\0';
strcpy(_str + _size, str);
_size += len;
}
4.operator+=
这是一个运算符重载,我们有两种形式,一种是加字符,一种是加字符串。注意我们需要一个返回值了,因为我们+=之后其实是有一个返回值的,这个返回值就是我们这个被修改后的对象。我们可以直接复用接口,然后返回this指针即可
string& operator+=(char ch)
{
pushback(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
5.insert
对于insert这个接口,他是在某个位置处插入n个字符或者插入一个字符串。那么这样的话我们首先需要做的就是扩容,然后就是移动原来的数据,最后就是插入数据。这里的移动数据我们下面这两个接口采用的是计数的方式来实现的
void insert(size_t pos, size_t n, char ch)
{
assert(pos <= _size);
if (_size + n > _capacity)
{
int newcapacity = _size + n;
reserve(newcapacity);
}
int count = _size - pos + 1;
int size = _size;
while (count--)
{
_str[size + n] = _str[size];
size--;
}
int i = pos;
for (i = pos; i < pos + n; i++)
{
_str[i] = ch;
}
_size += n;
}
void insert(size_t pos, const char* str)
{
assert(pos <= _size);
int len = strlen(str);
if (_size + len > _capacity)
{
int newcapacity = _size + len;
reserve(newcapacity);
}
int count = _size - pos + 1;
int size = _size;
while (count--)
{
_str[size + len] = _str[size];
size--;
}
int i = pos;
for (i = pos; i < pos + len; i++)
{
_str[i] = *str;
str++;
}
_size += len;
}
上面的两种方法是最容易理解的方法,但是我们来看一下这个写法
上面这种写法看似正确,实际上也有一些问题,当pos为0的时候,由于pos为size_t类型,导致end也被强制类型转化了,故代码崩溃。
为了进行修改,我们可以这样做,进行一次强制类型转换
我们还可以这样做,定义一个静态成员变量npos,使之不可以等于npos
注意上面这种写法我们需要注意的一点是,c++中虽然上面的是正确的,但是我们还可以这样写,但是不建议这样写:
这样写编译器是可以通过的,但是要记住只有整型类型可以这样写,浮点类型都不可以,这是一个特殊的语法。我们一般不会这样使用的
七、增删查改之删
1.erase
这段代码我们的思想就是挪动数据即可,当长度超出剩余的字符串长度时候直接将后面全部删完即可,也就是给该位置加上一个\0字符。其余情况直接将后面的挪动到前面即可。
//增删查改之删
void erase(int pos, size_t len = npos)
{
assert(pos <= _size);
if (len == npos || pos + len > _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
int end = pos + len;
while (end <= _size)
{
_str[pos++] = _str[end++];
}
_size -= len;
}
}
2.clear
void clear()
{
_str[0] = '\0';
_size = 0;
}
八、增删查改之查
查找我们一般是查找一个字符或者查找一个字符串,找字符简单,找字符串的话我们可以直接调用strstr这个函数,暴力匹配即可。或者kmp算法和BM算法也是可以的
//查
size_t find(char ch, size_t pos = 0)
{
assert(pos < _size);
for (int i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
size_t find(const char* str, size_t pos = 0)
{
assert(pos < _size);
char* ptr = strstr(_str + pos, str);
if (ptr)
{
return (ptr - str);
}
else
{
return npos;
}
}
九、获取子串
如下代码所示,我们想要获取一部分串,思路是这样的,先看len是否已经超出了串的长度,如果没有超出,那么可以,如果超出了,那就意味着从pos开始全部截取,那么修改len的值直至合适为止。然后我们定义一个空串。我们对这个串的容量刚好就是这个修改后的len的大小,我们从pos位置开始,一直加len次。这样我们九获取了我们希望获得的子串,我们现在返回它即可
在这里我们需要注意两点:
- 我们在定义这个空串的时候,不能将其设置为静态的。因为静态的串只会定义一次。以后再次调用这个函数是会出现问题的
- 我们的返回值不可以是引用返回,因为我们返回的串出了作用域就不在了
- 这个函数必须得有拷贝构造函数,因为其必须为传值返回。且该串涉及到开空间的问题,我们必须得自己写一个深拷贝构造。否则会对同一块空间调用两次析构函数而产生错误
//提取出指定位置的串
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;
}
string tmp;
tmp.reserve(n);
for (size_t i = pos; i < pos + n; i++)
{
tmp += _str[i];
}
return tmp;
}
十、resize
resize的功能是扩充size,当容量足够的时候,若新的size小于原来的size,那么就相当于删除数据,如果容量足够且size大于原来的数据,那么就为其补充数据直至size。如果容量不够size的值,那么就先扩容,然后再填数据
//调整size
void resize(size_t n, char ch = '\0')
{
if (n <= _size)
{
_size = n;
_str[_size] = '\0';
}
else
{
reserve(n);
for (int i = _size; i < n; i++)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
}
十一、流插入
1.流插入
首先我们在之前写日期类的时候,我们就知道了流插入的样子大概是这样写的
这里必须在类外进行定义,这是为了改变变量顺序,符合使用习惯
其次必须加上引用,因为ostream有防拷贝设计
对于流插入,我们有几种方式可以去实现,要么直接定义友元函数,然后直接访问s里面的字符串
要么就是不适用友元,使用循环逐个打印
ostream& operator<<(ostream& out, const string& s)
{
for (size_t i = 0; i < s.size(); i++)
{
out << s[i];
}
return out;
}
甚至我们还可以使用范围for
2. s.c_str()与cout<<s的区别
这两个我们在打印的时候,看似没有什么太大区别,其实还是差距蛮大的。
首先c_str()它是一个字符串,它遇到’\0’就结束了。而直接打印s的话看的并非是’\0’字符,而是size的大小。有多少size打印多少个字符。这样一来有可能在存储的字符串里面恰巧有一个是’\0’的话,那么这两者打印的结果是不一样的。
如下面所示,是我们自己写的string演示
下方是库里面的string演示
需要注意到’\0’这个字符vs2022中是不会被打印出来的,而在2013下是可以打印出来的
这样一想确实挺合理。我们自己写的与库里面的一样是我们基于循环写出来的。但是倘若我们将这个循环换位范围for,那么我们发现全乱了。
那么这是为什么呢?为什么范围for不符合我们的期望呢?我们想要知道答案,那么我们就得去看迭代器是如何实现了
注意到我们的迭代器事实上都是通过字符串的长度来实现的。这样一来的话,就无形之中将’\0’之后的数据没有给考虑进去。所以事实上我们的迭代器有bug。其实在我们的string类中,只要涉及到string.h相关的函数中,大部分都需要重新修改一下了
十二、修改由于字符函数导致的bug
我们在上面中,发现前面的大部分代码事实上是存在很大的问题的,存在很大的隐患。我们现在就先来进行修改,我们可以将原来的str系列的函数全部替换为mem系列的函数,这样就能解决掉这个问题了
1.修改构造函数
1>string(const char* str = “”)
这是我们原来的构造函数
string(const char* str = "")
:_str(new char[strlen(str) + 1])
, _size(strlen(str))
, _capacity(strlen(str))
{
strcpy(_str, str);
}
其实这个代码使用memcpy和strcpy都是可以的,但是我们为了统一以下,我们现在使用memcpy来进行修改
string(const char* str = "")
:_str(new char[strlen(str) + 1])
, _size(strlen(str))
, _capacity(strlen(str))
{
//strcpy(_str, str);
memcpy(_str, str, _size + 1);
}
需要特殊注意的是,这里是size+1
2>string(const string& s)
前面那个其实还好,但是对于拷贝构造函数就要小心了。它必须得换了
下面是我们之前写的
//拷贝构造函数
string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
我们试想一下,如果不换的话,一旦出现’\0’字符,那么后面的一部分全都出现问题了。
所以我们得用memcpy
//拷贝构造函数
string(const string& s)
{
_str = new char[s._capacity + 1];
memcpy(_str, s._str, s._size + 1);
_size = s._size;
_capacity = s._capacity;
}
2.对reserve的修改
下面是我们之前的函数,可见实际上是存在很多问题的
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
_capacity = n;
delete[] _str;
_str = tmp;
}
}
我们修改后的为
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
//strcpy(tmp, _str);
memcpy(tmp, _str, _size + 1);
_capacity = n;
delete[] _str;
_str = tmp;
}
}
3.append的修改
这是原来的,虽然确实没有什么太大的影响,但是我们为了统一还是进行处理一下
void append(const char* str)
{
int len = strlen(str);
if (len + _size > _capacity)
{
int newcapacity = (len+_size) ;
reserve(newcapacity);
}
//while (len--)
//{
// _str[_size++] = *str++;
//}
//_str[_size] = '\0';
strcpy(_str + _size, str);
_size += len;
}
处理后为
void append(const char* str)
{
int len = strlen(str);
if (len + _size > _capacity)
{
int newcapacity = (len+_size) ;
reserve(newcapacity);
}
//while (len--)
//{
// _str[_size++] = *str++;
//}
//_str[_size] = '\0';
//strcpy(_str + _size, str);
memcpy(_str + _size, str, len+1);
_size += len;
}
4.对于迭代器的修改
这是我们原来的迭代器代码
//迭代器
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + strlen(_str);
}
//const 迭代器
typedef const char* const_iterator;
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + strlen(_str);
}
修改后为
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
//return _str + strlen(_str);
return _str + _size;
}
//const 迭代器
typedef const char* const_iterator;
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
//return _str + strlen(_str);
return _str + _size;
}
十三、流提取
经历了前面修改后的bug以后,我们在回过头来继续实现流提取
我们先看如下代码
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch = in.get();
while (ch == ' ' || ch == '\n')
{
ch = in.get();
}
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in;
}
这段代码是可以实现我们的目的,首先我们知道我们得先清楚之前s中的数据,否则插入就有问题。会保留原来的数据
其次,我们输入的时候需要用in.get()这个函数,必须用这个,如果我们使用的是in的话,它本身就是无法读取空格和‘\n’的,因为这两个字符被用作分隔符,分割读取的数据。故后面的条件恒成立或恒不成立。而get是可以读取这两个字符的
然后我们用一个循环,先清除一开始可能会输入的分隔符。清楚了一开始的分隔符以后,我们就开始读取数据,我们可以直接调用+=这个运算符重载。当遇到分隔符的时候结束读取即可
同样的这个代码假如我们想要改为getline也是很容易的,我们直接将空格这个标识符删掉即可。
上面的代码虽然也确实可以解决问题,但由于我们连续的使用+=这个函数,我们需要不断的进行扩容,这是一种极大的消耗。我们也不能一开始直接对其扩容很大,这样是伤敌一千自损八百的策略
为了解决这个问题,我们可以这样做
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch = in.get();
while (ch == ' ' || ch == '\n')
{
ch = in.get();
}
char buff[128] = { 0 };
int i = 0;
while (ch != ' ' && ch != '\n')
{
//s += ch;
buff[i++] = ch;
if (i == 127)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
在栈上运用一个buff数组,这个数组它起到一个缓冲的作用,先将数据都存到这个数组里面,当这个数组存满的时候,一次性将数据全部给s,然后结束的时候如果还有数据,那么也将他给放进去即可。这样的代价就很小了
十四、比较大小
1.operator<
如下代码所示,我们先进行依次比较,若前面可以分出胜负自然好说,但若分不出胜负,那么就只有一种情况是满足小于的情况,即第一个没有值了,第二个还有值,其余皆为不满足小于的情况
bool operator<(const string& s) const
{
size_t i1 = 0;
size_t i2 = 0;
while (i1 < _size && i2 < s._size)
{
if (_str[i1] < s._str[i2])
{
return true;
}
else if (_str[i1] > s._str[i2])
{
return false;
}
else
{
i1++;
i2++;
}
}
return (i1 == _size) && (i2 != s._size);
//return _size<s._size;
}
上面是我们自己手撕的代码,同样的我们也可以利用c语言中的一些库来更方便的完成这个代码
下面这串代码虽然短小,但是可能不是很好理解,我们第一行的目的就是先求出两个对象最小的size,然后让两个对象的前size个进行比较,如果相等的话,那么ret就是0,我们就只需要确认一下第一个的size是否小于第二个的size即可。如果不相等,那么我们看ret是否小于0,小于既是正确的
bool operator<(const string& s) const
{
int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
return ret == 0 ? _size < s._size : ret < 0;
}
2.operator==(const string& s)
这个就比较简单了,我们先比较size,如果相等的条件下,两个串也相等,那么就是相等了
bool operator==(const string& s) const
{
return _size == s._size && (memcmp(_str, s._str, _size) == 0);
}
3.其他比较
解决了前两个,其实其他的就可以直接进行复用了,如下代码所示
bool operator<=(const string& s) const
{
return (*this < s) || (*this == s);
}
bool operator>=(const string& s) const
{
return !(*this < s);
}
bool operator>(const string& s) const
{
return !(*this <= s);
}
bool operator!=(const string& s) const
{
return !(*this == s);
}
十五、赋值运算符重载
1.传统写法
如下所示,是我们比较传统的写法,它跟拷贝构造函数是差不多的,我们不管原来的是什么样子,反正总归最后基本就是把右边的复制了一份。那么我们就直接开空间,释放原来的空间,随之拷贝数据即可。它与拷贝构造函数的区别就在于,它是已经存在的两个变量之间的赋值拷贝,这个过程是需要释放掉被赋值的对象原来的数据的。而拷贝构造函数是用一个已经存在的去构造一个不存在的,以前这个也不存在,所以就不需要释放空间。
总归就是一个深拷贝的写法
//赋值运算符重载
string& operator=(const string& s)
{
if (this != &s)
{
char* tmp = new char[s._capacity + 1];
delete[] _str;
_str = tmp;
memcpy(_str, s._str, s._size + 1);
_size = s._size;
_capacity = _capacity;
}
return *this;
}
2.现代写法
如下代码所示,这段代码就充分利用了拷贝构造函数,然后移花接木即可,顺便原来的空间也可以因为tmp的析构而带走,充分的榨干了tmp的作用。
string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s);
std::swap(_str, tmp._str);
std::swap(_size, tmp._size);
std::swap(_capacity, tmp._capacity);
}
return *this;
}
但是要注意,我们不可以这样写:
程序会崩溃的
这是因为swap函数中是通过三次赋值操作来实现的,而赋值操作又通过swap来实现,实现了死递归。导致栈溢出了
事实上,库里面也自己提供了两个string对象的交换
而我们如果需要自己手动实现swap的话,也正好就是前面的代码
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)
{
string tmp(s);
swap(tmp);
}
return *this;
}
上面这段代码还可以继续进行简化
string& operator=(string s)
{
swap(s);
return *this;
}
这段代码我们就厉害了,直接在传参的过程中调用拷贝构造,可谓精简到极致了
十六、拷贝构造的现代写法
如下代码所示,是我们较为传统的写法,也是上面的写法。
//拷贝构造函数
string(const string& s)
{
_str = new char[s._capacity + 1];
memcpy(_str, s._str, s._size + 1);
_size = s._size;
_capacity = s._capacity;
}
上面这段代码,其实也可以进一步的进行简化。即现代写法
string(const string& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(s._str);
swap(tmp);
}
注意在这段代码中,初始化列表必须得写法,否则在调用赋值运算符重载的过程中,由于this所指向的对象未初始化,导致其的_str随意指向一块空间,交换以后,析构的过程中直接崩溃。故必须得加初始化
其次在这段代码中,还有一个问题是如果s中的_str中间有一个’\0’字符,那么也会出现问题。导致后面的部分无法拷贝上去。
综上所述,对于拷贝构造函数,还是传统写法更优一些
十七、string模拟实现完整代码
下面是类实现的具体代码
#pragma once
#include<iostream>
#include<string.h>
#include<assert.h>
using namespace std;
namespace String
{
class string
{
public :
/*********************************************/
//迭代器
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
//return _str + strlen(_str);
return _str + _size;
}
//const 迭代器
typedef const char* const_iterator;
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
//return _str + strlen(_str);
return _str + _size;
}
/**************************************************************************************/
//分开的构造函数写法
给一个字符串去构造一个string
//string(const char* str)
// :_str(new char[strlen(str) + 1])
// , _size(strlen(str))
// , _capacity(strlen(str))
//{
// strcpy(_str, str);
//}
无参的默认构造函数
//string()
// :_str(new char[16])
// ,_size(0)
// ,_capacity(15)
//{
// memcpy(_str, "\0\0\0\0", 4);
//}
/**************************************************************************************/
//以上合二为一的构造函数写法
string(const char* str = "")
:_str(new char[strlen(str) + 1])
, _size(strlen(str))
, _capacity(strlen(str))
{
//strcpy(_str, str);
memcpy(_str, str, _size + 1);
}
//拷贝构造函数
string(const string& s)
{
_str = new char[s._capacity + 1];
memcpy(_str, s._str, s._size + 1);
_size = s._size;
_capacity = s._capacity;
}
//string(const string& s)
// :_str(nullptr)
// ,_size(0)
// ,_capacity(0)
//{
// string tmp(s._str);
// swap(tmp);
//}
/**************************************************************************************/
//析构函数
~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
/**************************************************************************************/
//获取string中的字符串
const char* c_str() const
{
return _str;
}
/**************************************************************************************/
//获取当前有效元素个数
size_t size() const
{
return _size;
}
/**************************************************************************************/
//operator[]
//读写接口
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
//只读接口
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
/**************************************************************************************/
//增删查改之增
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
//strcpy(tmp, _str);
memcpy(tmp, _str, _size + 1);
_capacity = n;
delete[] _str;
_str = tmp;
}
}
//pushback
void pushback(char ch)
{
if (_size == _capacity)
{
int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size++] = ch;
_str[_size] = '\0';
}
void append(const char* str)
{
int len = strlen(str);
if (len + _size > _capacity)
{
int newcapacity = (len+_size) ;
reserve(newcapacity);
}
//while (len--)
//{
// _str[_size++] = *str++;
//}
//_str[_size] = '\0';
//strcpy(_str + _size, str);
memcpy(_str + _size, str, len + 1);
_size += len;
}
string& operator+=(char ch)
{
pushback(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
void insert(size_t pos, size_t n, char ch)
{
assert(pos <= _size);
if (_size + n > _capacity)
{
int newcapacity = _size + n;
reserve(newcapacity);
}
int count = _size - pos + 1;
int size = _size;
while (count--)
{
_str[size + n] = _str[size];
size--;
}
int i = pos;
for (i = pos; i < pos + n; i++)
{
_str[i] = ch;
}
_size += n;
}
void insert(size_t pos, const char* str)
{
assert(pos <= _size);
int len = strlen(str);
if (_size + len > _capacity)
{
int newcapacity = _size + len;
reserve(newcapacity);
}
int count = _size - pos + 1;
int size = _size;
while (count--)
{
_str[size + len] = _str[size];
size--;
}
int i = pos;
for (i = pos; i < pos + len; i++)
{
_str[i] = *str;
str++;
}
_size += len;
}
void insert1(size_t pos, size_t n, char ch)
{
assert(pos <= _size);
if (_size + n > _capacity)
{
int newcapacity = _size + n;
reserve(newcapacity);
}
int end = _size;
while (end >= (int)pos)
{
_str[end + n] = _str[end];
end--;
}
int i = pos;
for (i = pos; i < pos + n; i++)
{
_str[i] = ch;
}
_size += n;
}
void insert2(size_t pos, size_t n, char ch)
{
assert(pos <= _size);
if (_size + n > _capacity)
{
int newcapacity = _size + n;
reserve(newcapacity);
}
int end = _size;
while (end >= pos && end != npos)
{
_str[end + n] = _str[end];
end--;
}
int i = pos;
for (i = pos; i < pos + n; i++)
{
_str[i] = ch;
}
_size += n;
}
/*****************************************************************************/
//增删查改之删
void erase(int pos, size_t len = npos)
{
assert(pos <= _size);
if (len == npos || pos + len > _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
int end = pos + len;
while (end <= _size)
{
_str[pos++] = _str[end++];
}
_size -= len;
}
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
/*****************************************************************************/
//查
size_t find(char ch, size_t pos = 0)
{
assert(pos < _size);
for (int i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
size_t find(const char* str, size_t pos = 0)
{
assert(pos < _size);
char* ptr = strstr(_str + pos, str);
if (ptr)
{
return (ptr - _str);
}
else
{
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;
}
string tmp;
tmp.reserve(n);
for (size_t i = pos; i < pos + n; i++)
{
tmp += _str[i];
}
return tmp;
}
//调整size
void resize(size_t n, char ch = '\0')
{
if (n <= _size)
{
_size = n;
_str[_size] = '\0';
}
else
{
reserve(n);
for (int i = _size; i < n; i++)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
}
//比较大小
//bool operator<(const string& s)
//{
// size_t i1 = 0;
// size_t i2 = 0;
// while (i1 < _size && i2 < s._size)
// {
// if (_str[i1] < s._str[i2])
// {
// return true;
// }
// else if (_str[i1] > s._str[i2])
// {
// return false;
// }
// else
// {
// i1++;
// i2++;
// }
// }
// return (i1 == _size) && (i2 != s._size);
// //return _size<s._size;
//}
bool operator<(const string& s) const
{
int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
return ret == 0 ? _size < s._size : ret < 0;
}
bool operator==(const string& s) const
{
return _size == s._size && (memcmp(_str, s._str, _size) == 0);
}
bool operator<=(const string& s) const
{
return (*this < s) || (*this == s);
}
bool operator>=(const string& s) const
{
return !(*this < s);
}
bool operator>(const string& s) const
{
return !(*this <= s);
}
bool operator!=(const string& s) const
{
return !(*this == s);
}
//赋值运算符重载
//string& operator=(const string& s)
//{
// if (this != &s)
// {
// char* tmp = new char[s._capacity + 1];
// delete[] _str;
// _str = tmp;
// memcpy(_str, s._str, s._size + 1);
// _size = s._size;
// _capacity = _capacity;
// }
// return *this;
//}
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)
// {
// string tmp(s);
// swap(tmp);
// }
// return *this;
//}
string& operator=(string s)
{
swap(s);
return *this;
}
private:
char* _str;
size_t _size;
size_t _capacity;
public:
const static size_t npos;
};
const size_t string::npos = -1;
ostream& operator<<(ostream& out, const string& s)
{
//for (size_t i = 0; i < s.size(); i++)
//{
// out << s[i];
//}
//out << endl;
//return out;
for (auto ch : s)
{
out << ch;
}
out << endl;
return out;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch = in.get();
while (ch == ' ' || ch == '\n')
{
ch = in.get();
}
char buff[128] = { 0 };
int i = 0;
while (ch != ' ' && ch != '\n')
{
//s += ch;
buff[i++] = ch;
if (i == 127)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i != 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
}
下面是测试的代码
#define _CRT_SECURE_NO_WARNINGS 1
#include "String.h"
#include <string>
void teststring1()
{
String::string s1("hello world");
cout << s1.c_str() << endl;
String::string s2;
cout << s2.c_str() << endl;
//string s;
//cout << s << endl;
int i = 0;
//for (i = 0; i < s1.size(); i++)
//{
// s1[i]++;
//}
//cout << endl;
for (i = 0; i < s1.size(); i++)
{
cout << s1[i] << ' ';
}
cout << endl;
//const String::string s3("hello hello");
//for (i = 0; i < s3.size(); i++)
//{
// s3[i]++;
//}
//cout << endl;
String::string::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it << ' ';
it++;
}
cout << endl;
for (auto ch : s1)
{
cout << ch << ' ';
}
}
void teststring2()
{
const String::string s1("hello world");
String::string::const_iterator it = s1.begin();
while (it != s1.end())
{
cout << *it << ' ';
it++;
}
cout << endl;
for (auto ch : s1)
{
cout << ch << ' ';
}
}
void teststring3()
{
String::string s1("hello world");
cout << s1.c_str() << endl;
s1.pushback('!');
cout << s1.c_str() << endl;
s1.append("hello world");
cout << s1.c_str() << endl;
String::string s2("hello world");
cout << s2.c_str() << endl;
s2 += '!';
cout << s2.c_str() << endl;
s2 += "hello world";
cout << s2.c_str() << endl;
String::string s3("hello world");
cout << s3.c_str() << endl;
s3.insert(0, 5, 'x');
cout << s3.c_str() << endl;
s3.insert1(0, 5, 'e');
cout << s3.c_str() << endl;
s3.erase(5, 4);
cout << s3.c_str() << endl;
}
void teststring4()
{
String::string url = "ftp://www.baidu.com/?tn=65081411_1_oem_dg";
size_t pos1 = url.find("://");
if (pos1 != String::string::npos)
{
String::string protocol = url.substr(0, pos1);
cout << protocol.c_str() << endl;
}
size_t pos2 = url.find('/', pos1 + 3);
if (pos2 != String::string::npos)
{
String::string domain = url.substr(pos1 + 3, pos2 - (pos1 + 3));
String::string uri = url.substr(pos2 + 1);
cout << domain.c_str() << endl;
cout << uri.c_str() << endl;
}
}
void teststring5()
{
String::string s("hello world");
s.resize(8);
cout << s.c_str() << endl;
s.resize(13, 'x');
cout << s.c_str() << endl;
s.resize(20, 'y');
cout << s.c_str() << endl;
cout << s;
}
void teststring6()
{
String::string s("hello world");
s += '\0';
s += "xxxxxx";
cout << s.c_str() << endl;
cout << s << endl;
}
void teststring7()
{
String::string s;
cin >> s;
cout << s << endl;
cin >> s;
cout << s << endl;
}
void teststring8()
{
//String::string s1("hello world");
//s1 += '\0';
//s1 += "xxxxx";
//String::string s2("hello world");
//s2 += '\0';
//s2 += "yyyyy";
//cout << (s2 < s1) << endl;
//cout << (s2 == s1) << endl;
String::string s1("hello");
String::string s2("hello");
cout << (s1 < s2) << endl;
cout << (s1 > s2) << endl;
cout << (s1 == s2) << endl << endl;
String::string s3("hello");
String::string s4("helloxxx");
cout << (s3 < s4) << endl;
cout << (s3 > s4) << endl;
cout << (s3 == s4) << endl << endl;
String::string s5("helloxxx");
String::string s6("hello");
cout << (s5 < s6) << endl;
cout << (s5 > s6) << endl;
cout << (s5 == s6) << endl << endl;
s6 = s5;
cout << s6 << endl;
}
int main()
{
//teststring1();
//teststring2();
//teststring3();
//teststring4();
teststring8();
return 0;
}
好了本期内容就到这里了
本期内容确实比较硬核,希望读者能够认真消化
好了我们下期内容再见!!!