目录
一、为什么要学习string类
(1)c语言中的字符串
二、标准库中的string类
(1)string类的了解
(2)string类的常用接口说明
1.string类对象的常见构造
2.string类对象的容量操作
(1)size
(2)lengh
(3)capacity
(4)empty
(5)clear
(6)reserve
(7)resize
总结:
3.string类对象的访问和遍历操作
(1)重载的[ ]下标运算符
(2)at函数
(3)迭代器
1. begin和end
2.rbegin和rend
3.cbegin和cend等函数
(4)范围for
编辑
4.string类对象的修改操作
(1)push_back
(2)append
(3)operator +=
(4)c_str
(5)find+npos和rfind
(6)substr
5.string类的非成员函数(全局函数)
(1)operator +
(2)流插入和留提取函数的重载
三、vs和g++下string结构的说明
(1)vs下的string结构
(2)g++下的string结构
四、关于string类的一道oj题
(1)反转字母
编辑
在c语言中,字符串是以'\0'结尾的一些字符合集,为了操作方便,c标准库中提供了一些str系列的库函数,但是这些库函数是和字符串分离开来的,不太符合面相对象编程的思想,而且底层空间还需要用户自己管理,稍不注意还可能越界访问
(1)字符串是表示字符序列的类
(2)标准的字符串类提供了对此类对象的支持,其接口和普通的stl类似,但添加了专门用于操作单字节字符字符串的设计特性
(3)string类是使用char作为它的字符类型,使用它的默认char_traits和分配器类型(
(4)string类是basic_string模版类的一个实例化,ta使用char来实例化basic_string模版类
(5)注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或边长字节(UTF-8)的序列,这个类的所有成员(长度和大小)以及他的迭代器,将仍然按照字节来操作,(而不是实际编码的字符)来操作
总结:
(1)string是表示字符串的字符串类
(2)该类的接口和常规的容器接口基本一致,但是专门添加了一些用来处理string的常规操作
(3)string在底层的实现是:basic_string模版类的别名,
typedef basic_string
(4)不能操作多字节或边长字节的序列
(5)在使用stirng类的时候,必须要包含头文件#include
这三种写法都是符合其构造的
这是返回字符串有效字符的长度的成员函数,当然他的长度可能与容量不同
这里我们的size就是12,因为"hello world!"刚好是12个字节,但是它的容量是多少呢?
那么有人就会问了,即使加上后面的'\0'也只是13啊,为什么会是15呢?其实这和vs中实现的string类有关,vs中底层代码中string的初始容量就是16,减去'\0'就是15了
我们还在文档中注意到了关于大小的函数还有一个lengh,但是为什么有了size还需要lengh呢?其实是因为string的出现比STL要早,在那是我们用的是lengh,后来为了和STL的使用兼容,我们才加上了一个size,总的来说他们都是返回字符串返回字符串有效字符的长度的函数,但是我们以后尽量使用size!
返回空间的总大小,在c++中的类对象都是在堆中开辟空间的(为了方便扩容)而capacity函数就是返回对象在堆中的真正空间的大小,当新插入的内容大于当前容量时,会自动扩容
检测字符串是否为空串,是返回true,不是空串返回false
我们可以看到在文档中说到:empty函数不会去清理对象的空间,仅仅是判断对象是否为空,真正的清理工作需要clear函数去完成
这里s1不是空串,所以返回的是false,也就是0
清空有效字符,让size变成0,但是空间不会释放
我们可以看到在s1清理后size变成了0,但是capacity仍然是15,这说明clear函数可能仅仅是把s1对象指向字符串的指针给移动了,并不会去释放它开辟的空间
reserve仅仅是一个申请空间的改变的函数。如果申请的大小大于现有的capacity,则该函数会将容量提升到指定大小n(或者由于实现机制导致大于n);如果申请的大小小于现有的capacity,则不会缩容。这个函数不会改变他的长度或者改变他的内容。
可以扩容
可能不会缩容(不绝对,取决于编译器)
缩容
他会调整string类对象的长度。如果n小于当前长度,会将size减小到n(移动指针)然后去除掉多余的内容;如果n大于当前长度,这个对象的空间会扩容到n(由于对齐机制,可能也会大于n),如果有指定字符c会用c去填充,如果没有指定字符,会用'\0'去填充
其实resize这个函数,在n>当前容量的时候相当于reserve+初始化;在n<当期容量的时候会移动指针减少长度,但是不会对空间大小capacity有影响
(1)size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
(2) clear()只是将string中有效字符清空,不改变底层空间大小。
(3) resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
(4) reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
遍历string类对象有几种方法,一是使用重载的[ ]或at函数;二是使用迭代器
operator [ ]让我们可以像数组一样直接访问string对象的任意下标的元素,并且由于他重载了两种函数,一个const对象调用,一个非const对象调用,所以非const对象还可以直接通过下标来修改
at函数其实和operator [ ]类似,只不过其操作较为麻烦,所以我们一般都习惯于使用operator [ ]来对string类对象进行操作
迭代器可以当做指针来使用,但是具体到每一种容器的迭代器是否真的是指针就得看他底层的代码了。
这两个迭代器其实就是倒着走的迭代器,与上述的没什么区别,就照常当做指针使用就行,至于迭代器的++和- -等操作迭代器内部会自己封装好
这几个函数是常量迭代器,使得该对象的任何数据不能被修改,只能用于读
添加一个字符到string对象的末尾处,将size+1,如果空间不够会自动扩容
在string类对象后面追加一个字符串,其中这个字符串可以是类对象,可以是普通的字符串常亮,也可以是一段迭代器区间
文档中的size_t n是指要尾插多少个字符,如果是对于常量字符串或者string对象,就是某一段长度,如果是尾插char类型的,就是插入多少个char
operator +=是我们平时最为常用的一个接口,因为他最为符合使用习惯,而且他也重载了几种接口,让我们可以方便的使用
这是一个返回字符串地址的接口,因为c++的封装特性,我们不能直接访问类中的私有成员变量,但是可以通过公有的函数来提供字符串的地址。
为什么要提供这样一个地址呢?直接使用string类不是更为方便吗?其实这是因为c++为了兼容c语言而设计的接口,在c语言中,我们常常会用到文件流操作等,但是系统中的很多接口都只提供了c语言的操作,并没有stirng单独的接入方式,所以我们只需要把string的真正字符串地址传递就可以和c语言一样使用了!
find是一个用来查找字符串中某个字符的函数,可以用来查找某个字符,至于他后面的缺省值pos=0默然就是从第一个位置开始找罢了,找到后返回的是该位置的下标
rfind和find大致是一样的,唯一不同的就是rfind是从后往前去找字符
顾名思义,substr就是在字符串中从pos的位置开始,截取n个长度的字符串,并返回截取的部分
注意:
(1)1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
(2) 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好,可以减少因为不断扩容产生的消耗。
这个函数和普通的+没有什么用法上的区别,唯一的不同就是他是用来string类对象相加的,但是我们建议少用这个运算符重载函数,因为需要传值返回,而传值返回又是深拷贝,导致效率很低
这两个函数为什么必须要重载成全局函数而不是成员函数?我们在之前已经有提到过,因为类里面的成员函数的形参第一个位置默认会被this指针所抢占,导致如果重载成成员函数,使用习惯就和我们相反了,而在类外部就没有this指针的占位影响!
其余的函数我们用到的情况比较少,大家可以在要使用的时候查文档即可,只需要记忆有这么个功能的函数即可
vs下的结构体一共占据28个字节,内部实现的稍微复杂一点,先是有一个联合体,联合体用来定义string类中的字符串的存储空间
union _Bxty
{
// storage for small buffer or pointer to larger one
value_type _Buf[_BUF_SIZE];
pointer _Ptr;
char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;
(1)当字符串的长度小于16时,使用内部固定的字符数组来存放;
(2)当字符串的长度大于16时,才会去堆上另外开辟空间,这时会用指针指向那块开辟的空间,而不使用内部固定的16个字节空间
这种设计也是有一定的道理的,因为大多数情况下字符串的长度都是小于16,那么这时候会使用内部固定的字符数组来存放,就不需要另外开辟空间,效率较高
其次:还有一个size_t字段保存字符串的长度,一个size_t字段保存从堆上开辟空间的总容量
最后:还有一个指针做一些其他事情
故一共是16+4+4+4=28个字节
g++下的stirng结构实现的比较简单,和我们后续要模拟实现的基本一致,g++下的string是通过写时拷贝完成的,一共只有4ge字节(32位机器指针的大小就是4字节)该指针指向了一块堆开辟的空间,其中包含以下内容:
扩展了解写时拷贝
写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。
写时拷贝能够极大程度上减少了深拷贝带来的消耗,只需要rangcount++就能代表一次空间的释放,唯一的问题就是只能读不能写,因为一旦写就会对其他的对象也造成修改,所以写时拷贝在修改时还是会去深拷贝一块空间,不过如果你不需要写只用读就赚大发啦
关于这道题,我们可以利用霍尔排序的思想,先只找到字母,然后交换位置,直到两个指针相遇