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