目录
(3)string在底层中实际上是basic_string模板类的别名
6.string中的数据操作(String operations)中的函数
7.string中的Non-member function overloads
(7)relational operators判断运算符重载函数
一、STL概念
STL(standard template library-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架
有人可能会误以为STL就是C++标准库,其实这个认识是不对的,STL是C++标准库重要组成之一,是C++标准库的子库。
二、STL六大组件
在STL中,我们要对STL的六大组成有所了解。它们分别是:仿函数、空间配置器、算法、容器、迭代器、配接器。
在这里我们只是初识STL,所以就不对这六大组件一一介绍,主要介绍的是算法和容器。算法不用多说,这里的容器其实就是指数据结构,只是在STL中统一称为容器
当然,由于算法和容器中的内容都非常多,这里就不一一介绍里面的各个内容,而是选择几个在现阶段我们能够理解且使用比较频繁的内容介绍其用法
三.string
string是容器的内容之一。在使用时,必须要包含#include<string>。using namespace std或using std::string选择性使用
在编程中,不可避免的会遇到需要处理字符串的情况。字符串是以“\0”为结尾的字符的集合。为了方便用户使用及更好的检查数组越界等问题,STL中便提供了tring。
1.string特点
(1)string是表示字符串的字符串类
这一点不必多说,STL在本质上其实就是运用了类模板和重载函数等实现的
(2)string类的接口与常规容器的接口基本相同
但是在string中存在一些专门用来操作string的常规操作
(3)string在底层中实际上是basic_string模板类的别名
可以看到,在string下面有一个宏:typedef basic_string <char> string。在编译器中string其实是为了方便使用重命名后的名字
(4)不能操作多字节或者变长字符的序列
计算机中都存在一种编码:ASCII码。这个编码用一个字节存储,一个字节是8个bit位,这就意味着ASCII码可以存储256种不同符号。这对于英文字母加数字等其他符号的集合来说是足够的。但是对于诸如汉字这种文字太多的语种来说是远远不够的。
为了应对这种状况,计算机界便提出了Unicode(统一码)的概念,其采用两个字节的编码形式。两个字节是16bit,这就意味着可以有2^16次方种状态,完全可以满足当今现存大部分语言的编码需求。
其实现方式有utf-8,utf-16,utf-32三种形式。这些形式分别采用8位无符号整数、16位无符号整数、32位无符号整数进行存储编码。但utf-8是应用最多的,因为它可以做到兼容ASCII码。这里就不一一讲解了,有兴趣的可以自行了解
这里要记住的一点就是,不同编译器采用的Unicode实现方式不同,有些可能是utf-8,也有可能是utf-16或utf-32。如果是utf-8就可以直接用string。但如果是utf-16或utf-32就要用u16string或u32string
2.string中的常用成员函数
实际上,我们并不需要记住所有的string用法。因为string中有上百个函数用法,要全部记住是非常困难的,这还仅仅只是一个string,而STL还存在大量的其他类
因此,在这里我们就单单介绍string中比较常用的几个函数
(1)构造函数(constructor)
在类与对象中就说过,类中为了满足使用者的不同需求,往往会存在多个构造函数以应对不同的情况。STL中的类也不例外
1.string()
一个基本的无参构造。构造空的string类对象,即空字符串
string s1;
2.string (const char* s)
带一个参数的构造函数,用字符串来构造string类对象。但要注意这里的const修饰的是char* s,而不是s。因此是不能改变指针的地址,而不是不能改变指针指向的内容
string s1("address");
3.string (size_t n, char c)
string类对象中包含n个字符c
string s1(5, 'a');
这里可以直接用cout来打印自定义变量是因为在string类中已经提前重载好了"<<"。在自己写的类中想这样用就需要自己进行重载
要把这个构造函数与string (const char* s, size_t n)相区分。前者是包含n个字符c,后者则是以字符串的前n个字符进行构造:
(2)拷贝构造函数
1.string (const string& str)
拷贝构造函数。这里的拷贝构造采用的是深拷贝的形式,因为其底层是一个数组,如果两个对象的内容指向同一块空间,就可能导致问题
string s1("address");
string s2(s1);
3.string类对象遍历
假设现在我们有以下string类对象:
string s1("1234");
现在我们想将这个对象里面的字符串全部+1,那就涉及到需要遍历这个字符串。那么类对象中的字符串我们如何遍历呢?这里我们介绍两种方法
(1)使用“[]”
[]作为一个运算符,在C++中也是支持运算符重载的。而string类中为了使用方便,也对其进行了重载:
可以看到,在这里我们直接使用sl[i]++就完成了对类对象中的字符串+1。有人可能奇怪size()是哪里来的。size()也是定义在string类中的一个函数,可以直接使用以获取string类对象中的字符串长度。
(2)范围for
范围for也是可以用于自定义类型的:
要注意,这里的auto要用引用,否则就只是单纯的将是s1的字符拷贝赋值给ch,ch的修改无法影响s1
(3)迭代器遍历
在学习迭代器遍历之前,我们要先了解什么是迭代器。在现在,我们可以将迭代器理解为一个形似指针的类型。它的行为上有时候是指针,有时候又不是指针。
1.迭代器中包含的函数
string类的迭代器中包含以上函数,但是比较常用的就前四个函数。只要会用这四个函数,其余的函数也就会用了
2.迭代器的基本用法
void test_string()
{
string s1("1234");
string::iterator it1 = s1.begin();
while (it1 != s1.end())
{
*it1 += 1;
cout << *it1 << ' ';
++it1;
}
cout << endl;
}
在这里面,我们先要创建一个迭代器(iterator)类型。因为iterator属于string中,在string没有展开的情况下要被写出对饮的string域。然后就可以直接使用iterator中的函数。且操作对应的字符串时的操作也与指针的操作方法相似。
(1)string正向迭代器begin()和end()
在上面我们展示了迭代器的基本使用。正向迭代器,顾名思义就是正方向迭代。
那大部分人应该都知道了begin()指向字符串开头。但是对于end()有些人可能就会误解为指向字符串最后一个字符。但其实end()是指向字符串最后一个字符的下一个字符,即“\0”:
(2)string反向迭代器rbgin()和rend()
反向迭代器,顾名思义就是反方向迭代。
rbegin()就是指向字符串的最后一个字符的下一个字符,即‘\0’。而rend()则是指向字符串的开头。
我们先看看它的使用:
可以看到,这里与正向迭代器不同,要使用另一个迭代器——“reverse_iterator”。但使用方法都是类似的
void test_string2()
{
string s1("1234");
string::reverse_iterator it1 = s1.rbegin();
while (it1 != s1.rend())
{
*it1 += 1;
cout << *it1 << ' ';
++it1;
}
cout << endl;
}
注意,有些人可能在使用反向迭代器时,以上面的代码为例,就可能用“--it1”,因为觉得rebegin()是最后一个字符,要向前走,就要用“--”。但其实这里依然是用“++it1”。如果不理解这种用法,可以认为因为正向迭代器“++”是往后走,那么反向迭代器“++”就应该和正向迭代器的方向相反,即往前走。这里就不能将reverse_iterator看做指针。
(3)const迭代器
如果看了iterator中的begin()等的支持函数,就可以发现里面还有const_iterator:
带有const的iterator不支持修改:
当然,反向迭代器也是有对应的const迭代器的:
这里面的在begin()、rbegin()后的const在用时不需要加
4.string中的capacity相关函数
在string中与capacity相关的函数有很多:
这里就不一一介绍了,而是选几个比较常用的介绍
(1)size()与length()
想必大家从上图中可以看到,size()和length()的介绍是一模一样的。这就表示这两个函数的功能都是一样的,用于计算字符串长度:
有人可能就会奇怪,既然这两个函数的功能都是一模一样的,为什么要有两个呢?原因在于string在一开始并不属于stl,而是后来被加进去的。而原来的string中用的就是length()。size()出现是由于stl中不只有字符串,还有链表、图、树等数据结构。这些数据结构中都需要有一个函数来获取大小。此时length()的名字就不太合适。为了风格统一和兼容问题,就在string中新增了一个和length()功能相同的size(),而没有删除length()改为size()。
(2)capacity()
学了数据结构的都应该知道这个函数,就是指空间容量。字符串的存储也是需要空间的,这个空间的开辟是有strin来实现的:
(3)max_size()
这个函数没什么用,不同编译器的下可能返回不同的数,但有一个共同点,就是返回的都是非常大的数且返回值与你的字符串无关:
不用过多了解,只需要知道有这个函数就行了
(4)clear()
clear()函数从字面就可以理解,就是清除字符串:
注意,clear()一般来讲不会清理空间,只会清理数据。但也可能会有一些编译器下的clear()会清理空间。如果想验证空间有没有被清除,可以用capacity()来看一下有没有清:
(5)empty()
empty()也很简单,就是用来判空判满的,学过数据结构的人对这个函数应该并不陌生
(6)reserve()
我们都知道在存储数据时,如果空间不够,就需要扩容。不同的编译器下的扩容机制不一样,但一般都是按照2倍去扩容。这种方式是为了适应当我们不知道有多少数据需要存储的情况。
但如果我们在一开始就知道有多少数据时,再按照扩容机制一步步去扩容就会拉低运行效率。而reserve()函数则是为了处理这种情况,用于手动开辟对应个数的函数:
不过在有些编译器,如vs上,就可能会给你多开几个空间,可能是考虑到内存对齐等影响因素造成的。
注意,reserve()函数要与reverse()函数相区分。一个是扩容,一个是翻转。
(7)resize()
该函数有两个接口:
resize()函数是用来重置字符串长度的。而根据重置的长度,又分为三种情况。为了方便显示,我们以以下代码来举例:
1.长度小于原字符串长度
如果重置后的字符串长度小于原来的字符串长度,那么就是删除数据:
可以看到,此时的size()被修改为8,但是capacity()并没有改变
2.长度在原字符串长度和容量之间
如果重置后的长度在原来的字符串的长度和容量之间,则是增加数据:
如果在resize()的参数中不添加字符,则只是增加size的大小。缺的字符以‘\0’填充
3.长度比原字符串的空间容量大
如果重置后的长度比原来的字符串的空间容量大,则是扩容+增加数据:
如果参数中没有字符,则是扩容和增大size()。缺的字符以‘\0’填充
5.string中修改(modifiers)相关函数
在string中有许多涉及修改相关的函数:
同样的,这些函数及其接口也不会全部讲解,而是选择几个比较常用的讲解
(1)尾插push_back()
这个函数是用来尾插的,从命名上也可以看出来:
但是这个函数其实不太好用。因为这个函数一次只能插入一个字符。这就导致如果你想插入一个单词,用push_back()函数就需要重复写多次。
(2)尾插append()
append()函数就支持插入字符串,同时根据插入方式的不同,有多个版本:
这些接口就不一一介绍了,有兴趣的话可以自行查文档和实验
(3)尾插operator+=
上面两种方式都可以用,但是都不是最常用的。在string中插入字符串最常用的是“operator+=”。这个想必大家都不陌生,就是一个运算符重载函数:
这个运算符重载的用法也非常简洁:
(4)插入函数insert()
insert()函数是用于在字符串的某个位置插入数据。也就是说这个函数既可以用于头插尾插,也可以用于中间插入。但是这个函数实际上并不太常用,因为无论是头插还是中间插入都需要挪动数据,代价很大。而为了适应多种情况,该函数也提供了大量的接口:
(5)删除函数erase()
这个函数用于删除数据。该函数提供了三个不同的接口:
同样的,这个因为挪动数据的代价问题也不太常用
注意,如果删除的个数超过了字符串中可删除的字符个数,则默认删除到结尾:
(6)替换函数replace()
replace()函数的接口非常多:
该函数是用于替换字符串中的部分数据:
但是,在这里面还有一个assign()函数,也是用于数据替换。不同的是,replace()支持部分替换,assign()则只能全部替换:
6.string中的数据操作(String operations)中的函数
(1)查找函数find()
find()函数在字符串中查找指定数据,提供了多种接口以支持字符查找和字符串查找:
返回值是第一次遇见的数据的下标:
如果没有查找到,则是返回“string::npos”。这个值是string类中的一个值,不支持修改,该值一般是size_t类型的-1,即42亿的值。但在不同的编译器下也可能不同
(2)逆序查找函数rfind()
rfind()函数支持逆序查找,即从字符串的末尾向前查找
该函数使用起来和find()相差无几,也是返回对应字符的下标:
(3)返回底层指针函数c_str()
从其函数返回值我们也可以看到,返回的是一个const修饰的字符串指针。主要用于c与c++交互式使用
这里面还有一个data()函数,和c_str()的功能是相同的,主要是早起设计时的遗留。在现在一般使用c_str()函数
(4)取部分字符函数substr()
substr()函数可以将一个字符串中的部分字符串取出,并构建一个string类返回
使用时是从pos位置开始取len个字符。如果len的个数比字符串的剩余个数多,则取到结尾就结束:
7.string中的Non-member function overloads
这个里面提供了大量的运算符重载
(1)关系比较relational operators
该运算符重载中包含了大量的比较相关的运算符重载:
(2)operator>>和operator<<
用于支持流插入和流提取
(3)流提取函数getline()
在理解getline()函数前,我们先来看看一下一个例子:
可以看到,这里我们输入的是“hello s”,但是打印的时候却只打印的了“hello”。其原因就在于缓冲区。如果不理解,我们可以想想,在以前无论是用cin还是用scanf()函数,当存在多个输入值时,我们都是用“ ”来区分的,如输入年月日。
而这里也是同样的道理,hello之后的空格被系统看做一个分隔符,将空格前的内容视为输入到s1中的内容,而空格后的则被视为下一个待输入数据的变量的值,并被留存在缓冲区内。
为了适应在某些情况下需要连同空格作为一个字符串的内容输入到一个string中的情况,就有了getline()函数。用于将一次性输入的所有数据输入到对应的变量中。
getline()函数有两个接口:
第一个接口中的delim用于指定结束符号。而第二个接口的默认结束符号是“换行符”
8.string的简单模拟实现
因为string中存在上百个接口,要全部实现太耗篇幅,所以此处我们只节选其中常用的部分进行模拟实现。此处仅仅是模拟实现,不代表与string中真正的底层实现相同
如果对string中的其他实现感兴趣,可以自行模拟实现或参考《stl原码剖析》
(1)头文件包含及函数声明
此处为了与库中的string相区分,用了一个“MockString”命名空间来包含。第二行的define是为了避免vs下的部分安全检查导致部分函数不可用
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
#include <assert.h>
using namespace std;
namespace MockString
{
class string
{
friend istream& operator>>(istream& in, MockString::string& s);//流提取>>运算符重载
friend ostream& operator<<(ostream& out, const MockString::string& s);//流插入<<运算符重载
public:
typedef char* iterator;
static const size_t npos = -1;//用于某些特定函数的返回值或参数
public:
//基本成员函数
string(const char* str = "");//字符串及无参构造
string(const string& s);//拷贝构造
string& operator=(const char* str);//“=”运算符的字符串重载
//string& operator=(const string& s);//“=”运算符类类型重载的传统写法
string& operator=(string str);//“=”运算符类类型重载的现代(常用)写法
~string();//析构函数
//iterator迭代器
iterator begin();
iterator end();
//modify修改字符串相关函数
void push_back(char ch);//单字符插入
string& operator+=(char ch);//单字符插入的运算符+=重载
void append(const char* str);//字符串插入
string& operator+=(const char* str);//字符串插入的元素安抚+=重载
void clear();//清除字符串函数
void swap(string& s);//类的字符串交换
void insert(size_t pos, char ch);//在pos位置插入ch字符
void insert(size_t pos, const char* s);//在pos位置插入一个字符串
string& erase(size_t pos = 0, size_t len = npos);//在pos位置删除len个字符
//capacity数据个数、容量等相关函数
void reserve(size_t capacity);//容量不够扩容
size_t size()const;//返回字符串个数
size_t capacity()const;//返回字符串对应的空间容量
bool empty();//判断空间是否满
void resize(size_t n, char ch = '\0');//将字符串长度设置为n,如果新长度比原长度长,则长的部分用ch补充
const char* c_str();//返回数据的地址
// access支持用"[]"的形式访问字符串
char& operator[](size_t pos);//[]运算符重载,支持字符串下标形式获取
const char& operator[](size_t pos)const;//[]运算符重载,支持常量字符串下标形式获取
//relational operators//判断运算符重载函数
bool operator<(const string& s)const;//“<”运算符重载
bool operator<=(const string& s)const;//“<=”运算符重载
bool operator>(const string& s)const;//“>”运算符重载
bool operator>=(const string& s)const;//“>=”运算符重载
bool operator==(const string& s)const;//“==”运算符重载
bool operator!=(const string& s)const;//“!=”运算符重载
//operations查找相关函数
size_t find(char ch, size_t pos = 0)const;//从pos位置开始查找一个ch字符,并返回其下标
size_t find(const char* s, size_t pos = 0)const;//从pos位置开始查找一个字符串,并返回第一个字符的下标
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
(2)部分基本成员函数实现
1.构造函数(string)
MockString::string::string(const char* str)//字符串及无参构造
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
2.拷贝构造函数(string)
拷贝构造函数有两种写法,第一种就是将想要拷贝的类对象的数据赋值给对应的类对象,再用strcpy()函数复制数据。
而第二种则是利用构造函数重新构造一个局部类对象,再利用swap()函数交换双方的数据。这里的swap()函数要先自己实现。使用初始化列表提前初始化原对象是为了避免构造的局部类对象出现野指针的问题
//MockString::string::string(const string& s)//拷贝构造的传统写法
//{
// _size = s._size;
// _capacity = s._capacity;
//
// _str = new char[_capacity + 1];
// strcpy(_str, s._str);
//}
MockString::string::string(const string& s)//拷贝构造的现代(常用)写法
:_str(nullptr)
,_size(0)
,_capacity(0)
{
string tmp(s._str);
swap(tmp);
}
3.“=”运算符的字符串重载
MockString::string& MockString::string::operator=(const char* str)//运算符=重载
{
size_t len = strlen(str);
if (_capacity < len)
{
reserve(len);
}
strcpy(_str, str);
_size = len;
return *this;
}
4.=”运算符类类型重载
该函数同样存在两种写法。
第一种是新开辟一个字符串空间复制原类对象的字符串,然后释放原类对象的字符串所指空间,并将复制好后的字符串和对应的数据给对应的类对象的字符串
第二种则是采用传值给参数的方式作为参数的书写形式。因为传值的过程中会对传进来的数据进行一次拷贝,再将拷贝的数据传给参数。这种写法就是利用传值过程的拷贝,直接交换对应的类对象和拷贝的类对象的数据
//MockString::string& MockString::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;
//}
MockString::string& MockString::string::operator=(string s)//“=”运算符类类型重载的现代(常用)写法
{
swap(s);
return *this;
}
5.析构函数(~string)
MockString::string::~string()//析构函数
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
(3)iterator迭代器实现
在使用iterator之前,要先用#define定义宏:
typedef char* iterator;
我们之前说过,iterator有时可以看做指针,有时又不是指针。但在模拟实现这里我们就把它当做指针看待
1.iterator的begin()迭代
MockString::string::iterator MockString::string::begin()
{
return _str;
}
2.iterator的end()迭代
MockString::string::iterator MockString::string::end()
{
return _str + _size;
}
3.返回for的底层实现
之前我们讲过,范围for可以无需指定结束条件和开始条件,在类中可以自己迭代。而其底层其实就是使用的iterator迭代器。在类中只要有iterator迭代器,范围for就可以使用
为了证明这点,我先将自己实现的string中的iterator迭代器屏蔽掉:
然后我们执行以下代码:
此时就会出现报错:
报错中说找不到对应的“end()”和“begin()”。这其实就是因为我们的迭代器没有实现。要注意,就算我们实现了迭代器,其函数名必须是begin或end这种库中的迭代器函数名称,不能用如Begin这种函数名。因为范围for是直接按函数名去匹配,如果函数名字与编译器中的不同,也会报错
(4)modify修改字符串相关函数
1.单字符尾部插入( push_back)
此处要注意的就是在插入完后要将最后一个数据设置为‘\0’。因为尾插后会覆盖原来的‘\0’,导致字符串没有结束标志,可能出现越界访问的情况
void MockString::string::push_back(char ch)
{
if (_size == _capacity)//容量不够扩容
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
2.单字符插入的运算符+=重载
MockString::string& MockString::string::operator+=(char ch)//运算符+=重载
{
push_back(ch);
return *this;
}
3.字符串插入(append())
void MockString::string::append(const char* str)//字符串插入
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t newcapacity = _capacity + len;
reserve(newcapacity);
}
strcpy(_str + _size, str);
_size += len;
}
4.字符串插入的运算符+=重载
MockString::string& MockString::string::operator+=(const char* str)
{
append(str);
return *this;
}
5.清除字符串函数(clear)
void MockString::string::clear()
{
memset(_str, '\0', sizeof(char) * _size);
_size = 0;
}
6.类的字符串交换(swap)
void MockString::string::swap(string& s)//类的字符串交换
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
7.在pos位置插入ch字符(insert)
void MockString::string::insert(size_t pos, char ch)
{
assert(pos <= _size);
size_t end = _size;
if (_size + 1 > _capacity)//容量不够扩容
reserve((_size + 1) * 2);
while (pos < end)//挪动数据
{
_str[end] = _str[end - 1];
end--;
}
_str[pos] = ch;
++_size;
_str[_size] = '\0';
}
8.在pos位置插入一个字符串(insert)
void MockString::string::insert(size_t pos, const char* s)
{
assert(pos <= _size);
size_t len = strlen(s);
size_t end = _size + len;
if (end > _capacity)//容量不够扩容
reserve(end * 2);
while (end >= pos + len)//挪动数据
{
_str[end] = _str[end - len];
--end;
}
strncpy(_str + pos, s, len);//拷贝字符串
_size += len;
_str[_size] = '\0';
}
9.在pos位置删除len个字符(erase)
注意,这个函数是写了缺省值的,但因为用声明和定义相分离的方式,就只能在头文件中的声明中写缺省值,定义处不能写:
npos是一个静态的static修饰的siz_t类型的变量,其值为-1,也就是42亿多
MockString::string& MockString::string::erase(size_t pos, size_t len)
{
assert(pos <= _size);
if (len >= _size - pos)//全删除或未传参时用于全部删除,直接在pos位置赋‘\0’
{
_str[pos] = '\0';
_size = pos;
return *this;
}
size_t end = pos + len;
size_t i = pos;
while (_str[end] != '\0')//部分删除
{
_str[i] = _str[end];
++i;
++end;
}
_size -= len;
_str[_size] = '\0';
return *this;
}
(5)capacity数据个数、容量等相关函数
1.空间扩容(reserve)
void MockString::string::reserve(size_t capacity)//空间扩容
{
char* str = new char[capacity + 1];//多一个空间的原因是capacity是有效字符空间,不包含‘\0'
strcpy(str, _str); //多的一个空间是为了存储'\0'
delete[] _str;
_str = str;
_capacity = capacity;
}
2.返回字符串个数(size)
size_t MockString::string::size()const//返回字符串个数
{
return _size;
}
3.返回字符串对应的空间容量(capacity)
size_t MockString::string::capacity()const//返回字符串对应的空间容量
{
return _capacity;
}
4.判断空间是否满(empty)
bool MockString::string::empty()//判断空间是否满
{
return (_size == _capacity);
5.将字符串长度设置为n,如果新长度比原长度长,则长的部分用ch补充(resize)
此处也是有缺省值的:
void MockString::string::resize(size_t n, char ch)
{
if (n <= _size)//设置的值小于原字符串长度
{
_str[n] = '\0';
_size = n;
}
else
{
if (n <= _capacity && n > _size)//设置的值大于原字符串长度但小于容量
{
if (_str[0] == '\0')
{
memset(_str, ch, sizeof(char) * (n - _size));
_str[n - _size] = '\0';
}
else
{
memset(_str + _size, ch, sizeof(char) * (_capacity - _size));
_str[n] = '\0';
}
_size = n;
}
else if (n > _capacity)//设置的值大于容量
{
reserve(n);
if (_str[0] == '\0')
{
memset(_str, ch, sizeof(char) * (n - _size));
_str[n - _size] = '\0';
}
else
{
memset(_str + _size, ch, sizeof(char) * (_capacity - _size));
_str[n] = '\0';
}
_size = n;
}
}
}
6.返回数据的地址(c_str)
const char* MockString::string::c_str()//返回数据的地址
{
return _str;
}
(6)access支持用"[]"的形式访问字符串
1.[]运算符重载,支持字符串下标形式获取
char& MockString::string::operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
2.[]运算符重载,支持常量字符串下标形式获取
const char& MockString::string::operator[](size_t pos)const
assert(pos < _size);
return _str[pos];
}
(7)relational operators判断运算符重载函数
1.“<”运算符重载
bool MockString::string::operator<(const string& s)const
{
size_t i = 0;
while (_str[i] != '\0' || s._str[i] != '\0')
{
if (_str[i] == s._str[i])
++i;
else
{
if (_str[i] < s._str[i])
return 1;
else
return 0;
}
}
if (s._str[i] == '\0')
return 0;
return 1;
}
2.“<=”运算符重载
bool MockString::string::operator<=(const string& s)const
{
size_t i = 0;
while (_str[i] != '\0' || s._str[i] != '\0')
{
if (_str[i] == s._str[i])
return 1;
else
{
if (_str[i] < s._str[i])
return 1;
else
return 0;
}
++i;
}
return 1;
}
3.“>”运算符重载
bool MockString::string::operator>(const string& s)const
return !(*this <= s);
}
4.“>=”运算符重载
bool MockString::string::operator>=(const string& s)const
return !(*this < s);
}
5.“==”运算符重载
bool MockString::string::operator==(const string& s)const
{
size_t i = 0;
while (_str[i] != '\0' || s._str[i] != '\0')
{
if (_str[i] == s._str[i])
return 1;
else
return 0;
++i;
}
return 1;
}
6.“!=”运算符重载
bool MockString::string::operator!=(const string& s)const
{
return !(*this == s);
}
(8)operations查找相关函数
1.从pos位置开始查找以一个ch字符
size_t MockString::string::find(char ch, size_t pos)const
{
assert(pos < _size);
for (size_t i = pos; i < _size; ++i)
{
if (_str[i] == ch)
return i;
++i;
}
return npos;
}
2.从pos位置开始查找一个字符串
size_t MockString::string::find(const char* s, size_t pos)const//从pos位置开始查找一个字符串
{ //并返回第一个字符的下标
if (_size == 0 && s[0] == '\0')
return 0;
else
assert(pos < _size);
size_t i = pos;
while (_str[i] != '\0' && s[0] != '\0')
{
size_t per = 0;
if (_str[i] == s[per])
{
size_t rem = i;
while (_str[rem] == s[per])
{
++per;
++rem;
if (s[per] == '\0')
return i;
else if ((s[per] != '\0' && _str[rem] == '\0'))
return npos;
}
i = rem;
}
++i;
}
if (_str[0] == '\0' && s[0] == '\0')
return 0;
else
return npos;
}
(9)流提取和流插入重载
1.流提取重载
第一种方式就是一个字符一个字符的提取。第二种则是提前开辟一个数组,将数据存在数组里面,当数组满了就将数组中的数据一次提取出来,接着重复这个过程直到数据全部提取
istream& MockString::operator>>(istream& in, MockString::string& s)//流提取>>运算符重载
{
//char ch = in.get();
//while (ch != ' ' && ch != '\n')
//{
// s += ch;
// char ch = in.get();
//}
char tmp[128] = { '\0' };
size_t i = 0;
char ch = in.get();
while (ch != ' ' && ch != '\n')
{
if (i == 127)
{
s += tmp;
i = 0;
}
tmp[i++] = ch;
ch = in.get();
}
if (i > 0)
{
tmp[i] = '\0';
s += tmp;
}
return in;
}
2.流插入<<运算符重载
ostream& MockString::operator<<(ostream& out, const MockString::string& s)//流插入<<运算符重载
{
for (size_t i = 0; i < s.size(); ++i)
{
out << s[i];
}
return out;
}