C++ stl容器string的认识与简单使用

本文详细介绍了C++标准模板库STL中string容器的结构、构造接口、容量管理、修改接口、迭代器、元素访问方法及一些非成员函数重载,强调了实际使用中常用的功能。
摘要由CSDN通过智能技术生成

目录

前言:

1.stl的认识

stl的版本:

stl的六大组件:

stl的一些缺陷:

2.string的构成

3.Constructor接口

4.Capacity接口 

length和size:

Maxsize:

capacity:

reverse:

resize:

5.Modifiers接口

push_back和append: 

assign:

insert:

erase:

replace:

find:

库中的swap和string中的swap:

6.Iterators接口

迭代器:

反向迭代器:

const迭代器:

c++11规范:​

7.Element access接口

at和[]:

front和back:

8.String operations

c_str:

data:

copy:很少用: ​

空间配置器:

find和substr:

rfind:​

find-first-of,find-last-of,find-not-first-of,find-not-last-of很少用:

compare:

9.Non-member function overloads

operator+:​

getline:​

10.整形转字符串的一些用法:​

总结:

介绍了很多,实际常用的就那几个,在string实现中会调重要的实现例如迭代器。

参考文档:string - C++ Reference


前言:

从本篇开始介绍c++中的stl标准模版库中的一些容器以及底层实现,由于是开始,所以在string和vector中还会提到这两个容器的使用(使用只会挑常用的介绍举例),会介绍的比较详细一些,再到list,deque,stack,queue,priority_queue都会将实现与介绍放到一起,因为有些内容就相似了。本篇还会简单介绍一下stl。

1.stl的认识

首先,stl(standard template library)是c++的标准模板库,是c++标准库的一部分。

stl的版本:

stl的六大组件:

stl的一些缺陷:

2.string的构成

可以看到string是一个basic_string<char>类型的typedef,string类出现的比较早,在stl模版库之前就有了。

这里的一些补充就是为了适应不同的编码,wchar_t是2字节的,char16_t是2字节的,char_32_t是4字节的。这里扩展一下不同的编码:

像像unicode统一码,UTF-16或UTF-32有时用2字节或者3字节表达某个国家的一个符号,UTF-32可能更统一,但没有UTF-8节省空间,且UTF-8兼容ASCII码,UTF-8更常用。ASCII仅是美国的标准,所以stl出现了不同的string类;而对于编码,国内发明了属于自己的编码GBK,主要采用双字节编,一个汉字用2个字节,可能生僻字会用3个字节:

int main()
{
	char str[] = "博客";
	cout << sizeof(str) << endl;

	str[3]--;
	cout << str << endl;

	str[3]--;
	cout << str << endl;

	str[3]--;
	cout << str << endl;

	str[3]++;
	cout << str << endl;

	str[3]++;
	cout << str << endl;

	return 0;
}

 

前2个字节编'博',后面编其他汉字,这里对第4位进行修改,可以发现都是k为声母,改第4位都是k什么什么,现在就能理解游戏里面的违规词是打不出来的,而且还能做到谐音的违规词也是不行的。

3.Constructor接口

 在使用这些接口之前先认识一下,string的一些成员函数,以及对一些操作符的重载:

size()也就是返回的是这块字符串的有效字符个数(不包含'\0')。

返回下标对应的字符。

然后我们使用一下接口1.2:

int main()
{
	string s1;
	string s2("hello world");
	string s3 = "hello world";//隐式类型转换

	cout << s2.size() << endl;

	for (size_t i = 0; i < s2.size(); ++i)
	{
		s2[i]++;
	}

	cout << s2 << endl;

	for (size_t i = 0; i < s2.size(); ++i)
	{
		cout << s2[i] << " ";
	}
	cout << endl;

	return 0;
}

这里s1是无参的初始化,s2是带参的初始化,而s3是一个常量字符串赋值给给string类型的对象,并初始化,所以发生了隐式类型转换。

这里我们调试看看s2的内容:

这里的'\0'可以访问到,但是没有显示。这里的capacity是容量,后面实现会提到是怎么扩容的。allocator就跟内存池有关了,先不提。

使用一下接口3:

	string s3 = "hello world";
	string s4(s3, 6, 3);
	cout << s4 << endl;

	string s5(s3, 6, 13);
	cout << s5 << endl;

	string s6(s3, 6);
	cout << s6 << endl;

 

