STL string的使用介绍

在这里插入图片描述


二次修错于date:2024 3 14

从文档使用到模拟实现。一起进入STL的学习吧!

首先,学习STL的最好的办法就是看文档,这里有两个网址一个是C++官网,另一个就是cpluscplus官网的文档全,但是比较乱。

string的文档大纲image.png

string类

使用string类必须包含头文件,命名空间std可以选择展开和不展开。不展开需要使用域作用限定符指定访问std中的string。

string类是经过typedef的,实际类名是basic_string
image.png
是将basic_string这个模板,用char来实例化出来的类
也就是说明了呢,这个类是用来存放char类型的变量的。那么这个string还可以用来存放其他类型的数据吗,当然可以,比如wchar_t,wchar_t是占用两个字节的。image.png
这里的wstring就是basic_string<wchar_t>模板类,这里的wchar_t是宽字节的意思,就是两个字节,有时候因为字符串不一定都是使用ASCII编码的字符串,比如Unicode,UTF-8编码需要使用多个字节,就出现了wchar_t,char16_t,char32_t等类型。
image.png


字符编码知识

中文一般采用的是gbk编码规则。其他还有utf-8,utf-16,utf-32等等

Unicode(又称统一码、万国码、单一码)是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode是为了解决传统的字符编码方案的局限而产生的,为每种语言中的每个字符设定了统一并且唯一的二进制编码。

image.png
在内存中一个汉字占用两个字节,同时字符在内存中都是使用数字存储的,也就是对应着编码表,不同的语言有不同的编码表。
image.png
-42,-48一起组成了“中”,-71,-6一起组成了“国”,那么如果更改[3]的值会发生什么呢?
image.png

在中文编码里面近音字是被编在一起的,这可以用到净网行动的不良词汇检查,只要屏蔽掉这个字以及这个字的同音字,就可以一定程度上避免不良词汇出现了。

string类构造函数

stl的文档里面函数模块分为三个部分,下面用构造函数模块来举例

函数声明部分

image.png
这里声明了C++98中的七个构造函数,但是实际上经常用到的也就是几个,并不是全都会用。
第一个就是默认初始化,对象里面默认放的空字符串,并且会开辟一块空间。
image.png
定义对象的时候不传参调用的就是默认构造,开辟15个空间。
第二个,就是拷贝构造,用一个string对象来构造一个新的对象。
第三个,就是用一个对象的部分来构造新对象,第二个参数pos就是position位置,len就是长度,从pos位置开始的len长度的字符串来构造对象。
第四个,就是用一个C语言的字符串来构造一个对象。
第五个,就是用一个C语言字符串的从第一个字符开始到第n个字符构造一个对象。
第六个,就是用n个字符c来组成一个字符串
第七个,就是迭代器,使用迭代器区间来构造一个新的对象。
演示:image.png
构造对象的时候也可以这样写:

void Teststring_3()
{
	string s1 = "hello kisskernel";
}
int main()
{
	Teststring_3();
	return 0;
}

因为构造函数中存在使用C语言字符串构造对象的单参函数,所以这段就是进行了类型转换,先将字符串构造一个临时对象,然后再进行拷贝构造,经过编译器优化之后就变成了直接那这个字符串进行构造。

函数功能参数说明部分

image.png
这里对于每个构造函数都进行了解释,以及特殊情况下的执行状况,比如第三个构造函数,用一个对象那个的pos位置开始的len长度的字符串来构造对象,如果这个len太长了会怎么样,不会报错,会直接到end of str
如果这个len你输入的是npos也是直接到字符串结尾,这里的npos是-1,但是因为是无符号的-1,所以这个就变成了unsigned int 的最大值42亿多image.png
后面的8,9就是C++11新引入的函数。
参数含义说明部分
image.png

返回值说明部分

这个部分就是对这些函数的返回值进行解释说明,因为构造函数无返回值,所以这里没有返回值部分,我用一个赋值运算符的重载来演示:
image.png
这里就是返回值,可以看到返回的*this,使用的是引用返回。

