【C++】string介绍

String

    • 前言
    • 为什么学习string类?
    • string类的常用接口说明
      • string类对象的常见构造
      • 析构函数
      • 赋值运算符重载
      • [ ] 重载
      • size和length
      • 迭代器
      • 字符串追加
      • 关于容量的函数
      • insert和erase
      • find
      • replace
      • c_str
      • rfind
      • find_first_of
      • find_first_not_of
      • find_last_of
      • substr
      • getline
      • to_string
    • 总结

【C++】string介绍_第1张图片

前言

这篇博客讲的是STL中的string,作为一名C++程序猿,搞懂STL可是非常重要的。这篇博客就是介绍一些string类中比较常用的函数。但重要的不是这,因为记住所有的函数接口是不可能的,而且重要的函数接口就几个。重要的是让大家培养出查文档的习惯。

string类中成员变量大概长这样,其实就是一个专门放字符串的顺序表,不过是库里面给你提供了一个现成的,不需要你自己去实现:

【C++】string介绍_第2张图片
这里的size和可不是sizeof()中的那个理解了。size就是有效字符的个数。这里是hello这五个。
上面的图留个印象,可以帮助了解string类。

为什么学习string类?

C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。不是很方便。

C++中有string类来提供各种各样的函数接口,大大提高了办事效率。

学了数据结构的同学,简单理解string类的话,其实就相当于是存放数据类型为char的顺序表。

函数接口有很多,我这里给出相应的网站供大家参考:STL中string参考文档,里面是英文的,读起来比较费劲的话我也没什么办法。

简单说几点,看不懂没关系,刚接触肯定不能完全理解的,我刚学的时候也不懂。

  1. 字符串是表示字符序列的类。
  2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器(容器就相当于是数据结构)的接口,但添加了专门用于操作单字节字符字符串的设计特性。
  3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信息,请参阅basic_string)。
  4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
typedef basic_string<char, char_traits, allocator> string;
  1. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。

关于编码的东西,我直接给出大家一篇文章:刨根问底之——什么是编码?
感兴趣的可以自己看看,不过重点不在这。
要记住一点就是不同的编码中,一个字符可能占用不同的字节数。
比如utf-8一个汉字占用3个字节,utf-16一个汉字占用2个字节,utf-32一个汉字占用4个字节。

  1. 不能操作多字节或者变长字符的序列。

string类的常用接口说明

只讲常用的接口,如果想要自己拓展,可以查看文档,

string类对象的常见构造

先看一下提供了几个构造:
【C++】string介绍_第3张图片

总共七个构造,很多,但常用的就两三个。上面图中标上序号为1,2,4的记住就行。剩下的我待会给出用法,不需要记,等到写程序的时候需要某种功能的时候,去这里面查查就行。

上例子:

标号为1,2,4的。

1 string();
2 string (const string& str);
4 string (const char* s);

【C++】string介绍_第4张图片
上面cout能直接打印string类对象,是因为库中也有重载的>>函数。
【C++】string介绍_第5张图片

标号为3的。

string (const string& str, size_t pos, size_t len = npos);

这个函数功能是从string类对象str中的pos位置开始,往后len个长的字符作为某个string对象的初始值。
【C++】string介绍_第6张图片
当len超过了能够初始化的有效字符长度或者没有给len值的时候会直接将pos后面全部作为初始化的内容。

len过大:
【C++】string介绍_第7张图片

未给len(len为缺省参数npos的时候):
【C++】string介绍_第8张图片
npos的值为-1:
【C++】string介绍_第9张图片
上面第一句话就说了npos是string类中的一个静态成员常量。固定为-1。

当len以无符号整型size_t接收-1的时候,-1以补码的形式给了len,len会接收到一个非常大的数,有42亿多,就是二进制全1的情况(如果不懂去了解一下整数的存储规则),其实也就相当于是len过大的情况。因为基本不可能出现一个42亿字节大小的字符串,也就是4G左右的大小。

标号为5的

string (const char* s, size_t n);

这个函数是将常量字符串s中前n个字符作为对象初始化的值。
可以这样用:
【C++】string介绍_第10张图片

也可这样用:
【C++】string介绍_第11张图片
这里的c_str后面会讲,这里先不说。

标号为6的

string (size_t n, char c);

这个函数将类对象的初始化字符串设置为n个字符c。
【C++】string介绍_第12张图片
最后一个先不说,用到迭代器了,等会讲迭代器的时候再说。

析构函数

这个没啥讲的,自动调用的东西。

赋值运算符重载

【C++】string介绍_第13张图片
库里提供了三个,1,2重要,但挨个说一下。

第一个和第二个放一块:

string& operator= (const string& str);
string& operator= (const char* s);

上例子:
【C++】string介绍_第14张图片
第三个:

string& operator= (char c);

【C++】string介绍_第15张图片

再说个这个:
【C++】string介绍_第16张图片
上面的几个初始化,都是调用的构造函数,只有最后一个赋值才是赋值运算符重载,初始化时不管用没用=,都不是调用赋值运算符重载。编译器做了优化。
这个在我前面类和对象中的博客也讲了,不懂的可以看看:类和对象下,看explicit关键字那里就行
最下面那个才是赋值运算符重载。中间蓝色框框住的是调用对应函数时,反汇编中函数的地址,右边绿色的是调用的函数。

[ ] 重载

这个[ ]的重载,对于字符串来说非常有用,可以向数组一样来访问字符串中的任意位置字符。

在这里插入图片描述
例子:

访问任意位置:
【C++】string介绍_第17张图片

遍历字符串
【C++】string介绍_第18张图片

可以看到,库里面给了两个,还有一个const修饰的。
意思就是假如用string创建出的const的对象,这个对象是不能被修改的,此时再使用[ ]重载就会调用const修饰的[ ]重载。
来例子:
【C++】string介绍_第19张图片
此时就只能用来访问了,也就是只读的。
【C++】string介绍_第20张图片

[ ]会自动检查越界的情况。
【C++】string介绍_第21张图片

还有一个能随机访问的。
【C++】string介绍_第22张图片

看下例子:
【C++】string介绍_第23张图片
不过用起来没有[ ]那么方便,这个也是提供了有const修饰的函数的。
这个at和[ ]的区别就是,用at越界了之后是抛异常,[ ]是程序直接崩溃。

可以看到上面那张图中,for循环中我用到了s1.size()这个函数,讲讲这个。

size和length

在这里插入图片描述
库里提供了这两个。
二者表示的是一个意思,只不过当初STL还没出的时候string已经出了,没出STL的时候用的是length表示string中有效字符的个数,后来除了STL之后,为了跟STL中其他的容器看齐才加的size,因为别的容器中都用的是size来表示元素个数,所以后来也给string中加上了这个size,用来表示有效字符个数。

那么上面的那个例子就好理解了。s1.size()就相当于是字符串的长度。

这里再把lenth给出:
【C++】string介绍_第24张图片
一模一样。

迭代器

这个就要重点说了。

先看长啥样:
【C++】string介绍_第25张图片

其中stirng::iterator就是迭代器,s1.begin()就是迭代时的初始位置,s1.end()就是迭代的末尾位置,就相当于是首元素的地址和末尾有效元素的地址。

这个用法是死的,毕竟语法就是这样,没法改,只能记住。

迭代器用起来就像是指针,但是有的容器中iterator用的是指针,有的就不是。
string和vector(顺序表)中的迭代器其实就是指针实现的。
其他的就不是指针了。

但是在string和vector中不习惯用iterator,因为可以直接用[ ]来访问元素,用iterator反而麻烦了。

list/map/set…只能用iterator来访问,用法也是类似的。

这里给出list(链表)中使用iterator的例子:
【C++】string介绍_第26张图片
注意循环里面的 it++,我刚学的时候老是忘记。
可以看到,我们用C语言写的链表可没有这样的功能,只能是next来找下一个节点。所以说C++学STL是很有必要的,会方便很多。

上面的是正向的迭代器,还有反向的迭代器。
叫做reverse_iterator,对应的头和尾也要改成rbegin和rend。
【C++】string介绍_第27张图片

不仅有正反向还有const修饰的对象。
【C++】string介绍_第28张图片
像上面的一样,也是const对象就调用的是const修饰的begin和end。
把例子给出:
正向+const
【C++】string介绍_第29张图片

反向+const
【C++】string介绍_第30张图片

如果觉得写迭代器比较麻烦的话,可以用auto来替代它,auto可以自动识别类型,这个在我的第一篇C++博客当中也是说了的,感兴趣的可以看看直接看最后的auto就行 。
还是给例子:
【C++】string介绍_第31张图片
这时候不管有多长的类型,都可以直接用auto来替换,写起来就比较方便了。

提到auto了就说一下范围for。
给例子:
【C++】string介绍_第32张图片

看起来很,其实范围for底层就是用迭代器来实现的。

迫于vs2019下反汇编看起来二者相似度不大,但我又我没法展示出来更为相似的地方,但反汇编还是有些地方是相似的。
【C++】string介绍_第33张图片
【C++】string介绍_第34张图片
用vs2013可能更好观察一点。

迭代器就说到这。

下面把前面的构造函数的最后一个演示一下:

template <class InputIterator>
  string  (InputIterator first, InputIterator last);

上面的InputIterator first和InputIterator last这两个,其实传的就是迭代器。
【C++】string介绍_第35张图片
参数中 s1.begin() 和 s1.end() 可加可减。
在这里插入图片描述【C++】string介绍_第36张图片

字符串追加

有三种用法。
第一种是push_back接口。
第二种是append接口。
第三种是+=接口。
在这里插入图片描述

挨个演示

push_back
【C++】string介绍_第37张图片
加单个字符
【C++】string介绍_第38张图片

append
【C++】string介绍_第39张图片

展示几个,其实和构造函数里的一模一样那几种情况一模一样。

string& append (const string& str);

【C++】string介绍_第40张图片

string& append (const string& str, size_t subpos, size_t sublen);

这个和构造函数里面的那个很相似。
正常情况:
【C++】string介绍_第41张图片
sublen太大:
【C++】string介绍_第42张图片
不给sublen:
【C++】string介绍_第43张图片

string& append (const char* s)

【C++】string介绍_第44张图片

演示下迭代器,剩下的两个就不说了,跟构造函数里的用法一样。
【C++】string介绍_第45张图片

然后就是+=了
【C++】string介绍_第46张图片
这个用起来会比前两个方便很多。

演示一下:
【C++】string介绍_第47张图片
所以前两个方法只是为了演示一下,不建议用,真要用的话用+=就行,很方便。

关于容量的函数

前言部分,给了这张图:
【C++】string介绍_第48张图片
capacity函数可以返回string对象的容量
【C++】string介绍_第49张图片

预开空间reserve:
【C++】string介绍_第50张图片
这个函数会将_capacity开到n(或者更大)。不会初始化string对象中的内容。
最后开的_capacity的大小在不同的系统下是不一样的,vs下是这,g++下就不是这个了,会直接开为100。
在这里插入图片描述
里面啥也没放:
【C++】string介绍_第51张图片

预开空间并初始化resize:

【C++】string介绍_第52张图片
这个函数会将_capacity开到n,并且会将size的大小改为n,然后将前n个初始化为c,如果没给c,则初始化为0。
【C++】string介绍_第53张图片
看以看到size变为了100,capacity变为了111。

string的扩容机制在vs2019下有点怪:
我用下面的代码进行string类对象的扩容观察:

string s1;
	cout << "init size::" << s1.size() << endl;
	int capacity = s1.capacity();
	cout << "init capacity::" << s1.capacity() << endl;
	int count = 1000;
	while (count)
	{
		s1 += 'h';
		if (capacity != s1.capacity())
		{
			cout << "capacity changed ::" << s1.capacity() << endl;
			capacity = s1.capacity();
		}
		count--;
	}

在vs2019下:
【C++】string介绍_第54张图片
可以看到大概是1.5倍的扩。
而且如果用reserve的话,也不是给定的capacity值。
【C++】string介绍_第55张图片

但是g++下就不一样了:
同样的代码放到g++下:
【C++】string介绍_第56张图片
运行:
在这里插入图片描述
得到的结果就是100,不是vs2019下的111。

再看一下扩容的情况:
【C++】string介绍_第57张图片
这里能看到,g++下是2倍的扩的。

就是给大家演示一下,不同编译器下的结果可能是不一样的。

再演示一下resize缩小size的情况:
【C++】string介绍_第58张图片
上面resize(5),会将hello world!!!中的 world!!!全部劈掉,只剩下hello。
【C++】string介绍_第59张图片

insert和erase

先看inseret:
【C++】string介绍_第60张图片

其实参数和上面的构造等非常相似,都跑不开string对象,常量字符串,单个字符。

string& insert (size_t pos, const string& str);
//插string对象

string& insert (size_t pos, const string& str, size_t subpos, size_t sublen);
//插string对象的sobpos位置处的后sublen个字符

string& insert (size_t pos, const char* s);
//插常量字符串

string& insert (size_t pos, const char* s, size_t n);
//插常量字符串的前n个字符

string& insert (size_t pos, size_t n, char c);
//插n个字符c

上面的都是从pos位置插入,只不过是前插。
放一块演示一下:
【C++】string介绍_第61张图片
剩下的就不说了。

然后是erase
【C++】string介绍_第62张图片
就说第一个。
从pos位置开始,删除npos个字符。

默认情况下全删
【C++】string介绍_第63张图片
给上参数:
【C++】string介绍_第64张图片

insert和erase不建议多用,因为二者都可能存在挪动数据的情况,效率比较低。

find

【C++】string介绍_第65张图片
上面都是找字符(串),找到了返回下标,找不到返回npos。

