STL之string

小码农<^_^> 2024-10-03 12:05:02 阅读 74

STL之string

1. 为什么学习string类?1.1 C语言中的字符串1.2 两个面试题(暂不做讲解)

2. 标准库中的string类2.1 string类(了解)2.2 auto和范围for(重点)2.3 string类的常用接口说明(注意下面我只讲解最常用的接口)

1. 为什么学习string类?

1.1 C语言中的字符串

C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列

的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户

自己管理,稍不留神可能还会越界访问。

1.2 两个面试题(暂不做讲解)

https://www.nowcoder.com/practice/1277c681251b4372bdef344468e4f26e?tpId=13&&tqId=11202&rp=6&ru=/activity/oj&qru=/ta/coding-interviews/question-ranking

https://leetcode-cn.com/problems/add-strings/

2. 标准库中的string类

2.1 string类(了解)

前置:

1、在使用string类时,必须包含#includ,这里与C语言区别没有.h

2、string里的字符串是以类似于字符顺序表的形式存储的。

首先在学习STL这一节查文档必不可少,有两个文档供我们查找,一个是cplusplus,还有一个是官方文档,但是官方文档对初学者会特别的不友好,所以我们选择cplusplus。我们怎么查呢?

在这里插入图片描述

大家按照图中的方式,先打开reference,然后打开other中的string。这里面就是string的所有内容,里面我们只会选一些重要的来学习,其他的大家可以自己看看。

我们首先学一些基础操作:

在这里插入图片描述

我们需要点进string。

在这里插入图片描述

然后我们再点进下面的constructor

在这里插入图片描述

c++98里的函数就是我们目前需要学的

那我们就先来简单的使用一下string,再来学上面的函数

<code>#include<iostream>

using namespace std;

int main()

{

string s1;//默认构造

string s2("1111111");//传参构造

string s3(s2);//拷贝构造

cin >> s1;

cout << s1 << endl;

return 0;

}

在这里插入图片描述

大家可以看到string是重载了流插入与流提取的,所以我们是可以直接使用cin>>与cout<<的。

现在我们开始看文档,在这个文档里下面的英文部分是对上面函数的使用方式说明

在这里插入图片描述

在读文档时,这里的技巧是“蒙”,当然不是毫无根据的蒙,我们是有根据的蒙,例如 string (const string& str, size_t pos, size_t len = npos):

pos在英文中是position的前面的部分,那我们就猜测它是定位的意思,len是lenth的缩写,那我们就猜测是长度的意思,那连起来我们就猜测这个函数是拷贝指定位置后的len个长度到目标string里,那我们就来试一下?

在这里插入图片描述

这里我们有个问题,上面我们只拷贝了5个字符,那要是我们拷贝6个或者更多(字符长度不够)会发生什么呢?这个时候我们需要看文档,看文档需要我们下来自己完成,这里我们直接给结论,只会将该字符串读完,并且如果我们我不写len的长度,该函数也会有一个npos,它是直接从pos位置直接将字符串读完。

在这里插入图片描述

大家可能会好奇npos是什么呢?

在文档中可以看到,npos=-1,但这里是补码,它转换为无符号会变为整形最大值。

在这里插入图片描述

前四个我们其实已经完成了,我们来看第5个函数(string (const char* s, size_t n);)(拿前n个字符初始化),这里就不带大家一步一步来看了,大家自己读一下文档。

在这里插入图片描述

string (size_t n, char c),这个函数我们通过读文档大概可以知道,它的意思是n个字符c来初始化。

在这里插入图片描述

然后我们来看string的析构,就是进入下面这个。

在这里插入图片描述

上面我们说了,string底层是动态开辟的数组,那我们需要析构吗?析构函数是自动调用的,这一块我们可以不用管,当然有兴趣可以下来自己了解。

下面我们来看一个string特别牛的东西:

在这里插入图片描述

它的底层大概是这样的:

<code>class string

{

private:

char* _str;

size_t _size;

size_t _capacity;

public:

char& operator[](size_t i)

{

return _str[i];

}

};

也就是我们可以直接通过[]来访问任意位置的字符,并且我们返回的时引用,所以我们是可以修改的。

