怎么做网站的需求,广告网页设计,做电影网站放抢先版,做网站需要多少带宽STL—容器—string类 
其实string类准确来说并不是容器#xff0c;因为他出现的时间比STL要早#xff0c;但是也可以说是容器吧。 
1.为什么要学习string类#xff1f; 
1.1C语言当中的字符串 
C语言中#xff0c;字符串是以’\0’结尾的一些字符的集合#xff0c;为了操作…STL—容器—string类 
其实string类准确来说并不是容器因为他出现的时间比STL要早但是也可以说是容器吧。 
1.为什么要学习string类 
1.1C语言当中的字符串 
C语言中字符串是以’\0’结尾的一些字符的集合为了操作方便C标准库中提供了一些str系列的库函数但是这些库函数与字符串是分离开的不太符合OOP的思想而且底层空间需要用户自己管理稍不留神可能还会越界访问。 
1.2面试题 
415. 字符串相加 - 力扣LeetCode 
在OJ中有关字符串的题目基本以string类的形式出现而且在常规工作中为了简单、方便、快捷基本 
都使用string类很少有人去使用C库中的字符串操作函数。 
2.标准库中的string类 
2.1string类的了解 
string类的文档介绍 字符串是表示字符序列的类  标准的字符串类提供了对此类对象的支持其接口类似于标准字符容器的接口但添加了专门用于操作单字节字符字符串的设计特性。  string类是使用char(即作为它的字符类型使用它的默认char_traits和分配器类型(关于模板的更多信息请参阅basic_string)。  string类是basic_string模板类的一个实例它使用char来实例化basic_string模板类并用char_traits和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。  注意这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列这个类的所有成员(如长度或大小)以及它的迭代器将仍然按照字节(而不是实际编码的字符)来操作。  
总结 string是表示字符串的字符串类  该类的接口与常规容器的接口基本相同再添加了一些专门用来操作string的常规操作。  string在底层实际是basic_string模板类的别名typedef basic_stringchar, char_traits, allocator string;  不能操作多字节或者变长字符的序列。  
在使用string类时必须包含#include头文件以及using namespace std; 
我们在包含头文件的时候经常会遇到两个比较容易混淆的头文件引用#includestring.h 和 #include 两者的主要区别如下 
#includestring.h是C语言的标准库主要是对字符串进行操作的库函数是基于char*进行操作的例如常见的字符串操作函数stpcpy、strcat都是在该头文件里面声明的。 
#include是C语言的标准库该库里面定义了string类你可以包含这个头文件然后定义一个字符串对象对于字符串的操作就基于该对象进行例如:string str; 
一般来说c的头文件不会有.h的出现比如 
———————————————— 
文档方面个人觉得这个比较好用 - C Reference  
作为一个程序员查文档是一个必须具备的基本素质当遇到一个不熟悉的接口需要使用的时候查文档就是最好的解决方法因此我们需要学会怎么查文档。 
2.2string类的常用接口说明 
2.2.1.string类的常见构造 c98版本中全部的构造 实例 
// 初识string类 
// string类 有很多的接口和成员函数我们主要得熟悉的使用其中的2成剩下的8成需要我们去通过网络查询来解决
# includestring
# includeiostreamusing namespace std;
int main()
{// 关于string类的构造string s1; // 构造string s2(string); // 直接构造string s3(s2); // 拷贝构造// 上面这三个最常用string s4(10, s); // string类中的的不常用的特殊构造string s5(hello, 1, 3); // string类中的不常用的特殊构造 [这里如果给的不是3 是过大的值那么就是npos意思就是将字符串后面有多少给多少] 这些知识都可以查文档查到// 能使用  来输出是因为 库中 已经对 进行了重载cout  s1  endl; // [这里默认是空串]cout  s2  endl; // stringcout  s3  endl; // stringcout  s4  endl; // sssssssssscout  s5  endl; // ell [取hello中的 第 1~3号元素来构造的 正好就是ell]string s6  s4; // 这里还是拷贝构造string s7  string; // 这里正常来说就是发生隐式类型转化 意味着先直接构造在拷贝构造// 但是这里的直接构造  拷贝构造发生在了同一表达式因此编译器直接优化成直接构造cout  s6  endl; // sssssssssscout  s7  endl; // stringreturn 0;
}2.2.2string类对象的容量操作 
常见的与容量相关的接口 接口的代码使用 
1.size(重点) 
size可以统计字符串的有效字符长度并返回。一般来说会用作遍历。 
// string类对象有关容量的常见接口
// 1.size()
# includestring
# includeiostream
using namespace std;int main()
{	string s(hello);cout  s: ;// 遍历for (size_t i  0; i  s.size(); i){s[i]  1; // 写 [这个相当于对取出来的字符1就相当于给其ASCII码值1]}for (size_t i  0; i  s.size(); i){cout  s[i]; // 读}cout  endl; //s8: ifmmp [hello每个字符的ASCII码值分别1]return 0;
}2.length 
length也会统计字符串的有效长度并返回。其实和size是非常像的甚至其底层的实现原理都是一样的、 
其实在早期的时候用的都是length但是后面数据结构都用size了就统一用size了length和size可以说就是一样的这里推荐使用size 
// 2.length# includestring
# includeiostream
using namespace std;void test_string()
{string s1(hello, world);cout  s1.size()  endl; // 12cout  s1.length()  endl; // 12}int main()
{test_string();return 0;
}3.capacity 
capacity可以返回当前空间的总大小 
// 3.capacity
# includestring
# includeiostream
using namespace std;int main()
{string s(hello, world);cout  s.capacity()  endl; // 15// 目前s有12个空间存放数据如果超出空间会增容多少呢s  ddddd;cout  s.capacity()  endl; // 31// 可以发现直接将其增容到了2倍的容量。return 0;
}4.clear(重点) 
清空字符串内容 【注意空间不会被销毁只是内容被清空了】 
// 4.clear# includestring
# includeiostream
using namespace std;int main()
{string s(hello, world);cout  s  endl; // hello, worlds.clear();cout  s  endl; // 空串// 我们清除了字符串内容不代表空间被销毁了、cout  s.capacity()  endl; // 15return 0;
}5.empyt(重点) 
empty可以检测字符串是否为空串是就返回true不是就返回false 
//5.empty# includestring
# includeiostream
using namespace std;int main()
{string s(hello, world);if (s.empty())cout  该string类对象为空  endl;elsecout  该string类对象不为空  endl;s.clear();if (s.empty())cout  该string类对象为空  endl;elsecout  该string类对象不为空  endl;return 0;
}6.reserve/resize(重点) 
在学习这两个接口之前我们先来看一个代码 
这个代码可以观察到每次增容时容量的变化大小 
# includestring
# includeiostream
using namespace std;int main()
{string s;size_t sz  s.capacity();cout  s_capacity grow\n;for (int i  0; i  100; i){s.push_back(c); // 也可以s  cif (sz ! s.capacity()) // ! 的时候就说明s进行了增容{sz  s.capacity(); // 将新容量给到szcout  new capacity:   sz  endl; // 将此时的容量打印出来}}return 0;
}我们发现是接近1.5倍增容的。但是我们之前知道15容量在增容的时候是按两倍增容增到31的、 
规律是空间基数越大增容的倍数越小 
因为增容是有代价的。增容越多的空间执行越多次增容代价就越大。 
知道了这个小知识我们就可以来看resize和reserve了。 
reserve给字符串预留空间。 
其实就是给字符串开辟空间 
// 6.resize/reserve
# includestring
# includeiostream
using namespace std;int main()
{string s;s.reserve(100); // 我们知道s需要100个空间提前开好空间size_t sz  s.capacity();cout  s_capacity grow\n;for (int i  0; i  100; i){s.push_back(c); // 也可以s  cif (sz ! s.capacity()) // ! 的时候就说明s进行了增容{sz  s.capacity(); // 将新容量给到szcout  new capacity:   sz  endl; // 将此时的容量打印出来}}return 0;
}由于我们提前开辟好了100个空间【实际上不一定会开100个因为要考虑内存对齐最后还要放个\0】s没有再执行增容操作。 
这样我们只增容了一次比起自动增容的次数就减少了许多。 直接开辟了111个空间并不是我们输入的100。【了解一下】 
resize在reserve的基础上将有效字符的个数修改并填充字符 
int main()
{string s;//s.reserve(100); // 我们知道s需要100个空间提前开好空间s.resize(100); // 将有效字符个数修改到100并填充\0size_t sz  s.capacity();cout  s_capacity grow\n;for (int i  0; i  100; i){s.push_back(c); // 也可以s  cif (sz ! s.capacity()) // ! 的时候就说明s进行了增容{sz  s.capacity(); // 将新容量给到szcout  new capacity:   sz  endl; // 将此时的容量打印出来}}return 0;
}可以看到它相当于reserve(100)并且将有效字符个数修改到了100并且填充了\0、 由于前100个空间被resize填充了\0因此后面还是会执行增容操作 
因此如果想后面不执行增容操作这里要用reserve 
我们还可以指定填充的字符 
int main()
{string s;//s.reserve(100); // 我们知道s需要100个空间提前开好空间//s.resize(100); // 将有效字符个数修改到100并填充\0s.resize(100,6); // 将有效字符修改到100并填充6// 因此如果想后面不执行增容操作这里要用reservesize_t sz  s.capacity();cout  s_capacity grow\n;for (int i  0; i  100; i){s.push_back(c); // 也可以s  cif (sz ! s.capacity()) // ! 的时候就说明s进行了增容{sz  s.capacity(); // 将新容量给到szcout  new capacity:   sz  endl; // 将此时的容量打印出来}}return 0;
}可以看到不再填充\0而是填充我们指定的字符6 并且由于前100个空间都被resize填入了元素因此后面插入100个c的时候还是会增容的 
因此如果想后面不执行增容操作这里要用reserve 
总结重要 
string容量相关方法使用代码演示 
size()与length()方法底层实现原理完全相同引入size()的原因是为了与其他容器的接口保持一致一般情况下基本都是用size()。clear()只是将string中有效字符清空不改变底层空间大小resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个不同的是当字符个数增多时resize(n)用0来填充多出的元素空间resize(size_t n, char c)用字符c来填充多出的元素空间。注意resize在改变元素个数时如果是将元素个数增多可能会改变底层容量的大小如果是将元素个数减少底层空间总大小不变。 
string s1(hello, world);
s1.resize(5); // 会将原来的size减小到 5。
cout  s1  endl; // hellos1.resize(20, x); // 将size改到20由于前5个已经有元素了后15个填入x【空间不够会自动扩容】
cout  s1  endl; // helloxxxxxxxxxxxxxxxreserve(size_t res_arg0)为string预留空间不改变有效元素个数当reserve的参数小于string的底层空间总大小时reserver不会改变容量大小 
注意 
上面我们讲的有关容量的接口都是常用的并不是全部。全部的接口可以去文档中查看在查看接口的时候目前来说只需要查看接口如何使用不用查看接口的底层实现因为以目前的水平是完全看不懂的。常用的接口需要熟悉使用、 
2.2.3string类对象的访问及遍历操作 string中元素访问及遍历代码演示 
下面我们对于遍历一共有3种方法【[]  下标】【迭代器】【范围for】 
拓展迭代器 
我们来了解一下迭代器 如图所示迭代器一共四种这里我们就是了解一下具体的我们后面会详细学习。 
遍历的代码 
void test_string3()
{// 迭代器一共有4种迭代器这里在介绍一种string s(hello world);// 倒着遍历string::reverse_iterator rit  s.rbegin(); //要用rbegin指向字符串的最后一个有效元素// auto rit  s.rbegin();while (rit ! s.rend()) // rend指向字符串的首元素{cout  *rit;rit; // 其实是倒着走的}cout  endl;// dlrow olleh
}int main()
{	// []  下标  【平常推荐使用这个】string s(hello);cout  s: ;// 遍历for (size_t i  0; i  s.size(); i){s[i]  1; // 写 [这个相当于对取出来的字符1就相当于给其ASCII码值1]}for (size_t i  0; i  s.size(); i){cout  s[i]; // 读}cout  endl; //s8: ifmmp [hello每个字符的ASCII码值分别1]// 迭代器【每个容器都会有自己的迭代器】【迭代器一共有四种】这里了解一下迭代器string::iterator it  s.begin(); // string::iterator 实际是一个类型//auto it  s1.begin(); // 也可以这样写 [自动推演]// 遍历while (it ! s.end()){*it - 1; // 写it; // 迭代}it  s.begin(); // 重置while (it ! s.end()){cout  *it; // 读it; // 迭代}cout  endl;// hellotest_string3(); // 另外一种迭代器的使用// 上述是迭代器的使用方法熟悉一下就行后面会详细学习。// 范围for(c11)  [原理其实就是迭代器]for (auto ch : s) // 自动把s8从左到右依次给到ch auto会自动推断类型{cout  ch;}cout  endl; //helloreturn 0;
} 
再来看一个场景 
将字符串转化为整数 
// 将传进来的字符串转化成整数 【使用迭代器】
int string2int(const string str)// 用引用提高效率并且不改变strconst修饰
{int val  0;string::const_iterator cit  str.begin(); // 此时的str是const对象返回的是const类型的迭代器类型while (cit ! str.end()){val * 10;val  (*cit - 0);}cout  val  endl;
}int main()
{// 将字符串转化成整数string s(12345);int val  0; for (int i  0; i  s.size(); i) // 遍历{// 让val  上取出来的数字然后再*10 循环直至全部取出、val * 10; val  (s[i] - 0); // -0是因为 取出来的是字符 而不是整形 - 去0的ASCII值才是对应整形}cout  val  endl; // 12345string s1(12345);int ret  string2int(s1);cout  ret  endl; // 12345return 0;
}通过上面两个例子我们了解到了遍历有不同的方式去遍历。 
在这里面当中迭代器似乎是一个比较麻烦的使用方式。 
从方向上分正向和反向迭代器 
从权限上分普通迭代器const迭代器。 
因此使用的时候似乎不吃香从短期来看确实是如此因为经过了c语言的学习我们已经习惯于用数组的方式去遍历。但是数组这个方式只能用于vector 和 string。等到后面更多的数据结构也就是容器出现迭代器就会频繁使用因为所有容器的迭代器使用方法都是类似的。 
因此迭代器的学习是c中一个重要的部分。迭代器可以用于所有STL库中的容器的遍历 
2.2.4string类对象的修改操作 
常见的几个与修改操作相关的接口 1.push_back/append/(重要) 
push_back在字符串后边尾插字符 
append在字符串后边追加字符串 
在字符串后边追加字符串 
这三个我们推荐**** 
有关尾插的三个接口的使用的代码 
// 1. / push_back / append 【有关尾插的三个接口】
# includestring
# includeiostream
using namespace std;int main()
{string s1;s1.push_back(p);s1  p; // 推荐使用cout  s1  endl; //pp  可以看到  和 调用尾插接口的结果是一样的s1  hello; // 这里调用的是另外一个  的重载函数cout  s1  endl; // pphellos1.append( world); // 在s1后尾插 world字符串cout  s1  endl; // pphello world// 在上述三个尾插的操作接口中我们推荐使用return 0;
}如果想在具体位置插入的话可以使用insert接口想删除具体位置的话用erase接口 
可以上文档查具体使用方法这里不做详解 
2.c_str c_str 会将string类对象以const char 的格式返回* 
他会在string类对象中找到\0 把\0之前的字符串内容当做一个 const char 类型返回* 
代码操作 
//2.c_str
# includestring
# includeiostream
using namespace std;int main()
{string s(hello);const char* str  s.c_str();// str拿到的是const char* 类型。// 获取字符数组的首地址用c语言中的字符串形式去遍历while (*str) // 只要*str不是\0就继续{cout  *str;str;}cout  endl; // hello// 也可以直接输出cout  s  endl; // hello// 这里调用的是string类中的重载的, 它会将整个s进行输出cout  s.c_str()  endl; // hello// 这里相当于直接对 const char* 进行输出遇到\0就会结束输出s  \0;s   ;s  world;cout  s  endl; // hello world  中间的\0是不可见字符不会打印出来cout  s.c_str()  endl; // hello  因为从h到 \0 被当做了一个const char*,s.c_str()返回的就是 helloreturn 0;
}拓展编码表 
我们知道一个中文占据两个字节这是由编码表实现的因为中文的编码表是GDK 
而英文的编码表是ASCII表 
编码的本质是映射将对应的值映射到表中的某一个位置。 
我们来看一段代码: 
// 编码表
# includestring
# includeiostream
using namespace std;int main()
{// 我们知道英文是用ASCII码表来编码的string s  中国; // 中文使用GDK来编码的cout  s  endl; //中国s[3]  -7;cout  s  endl; // 中郭s[3]  -8;cout  s  endl; // 中锅s[3]  -9;cout  s  endl; // 中棍return 0;
}下图是s字符串中存储的信息 可以很清楚的看到中文是两个字节共同编码成一个中文。 
3.find/rfind/substr重要 find: 可以查询字符串中的某个字符 
如果找不到会返回npos【rfind找不到也一样返回npos】 npos是-1 size_t的-1相当于int类型的最大值42亿多 substr可以返回指定位置以后得字符串。如果不指定就是默认从头开始 
来看一段代码 
//3.find/rfind
# includestring
# includeiostream
using namespace std;int main()
{string s(text.cpp); // 如果我们想查询这个后缀.cpp// 我们就可以用find去查询.  再将其后边的打印出来size_t pos  s.find(.); // pos  4if (pos ! string::npos) // npos是-1  size_t的-1相当于int类型的最大值42亿多{// 用substr接口实现.后的打印cout  s.substr(pos)  endl; // .cpp// s.substr(pos)就是打印pos之后的数据包括pos}return 0;
}rfind可以查询字符串中的一个字符并且是最后一次出现的 
//3.find/rfind
# includestring
# includeiostream
using namespace std;int main()
{string s(text.cpp); // 如果我们想查询这个后缀.cpp// 我们就可以用find去查询.  再将其后边的打印出来size_t pos  s.find(.); // pos  4if (pos ! string::npos) // npos是一个-1  size_t的-1相当于int类型的最大值42亿多{// 用substr接口实现.后的打印cout  s.substr(pos)  endl; // .cpp// s.substr(pos)就是打印pos之后的数据包括pos}// 还会有一种情况如果想查询的字符串有多个后缀,我们想输出最后一个后缀的话// 我们就可以用rfind 重复的查找.  找到最后一个.string s1(test.cpp.zip); // pos1  8size_t pos1  s1.rfind(.);if (pos1 ! string::npos){cout  s1.substr(pos1)  endl; // .zip}return 0;
}拓展网址 
平常我们看到的网址是由协议、域名、资源名称 
// 网址
# includestring
# includeiostream
using namespace std;// 协议、域名、资源名称
int main()
{string url(https://gitee.com/wzf-sang);//https是协议 http和https的差别就是https是加密的///gitee.com其实是个ip但是绑定了域名。如果不绑定域名这里是个ip【域名更好记住】//wzf-sang就是资源名称。//【在我们输入网址敲回车之后服务器做的第一件事就是将你输入的网址拆成协议、域名、资源名称】//【因此才能在对应协议下申请访问对应的域名并根据资源名称返回资源】// 现在有个要求分离出url。size_t i1  url.find(:); // i1  5if (i1 ! string::npos){cout  url.substr(0, i1)  endl; // https//从0这个下标输出i1个元素}size_t i2  url.find(/, i1  3); // 从i1  3开始找if (i2 ! string::npos){cout  url.substr(i1  3, i2 - i1 - 3)  endl;// gitee.com// 从i1  3这个下标输出i2 - i1 - 3个元素}cout  url.substr(i2  1)  endl; //wzf-sangreturn 0;
}而我们可以将其功能分离出来抽象成一个函数 
// 网址
# includestring
# includeiostream
using namespace std;// 该函数用于分离网址的三个必要信息
void spilt_url(const string url)
{//【在我们输入网址敲回车之后服务器做的第一件事就是将你输入的网址拆成协议、域名、资源名称】//【因此才能在对应协议下申请访问对应的域名并根据资源名称返回资源】// 现在有个要求分离出url。size_t i1  url.find(:); // i1  5if (i1 ! string::npos){cout  url.substr(0, i1)  endl; // https//从0这个下标输出i1个元素}size_t i2  url.find(/, i1  3); // 从i1  3开始找if (i2 ! string::npos){cout  url.substr(i1  3, i2 - i1 - 3)  endl;// gitee.com// 从i1  3这个下标输出i2 - i1 - 3个元素}// 在i2这个下标后面的都是资源名称cout  url.substr(i2  1)  endl; // wzf-sang//  1是因为i2是 / i2后面的才是资源名称
}// 协议、域名、资源名称
int main()
{string url(https://gitee.com/wzf-sang);string url2(https://leetcode.cn/problems/majority-element/description/?envTypestudy-plan-v2envIdtop-interview-150);//https是协议 http和https的差别就是https是加密的///gitee.com其实是个ip但是绑定了域名。如果不绑定域名这里是个ip【域名更好记住】//wzf-sang就是资源名称。spilt_url(url);//	https//	gitee.com//	wzf - sangspilt_url(url2);//https//leetcode.cn//problems/majority-element/description/?envTypestudy-plan-v2envIdtop-interview-150return 0;
}2.2.5string类非成员函数 上面的几个接口了解一下下面的OJ题目中会有一些体现他们的使用。string类中还有一些其他的操作这里不一一列举在需要用到时不明白了查文档即可。 
2.2.6.vs和g下string结构的说明 
注意下述结构是在32位平台下进行验证32位平台下指针占4个字节。 
vs下string的结构 
string总共占28个字节内部结构稍微复杂一点先是有一个联合体联合体用来定义string中字 
符串的存储空间 
当字符串长度小于16时使用内部固定的字符数组来存放 
当字符串长度大于等于16时从堆上开辟空间 
这种设计是合理的大多数情况下字符串的长度都小于16那string对象创建好之后内 
部已经有了16个字符数组的固定空间不需要通过堆创建效率高。 
其次还有一个size_t字段保存字符串长度一个size_t字段保存从堆上开辟空间总的容量 
最后还有一个指针做一些其他事情。 
故总共占1644428个字节 g下string的结构 
G下string是通过写时拷贝实现的string对象总共占4个字节内部只包含了一个指针该指 
针将来指向一块堆空间内部包含了如下字段 空间总大小  字符串有效长度  引用计数  
struct _Rep_base
{size_type _M_length;size_type _M_capacity;_Atomic_word _M_refcount;
};指向堆空间的指针用来存储字符串。 
2.2.7与string相关的oj题目 917. 仅仅反转字母 - 力扣LeetCode  387. 字符串中的第一个唯一字符 - 力扣LeetCode  字符串最后一个单词的长度  125. 验证回文串 - 力扣LeetCode  415. 字符串相加 - 力扣LeetCode  541. 反转字符串 II - 力扣LeetCode  557. 反转字符串中的单词 III - 力扣LeetCode  43. 字符串相乘 - 力扣LeetCode较难  找出字符串中第一个只出现一次的字符较难