string类
1. 什么是STL
STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。
1.1 STL的六大组件
1.2 学好STL的三种境界
第一境界:熟练使用STL
第二境界:了解泛型技术的内涵与STL的学理乃至实作
第三境界:扩充STL
简单总结一下:学习STL的三个境界:能用,明理,能扩展 。
2. 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_string<char, char_traits, allocator> string;
- 不能操作多字节或者变长字符的序列。
- 在使用string类时,必须包含#include头文件以及
using namespace std;
这种方式在平时练习的时候使用,但是当你真正的在一个工程中的时候,最好使用std::string
这种方式单独的对string进行展开,如果这个string在这个工程中特别的常用,那么也可以单独的使用using std::string
进行展开
2.1 string类的常用接口说明
2.1.1.string类对象的常见拷贝构造
#include<iostream>
#include<string>
using namespace std;
int main()
{
//string();
string s1;
//string (const char* s);
string s2("hello world");
//string(const string& str);拷贝构造
string s3(s2);
//string(const string& str, size_t pos, size_t len = npos); 和上面的拷贝构造构成函数重载
string s4(s2,6,10);
//这里你不给npos这个传参,他就会给默认的最大值也就是-1的补码,也会默认到最后结束
string s5(s2, 6);
string s6(s2, 6, 3);
//string (size_t n, char c);
string s7(10, 'x');
//string是重载了operator<< 和operator>>的
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
cout << s5 << endl;
cout << s6 << endl;
cout << s7 << endl;
return 0;
}
2.1.2 string类对象的容量操作
size和length都是获取字符串的长度(是不算‘\0’的),那么为什么都有了size还会有一个length呢?那是由于历史性的原因,其实对于string类来说是比STL出现的早的,在早期都是使用length来表示字符串的长度,也好理解,但是后面为了和STL的风格保持一致,所以引入了一个size也是获取字符串的长度。
capacity是开空间的大小,显示的是能存放的数据个数,但实际开空间的大小要+1,因为他没有把‘\0’算上。
int main()
{
string s1("hello world");
cout << s1.size() << endl;
cout << s1.length() << endl;
cout << s1.capacity() << endl;
// 将s1中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
s1.clear(); //返回的是bool值
cout << s1.size() << endl;
cout << s1.capacity() << endl;
return 0;
}
int main()
{
int n = 100;
string numstr;
int capacity = numstr.capacity();
cout << "capacity:" << numstr.capacity() << endl;
for (int i = 0; i < n; i++)
{
numstr.push_back('0' + i);
if (capacity != numstr.capacity())
{
cout << "capacity:" << numstr.capacity() << endl;
capacity = numstr.capacity();
}
}
cout << numstr << endl;
return 0;
}
但是你会发现你的很多增容的过程都是无效的,因为你不断的在增容,一旦原先所开辟的空间不够,就需要在重新开辟一个空间,然后把上面的数据拷贝下来,在对原先开辟的空间进行释放,就会很繁琐,并且这个过程中会产生内存碎片的问题,所以当你知道你所要开辟的空间大小的时候,就直接可以使用reserve来进行一个空间的直接开辟。
int n = 100;
string numstr;
numstr.reserve(n);
int capacity = numstr.capacity();
cout << "capacity:" << numstr.capacity() << endl;
for (int i = 0; i < n; i++)
{
numstr.push_back('0' + i);
if (capacity != numstr.capacity())
{
cout << "capacity:" << numstr.capacity() << endl;
capacity = numstr.capacity();
}
}
cout << numstr << endl;
return 0;
reserve只会改变capacity,resize不但会改变capacity的大小,而且也会改变size的大小,当你没有给他指定size传什么的时候,他就会默认传‘\0’,所以resize的使用地方是:你不但想要对空间增容,而且想要对增容的空间初始化。
int n = 100;
string numstr;
//numstr.resize(n);
numstr.resize(n,‘x’);
int capacity = numstr.capacity();
cout << "capacity:" << numstr.capacity() << endl;
for (int i = 0; i < n; i++)
{
numstr.push_back('0' + i);
if (capacity != numstr.capacity())
{
cout << "capacity:" << numstr.capacity() << endl;
capacity = numstr.capacity();
}
}
cout << numstr << endl;
return 0;
numstr.resize(n);
numstr.resize(n,‘x’);
注意:
size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
clear()只是将string中有效字符清空,不改变底层空间大小。
resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用’\0’来填充多出的元素空间,resize(size_tn, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
2.1.3 string类对象的访问及遍历操作
char& operator[](size_t pos);
出了函数作用域,返回对象还存在,可以使用引用返回
1.使用引入可以减少拷贝
2.使用引入的好处就是不止可以读到字符串的pos位置,而且还可以修改
const char& operator[] (size_t pos) const
//将字符串转换为整数
//把字符串逆置
class Solution {
public:
int StrToInt(string str) {
int num = 0;
for (size_t i = 0; i < str.size(); ++i) //为了和C++中的size区分开来,这里定义变量使用的类型都是size_t(unsigned int)
{
num *= 10;
num += str[i] - '0';
}
return num;
}
string Reverse(string s)
{
int begin = 0, end = s.size() - 1;
while (begin < end)
{
swap(s[begin], s[end]);//在C++的库里面就有交换函数,所以不需要在自己去实现了
//但是平时是不咋使用的
//swap(s.at(begin), s.at(end));
begin++;
end--;
}
return s;
}
};
int main()
{
cout << Solution().StrToInt("1234") << endl;
cout << Solution().Reverse("1234") << endl;
return 0;
}
这里还可以使用at 和operator[]是一样的swap(s.at(begin), s.at(end));
,他们的区别就是[ ]是断言,而at是采用抛异常,但是at平时是不咋使用的
迭代器
现阶段就把迭代器理解为指针,当然后面会对其进行纠正。
int StrToInt(string str) {
int num = 0;
//一般给迭代器的名称就起为it
string::iterator it = str.begin();
while (it != str.end()) //这里str.end()指向的是‘\0’的位置
{
num *= 10;
num += *it - '0';
it++;
}
return num;
}
rbegin 和 rend反向迭代器
其中rbegin指向的就是字符串的最后一个有效的字符位置,然后这里++他是会往前遍历的,rend()指向的就是空的位置
int StrToInt(string str) {
int num = 0;
//反向迭代器
string::reverse_iterator rit = str.rbegin();
//这里的变量名字很长,很麻烦,所以这里就可以使用到auto了
//auto rit = str.rbegin();
while (rit != str.rend()) // 这里的str.rend()代表的是空
{
num *= 10;
num += *rit - '0';
rit++; //这里的++就是从最后一个位置往前走
}
return num;
}
总结:string的遍历建议使用下标+[ ]或者范围for,但是也要知道迭代器的用法,因为迭代器是容易通用的遍历方式
范围for(C++11)
范围for的底层又是迭代器,所以所有的容器都支持范围for
int main()
{
string s("hello");
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
}
2.1.4 string类对象的修改操作
int main()
{
string s("hello");
for (auto e : s)
{
cout << e <<" ";
}
cout << endl;
s.push_back('\0');
s.push_back('x');
s.push_back('x');
s.push_back('x');
s.append("world");
cout << s.c_str() << endl;
cout << s << endl;
}
所以对于s这个string所创造的对象来说,相当于迭代器遍历,不遇见最后的‘\0’是不会停止的,但是对于c_str来说,遇见‘\0’就相当于到头了。
对于在string所定义出来的对象的尾部增加字符使用的是push_back('x')
,但是当在尾部增加一个字符串的时候又要使用append('xxx')
,说实话很麻烦,所以一般的做法就是重载一个+=,那么这两个都可以同时实现 s += 'x'
; s+= 'xxxxx';
但是这个+=运算符的实现又是依靠push_back 和 append。
int main()
{
string s1("hello");
//basic_string& insert (size_type pos, size_type n, charT c);
cout << s1.insert(0, 1, 'x') << endl;
string file1("string.cpp");
string file2("string.cpp.tar.zip");
string file3("string.txt");
//size_type find (charT c, size_type pos = 0) const;
size_t pos1 = file1.find('.');
if (pos1 != string::npos)
{
//basic_string substr (size_type pos = 0, size_type len = npos) const;
cout << file1.substr(pos1, file1.size() - pos1)<<endl;
//cout << file1.substr(pos1)<<endl;
}
//size_type rfind (charT c, size_type pos = npos) const;
size_t pos2 = file2.rfind('.');
if (pos2 != string::npos)
{
cout << file2.substr(pos2)<<endl; //不给参数也是可以的,因为有默认的缺省值
}
}
int main()
{
string url("http://www.cplusplus.com/reference/string/string/find/");
cout << url << endl;
size_t pos1 = url.find(':');
if (pos1 != string::npos)
{
cout << url.substr(0, pos1)<<endl;
}
size_t pos2 = url.find('/', pos1 + 3);
if (pos2 != string::npos)
{
cout << url.substr(pos1 + 3, pos2-(pos1 + 3))<<endl;
}
cout << url.substr(pos2 + 1,string::npos)<<endl;
//隐藏前面的域名
string copy = url; //拷贝构造一个,不改变原始的url
//basic_string& erase(size_type pos = 0, size_type len = npos);
cout << copy.erase(0, pos1+3);
return 0;
}
2.1.5 string类非成员函数
3. Leetcode好的string类题
Leetcode链接: link.反转字符串
牛客网链接: link.找字符串中第一个只出现一次的字符
牛客网链接: link.字符串里面最后一个单词的长度
Leetcode链接: link.验证一个字符串是否是回文
Leetcode链接: link.字符串相加
Leetcode链接: link.翻转字符串III:翻转字符串中的单词
Leetcode链接: link.字符串相乘