一.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; //str
cout << s1 << endl;
s1 = "1111"; //char*
cout << s1 << endl;
s1 = '2'; //char
cout << 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_back
s1 += "world"; //本质上+=字符串就是调用append
}
4.迭代器(iterator)
对于一个字符串,在C语言中我们使用下标+[ ],在C++的string里面,我们可以使用迭代器来实现字符串的遍历。
在这之前,我们先学会begin()和end()的使用方法:
所以实际上,这两个东西就是返回一个指向字符串起始/末尾字符的迭代器。
C语言遍历string的方法:
int main()
{
string s1("hello world");
cout << s1 << endl;
// 遍历string
cout << 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本质上是一样的,推荐使用size
cout << 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,不清理capacity
cout << 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;//11
cout << s1.capacity() << endl;//111
// 开空间+填值初始化
s1.resize(200); //resize是对开辟出来的空间的200个对象初始化占用size大小,reserve比要求的的多一点
s1.resize(200, 'x'); //hello worldxxxxxxxxxxxxxxxxxxxxxxxx... 如果空间大于原本,先进行扩容然后再填值初始化
cout << s1.size() << endl;//200
cout << s1.capacity() << endl;//207
s1.resize(5); //如果小于size的长度,则缩小显示个数,但是不会缩容 //hello
cout << s1.size() << endl;//5
cout << s1.capacity() << endl;//207
s1.clear(); //可以把s1里面的内容给清除(size归0),但是空间不会在clear()函数上清除(capacity不归0)
s1.resize(0);
cout << s1.size() << endl;//0
cout << s1.capacity() << endl;//207
//没啥用,可以不记
s1.shrink_to_fit(); //这个函数名义上是把s1空间调整至合适的大小,但实际上空间大小还是会比实际稍微大一点点
cout << s1.size() << endl;//0
cout << s1.capacity() << endl;//15
//目前缩容的唯一办法:调用clear()函数后重新reserve
return 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个x
cout << 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替换成 xxxxxxxxxxxxxxxxxxxxxx
string s1("hello world hello bit");
s1.replace(6, 5, "xxxxxxxxxxxxxxxxxxxxxx"); //第6个位置起的5个字符,替换成xxxxxxxxxxxxxxxxxxxxxx
cout << 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* str
return 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_dg
size_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这块空间没有被使用浪费,不过这样的浪费是可以原谅的,相当于用空间换时间。