string类常用函数

operator+=

image.png
首先是重载的赋值运算符,有三种用法,第一就是赋值一个string对象,第二就是复制一个字符串,第三就是赋值一个字符,注意不管赋值的是那个赋值之后这个被赋值的对象里面只有赋值的内容,但是比如先赋值一个字符串,空间不足扩容了,然后再赋值一个字符,这时候对象里面只有字符,但是开辟的空间并不会缩小。

void Teststring_3()
{
	string s1 = "hello kisskernel";
	string s2;
	s2 = "hello";
	s2 = s1;
	s2 = 'c';
}

这里的s2最后是c,但是开辟的空间是32

size和lenth

image.png

这两个函数是返回这个字符串的长度,返回的是一个无符号数。size和length的功能是完全一样的,为什么会出现这两个功能一样的函数呢?因为string的诞生时间比stl要早,这也就是string为什么没有被受尽stl里面的原因,早期字符串类当然可以用length来表示长度,但是换了其他的类比如map就不可以用length了。于是为了和其他的stl类统一,就添加了size。

这里的max_size返回的是21亿多,官方解释说是返回字符串能到达的最大长度。实际上是没什么用的。

append、push_back、和operator+=

image.png
首先push_back就是在字符串末尾插入一个字符。
image.png
来看append,这里给出了六种函数,常用的其实只有第一种和第三种
简单介绍一下:
第一就是在字符串末尾插入一个string类的对象
第二就是在尾部插入一个string的subpos位置(下标)的字符开始向后sublen长度的字符串。如果len长度很长,那就直接从pos位置到结尾全插入,第三个参数是npos或者不写,也是一样的。
第三就是在尾部插入一个字符串。
第四个就是在尾部插入一个字符串的一部分,就是从这个字符串s开始的向后数n个字符。
第五个就是在后面插入n个字符c
第六个就是迭代器插入,参数是迭代器的范围。

image.png


但是实际上使用最多的是operator+=;

image.png
这里的+=就是在字符串的尾部连接上,可以是一个string类的对象,也可以是一个C语言形式的字符串,也可以是字符。因为+=可以提高代码的可读性,所以实际中使用的比较多。

image.png
+=的使用不仅简化代码,而且可读性上升,推荐使用+=。

insert

insert就是随机插入,在任意位置插入字符或者字符串等。
image.png
1,在pos位置插入一个string对象
2,在pos位置插入一个string对象从pos位置开始len长度的字符串,如果len很长或者是npos或者不给参数,就是从pos位置一直到尾部的这个字符串全部插入。
3,在pos位置插入一个C语言形式的字符串
4,在pos位置插入这个字符串的前n个字符
5,在pos位置插入n个字符c,第二种形式是从这个迭代器的位置开始向后插入n个字符c
6,在p迭代器的位置之前插入一个字符c
image.png

erase

image.png
erase就是删除从pos位置开始的len长度的字符,如果len大于pos位置之后字符串长度或者是len不传参,那就是全删。这里是不会改变capacity的在vs的stl中

注意:

这里的erase和insert尽量少用,因为在字符串中,随机插入和删除的时间复杂度都是O(n),效率比较低。这也就是为什么STL没有给string类型的头插。

capacity

image.png
这里返回的是string对象的可用的最大空间,实际空间会+1,因为要保存一个\0;

clear

image.png
清除字符串的所以内容,让他变成一个空字符串。

resize

image.png
解释一下文档:resize就是将这个字符串调整为n个字符的长度,如果原来的长度大于n,大于n的那一部分就会被移除,也就是将size变为n,如果原来的字符串长度小于n,那么会在原来字符串后面追加字符c使得size达到n,如果没有给c传参,那么c默认是空字符。

void Teststring_14()
{
	string s1("kisskernel");
	s1.resize(20, 'c');
	cout << s1 << endl;
	s1.resize(5);
	cout << s1 << endl;
}

