string的介绍和使用

文章详细介绍了C++中的string类,包括它的构造函数、赋值函数、获取元素的方法以及迭代器的使用。此外,还讨论了为什么需要实例化出u16string等其他类,主要是为了支持不同的字符编码,如UTF-16和UTF-32。文章还涵盖了string的一些重要操作,如c_str、find、resize等,并提到了VS对string的优化。
摘要由CSDN通过智能技术生成

目录

string杂谈

类模板basic_string实例化出string类后,为什么还要实例化出u16string等其他类呢?

string类的构造函数

1.默认构造和拷贝构造

2.利用c语言的字符串构造string

3.截取str1的部分构造str2

4.利用str1的前n字节构造str2

5.用n个字符c构造str

6.利用迭代器范围构造str

string类的赋值函数

string类的用于获取元素的函数

1.operator【】

2.string::at

3.back和front

string类的迭代器类

关于迭代器的背景

string类迭代器的使用

1.正向迭代器

2.反向迭代器

3.关于cbegin等带c的迭代器接口

string类的操作函数 

1.c_str和data

2.find和rfind

3.find_first_of

4.find_last_of

5.find_first_not_of和find_last_not_of

6.substr

string类的容量相关函数

1.reserve

2.resize

string类的修改函数

1.push_back

2.append

3.operator+=()

4.assign

5.insert

6.erase

7.replace

string类的非成员函数

1.operator<<()和operator>>()和getline()

2.operator+()

3.relational operators

头文件中的转换函数

VS中对string做了一些优化


string杂谈

如上图,string不在Containers中,这里我想说的是严格来说string并不属于STL库,因为string是早于STL出现的,但因为string的制作符合STL的规范,所以也就睁一只眼闭一只眼了。

string是类模板basic_string<char>实例化后的别名,所以string本质也是通过类模板实现的。可以认为string是一个字符类型的顺序表。

类模板basic_string<char>实例化出string类后,为什么还要实例化出u16string等其他类呢?

既然string是字符的数组,那么避免不了谈论和字符强相关的字符集,字符集的功能是将内存中存储的01序列转换成字符,因为存在不同字符集导致编码也不同,比如UTF-8是一种变长字符集,所以可以表示多种语言,而UTF-16只能用2字节表示一个字符,UTF-32只能用4字节表示一个字符,所以他俩都无法表示英文。

背景介绍完毕后回归正题,char只有一个字节,对于需要两个甚至更多的字节才能表示的字符来说,字节的数量就不够用了,此时就诞生了u16string等等其他类,其中u16string就采用的UTF-16字符集表示字符,而u32string就采用UTF-32字符集表示字符。

string类的构造函数

1.默认构造和拷贝构造

默认构造和拷贝构造没啥可说的,但注意string类遵循C语言的字符串末尾带\0这一标准。

2.利用c语言的字符串构造string

上图红框为最为常见的使用方式,值得一说的是:下图中两种方式是等价的,第二行实际上是隐式转换,原本应该是首先用hello world构造了一个临时对象,然后调用拷贝构造构造s2,但因为这两个构造操作在同一行执行,所以编译器实际上会把这两次构造优化成一次,即优化后直接使用C语言的字符串hello world构造string,这和第一行的操作是等价的。如果string的构造函数前加了关键字explicit,那么就不能发生隐式转换了,也就无法像第二行这样写了。

3.截取str1的部分构造str2

该方法不常见,意为顺序(连续)截取str1的一部分构造str2。截取部分从表示下标的参数pos开始,截取字节长度为 len字节。当len超出str1的范围时,直接截取到str1末尾,值得注意的是参数len带有缺省值npos,它是string类内部的一个静态无符号变量,值为-1,-1转换为有符号数是int的最大值42亿9千万,意为直接截取到str1末尾。

效果演示如下

4.利用str1的前n字节构造str2

不常用,不再赘述。

5.用n个字符c构造str

比如你想用100个‘x’构造出一个str,那就string str(100,‘x’);。

6.利用迭代器范围构造str

给一个迭代器区间,利用这个区间的string构造一个新的string。值得一说的是利用迭代器区间构造string时,这个构造函数是一个函数模板,即这个迭代器不一定是string类的迭代器,比如我也可以用vector的迭代器区间构造string对象。

string类的赋值函数

不常见的第三种赋值重载表示利用字符c赋值给string。值得一提的是对于各个自定义类型来说,第一种为默认的赋值重载函数,不写也会生成,后两种不写则不会生成。

string类的用于获取元素的函数

1.operator【】