//从pos位置开始找str
size_t find (const string& str, size_t pos = 0) const

【C++】string介绍_第66张图片

//从pos位置开始找s
size_t find (const char* s, size_t pos = 0) const;

【C++】string介绍_第67张图片

//从pos位置开始找s的前n个字符,很鸡肋,不如第二个
size_t find (const char* s, size_t pos, size_t n) const;

在这里插入图片描述

//从pos位置开始找字符c
size_t find (char c, size_t pos = 0) const;

【C++】string介绍_第68张图片
这里注意,找的永远是从pos位置往后的第一个c。

给道例题:将下面字符串中空格替换为字符串:::

hello world I am xxx

也就是换成:

hello:::world:::I:::am:::xxx

第一种方法:结合前面的insert和erase

【C++】string介绍_第69张图片
具体的就不讲了,注意画图理解。

第二种方法:空间换时间
【C++】string介绍_第70张图片

还有一个函数replace,听名字就是替换。

replace

【C++】string介绍_第71张图片

string& replace (size_t pos,  size_t len,  const string& str);

将string对象的pos位置往后的len个字符替换为str。

给几个例子:
0位置往后的2个字符换为tmp
【C++】string介绍_第72张图片
0位置往后的3个字符换为tmp
【C++】string介绍_第73张图片
0位置往后的1个字符换为tmp
【C++】string介绍_第74张图片
0位置往后的2个字符换为tmp
【C++】string介绍_第75张图片

然后也能用这个来搞前面那道例题了:
【C++】string介绍_第76张图片

然后replace的就不讲了,感兴趣的自己可以查文档。

但是如果想要找到最后一个单词呢?
可以用rfind。

c_str

这个比较有用,就是将string类对象中的字符串以常量字符串的形式返回。也就是将C++中的类对象转换为C中的常量字符串。
【C++】string介绍_第77张图片
其实二者没什么区别。区别都是C++和C的语法使用上的。
【C++】string介绍_第78张图片
但是有一个场景要注意:
C++中打印字符串是以字符串本身的size来决定打印多少的。
而C中的打印是以’\0’为结束标志的。
【C++】string介绍_第79张图片
就记住这个就行。

rfind

这个就是倒过来找的意思。reverse find。
【C++】string介绍_第80张图片

然后就可以做上面的那道题了:
【C++】string介绍_第81张图片

还是不细讲,用的时候查文档就行。

find_first_of

这个函数名字起得不好,应该叫find_any_of,这个函数用法是找到字符串里任意一个字符第一次出现的位置。
在这里插入图片描述
例子:
在这里插入图片描述
这里的2就是hello中的第一个 l 的下标。
在这里插入图片描述
在这里插入图片描述
这里的用法是从第五个位置开始找,找到oI中的任意一个。

find_first_not_of

找到对象中第一个没有出现字符串中字符的位置。
【C++】string介绍_第82张图片

在这里插入图片描述

不细讲。

find_last_of

找到最后出现的。
【C++】string介绍_第83张图片
其实就是倒着找。
【C++】string介绍_第84张图片

substr

这个函数是帮忙取出子串的。
【C++】string介绍_第85张图片

直接给例子:
【C++】string介绍_第86张图片
取出下标为3位置处的后4个字符

再来个例子:将网址的协议、域名、后面的分开打印出来。

网址都是死格式:

protocal://hostname:portname/pathname/?search#hash
就先找://,搞成字串
再找/,搞成字串
后面的再搞成字串就OK了

我就以我现在所编写的网址为例:
【C++】string介绍_第87张图片

getline

还有一个比较重要的东西,就是getline,相当于C语言中的fgets。
就是获取一行字符串。
【C++】string介绍_第88张图片
有的同学可能会说为啥不直接用cin呢?
看:
【C++】string介绍_第89张图片
cin和scanf一样,遇到空格或者回车就会当成是字符串就结束了。

而getline和fgets(gets)就是C++和C中能够读取包含空格字符串的两个函数。

所以这里用getline就好了。
【C++】string介绍_第90张图片

to_string

这个也比较好用。

将数字转成字符串。
【C++】string介绍_第91张图片
这些所有参数的类型都能转换成string类型。
【C++】string介绍_第92张图片
C语言中的atoi没有这个好用。

还有字符串转数字的。
【C++】string介绍_第93张图片
【C++】string介绍_第94张图片

总结

剩下的没什么好讲的了,其实最常用的就是[] += 迭代器 size这几个常用点,剩下的留个印象就行,没必要全记住,其他的想用的时候忘了查文档就行。

到此结束。。。

你可能感兴趣的:(c++,开发语言)