image.png
image.png
注意这里size的改变,在windows下缩小的时候capacity是没有跟着变化的,但是在别的编译器就不一定了,因为不同的版本的stl都是按照c++标准来实现的,功能是一样的,但是底层的一些细节是不同的,比如增容的时候是标准扩容二倍还是根据情况。这些都是没有规定的。

reserve

image.png
reserve是用来改变容器的capacity的,如果n大于capacity那么会将capacity变为n或者更大。如果n小于capacity那么会收缩字符串,但是不会改变字符串的内容不会删减字符串,最少就是字符串的长度,但实际一般比这个size更长一点。n<capacity的用法优点类似于shrink_to_fit,将字符串空间减少到合适的大小。
实际中的reserve都是用来扩大capacity的,比如已知这个字符串有多少个字符,可以直接用reserve开辟出来这个capacity或者更大的空间,这样就可以减少空间不足的时候向内容申请空间的性能消耗。

void Teststring_15()
{

	string s1("kisskernel");
	s1.reserve(100);
	s1.reserve(15);
	s1.shrink_to_fit();
}
//这里就是先将容器扩到100,然后再缩小到15,但是字符串内容是不会改变的
//也就是size是不会改变的

c_str

image.png
该函数的作用实际就是返回一个C语言形式的字符串,是以\0结尾的字符串,因为在string中可能中间有\0间隔但是后面还有字符,这时候用string类提供的cout打印和用系统的cout打印效果是不一样的。
image.png
C语言形式的字符串遇到\0就会终止了。

substr

image.png
substr返回的是一个字符串类对象,这个对象的内容是从pos位置开始len长度的字符串,如果len过长,或者没有传参(默认是npos)那么就是返回从pos到结尾的字符串。
注意:这里如果pos等于字符串长度就会返回空字符串,如果是大于字符串长度就会抛出一个out_of_range的异常

find和rfind

image.png
这里的find意思是从pos位置开始找一个和string类对象或者是C语言形式的字符串又或者是一个字符相匹配的字符串,并且返回第一次匹配位置的下标。第三个的意思就是可以指定要进行匹配查找的字符串的一部分,从pos位置开始去对象里面查找s字符串的前n个字符,就像下面这样:
image.png
第一次打印的是第一次ss出现的位置,第二次查找的是sskkkkk中前2个字符在字符串中出现的位置,也就是ss在第一次出现的位置后面,即ss第二次出现的位置。
find如果能找到匹配的字符串就返回第一个字符的下标位置,如果找不到就返回size_t类型的-1,也即是42亿多打印出来。

rfind

image.png
rfind可以看到pos默认位置是npos,也就是和find是相反的,rfind是从后向前查找匹配的字符串,依然是上面的代码,查找ss出现的位置,我们来看看如果是rfind回事怎样的结果。
image.png
可以看到正好和find是相反的。

下面来看一下substr和find在实际中可以用到的地方,这几举例:取出一个网址的域名和协议名

string GetDomain(const string& s)
{
	size_t pos = s.find("://");
	if (pos != string::npos)
	{
		return s.substr(0, pos);
	}
	return string();
}
string GetProtocal(const string& s)
{
	size_t pos = s.find("://");
	if (pos != string::npos)
	{
		size_t start = pos + 3;
		size_t end = s.find('/', start);
		return s.substr(start, end-start);
	}
	return string();
}
void Teststring_19()
{
	string url1("https://www.bilibili.com/");
	string url2("https://cplusplus.com/reference/string/string/rfind/");

	cout << GetDomain(url1) << endl;
	cout << GetDomain(url2) << endl;

	cout << GetProtocal(url1) << endl;
	cout << GetProtocal(url2) << endl;

}

image.png

如上就是。end-start就是这个域名字符串的长度。要注意判断是否匹配成功都是使用的npos,失败返回的是空字符串,这里使用的是匿名对象。

string类的三种遍历方法

学习一个容器最重要的就是遍历这个容器内的所有数据

1,下标运算符重载遍历

