C++之string类的使用
- 1.为什么要学string类
- 2.标准库中的string类
- 3.string类的接口的使用
- 3.1默认成员函数
- 3.2容量操作
- 3.3访问操作
- 3.4遍历操作
- 3.5修改操作
- 3.6字符串操作
- 3.7非成员函数
1.为什么要学string类
我们所学的字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库提供了一些str系列的库函数,但是这些库函数与字符串是分离的,不太符合面向对象的思想,而且底层空间需要用户自己管理,稍不留神可能还会访问越界。
因此C++标准库提供了字符串类模板,为我们提供了各种功能的接口,而不在关心底层是如何实现的,方便我们去使用。
在学习一个模板的时候,我们需要了解这个模板提供了哪些接口,接口实现了什么功能,返回值等等,这里提供C++标准库,里面有各种模板的详细介绍。
2.标准库中的string类
basic_string是一个标准库提供的字符串类模板,charT是模板参数,表示的是字符类型,字符串由这种类型的一系列字符组成。后面的参数暂时不用管,后面再说。
这个类模板可以实例化上面4种类。
可能会有这样的疑问,字符串里面包含的都是普通字符,为什么要实例化4种类呢?
其原因是因为字符编码的问题。
补充知识
1.我们在C语言所接触最多的是ASCLL,包含了大小写字母,数字,标点符号等等…这套编码是由美国发明的,是符合老美文字习惯。但是为了将计算机推展到全世界,还需要包括其他国家文字。因此有了统一码(unicode)也叫万国码。
2.unicode有UTF-8、UTF-16、UTF-32三种将数字转换到程序数据的编码方案。一个字大小分别占1个字节,2个字节,4个字节。
3.我们使用最多是UTF-8,这种编码方案兼容ASCLL,还省空间。
以后根据字符编码不同,可以选择对应的实例化的类。
简单认识string类的构成
template<class T>
class basic_string
{
public:
//各种成员函数
private:
T* _str;
size_t _size;
size_t _capacity
};
typedef basic_string<char> string;
int main()
{
//这两种是等价的
basic_string<char> s;
string s;
}
字符串类其实本质就是动态增长的数组,学过数据结构的数组,这里更容易理解。
使用string必须包含头文件
#include<string>
3.string类的接口的使用
string类每一种接口都有很多函数重载,下面主要讲述的是一些常用的需要重点掌握的,不需要看文档就会用的,其他没有讲述的如果需要用,在查文档。
3.1默认成员函数
默认成员函数包括:构造函数(包含拷贝构造函数),析构函数,赋值运算符重载函数
构造函数
拷贝构造函数是构造函数的重载,因此构造函数包含里包含拷贝构造函数。
(constructor)函数名称 | 功能说明 |
---|---|
string() (重点) | 构造空的string类对象,即空字符串 |
string(const char* s) (重点) | 用C-string来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string&s) (重点) | 拷贝构造函数 |
赋值运算符重载函数
3.2容量操作
函数名称 | 功能说明 |
---|---|
size(重点) | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty (重点) | 检测字符串释放为空串,是返回true,否则返回false |
clear (重点) | 清空有效字符 |
reserve (重点) | 为字符串预留空间 |
resize (重点) | 将有效字符的个数该成n个,多出的空间用字符c填充 |
size和length都返回字符串有效字符长度,不包括’\0’。
这里可能会有疑问,一样功能的实现两个函数?
1.最开始求字符串有效字符长度只有length,但是有了STL(标准模板库)之后,涉及的模板多了起来,如求二叉树结点个数,链表长度,就不是能用length能表达清楚的了,所以有了size。
2.string为了符合STL(标准模板库),也有了size,在后面我们经常使用的是size。
当string类存放字符空间是动态开辟的,空间不够会扩容。
//插入1000个字符,看是否扩容
string s;
size_t capacity = s.capacity();
cout << "capacity:" << capacity << endl;
for (int i = 0; i < 1000; ++i)
{
s.push_back('x');
if (capacity != s.capacity())
{
capacity = s.capacity();
cout << "change capacith:" << capacity << endl;
}
}
不同编译器扩容是不一样的。下面看看在Linux情况下,同一段代码扩容情况。
可以看见在Liunx下,同样代码,每次申请空间都是2倍。
我们知道频繁扩容是有代价的。如果我们预先知道开辟多大空间,就可以减少扩容,提高效率。
reserve为字符串预留空间。
empty判断字符串是为空。
clear清楚有效字符,清除之后是否缩容,看string类是如何规定。
resize,将有效字符的个数改成n个,多出的空间用字符c填充
resize有三种情况:
1.n<size
删除数据
2.size<n<=capacity
插入数据,如果没有指定用’\0’填充。
3.n>capacity
扩容+插入数据
3.3访问操作
函数名称 | 功能说明 |
---|---|
operator[] (重点) | 返回pos位置的字符,const string类对象调用 |
operator[]是常用的访问操作,不仅可以访问,还可以修改。
3.4遍历操作
void test_string4()
{
string s("hello world");
//遍历
//1.普通遍历
for (size_t i = 0; i < s.size(); ++i)
{
//字符+1
s[i] += 1;
cout << s[i] << " ";
}
cout << endl;
//2.范围for遍历
for (auto& ch : s)
{
//字符-1
ch -= 1;
cout << ch << " ";
}
cout << endl;
//3.迭代器iterator
}
迭代器:是一种通用的访问方式。
iterators:行为上像指针一样的类型。
注意不同迭代器是不一样的。
//3.迭代器iterator
//正向迭代
string::iterator it1 = s.begin();
while (it1 != s.end())
{
*it1 += 1;
++it1;
}
it1 = s.begin();
while (it1 != s.end())
{
cout << *it1 << " ";
++it1;
}
cout << endl;
返回字符串第一个数据位置
返回字符串最后一个有效数据的下一个位置。
//反向迭代
string::reverse_iterator it2 = s.rbegin();
while (it2 != s.rend())
{
cout << *it2 << " ";
++it2;
}
cout << endl;
rebegin返回有效字符最后一个位置
rend返回有效字符第一个位置
//const正向迭代
//只能遍历,不支持修改
string::const_iterator it3 = s.begin();
while (it3 != s.end())
{
cout << *it3 << " ";
++it3;
}
cout << endl;
//const反向迭代
//只能遍历,不支持修改
//string::const_reverse_iterator it4 = s.rbegin();
//auto 根据右边推测左边的类型
auto it4 = s.rbegin();
while (it4 != s.rend())
{
cout << *it4 << " ";
++it4;
}
cout << endl;
我们看见有的接口实现了两种类型,一个加const,一个没加const。
如果我们自己实现一个类也需要考虑要不要加上实现加const版本的函数。
总结:
1.只读功能函数,只提供const版本即可
2.只写功能函数,只提供非const版本即可
3.读写功能函数,需提供const+非const版本
3.5修改操作
函数名称 | 功能说明 |
---|---|
push_back | 在字符串后尾插字符c |
append | 在字符串后追加一个字符串 |
operator+= (重点) | 在字符串后追加字符串str |
insert | 插入字符串 |
erase | 删除字符串中的字符 |
assign | 为字符串指定一个新值,替换其当前内容。 |
swap | 交换字符串值 |
push_back
append 和构造函数的重载函数一样,但是常用的是第一个和第三个
operator+=是我们需要重点掌握的
insert 插入字符串
插入意味着挪动数据,所以string很少使用
剩下的可以自行了解文档。
erase删除字符串中的字符
也是要挪动属性,很少使用。
默认开始位置是0,默认长度是npos
size_t是无符号整型 ,-1会整型提升无符号整型,因此-1就变成了4294967295。
assign和replace都是替代原有的字符串,但是assign是清理掉原数组之后在赋值,而replace不清理原数组,直接替代。
swap交换字符串的值
3.6字符串操作
函数名称 | 功能说明 |
---|---|
c_str(重点) | 返回C格式字符串 |
find + npos(重点) | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
c_str返回c格式字符串。
注意返回值是 const类型。
find,正向在字符串中查找内容
找到返回第一个匹配的第一个字符的位置。找不到返回string::npos
rfind,反向在字符串中查找内容。
**find_first_of,查找字符串中的字符,该函数和find不一样,这个函数会搜索参数中指定的任何字符匹配的第一个字符。。当指定位置时,搜索仅包括位置位置处或位置之后的字符,忽略位置之前可能出现的任何字符。**当作了解即可,用的很少。
搜索成功返回匹配字符的位置,不成功返回npos
substr生成子字符串
返回一个新构造的字符串对象
3.7非成员函数
函数名称 | 功能说明 |
---|---|
operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
operator>> (重点) | 输入运算符重载 |
operator<< (重点) | 输出运算符重载 |
getline (重点) | 获取一行字符串 |
relational operators (重点) | 大小比较 |
operator>>从流中提取字符串
operator<<将字符串插入流
为什么输的是hello world,结果只打印处理hello呢?
cin和scanf都是输入,cin标准化输入,scanf格式化输入。
cin取数据时会忽略空格和回车继续输入。
scanf取数据时遇到回车、空格就会停止。
为了使cin和scanf一样,库里面实现的operator<<就针对做了特殊处理。
如果就想拿到整个字符串,使用getline函数将行从流转换为字符串
relational operators是字符串大小比较的接口
string类接口还有很多,这里只是例举了一些比较重要的,都标上了重点。需要不看文档就知道怎么。剩下的内容大家在下面在多多练习。好了本篇string类的使用到这里就结束喽。
下篇string类的模拟实现.
喜欢的小伙伴,点赞,评论,加收藏!感谢支持!