可以让string像数组一样遍历访问string内的元素。编译器会根据string类型去匹配该调用哪个operator【】,如果是const对象则调用第二个,如果是非const对象则调用第一个。注意返回值是引用类型,这里不是为了减少拷贝,而是想像数组一样,可以通过str【3】=‘a’修改原string。

2.string::at

功能和operator[]完全一致,但区别在于at越界失败后不是终止程序,而是抛异常,如下图红框处描述,而operator【】越界会报断言错误并终止程序。

3.back和front

用于返回第一个和最后一个元素。因为at或者operator[]的存在,所以back和front比较多余,因为operator【0】就等价于front。

string类的迭代器类

关于迭代器的背景

迭代器是一个类,是为了给STL容器提供一种统一的、通用的遍历方式而存在的,在string这里不常用,因为operator[]比迭代器更好用,但对于树或者链表这样的底层数据不连续的类型来说,operator[]就不太合适了,所以迭代器的意义就在于此。

迭代器是容器的内嵌类型,一般要么是内部类【除了内部类是外部类的友元以外,它们之间没有任何关系】,要么是在类内给某个类型typedef过别名【注意typedef的功能是很强大的,即使某个类与容器完全无关,但只要将这个类取别名,那么这个别名就是容器的成员,即这个类也就是容器的成员了】。

迭代器可能就是重命名后的指针类型,也有可能不是指针类型,但用法和指针类型是一样的。

STL所有容器的end()接口,都是返回的指向最后一个元素的后一个位置的迭代器。

迭代器是一个左闭右开的区间,当有容器 container con 和迭代器 container ::iterator it 时,使用迭代器进行容器遍历最好为 it!= con.end(),而不是 it<con.end(),因为不是所有容器都是连续存储,【如果不是连续存储,那么比较地址大小将毫无意义,因为我物理地址比你小,但我在逻辑地址上可能是排在你前面的】并且全局比较函数operator<()也不是对所有的迭代器类都实现了重载,在string类型这里可以使用  it<con.end()是因为string类是一个字符数组,它的元素在物理空间上是连续存储的,并且刚好元素的地址是从低到高,并且string的迭代器类型就是经过重命名后的原生指针类型,operator<()对指针类型是早就重载过的。

string类迭代器的使用

1.正向迭代器

这里简单介绍下begin()即可,其余接口类似。begin()用于返回 “指向” 首元素的迭代器。之所以带 “ ” 是因为迭代器的类型不一定是指针类型,但用法却和指针一样。值得注意的是,如果容器对象是const对象,那么使用begin()函数返回的首元素的迭代器也必须使用const_iterator类型的变量接收,如下图。想必你也能发现const_iterator类型和const指针很相似,注意这里所说的const指针的const在 * 左边(左定值,右定向)。

2.反向迭代器

这里简单介绍下rbegin即可,其余接口类似。可以发现即使是反过来遍历,对于迭代器还是使用的是operator+()。和begin一样,const对象使用rbegin时,也得使用const_reverse_iterator变量接收rbegin返回的迭代器。

值得一提的是,rbegin接口用于返回指向最后一个元素的迭代器对象,rend()用于返回指向第一个元素的前一个位置的迭代器对象。

3.关于cbegin等带c的迭代器接口

cbegin()用于返回const迭代器,所以事实上c就表示const的意思。这里有个问题,当const对象调用begin()时,不就已经能够返回const迭代器了吗?为什么C++11要更新cbegin或者crbegin这样的接口呢?

答案:因为begin()等接口不够直观,代码的可读性不高,当你调用begin()时,可能返回const迭代器,也可能返回普通迭代器,我得观察begin()是否是const对象调用的,以此辨别返回的迭代器类型是否为const迭代器。但如果使用cbegin()等接口,那就能够立马得出cbegin()这个接口是由const容器对象调用的,提高代码的可读性,同时因为代码的规范只能更新,不能删除,所以begin()等接口也就留下来了。所以如果是const对象,那我们未来编写代码时是期望我们调用cbegin()等接口的。但搞笑的是大部分人并不买账,依然觉得begin()等接口就挺方便,导致cbegin()等接口成为了C++的一种累赘。

string类的操作函数 

1.c_str和data

作用为以string为原型,返回一个C语言字符串。有些接口是需要C语言字符串的,string无法使用,这时可以使用c_str(),使用场景如下图演示。data()接口和c_str()的含义是一模一样的,它们的关系类似size和lenth之间的关系。

2.find和rfind

如果没找到,返回npos(是size_t的-1,即MAX_INT);如果找到了,返回字符对应的下标。