先看文档,元素访问这一栏
image.png
第一个就是下标操作符的重载,第二个就是at函数其实作用是和[]一样的,用的很少,第三个就是访问最后一个字符,front就是访问第一个字符。
image.png
这里有两个函数重载,一个是普通对象,另一个是const对象。

void Teststring_4()
{
	string s1("hello kisskernel");
	for (size_t i = 0; i < s1.size(); i++)
	{
		cout << s1[i] << " ";
	}
	cout << endl;
	for (size_t i = 0; i < s1.size(); i++)
	{
		cout << s1.at(i) << " ";
	}
}

这就是第一种遍历,要注意参数是size_t就是无符号整形,这里用int也可以,会发生类型转换,要注意无符号这个i=0的时候不可以再进行减少的操作,因为会直接变成42亿多,可能会有些地方出错。
[ ]和at的区别:[ ]这里越界会直接报错,断言错误,终止程序,如果at( )越界会抛出异常,需要主动捕获异常。

image.png

2,迭代器遍历

image.png
这里就是迭代器一栏的函数,begin和end分别会返回开始位置和结束位置的迭代器,rbegin和rend的意思是反向迭代器,前缀加上c代表const类型的对象(这是C++11中新加入的,但是begin和end本来就以及有重载了const类型对象的函数,新加入的这些cbegin函数很少会用到,作用是为了更加明显吧让你知道这里调用的是const类型的对象。)
image.png
这里的iterator就是迭代器的意思下面来看一下如何使用

void Teststring_5()
{
	string s1("12345");
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		*it += 1;
		it++;
	}
	cout << endl;
	it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
}

这里还可以通过迭代器来修改对象的内容,比如*it+=1;
这里要注意it一定是!=end()的,不可以用小于,小于在这没有问题但是再别的地方会出现问题。
这里begin的位置是第一个字符的位置,end是最后一个字符的下一个的位置,也即是[begin,end)是左闭右开的区间
这里可以想象迭代器是类似指针的东西,因为他的用法就是模拟指针的用法。
普通的迭代器是可以修改对象的内容的,但是如果是const对象就需要const类型的迭代器了。

迭代器的意义:为什么有了下标重载还需要迭代器遍历呢?因为在后面的有些容器是不支持使用[ ]去遍历的,比如map,list,但是迭代器是通用的,在那种容器都可以使用。
再来看一下反向迭代器

void Teststring_6()
{
	string s1("12345");
	string::reverse_iterator it = s1.rbegin();
	while (it != s1.rend())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
}

image.png

要注意在反向迭代器这里使用的是reverse_iterstor,代表了反向迭代器类型,rbegin和rend返回的都是反向迭代器,特别注意:这里的it仍然是++,因为并不是从后向前,而是从反向迭代器开始位置++到结束位置,要特别注意。

这里列举一下常用的迭代器类型

1.iterator;
2.reverse_iterator;
3.const_iterator;
4.cosnt_reverse_iterator;

至于cbegin和cend这些,由于使用const会自动匹配到begin的const类型所有很少用到cbegin。

其他类型的遍历也可以使用迭代器,来看代码:

image.png
可以看到针对list和vector类,迭代器遍历也是没有问题的。

3,范围for遍历

最后就是范围for遍历,这是C++11引入的,其实范围for底层依靠的还是迭代器,在编译的时候会被编译器转换成迭代器,就像是函数模板一样,在编译的时候会根据参数实例化出具体的函数,所以实际这些语法都是依靠编译器的转换。

void Teststring_8()
{
	string s1 = "kisskernel";
	for (char e : s1)
	{
		cout << e << " ";
	}
	cout << endl;
}

如上就是范围for循环,代码非常简洁,这里要注意:首先如果没有写引用的话,这里是不可以改变对象的成员的,还有就是如上不一定都是auto,如果已经知道了类型直接写也是没有问题的。
范围for的缺点就是无法返回元素的下标索引,所以根据情况选择是不是要用范围for。

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KissKernel

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值