目录
6. append 与 push_back 与 operator+=
STL简介
STL(Standard TemplateLibrary),即标准模板库,从根本上说,STL是一些“容器”的集合,这些“容器”有list、vector、set、map等,STL也是算法和其他一些组件的集合。STL的目的是标准化组件,这样就不用重新开发,可以使用现成的组件。
STL包含了诸多在计算机科学领域里常用的基本数据结构和基本算法。为广大C++程序员们提供了一个可扩展的应用框架,高度体现了软件的可复用性。从逻辑层次来看,在STL中体现了泛型化程序设计的思想(generic programming)。在这种思想里,大部分基本算法被抽象,被泛化,独立于与之对应的数据结构,用于以相同或相近的方式处理各种不同情形。
STL六大组件
- 容器(Containers):各种数据结构,如
Vector
,Deque
,List
,Set
,Map
,用来存放数据,STL容器是一种Class Template,就体积而言,这一部分很像冰山载海面的比率。 - 算法(Algorithms):各种常用算法,如
Sort
,Search
,Copy
,Erase
,从实现的角度来看,STL算法是一种Function Templates。 - 迭代器(Iterators):扮演容器与算法之间的胶合剂,是所谓的“泛型指针”,共有五种类型,以及其它衍生变化,从实现的角度来看,迭代器是一种将:
Operators*
,Operator->
,Operator++
,Operator–
等相关操作予以重载的Class Template。所有STL容器都附带有自己专属的迭代器,只有容器设计者才知道如何遍历自己的元素,原生指针(Native pointer)也是一种迭代器。 - 仿函数(Functors): 即函数对象,行为类似函数,可作为算法的某种策略(Policy),从实现的角度来看,仿函数是一种重载了Operator()的Class 或 Class Template。一般函数指针可视为狭义的仿函数。
- 配接器(适配器)(Adapters):一种用来修饰容器(Containers)或仿函数(Functors)或迭代器(Iterators)接口的东西,例如:STL提供的
Queue
和Stack
,虽然看似容器,其实只能算是一种容器配接器,因为它们的底部完全借助Deque
,所有操作有底层的Deque供应。改变Functor接口者,称为Function Adapter;改变Container接口者,称为Container Adapter;改变Iterator接口者,称为Iterator Adapter。配接器的实现技术很难一言蔽之,必须逐一分析。 - 分配器(Allocators):即空间配置器,负责空间配置与管理,从实现的角度来看,配置器是一个实现了动态空间配置、空间管理、空间释放的Class Template。
标准库中的string类
-
string是表示字符串的字符串类
-
该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
-
string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator> string;
-
不能操作多字节或者变长字符的序列。
在使用string类时,必须包含#include头文件以及using namespace std。
string类的常用接口说明
1. string::string
int main()
{
//常用
string s1; //空串
string s2("hello world");//将hello world字符串拷贝到s2
string s3(s2); //将s2拷贝到s3
string s4(s2, 2, 5); //将s2从下标为2的字符开始拷贝5个字符到s4
string s5("hello", 3); //将hello字符串的前3个字符拷贝到s5
string s6(5, 'd'); //连续拷贝5个d到s6
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
cout << s5 << endl;
cout << s6 << endl;
return 0;
}
也可以直接用流插入和流提取
string s7;
cin >> s7;
cout << s7 << endl;
2. string::operator[]
返回字符串中对位置pos处字符的引用
-
越界可以检查出来
-
非const类型 可以对字符串进行修改
-
const与非const同时存在时,编译器会自动调用最合适的
size和length的功能相同,主要是为与STL相同使用,以后都用size
遍历方式1:下标访问
//遍历字符串
void test3() {
string s1("hello world");
cout << s1.size() << endl;//11
cout << s1.length() << endl;//11 同size相同功能
//遍历方式1:下标
for (size_t i = 0; i < s1.size(); i++) {
//cout << s1.operator[](i) << " ";
cout << s1[i] << " ";
cout << s1[i+1] << " ";//修改
}
s1[2] = 'c';
s1[30];//越界能检查出来
cout << endl;
}
const string s2("hello world");
//不能修改
s2[3] = 'c';//error
3.iterator
begin:任何容器返回第一个数据位置的iterator
end:任何容器返回最后一个数据位置的iterator
遍历方式2:迭代器
void test4() {
//遍历方式2:iterator
//迭代器 iterator--通用方式
string s1("hello world");
string::iterator it1 = s1.begin();
while (it1 != s1.end()) {
cout << *it1 << " ";
++it1;
}
cout << endl;
cout << typeid(it1).name()<< endl;//得到it1 的类型-理解为类似指针的东西
}
遍历方式3:范围for
//遍历方式3:范围for-所有容器支持
for (auto e : s1) {
//自动取值给e,自动++,自动判断结束
cout << e << " ";
}
//底层就是迭代器
cout<<endl;
const_iterator与const iterator的区别
void test4() {
const string s1("hello world");
//string::const_iterator it1 = s1.begin();
auto it1 = s1.begin();//自动识别类型
while (it1 != s1.end()) {
//不能修改
//*it1 += 2;//error
cout << *it1 << " ";
++it1;
}
}
4. 反向迭代器
rbegin 与 rend
void test5() {
const string s1("hello world");
//string::const_reverse_iterator it1 = s1.rbegin();
auto it1 = s1.rbegin();
while (it1 != s1.rend()) {
//不能修改
//*it1 += 2;//error
cout << *it1 << " ";
++it1;
}
}
5.std::sort
包含在<algorithm>头文件中
Sorts the elements in the range [first,last)
into ascending order.
左闭右开
#include<algorithm>
void test6(){
string s1("hello world");
cout << s1 << endl;
//按字典序排序
//sort(s1.begin(), s1.end());
//从第2个到倒数第2个排序
//sort(s1.begin() + 1, s1.end() - 1);
//排前5个字母——[0,5)
sort(s1.begin() , s1.begin() + 5 );
cout << s1 << endl;
}
6. append 与 push_back 与 operator+=
三个都是尾插
void test7()
{
string s1("hello world");
cout << s1 << endl;
string s2(s1);
cout << s2 << endl;
string s3(s1);
cout << s3 << endl;
s3.append(s1);//尾插s1
cout << s3 << endl;
s2.append(s1, 0, 5);//尾插s1中的前5个字符
cout << s2 << endl;
s1.append("hhh", 2);//尾插hhh中的前2个字符
cout << s1 << endl;
s1.append("lll");//尾插lll
cout << s1 << endl;
s1.append( 2,'j');//尾插2个j
cout << s1 << endl;
//push_back
s1.push_back('2');//尾插一个字符
cout << s1 << endl;
}
但是尾插接口还是operator+=是最好用的
//operator+=
s1 += "1111";
s1 += s2;
cout << s1 << endl;
7.insert 与 erase
intsert 和 erase 慎用,效率低下,每个字符都要挪动
void test8() {
string s1("hello world");
cout << s1 << endl;
string s2(s1);
s1.assign("111");//替代了s1
s1.insert(0,"222");//在下标是0 的位置 插一个字符串
s1.insert(0, 1, '0');//插入一个字符
s1.insert(s1.begin(), 'y');//插入一个字符
s1.insert(s1.begin(), s2.begin(), s2.end());//在s1的begin插入 从s2的begin到s2的end
//下标要合法,长度可以不合法
s1.erase(0, 1);//头删一个字符
s1.erase(100);//不传参数/传参数很大-直接删完
cout << s1 << endl;
}
8.replace
同样效率低,需要挪动每个字符
void test9()
{
string s1("hello world");
s1.replace(0, 1, "jj");//把第一个字符替换成jj
cout << s1 << endl;
}
练习:把所有空格替换成%20
//低效率
string s2("hello world hello bit");
for (size_t i = 0; i < s2.size();) {
if (s2[i] == ' ') {
s2.replace(i, 1, "%20");
i += 3;
}
else {
i++;
}
}
cout << s2 << endl;
要想提高效率,就牺牲空间
string s3(s2);
string s4;
//将数据放在新字符串中,提高效率
for (auto ch : s3) {
if (ch != ' ') {
s4 += ch;
}
else {
s4 += "%20";
}
}
相关的练习:
class Solution {
public:
bool isLetter(char ch){
if(ch >= 'A' && ch <= 'Z'){
return true;
}
if(ch >= 'a' && ch <= 'z'){
return true;
}
return false;
}
string reverseOnlyLetters(string s){
if(s.empty()){
return s;
}
int begin = 0,end = s.size() - 1;
while(begin < end){
while(begin < end && !isLetter(s[begin])){
++begin;
}
while(begin < end && !isLetter(s[end])){
--end;
}
swap(s[begin],s[end]);
++begin;
--end;
}
return s;
}
};
387. 字符串中的第一个唯一字符 - 力扣(LeetCode)
class Solution {
public:
//计数排序
int firstUniqChar(string s) {
int count[26] = {0};
for(auto ch : s){
count[ch - 'a']++;
}
for(size_t i = 0; i <s.size(); i++){
if(count[s[i] - 'a'] == 1){
return i;
}
}
return -1;
}
};
class Solution {
public:
bool isLetterOrNumber(char ch){
return (ch >= '0' && ch <= '9')
||(ch >= 'a' && ch <= 'z')
||(ch >= 'A' && ch <= 'Z');
}
bool isPalindrome(string s) {
for(auto& ch: s){
if(ch >= 'A' && ch <= 'Z')
ch += 32;
}
int begin = 0,end = s.size() - 1;
while(begin < end){
while(begin < end && !isLetterOrNumber(s[begin])){
++begin;
}
while(begin < end && !isLetterOrNumber(s[end])){
--end;
}
if(s[begin] != s[end]){
return false;
}
else{
++begin;
--end;
}
}
return true;
}
};
class Solution {
public:
string addStrings(string num1, string num2) {
int end1 = num1.size() - 1;
int end2 = num2.size() - 1;
int next = 0;
string str;
while(end1 >= 0 || end2 >= 0){
int x1 = end1 >= 0 ? num1[end1--] - '0': 0;
int x2 = end2 >= 0 ? num2[end2--] - '0': 0;
int x = x1 + x2 + next;
//处理进位问题
next = x / 10;
x = x % 10;
// //头插
// str.insert(str.begin(), '0'+x);
//尾插
str +=('0' + x);
}
// if(next == 1){
// str.insert(str.begin(), '1');
// }
if(next == 1){
str += '1';
}
reverse(str.begin(), str.end());
return str;
}
};
9.size 与 capacity 与 max_size
size与length功能相同,size是通用的,所以尽量使用size
capacity 在vs2022中开辟的空间小于16时,编译器会在buff[ ]存储,当扩容时,会在栈上重新开启空间,释放寄存器的空间,主要是为了提高效率,减少内存碎片。
max_size不常用,可得到的最大内存数,了解即可。
void test1() {
string s1("hello world");
cout << s1.size() << endl;
cout << s1.length() << endl;
cout << s1.capacity() << endl;
cout << s1.max_size() << endl;
}
10.reserve
扩容—VS2022中本身存在buff[ ]上,第一次扩容属于在堆上开空间,第二次扩1.5倍 具体如何扩容,C++没有明确的规定,具体看编译器
reserve可以扩容,但是用于缩容是不可取的,因为不同编译器下结果不同。
void test2() {
//reserve扩容 但不一定缩容
string s1("1111111");
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.reserve(100);
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << endl;
//缩容
s1.reserve(10);
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << endl;
}
11.resize
将字符串大小调整为 n 个字符的长度。
如果 n 小于当前字符串长度,则当前值将缩短为其第一个 n 个字符,从而删除第 n个字符之外的字符。
如果 n 大于当前字符串长度,则通过在末尾插入所需数量的字符来扩展当前内容,以达到 n 的大小。如果指定了 c,则新元素将初始化为 c 的副本,否则,它们是值初始化的字符(空字符)。
删除的空间是不能单独释放的
//resize
void test3() {
string s1;
s1.resize(5,'0');
s1[4] = '4';
s1[3] = '3';
s1[2] = '2';
s1[1] = '1';
s1[0] = '0';
cout << s1 << endl;
//resize扩容
string s2("2222222222222222222222222222");
cout << s2.size() << endl;
cout << s2.capacity() << endl;
s2.resize(100);
cout << s2.size() << endl;
cout << s2.capacity() << endl;
//插入(空间不足就扩容)
string s3("hello world");
s3.resize(20, 'x');
cout << s3 << endl;
//删除
s3.resize(5);
cout << s3 << endl;
}
12.find查找 substr提取字符
find正序查找,rfing逆序查找
//find查找 substr提取字符
void test4() {
//查找到最后一个.及后所有内容
string file("string.cpp.zip");
size_t pos = file.rfind('.');
string suffix = file.substr(pos);
cout << suffix << endl;
string url("https://legacy.cplusplus.com/reference/string/string/?kw=string");
//查找并提取网站的协议
size_t pos1 = url.find(':');
string url1 = url.substr(0, pos1 - 0);
cout << url1 << endl;
//查找并提取网站的网址
//从pos1后3个字符的位置开始找
size_t pos2 = url.find('/', pos1 + 3);
string url2 = url.substr(pos1 + 3, pos2 - (pos1 + 3));
cout << url2 << endl;
//查找并提取网站后面的内容
string url3 = url.substr(pos2 + 1);
cout << url3 << endl;
}
练习:字符串最后一个单词的长度牛客题霸牛客网 (nowcoder.com)
void test6() {
string s;
//默认规定空格或者换行时多个值的分割,不能提取有空格的内容
//cin >> s;
//getline 遇到空格才截止包含在<string>头文件中
getline(cin, s);
size_t pos = s.rfind(' ');
cout << s.size() - (pos + 1) << endl;
}
getline用于输入带有空格的内容,遇到空格不会截止,也可以自定义遇到其他符号才能截止
还有一种输入空格不截止的方法:
//输入多行字符,连续打印
void test7()
{
string str;
while (cin >> str) {
cout << str << endl;
}
//Ctrl+c杀进程
//ctrl+z结束进程
}
13.operator+
operator+的功能是 字符串加string类型,string加字符串,string加string。
void test5() {
string s1("hello");
string s2("world");
string ret1 = s1 + s2;
cout << ret1 << endl;
string ret2 = s1 + " " + "hhhh";
cout << ret2 << endl;
string ret3 = "kkkk" + s1;
cout << ret3 << endl;
//operator< 按ASCII值比较
cout << (s1 < s2) << endl;
}
14.stoi 与 to_string
stoi将string类型转换成int类型
to_string将整形或浮点型转换为string类型
注意:太长的字符串是不能转成整型的,因为整型有范围,存不下,容易崩溃。
//stoi to_string
void test8()
{
//to_string 整形转成字符串
int x = 0, y = 0;
cin >> x >> y;
string str = to_string(x + y);
cout << str << endl;
//stoi stoll 字符串转成整型——很长的字符串转不了整型
int z = stoi(str);
cout << z << endl;
}
15.at与operator[ ]
越界提示
operator[ ]会出现警示弹窗
at则会出现提示语“invalid string position”
void test9{
string s2("11111111111111");
try {
//at_访问pos位置的字符
//s2[10];//暴力
s2.at(10);
}
catch (const exception& e) {
cout << e.what() << endl;
}
}
//invalid string position
总结
string的接口是了解C++接口的开始,以上每个接口都应熟练掌握,其他只需了解即可,要常是阅读英语文档,了解本意,有时候翻译会不准确会造成误差。熟练掌握这些接口后,使用其他接口时就会轻松很多。