接口1:在调用find函数的string中从下标pos开始找str,str也是string类型。

接口2:在调用find函数的string中从下标pos开始找str,str是C语言字符串。

接口3:在调用find函数的string中从下标pos开始,向后寻找n字节,看能否找到C语言字符串s。

接口4:在调用find函数的string中从下标pos开始找字符c。

接口1:在调用rfind函数的string中从下标pos开始,从后往前找str,str也是string类型。

接口2:在调用rfind函数的string中从下标pos开始,从后往前找s,s是C语言字符串。

接口3:在调用find函数的string中从下标pos开始,从后往前寻找n字节,看能否找到C语言字符串s

接口4:在调用find函数的string中从下标pos,从后往前开始找字符c。

3.find_first_of

此接口和find不同,find要求需要寻找的字串必须和调用find的string的部分完全匹配。如有string1为“hello world”,如果需要寻找的字串为”hellx“,则找不到并返回npos。

find_first_of则不同,只要 “hellx” 中有任意字符和“hello world”中匹配,则返回其下标。如 “hellx” 中有字符 ‘h’、‘e’、‘l’、‘l’,这些都能在“hello world”中找到,所以会返回对应元素的下标,每调用一次只能返回一个。

接口1:在调用find_first_of函数的string中从下标pos开始,从前往后找和str中任意一个字符匹配的字符的下标,并返回该下标,如果不存在则返回npos。

接口2:在调用find_first_of函数的string中从下标pos开始,从前往后找n字节,如有和str中任意一个字符匹配的字符,则返回该字符的下标,如果不存在则返回npos。

剩余接口类似,不再赘述。

4.find_last_of

和find_first_of完全一致,只不过从后往前找。

5.find_first_not_of和find_last_not_of

find_first_not_of的功能和find_first_of恰好相反,功能为:在调用find_first_not_of函数的string中从下标pos开始,从前往后找和str中任意一个字符都匹配的字符的下标,并返回该下标,如果不存在则返回npos。

find_last_not_of和find_first_not_of功能完全一致,只不过是从后往前找。

6.substr

substr 是一个常用的字符串处理函数,用于从一个字符串中提取子字符串。在C++中,substr 函数用于操作 std::string 类型的字符串。

  • pos:子字符串的起始位置,即从哪个字符开始提取子串。位置的索引从 0 开始,表示字符串的第一个字符。
  • len:要提取的子字符串的总长度。

substr 函数会返回从指定位置 pos 开始的总长度为 len 的子字符串。

string类的容量相关函数

1.reserve

如果我们知道大致要开多少空间,那么可以提前开辟n字节空间,避免string频繁扩容产生消耗,可以提高效率。注意这里开完空间后,string类内部的_capacity也会变成n,_size不变

2.resize

resize用于改变string内部_size的大小,如果n小于等于_capacity,则将_size赋值为n;如果n大于_capacity,则容器大小扩容到n后再将_size也赋值成n。

resize和reserve不同,resize除了扩容,还会初始化,上图两个接口,第一个接口不带字符c,则默认全部初始化成\0(就是0),第二个接口全部初始化为c。

string类的修改函数

1.push_back

比较鸡肋,一次只能往string中添加一个char。

2.append

接口如上图,可以一次向原string中添加更多的字符。其接口含义和string构造函数的用法完全一致,仔细阅读,你一定能在上文找到答案。样例如下图。

3.operator+=()

对比append和push_back,+=更优,日常中被使用的也更频繁,但实际上+=就是通过append和push_back实现的,如果+=字符串,则底层调用append,如果+=单个字符,则底层调用push_back。从这里也能体现C++对比C语言的优势,如果用C语言的字符串,则需要自己考虑开辟字符串大小,太小不够,太大浪费,且不能动态扩容,用C语言在原字符串后追加字符串或者字符时,一般使用strcat等接口,该接口需要先找\0,之后其后追加字符串,并且strcat还需要考虑追加的字符串不能超过原字符串锁开辟空间的范围,不然报错,可以说是相当不方便了。但如果是string的operator+=(),则只需像下图使用即可。

4.assign

作用和构造函数类似,都是给string赋值,而且接口的功能以及用法完全等同于构造函数。

5.insert

接口不必多说,理解上文后,这里的接口也一看就懂。额外说一下inert头插的效率是非常低的,因为每次头插后,都得往后挪数据,string中有n个元素就得挪n次。当然尾插就不用挪动数据了,并且也不需要找尾,尾插的时间复杂度是O(1)的。

