一.STL简介
1.STL
2.STL六大组件
二.标准库里的string类
标准string库网址:string - C++ Reference
1.string构造函数
int main()
{string s1;string s2("张三");string s3("hello world");string s4(10, '*'); //10个*string s5(s2); //拷贝构造string s6(s3, 0, 5); //以s3为基准,拷贝第0到第5个字符string s7(s3, 6); //第三个参数没有给的时候会自动填充一个npos(-1),指向最后一个位置string s8(s3, 6, 100); //超出string长度的时候,会以最后一个位置为结尾cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;cout << s4 << endl;cout << s5 << endl;cout << s6 << endl;cout << s7 << endl;cout << s8 << endl;
}
2.赋值运算符重载(operator=)
int main()
{string s1;string s2("张三");s1 = s2; //strcout << s1 << endl;s1 = "1111"; //char*cout << s1 << endl; s1 = '2'; //charcout << s1 << endl;return 0;
}
大于小于号运算符重载:
cout << (s1 == s2) << endl; //0
cout << (s1 > s2) << endl; //0
3.string尾插(字符,字符串)
(1).尾插一个字符(push_back)
int main()
{string s1("hello");// 尾插一个字符s1.push_back('7'); //hello7
}
(2).尾插一个字符串(append,+=)
不过在一些情况下+=是更好的选择:
int main()
{// 增string s1("hello");// 尾插一个字符s1.push_back('7');// 尾插一个字符串s1.append("world");// +=s1 += '7'; //本质上+=字符就是调用push_backs1 += "world"; //本质上+=字符串就是调用append
}
4.迭代器(iterator)
对于一个字符串,在C语言中我们使用下标+[ ],在C++的string里面,我们可以使用迭代器来实现字符串的遍历。
在这之前,我们先学会begin()和end()的使用方法:
所以实际上,这两个东西就是返回一个指向字符串起始/末尾字符的迭代器。
C语言遍历string的方法:
int main()
{string s1("hello world");cout << s1 << endl;// 遍历stringcout << s1.size() << endl; //不包括\0// 下标+[]for (size_t i = 0; i < s1.size(); i++){cout << s1[i] << " ";}
}
C++可以使用迭代器来实现遍历:
int main()
{string s1("hello world");cout << s1 << endl;//迭代器string::iterator it = s1.begin();while (it != s1.end()){// 写(*it)--;++it;}cout << endl;it = s1.begin();while (it != s1.end()){// 读cout << *it << " ";++it;}cout << endl;
}
上面是正向迭代器,还有一个反向迭代器reverse_iterator,如果需要的是只读的对象,还可以有const_iterator和const_reverse_iterator。
范围for
相比于迭代器,范围for的迭代方式往往更加简洁:
int main()
{string s1("hello world");// 范围for 应用范围更广 //缺点:只能正向遍历,不能反向遍历// 底层替换为迭代器//for (char& ch : s1)for (auto& ch : s1){// 写 ch--;}cout << endl;for (char ch : s1){// 读cout << ch << " ";}cout << endl;return 0;
}
5.内存相关函数和扩容机制
(1).内存相关函数
int main()
{string s1("hello world");cout << s1.size() << endl;cout << s1.length() << endl; //size 和 length本质上是一样的,推荐使用sizecout << s1.max_size() << endl; //最大长度(没啥用)cout << s1.capacity() << endl; //已开辟的空间大小(不同类型的编译器结果是不一样的)
}
clear()函数可以用来清空string,但是只能清空size,清空不了capacity,因为可能后续还会使用所以不会清理。
int main()
{cout << s1.size() << endl;cout << s1.capacity() << endl;s1.clear(); //clear只清理size,不清理capacitycout << s1.size() << endl;cout << s1.capacity() << endl;
}
reserve
reserve会开一块空间,vs会比给定的空间多扩容一点,XSell按实际开辟。
改变开辟空间的大小需要先清理数据,直接reserve不会起作用。
void TestPushBackReserve()
{string s;s.reserve(100); //这里就是提前开好了空间,后续不再进行改变 //vs实际开出来的大小会比100(给出来的数)大 XShell就是按照实际的开辟 原则就是必须比给的数大size_t sc = s.size();size_t sz = s.capacity();cout << "capacity changed: " << sc << '\n';cout << "capacity changed: " << sz << '\n';cout << "making s grow:\n";for (int i = 0; i < 100; ++i){s.push_back('c');if (sz != s.capacity()){sz = s.capacity();cout << "capacity changed: " << sz << '\n';}}//想要改变空间大小需要先clear(),然后再设置新的空间大小s.clear(); cout << "capacity changed: " << sz << '\n';s.reserve(10); //不清数据是不改变的sz = s.capacity();cout << "capacity changed: " << sz << '\n';
}// reserve // 保留
// reverse // 反转
int main()
{TestPushBackReserve();return 0;
}
resize
int main()
{string s1("hello world");// 开空间s1.reserve(100);cout << s1.size() << endl;//11cout << s1.capacity() << endl;//111// 开空间+填值初始化s1.resize(200); //resize是对开辟出来的空间的200个对象初始化占用size大小,reserve比要求的的多一点s1.resize(200, 'x'); //hello worldxxxxxxxxxxxxxxxxxxxxxxxx... 如果空间大于原本,先进行扩容然后再填值初始化cout << s1.size() << endl;//200cout << s1.capacity() << endl;//207s1.resize(5); //如果小于size的长度,则缩小显示个数,但是不会缩容 //hellocout << s1.size() << endl;//5cout << s1.capacity() << endl;//207s1.clear(); //可以把s1里面的内容给清除(size归0),但是空间不会在clear()函数上清除(capacity不归0)s1.resize(0);cout << s1.size() << endl;//0cout << s1.capacity() << endl;//207//没啥用,可以不记s1.shrink_to_fit(); //这个函数名义上是把s1空间调整至合适的大小,但实际上空间大小还是会比实际稍微大一点点cout << s1.size() << endl;//0cout << s1.capacity() << endl;//15//目前缩容的唯一办法:调用clear()函数后重新reservereturn 0;
}
(2).扩容机制(vs)
int main()
{string s1("hello world");size_t old = s1.capacity();for (size_t i = 0; i < 100; i++){s1 += 'x';//vs扩容机制:除第一次是原本的两倍,剩下的都是上一次容量的1.5倍//XShell是二倍扩容if (old != s1.capacity()) {cout << "扩容:" << s1.capacity() << endl;old = s1.capacity(); }}
}
6.at和错误分析
(1).at
有个引用符号,说明是可以修改的(下面会输出xello world)
int main()
{try {string s1("hello world");s1.at(0) = 'x';cout << s1 << endl;//s1[15]; // 暴力处理s1.at(15); // 温和的错误处理}catch (const exception& e){cout << e.what() << endl;}return 0;
}
(2).错误分析
如上图,类似于try-catch这样的就是一个错误检查,比如我们用s1.at(15);这是一个温柔检查,会抛出一个异常(即进入catch),如果直接用s1[15]会直接中断,因为[]是有强制性的,它是决不允许越界访问的。
s1.at(15);
s1[15];
7.插入删除
(1).insert
int main()
{string s1;s1.insert(0, "hello"); //在第几个位置插入cout << s1 << endl;s1.insert(5, "world");cout << s1 << endl;s1.insert(0, 10, 'x'); //第0开始插入10个xcout << s1 << endl;s1.insert(s1.begin()+10, 10, 'y');cout << s1 << endl;return 0;
}
(2).erase
int main()
{string s1("hello world");s1.erase(5, 1); //如果类似于(5,7)超出了界限,则直接把后面的删掉,不会报错cout << s1 << endl;s1.erase(5); //从第5个位置开始后面的全删掉cout << s1 << endl;try{s1.erase(11); //如果位置超了,会抛出一个异常cout << s1 << endl;}catch (const exception& e){cout << e.what() << endl;}string s2("hello world");s2.erase(0, 1);cout << s2 << endl;s2.erase(s2.begin());cout << s2 << endl;return 0;
}
其中要注意,s.erase()对应的作用,s1.erase(5);是传入了一个范围,s2.erase(s2.begin());是传入了一个指向具体位置的迭代器。所以前者为删除一串数据,后者是删除一个确定的元素。
(3).replace
int main()
{// world替换成 xxxxxxxxxxxxxxxxxxxxxxstring s1("hello world hello bit");s1.replace(6, 5, "xxxxxxxxxxxxxxxxxxxxxx"); //第6个位置起的5个字符,替换成xxxxxxxxxxxxxxxxxxxxxxcout << s1 << endl;//replace没有缺省值s1.replace(6, 23, "yyyyy");cout << s1 << endl;// 所有空格替换成20%string s2("hello world hello bit");string s3;for (auto ch : s2){if (ch != ' '){s3 += ch;}else{s3 += "20%";}}s2 = s3;cout << s2 << endl; //string的流插入重载cout << s2.c_str() << endl; //本质上是调用的底层的private里的char* strreturn 0;
}
8.查找
(1).find
int main()
{string url = "ftp://www.baidu.com/?tn=65081411_1_oem_dg";// 协议:ftp// 域名:www.baidu.com// 资源名:?tn=65081411_1_oem_dgsize_t pos1 = url.find("://");//会返回第一个匹配项的第一个字符的位置。且不给第二个参数的情况下,默认从第一个字符开始找//如果没有相匹配的字符,返回string::npos (-1)//请注意,与成员find_first_of不同,每当搜索多个字符时,仅匹配其中一个字符是不够的,整个序列都必须匹配。cout << pos1 << endl;string protocol;if (pos1 != string::npos){protocol = url.substr(0, pos1); //子串,把第一个参数的位置到第二个参数的位置提取出来}cout << protocol << endl;string domain;string uri;size_t pos2 = url.find('/', pos1 + 3);if (pos2 != string::npos){domain = url.substr(pos1 + 3, pos2 - (pos1 + 3));uri = url.substr(pos2 + 1);}cout << domain << endl;cout << uri << endl;//rfind 从后往前找的return 0;
}
* substr
(2).find_first_of
int main()
{string url = "ftp://www.baidu.com/?tn=65081411_1_oem_dg";size_t pos1 = url.find_first_of(":&/");cout << pos1 << endl;return 0;
}
(size_t pos1 = url.find_first_of(":&/");)
如果是用的find,会返回-1,这就是find和find_first_of的区别。find与成员find_first_of不同,每当搜索多个字符时,仅匹配其中一个字符是不够的,整个序列都必须匹配。
(size_t pos1 = url.find(":&/");)
9.例题
. - 力扣(LeetCode). - 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/add-strings/description/
class Solution {
public:string addStrings(string num1, string num2) {int end1 = num1.size()-1;int end2 = num2.size()-1;string strRet;int carry = 0;//只要还有数,就得接着算,所以用或while(end1 >= 0 || end2 >= 0) {//没有数的进来只能置0,有数的进来置其距离'0'字符的距离(即实际数字)int val1 = end1 >= 0 ? num1[end1] - '0' : 0;int val2 = end2 >= 0 ? num2[end2] - '0' : 0;int ret = val1 + val2 + carry;carry = ret / 10;ret = ret % 10;strRet.insert(strRet.begin(), ret + '0');--end1;--end2;}if(carry == 1){strRet.insert(strRet.begin(), '1');}return strRet;}
};
三.vs下string结构说明
首先看下面一个例子:
int main()
{std::string s1("hello world");std::string s3("hello worldxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");cout << sizeof(s1) << endl; cout << sizeof(s3) << endl;return 0;
}
我们可以看到对应的sizeof结果都是40,他具体是怎么存储的呢?进入调试:
可以看到,string有一个buf数组,字符串的内容保存在这个buf数组里。
但是对于s3,他的数据不在buf数组里:
可以看到string存在于_ptr指向的堆空间中。
由此可见:size<16,存在数组中;size>=16,存在_ptr指向的堆空间中。
作用:提供buf数组能提高效率,不过当size较大时会导致buf这块空间没有被使用浪费,不过这样的浪费是可以原谅的,相当于用空间换时间。