示例:

在这里插入图片描述

这一块在后续的学习中会非常的方便。

下面我们来学习遍历输出字符串:

(1)下标加[]

首先string是有很多函数的,其中有个叫做size的可以返回字符串中字符个数

在这里插入图片描述

其他的函数大家下来自己去看文档来使用,那我们就可以通过上面的方式来遍历输出字符。

(2)迭代器(***重点)

<code>string::iterator it = s1.begin();

while (it != s1.end())

{

cout << *it << ' ';

it++;

}

我们可以将it理解为指针,但它并不一定是指针,begin是首字符的地址,end是尾字符下一个字符的地址(也就是’\0’),当it不为指针时,* 可能是运算符重载 (在list里我会介绍)

迭代器是一个很重要的东西,因为所有的容器,迭代器都可以使用,例如链表:

list<int> lt = { 1,2,3,4,5,6,7 };

list<int>::iterator lit = lt.begin();

while (lit != lt.end())

{

cout << *lit << ' ';

++lit;

}

上面的iterator我们称之为正向迭代器,此外还有一个东西叫做反向迭代器,reverse_iterator,它的用法如下:

在这里插入图片描述

反向迭代器可以用于链表,图,树等结构。

const迭代器,如果我们的字符串加了const修饰,那么我们的迭代器就不可以使用了,因为在上面的迭代器中我们的串可读可写,这时候就需要const迭代器(只可读)

<code>const string s1("hello world");

string::const_iterator cit = s1.begin();

while (cit != s1.end())

{

cout << *cit << ' ';

cit++;

}

cout << endl;

const反向迭代器是一样的,大家自己下来写一下就行。

总结一下:迭代器一般有四种。

(3)范围for

下面讲。

然后我们来看capacity部分

在这里插入图片描述

1、首先lenth与size其实都是一样的(求字符长度),lenth是c++早期为了适应c语言而使用的,但在c++更新了STL后,在图等数据结构中lenth是不太合理的,所以c++就更新了size,总而言之size适用范围更广。

2、max_size是字符能开多少空间,这个用处不大

3、capacity,字符数组当前容量(不包含\0)

在这里插入图片描述

这段代码可以简单的来观察一下容量的变化,大家可以看到从15到31,实质上是16->32,因为数组最后一个空间存“\0”,只有这一部分是成2倍增长的,之后是成1,5倍增长的,这是为什么呢?

<code>class String

{

private:

char buffer[16];

char* _str;

size_t _size;

size_t _capacity;

public:

};

string在官方库里是有一个buffer的数组的,如果字符串小于16就存于这里,如果大于就会存在下面的动态数组里,buffer就会被销毁。在linix环境下是标准的2倍增,且没有buffer数组。

当然扩容不建议扩的次数太多,因为扩容也会有消耗,所以c++引入了reserve(将空间一次性开好)

在这里插入图片描述

如果我们开100空间,这个100是不包括\0,所以如果不整数对齐的话也会开101。

此外如果我们要申请的空间如果小于当前的capacity,那空间会不会缩水呢?

结论:

一般是不会的,但是不能

n<10不会缩

10<n<20不确定

大于20一定会扩

4、clear(清空)

在这里插入图片描述

这里明白clear清空,只清空数据,不清空容量就行了。

5、empty(判空)

这里大家自己简单了解一下就行。

6、shrink_to_fit(缩容),将capacity缩到size

2.2 auto和范围for(重点)

在这里插入图片描述

这里的auto在c++里叫做“自动推导”,它也很好用,它的特性是自动赋值,自动迭代,自动结束。

它虽然看起来很厉害,但它的底层就是迭代器,所以所有的容器也支持范围for。

除此之外迭代器是支持运算的:

在这里插入图片描述

大家可以看这里,迭代器转换后原来的字符串被改变了,但是我们让范围for恢复却并没有成功,这是因为ch是s1的拷贝,如果我们希望其能够改变的话,需要加&引用。

在这里插入图片描述

<code> auto关键字

在这里补充2个C++11的小语法,方便我们后面的学习。

1、在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个

不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型

指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期

推导而得。

