STL简介
STL
是C++
标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。STL
由六大组件构成:仿函数
、算法
、迭代器
、空间配置器
、容器
和配接器
。
其中各种容器可以很大帮助的提升我们编写程序的效率,后续都会一一介绍,今天我们就先拿!string
来小试牛刀,用上了它,我们就能对字符串的操作更加行云流水。
string使用
注意:string
诞生于STL
之前,所以会存在接口冗余的现象,本文介绍的是string
的部分常用接口,大佬们想了解更多关于string类的细节,请前往官方文档(点击跳转)查阅
1. 构造函数
注意:string
包含于iostream
头文件中,使用时还需展开std
命名空间
1.1 默认(无参)构造函数
int main()
{
string s; //调用默认构造函数,只包含'\0'
return 0;
}
调用默认构造函数
时,默认将对象初始化为只包含 '\0'
的空串
1.2 带参构造函数
我们可以自己指定string
对象中的内容
int main()
{
string s1("happy"); //带参构造函数,指定字符串+'\0',下同
string s2 = "happy";
string s3(12, 'c'); //构造12个c
return 0;
}
1.3 拷贝构造
int main()
{
string s1("happy");
string s2 = "happy";
string s3(s1); //拷贝构造,下同
string s4 = s1;
return 0;
}
2. 容量操作
string
类中包含容量、长度、字符指针等,我们可以将其看作一个存放字符的顺序表来处理
2.1 获取数据
c_str()
接口:获取string
对象中指向字符串的指针,当指针指向对象为常量字符串时,编译器会直接打印内容
int main()
{
string s1("hello");
string s2 = "happy";
//指针指向常量字符串时编译器直接打印
cout << s1.c_str() << endl; //获取对象s1中的字符串指针
cout << (void*)s2.c_str() << endl; //非常量字符指针
return 0;
}
capacity()
接口和size()
接口:获取string
对象的容量和大小
int main()
{
string s(88, 'j');
cout << "capacity: " << s.capacity() << endl;
cout << "size: " << s.size() << endl;
return 0;
}
2.2 空间扩容
new
出来的空间不支持使用relloc
直接扩容,而需要使用专门的接口来扩容
reserve()接口:实现string
对象异地扩容
int main()
{
string s(12, 's'); //初始容量为12
cout << "capacity: " << s.capacity() << endl;
s.reserve(24); //扩容为24
cout << "capacity: " << s.capacity() << endl;
return 0;
}
如果我们不手动扩容,string
也会在识别到容量不够时,自动扩容
VS下string
的扩容规则
- 默认给一个大小为
15
的数组存储数据,当数组够用时,都是用的数组 - 当数组容量不够时,改用指针,先
2倍
扩容至30
,后续字符都是存在指针中,之后每次扩容,都是1.5倍
扩容 - 会多开辟一些空间
Linux下string
的扩容规则
- 默认大小为
0
的空间 - 当第一次扩容时,会先扩至
1
,之后每次都是2倍
扩容 - 不会多开空间
注意:频繁的扩容会导致内存碎片问题,我们在使用string时,可以先提前计算好大致需要的空间,使用reserve提前进行扩容,来避免此问题
2.3 长度调整
size()
接口:改变string
对象的长度
int main()
{
string s(24, 'a'); //初始size = 24
cout << "size: " << s.size() << endl;
s.resize(12); //改变size为12
//s.resize(36, 'b'); //改变后12块空间为b
cout << "size: " << s.size() << endl;
return 0;
}
resize()
接口的两种情况:
- 如果调整后空间比原空间大,就相当于扩容,
resize()
还有初始化的功能,可以将第二个参数设置为指定字符,如果没有指定就默认为\0
,如以上示例。 - 如果调整后空间比原空间小,此时将会size调整至目标空间,而
capapcity
不会改变,此时我们无法访问到size
之外的数据
resize()
并不会达到缩容的效果,因为缩容的代价比较大,需要先开辟新空间,然后拷贝,释放原空间,因此 resize()
在处理时,若新空间比原空间小,不会改变capaciy
3. 字符串遍历
string
字符串的遍历主要有三种方式:下标访问[]
、at()
、迭代器访问
3.1 下标访问
下标访问
的原理就是运算符重载operator[]
int main()
{
string s("hello sakura!");
size_t pos = 0;
while (pos < s.size())
{
cout << s[pos++]; //hellosakura
}
return 0;
}
at()
访问字符串
int main()
{
string s("hello sakura!");
size_t pos = 0;
while (pos < s.size())
{
cout << s.at(pos++);
}
return 0;
}
这里的运行结果和上面使用下标访问时一致的,二者实现的原理一样,区别是当出现越界访问时,at()
会抛异常
,而下标访问会通过assert
报错
3.2 迭代器
迭代器遍历iterator
字符串
使用begin()
获取第一个字符,end()
获取最后一个字符的下一个字符即'\0'
int main()
{
string s("hello world!");
string::iterator it = s.begin(); //此时it相当于指向第一个字符的指针
//auto it = s.begin(); //也可以用auto自动类型识别
while (it != s.end())
{
cout << *it;
it++;
}
return 0;
}
除了可以使用iterator
进行正向遍历外,还可以使用reverse_iterator
进行反向遍历
rbegin()
获取最后一个字符,rend()
获取第一个字符的前一个字符
int main()
{
string s("hello world!");
string::reverse_iterator rit = s.rbegin(); //此时rit相当于指向最后一个字符的指针
while (rit != s.rend())
{
cout << *rit;
rit++;
}
return 0;
}
除了上面两种普通迭代器外,还有两个 const
修饰的迭代器,用来遍历常量字符串
const_iterator
正向遍历常量字符串const_reverse_iterator
反向遍历常量字符串
注意:
- 迭代器遍历的区间都是左闭右开的
- 迭代器名中 的
const
并不是const
操作符,而是与普通迭代器构成重载 - 迭代器不适合用来遍历顺序表,适合遍历链表
范围for
的底层就是调用迭代器遍历
4. 字符串修改
4.1 尾插字符/字符串
对于尾插字符/字符串,string提供了三种方式:
首先是push_back()
:尾插字符
int main()
{
string s = "happ";
cout << s << endl;
//尾插字符
s.push_back('y');
cout << s << endl;
return 0;
}
push_back()
类似顺序表尾插,一次只能插入一个字符
第二种是append()
:尾插字符字符串
int main()
{
string s = "happy";
cout << s << endl;
//尾插字符/字符串
s.append(2, 'aa'); //插入两个a
s.append(" cjc"); //插入字符串
cout << s << endl;
return 0;
}
第三种是operator+=
:尾插字符/字符串
int main()
{
string s = "happy";
cout << s << endl;
//尾插字符/字符串
s += 'c';
s += "jc";
cout << s << endl;
return 0;
}
在日常使用中operator+=
是使用频率最高,最方便的
4.2 任意位置插入字符/字符串
insert()
接口:支持在string
对象任意位置插入字符/字符串
int main()
{
string s("xxxxxx");
cout << s << endl; //xxxxxx
s.insert(2, 1, 'c'); //在第2个位置插入1个c
s.insert(3, "jc"); //在第3个位置插入字符串jc
cout << s << endl; //xxxcjcxxx
return 0;
}
4.3 任意位置删除字符/字符串
erase()
接口:支持在string对象任意位置删除字符/字符串
int main()
{
string s("happynewyear");
cout << s << endl;
s.erase(5, 1); //从第5个位置开始删除1个字符
cout << s << endl;
s.erase(5, 2); //从第5个位置开始删除2个字符
cout << s << endl;
s.erase(); //默认全部删除
cout << s << endl; //空串
return 0;
}
erase()
是一个全缺省参数,第一个参数为0
,表示默认从pos[0]
开始,第二个参数为npos
,这是无符号整型中的-1
,为无符号整型最大值,意思是如果不写第二个参数,默认就全部删除
4.4 查找字符/字符串位置
find()
接口:查找string
对象中指定字符/字符串的位置
int main()
{
//返回的是目标字符/字符串第一次出现的下标
string s("happycjchappy");
//找字符c,默认从pos0位置开始找
cout << s.find('c') << endl;
//找字符串ha
cout << s.find("ha") << endl;
//找字符串ha,从pos3位置开始找
cout << s.find("ha", 3) << endl;
//没找到的情况
cout << s.find("bb") << endl; //返回npos
return 0;
}
find
返回的是目标字符/字符串第一次出现的下标,这里可以看到,当目标不存在时,返回的就是 npos
find()
还有另外几种使用方式:
rfind()
从后往前找find_first_of(str, pos = 0)
从pos
位置往后,找str
中出现的任意字符find_last_of(str, pos = npos)
从npos
位置往前,找str
中出现的任意字符find_first_not_of()
反向查找find_last_not_of()
反向查找
4.5 截取字符串
substr()
接口:截取string对象中的目标字符串
int main()
{
string s("happy new year");
cout << s.substr(s.find('n'), 3) << endl;
return 0;
}
5. 非成员函数
string
中还有很多定义在类外的非成员函数
5.1 流操作
可以直接对string
对象进行流插入operator<<
和流提取operator>>
int main()
{
string s;
cin >> s;
cout << s;
return 0;
}
5.2 获取字符串
当输入的字符串中包含 ' '(空格)
时,cin
会认为这是结束标志,而不再继续读取字符,因此单纯的流插入是无法满足字符串插入需要的,为此string
专门的函数获取字符串 的接口getline()
int main()
{
string s;
getline(cin, s);
cout << s;
return 0;
}
5.3 比较函数
string
类中的比较函数特别冗余,饱受吐槽,友友们需要时可以到官方文档查阅
C++【STL】之string的使用,到这里就介绍结束了,本篇文章对你由帮助的话,期待大佬们的三连,你们的支持是我最大的动力!
文章有写的不足或是错误的地方,欢迎评论或私信指出,我会在第一时间改正!