接口3就是从pos位置取len个字符进行构造初始化,如果取的超出了范围就有多少取多少,知道字符串的结束。

这里的s6没有给第三个参数,会使用给的缺省值npos,而npos是什么呢?当我们见到文档中的一些不认识的,就去文档的其它地方找找,有可能是一个内容的typedef等等:

所以npos是一个类里面的静态成员常量,值为-1,并且是无符号整形,所以最大为42亿多,也就是超过了字符串的范围,所以直接取到字符串的结束,我们直接想取到字符串的结束就可以使用这个值。

接口5与6:

 

分别意思就是拷贝从开始的第n个进行构造与填n个字符。

4.Capacity接口 

length和size:

二者一模一样,length更早,建议使用size。

由于string字符串不包含'\0',所以后面的size,capacity做下标都是表示最后一个有效位置(有效数据不包括'\0')的下一个,即'\0'(这个字符0可以访问到,但是调试看不到,不是有效的数据):

	string s1("hello world");
	cout << sizeof(s1[s1.size()]) << endl;//1
	cout << s1[s1.size()-1] << endl;//d

Maxsize:

知道就行,64位最大40多亿字节。

capacity:

	string s1("hello world");
	cout << s1.capacity() << endl;//15

结果是15,实际是16字节(不包含/0),为什么是16字节?因为返回值是整型,返回为字符串分配的大小,同样也不包含'\0'。简单来说就是给capacity开大小的时候开的是16字节,并且不包括'\0',但是返回的为给字符串分配的大小也就是15字节(同样没有包括'\0',其实string类的对象后面都有'\0',但是大部分情况都不包括)

reverse:

先介绍:

这里buf是为了不让小空间再去堆上申请空间而造成内存浪费,所以s是28字节,进而capacity想上堆上开辟空间起始就是32字节,然后vs按1.5倍继续申请,而g++又是2倍扩容,起始大小也不一样,后面会详细说。

vs观察扩容情况,插入数据capacity会自动扩容:

而我们提前使用reverse开好空间:

 这里使用reverse就可以提前开空间,减少扩容,提升效率,vs这里可能会多开,而g++可能又正好100,但都不会低于100。

resize:

resize不但会扩capacity,还会对size初始化,这里初始化为100,多出来的补缺省值'\0',

如果想让多的空间初始化为其他值,再传一个参数即可。

注意,size变了但capacity不会变,缩容不能原地缩,因为缩容会去开一块新的空间去拷贝,再把原空间释放,节省空间了但是性能会付出代价,所以不会随意的缩容。

5.Modifiers接口

push_back和append: 

相比上面两个字符或字符串追加,更喜欢用operator+=,但是实际都是尾插,而头插效率不好,需要扩容,有时需要配合reserve使用:

	string s("hello world");
	cout << s.capacity() << endl;

	s += "world";
	cout << s << endl;
	cout << s.capacity() << endl;
assign:

给字符串赋值,不常用,了解即可 。

insert:

 

第一个insert是在开始的位置插入一个字符串,注意0是下标,所以是在开始;第二个是在字符串的下标5的位置插入一个字符,这个字符是空字符(不包括'\0',但是不能直接插入一个'\0'会报错);第三个insert就是在下标为5的位置插入一个空字符串,包括'\0';第四个insert使用了一个迭代器,begin指向第一个字符,由于迭代器底层是指针,所以加5指向第六个。

在string里面不推荐使用insert,能少用就少用,因为插入数据就要往后挪,往后挪数据效率低。

erase:

 

第一个erase是从第五个位置后面开始删除1字符;第二个是从第六个字符开始删除后面所以的字符;如果什么都不传,缺省值分别是0和npos,也就是全部删完(这里的npos跟前面的用法一样)。 

如果不够删了,就有多少删多少,知道字符串的结尾:

erase也不推荐使用,删中间的还有挪动数据,效率低下。 

replace:

替换某个位置的字符,能不用就不用,空间不够需要扩容,还要挪到数据。

5也是下标,5这里是被第一个替代位置的下标即空白字符,1是在原字符串里被替代的长度,如果不够替换,尽可能多的替换直到结束,'\0'不会被替换进去,反正调试的窗口不显示,不算有效字符。