2、用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&

当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际

只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

3、auto不能作为函数的参数,可以做返回值,但是建议谨慎使用

4、auto不能直接用来声明数组

auto最大的作用是简化代码。

此外auto还可以用来简化数组的输出,例如:

int arr[] = { 1,2,3,4,5 };

for (auto& it : arr)

cout << it << ' ';

cout << endl;

总结一下范围for适用于容器与数组。

我们再介绍一个东西叫做typeid,这个函数可以来查看变量的类型,例如:

在这里插入图片描述

2.3 string类的常用接口说明(注意下面我只讲解最常用的接口)

1、插入型接口。

在这里我只简单的介绍一下字符插入与字符串插入;

(1)push_back(1个字符插入)

(2)append(字符串插入)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这两个接口实际用的比较多,但是string重载了+=运算符,我们可以通过+=来插入字符或字符串。

在这里插入图片描述

上面的函数都是尾插,我们如果要实现任意位置插入怎么办呢?

(3)insert(给定位置插入)

在这里插入图片描述

这里比较冗余,我们只需要记住3、4这两个方式即可,一个是在pos前插入一个字符,一个是插入n个字符,如果pos为字符串首个位置,这就是头插了。

<code>string str1;

str1.insert(0, "hello school");//头插

cout << str1 << endl;

str1.insert(12, "!");//尾插

cout << str1 << endl;

(4)erase(删除字符或字符串)

在这里插入图片描述

<code>void test4()

{

string str("hello world");

str.erase(0, 1);//第0个位置开始,删除一个字符,也就是头删

str.erase(0, 3);//删除3个字符

cout << str << endl;

str.erase(str.begin());//迭代器版本头删

cout << str << endl;

str.erase(--str.end());//尾删

cout << str << endl;

}

(5)replace(替换)

在这里插入图片描述

在这里插入图片描述

(6)find(查找字符)

在这里插入图片描述

这个函数我们是需要关心它的返回值的。

在这里插入图片描述

这里的意思就是如果找到了就返回第一个所找字符的下标,如果没有找到的话就返回npos。

在这里插入图片描述

上面替换这道题当空格特别的多的时候,程序就会特别的低效,我们可以使用另一种思路:

<code>//现在有这样一个题目就是将字符串中空格全部替换为%,这时候就可以使用find+replace。

string str("h l l o !");

size_t z = str.find(" ");

while (z != string::npos)

{

str.replace(z, 1, "%");

z = str.find(" ",z+1);//z是替换的位置

}

cout << str << endl;

//第一种思路,如果替换不多可以使用

string tep;

for (auto e : str)

{

if (e == ' ')

tep += '%';

else

tep += e;

}

cout << tep << endl;

//第二种以空间换时间

(7)rfind从尾查找,substr(字串)

string str("test.cpp");

size_t pos = str.rfind('.');

string sub = str.substr(pos);//从pos开始取子串,如果后面不给参数,默认取完

cout << sub << endl;

(8)

在这里插入图片描述

这四个接口大家下来自己读文档解决。(不太重要)

(9)非成员函数

在这里插入图片描述

第一个重载+,为什么这里将它设置为非成员函数呢?这是为了满足如下操作:

在这里插入图片描述

如果将其设置为成员函数,我们之前讲过成员函数的第一个参数是隐式的this,那样就不能字符串+string了。

swap就是直接交换即可。

最后就是getline这个函数

我们需要在题目中来看:

在这里插入图片描述

这道题我们直接使用rfind找到最后一个空格的位置,然后用size来减即可。

在这里插入图片描述

大家可以看到正确但是应该是1,但是这里是5,这是为什么?

在流输入中编译器会默认使用空格或者\0来分割字符串,空格会存在缓冲区,并且空格不会被使用,所以s1只存了asull这5个字符,所以这里需要我们使用getline这个函数

在这里插入图片描述

它的作用是默认将\0作为字符串结束标志,此外getline是支持其他的字符串结束标志的

在这里插入图片描述

我们只需要在最后补上我们需要作为字符串结束标志的字符即可。

这一部分就是string的使用介绍,下一章我们就来尝试来写string。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。