6.erase

也可能需要挪动数据,导致效率低下。

第一行的接口都有缺省值,默认将整个string的有效元素全删除,即把_size置为0,_capacity不变。当然你也可以自己给值,从pos位置开始,删除len字节的元素。

第二行的接口用于删除迭代器 “指向” 的元素。

第三行接口用于删除位于迭代器区间的元素,左闭右开。

7.replace

用于替换string内部的元素。 

string类的非成员函数

1.operator<<()和operator>>()和getline()

因为在全局里重载了operator<<等运算符,所以可以对string类使用如下图的运算符。注意对于C语言字符串即const char *  也重载过operator<<(),在输出string和const char*时,底层的原理是不同的,string输出内容的多少是看_size有多大,而const char*是输出到 \0 才会停止输出。

 

cin>>str这边有一个坑,比如你输入hello world时,str中只会录入hello,因为cin和scanf等接口都支持连续输入,而连续输入时是以换行符或者空格来进行间隔的,cin发现hello后带空格,则认为只需将hello给str即可,此时world会存储在用户层缓冲区里,后序可以通过cin将world赋给别人。如果想把完整的一句话,就比如hello world全部输入进str,则需要使用getline。

is一般传入cin即可,delim意为以什么字符作为结束输入的标志,默认为换行符\n,所以输入回车即可停止输入。如果如下图,使用第一个接口时给delim传入p,则之后输入以字符p判断输入结束。

2.operator+()

不常使用,一般使用+=,因为+返回的是拷贝,但也不排除有些时候就是需要另拷一份,以免影响原数据。

3.relational operators

字符串之间是支持比较大小的,比较方式为按照字符映射的编码大小比较。

<string>头文件中的转换函数

这些函数不是string的成员函数,而是头文件中的全局函数。 

VS中对string做了一些优化

为什么是28呢?按照我们的理解,string类的成员是_size和_capacity和char*_str,这三个成员组成的结构体大小应该只有12啊。

事实上这是VS对于string类做出的优化。VS认为,如果大量地创建string对象,但每个对象管理的字符串数据非常小,相当于在堆上开辟了很多个小块内存,这相当于有很多内存碎片,会影响动态开辟内存的性能,所以VS在string类中额外添加了一个char数组,如下图所示,这样当string对象管理的字符串数据很小的话就无需去堆上开辟内存,直接存进数组里即可,如果数组不够存,再去堆上开辟内存。

如下图,可以看到即使s0内什么也没有,capacity的大小都是15,更加证明了VS优化后的string内部是存在一个16字节的char数组的。 

 

当string对象管理的字符串数据比较小时,该优化能提升不少性能,因为不必去堆上开辟内存。但VS的这个优化虽然提升了性能,也解决了堆上的内存碎片问题,但也有一个缺点,就是string对象变大了。

同时因为该优化的逻辑:小于15字节的数据存进数组,大于15字节的数据存在堆开辟的空间上,导致了string类内部大量的接口都可能要重新编写,为了方便,我们在模拟实现时不必仿照VS的这个优化版的string。

`Future<String>`是Java并发编程中的一个核心组件,它代表了一个异步计算的结果。当我们在主线程之外执行某个耗时的操作,并希望在操作完成之后获取其结果,但不想阻塞当前线程时,可以使用`Future`。 `Future<String>`的工作原理如下: 1. **创建**:通过`ExecutorService.submit()`或`Callable`接口的call()方法创建一个新的任务并返回一个`Future`实例。 ```java ExecutorService executor = Executors.newSingleThreadExecutor(); Future<String> future = executor.submit(new Callable<String>() { public String call() throws Exception { // 长时间运行的任务 return "Task result"; } }); ``` 2. **检查完成状态**:可以使用`isDone()`方法检查任务是否已完成。 3. **获取结果**:一旦任务完成,可以使用`get()`方法获取结果。如果任务尚未完成,会阻塞直到完成。提供超时选项(`get(long timeout, TimeUnit unit)`)也可以设置等待的时间限制。 ```java try { String result = future.get(); // 如果未完成,会阻塞等待 } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } ``` 4. **处理异常**:如果`get()`抛出`ExecutionException`,通常表示任务执行过程中出现了错误;如果是`InterruptedException`,则表示因为其他原因(如中断请求)被打断了。 `Future`使得我们能够异步地执行任务并处理成功或失败的结果,提高了程序的响应性和可维护性。然而,需要注意的是,`Future`本身并不持有实际数据,只是一个对未来结果的引用,真正的数据存储在后台执行的线程中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值