find:

 结合上面的replace,写一个将第一个空格替换为字符串的代码:

find找到返回对应的位置(也是下标),找不到返回npos,这也解释了为什么find的返回值类型是size_t。

那如何让所有的空白字符都替换成%呢?

先遍历一遍,看为了replace需要开多少空间,这里使用的reserve,resize如果替换的字符比较多,size的大小还会改变。

这里循环里需要让pos往下走,因为find是从头找的,第一个pos是空白字符的位置的下标即3,+1跳过替换的字符串(没有'\0')然后指向第一个替换字符后面的位置。

效率更快不用挪到的方法,但是是空间换时间了:

空间换时间,也可以给newStr提前开好空间,因为+=空间不够也会扩容,扩容需要不断的开空间拷贝,一步开好更好。 

库中的swap和string中的swap:

需要进行深拷贝和赋值。

由于是string内部的,只需要改变指针的指向即可。 

6.Iterators接口

迭代器:

迭代器是比较重要的内容,在实现部分也会重点使用,其实底层就是指针的封装(linux下是指针的封装,vs下是一个复杂的封装)。

迭代器目前理解成指针,左开右闭,end()指向最后一个字符的后一个位置,linux下是指针,vs经过封装。

范围for(底层是迭代器)也能访问每个元素,上面讲的string实现的[]重载也可以访问每一个元素。

反向迭代器:

这里的++rit是反向的,是往左走的。

const迭代器:

这里const的含义是表达*it不能修改(暂时理解为指针),it还是可以改的,和直接的const关键字不一样。

注意普通迭代应该用普通迭代器接收,const迭代用const迭代器接收,reverse迭代用reverse迭代器接收,同时也有string::const_reverse_iterator迭代器,我们可以使用auto来自动识别右边的类型,但是可读性不高:

c++11规范:

c++11为了规范const迭代器用const,const_reverse迭代器用const_reverse,但可能用的不多。

7.Element access接口

at和[]:

 

二者有些不一样,访问越界是,at会抛异常,可以用catch捕获,而[]里面实际含有assert,会直接报错。

front和back:

 用[s.size()-1]等也可以实现back的效果。

8.String operations

c_str:

 c_str返回const char*的指针,char*不会按照指针的形式打印地址,而转为void*就可以了。

 string自己重载的<<会根据size打印,c_str识别到'\0'就停止了(这里加个'\0'是因为string的的字符串本身不带字符0),识别字符0是因为它要兼容C,不然会出问题。

应用场景:

文件名类型是string,要用c_str来读,要识别到字符0。

data:

与c_str相似:

copy:
很少用:
 

空间配置器:

一般用不上:

 

find和substr:

从pos位置开始,pos也取,取后面的子串,用缺省值一样是有多少取多少。

取文件后缀:

substr取子串,从pos开始,往后取len个字符,suffix是后缀的意思。

取网络协议和域名:

rfind:

找到的位置是这个字符在这个字符串最后一次出现的位置。

应用:如果想取一个文件的真正后缀也就是最后一个后缀呢?

 

find-first-of,find-last-of,find-not-first-of,find-not-last-of很少用:

find-first-of:

 

find-not-first-of与上面的相反,找不匹配的。

find-last-of,找对应字符串中匹配的字符的最后一个,对应的not和它相反:

compare:


比较函数,几乎不会用,一般用重载的操作符:
 

这里的==的重载其实有点多余:
 

s1=="hello world"其实是跟s1==s2是一样的,因为对于单参数的构造函数(c++98),内置类型支持隐式类型转换,const char*可以转换为自定义类型

9.Non-member function overloads

operator+:

少用,+比+=多了传值返回,多了拷贝,在日期类的实现中介绍过。 

getline:

应用:

题目想计算最后一个字符串的长度,但是遇到例如abcd a就不行了呢?

因为cin>>默认空格是分割两次输入内容的,这相当于只输入了一个字符串abcd,所以不行,这时就要用到getline了:
 

加上getline表示就算有空格输入也是一次的输入内容(不像cin),只有读到'\n'才结束第一次的输入。

10.整形转字符串的一些用法:

总结:

介绍了很多,实际常用的就那几个,在string实现中会调重要的实现例如迭代器。

参考文档:string - C++ Reference

评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值