一.标准库中的string类
1.1string类了解
1. 字符串是表示字符序列的类
2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作 单字节字符字符串的设计特性。
3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信 息,请参阅basic_string)。
4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits 和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个 类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。
总结:
1. string是表示字符串的字符串类
2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
3. string在底层实际是:basic_string模板类的别名,typedef basic_string string;
4. 不能操作多字节或者变长字符的序列。
在使用string类时,必须包含#include头文件以及using namespace std;
1.2string类的常用接口说明
1.string类对象的常见构造
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s1;
string s2("hello string");
string s3(s2);
string s4(10, 'A');
//string的对象可以直接用cout打印
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
return 0;
}
string就是动态类型的顺序表----》元素类型是char
2. string类对象的容量操作
注意:
1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一 致,一般情况下基本都是用size()。
2. clear()只是将string中有效字符清空,不改变底层空间大小。
3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字 符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的 元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大 小,如果是将元素个数减少,底层空间总大小不变。
4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于 string的底层空间总大小时,reserver不会改变容量大小。
void TestString1()
{
string s("hello");
cout << s.size() << endl;
cout << s.length() << endl;
cout << s.capacity() << endl;
if (!s.empty())
{
cout << "s is not empty:" << s<<endl;
}
else
{
cout << "s is empty:" << endl;
}
s.clear();
cout << s.size() << endl;
if (!s.empty())
{
cout << "s is not empty:" << s << endl;
}
else
{
cout << "s is empty:" << endl;
}
}
reserve:(size_t newcapacity)用来进行扩容的
//reserve(size_t newcapacity):
void TestString2()
{
string s("hello");
cout << s.size() << endl;
cout << s.capacity() << endl;
s.reserve(20);
cout << s.size() << endl;
cout << s.capacity() << endl;
s.reserve(35);
cout << s.size() << endl;
cout << s.capacity() << endl;
s.reserve(60);
cout << s.size() << endl;
cout << s.capacity() << endl;
s.reserve(100);
cout << s.size() << endl;
cout << s.capacity() << endl;
s.reserve(80);
cout << s.size() << endl;
cout << s.capacity() << endl;
s.reserve(50);
cout << s.size() << endl;
cout << s.capacity() << endl;
s.reserve(20);
cout << s.size() << endl;
cout << s.capacity() << endl;
s.reserve(15);
cout << s.size() << endl;
cout << s.capacity() << endl;
s.reserve(10);
cout << s.size() << endl;
cout << s.capacity() << endl;
}
由上图结果可知,当s.reserve增大时 容量不断增大。
但是当 s.reserve减小时,空间容量却不变。
但是当s,reserve缩小到一个临界点时,空间容量却减小了
解释:
通过reserve(size_t newcapacaity)将string底层的空间扩大
假设string底层旧空间的大小为oldcapacity
如果newcapacity>oldcapacity:空间确实会变大,扩容具体做法:
string类内部会按照一定的机制跟进用户所传递的容量去计算新的容量
扩容机制:开辟新空间 拷贝元素 释放旧空间
如果newcapacity<=oldcapacity && newcapacity>=16
reserve会忽略本次操作,即不会扩容缩小
newcapacity<16 && 有效元素个数小于16 :reserve才会将底层空间大小缩小
否则忽略本次操作
注意:string是在vs2019下验证的
为什么时newcapacity小于16时候,reserve才会真正将空间缩小?
在vs使用STL版本中:PJ版本
string对象内部包含一个固定大小的数组----> char buff[16]
原因:绝大多数情况下的字符串中有效个数都是小于16 比如:人名 单词等
直接使用固定大小的数组 不从堆上开辟空间可以让string类的效率更高
当有效元素个数超过16时,string类才需要真正从堆上开辟空间之后一般不会再将空间缩小
resize(size_t newsize,char ch = '\0'):将有效元素个数修改到newsize,如果newsize大于string对象中原有字符的个数多出的字符使用ch填充。
void TestString04()
{
string s("hell0");
s.resize(10, 'A');
s.resize(20, 'B');
s.resize(40, 'C');
s.resize(30);
s.resize(20);
s.resize(17);
s.resize(16);
s.resize(15);
s.resize(15);
}
如果newsize大于string对象中原有字符的个数多出的字符使用ch填充。并且会进行扩容,然后使用ch填充多出来的字符。
注意:如果是将元素个数增多,可能会改变底层容量的大 小,如果是将元素个数减少,底层空间总大小不变。
3.string类对象的访问及遍历操作
迭代器就当成指针来想,怎么使用指针就怎么使用迭代器
类似于:typedef char* iterator 指针可以++ 可以解引用迭代器当然也可以
for循环 或者范围for来遍历string的
迭代器更多的是给算法使用 比如:要将string对象逆置
void TestString11()
{
string s("123456789");
//string遍历
//更推荐使用下表:也就是for循环+下表的方式
for (size_t i = 0; i < s.size(); ++i)
{
cout << s[i];
}
cout << endl;
//其次可以使用范围for
for (auto e : s)
cout << e;
cout << endl;
//也可以使用迭代器遍历,不过对于string,一般情况下不太推荐
string::iterator it = s.begin();
while (it != s.end())
{
cout << *it;
++it;
}
cout << endl;
string::reverse_iterator rit = s.rbegin();
while (rit != s.rend())
{
cout << *rit;
++rit;
}
cout << endl;
}
string在遍历的时候,很少用迭代器,更多是跟算法结合
逆置:reverse
排序:sort
#include<algorithm>
void TestString13()
{
string s("123456789");
reverse(s.begin(), s.end());
cout << s << endl;
sort(s.begin(), s.end());
cout << s << endl;
}
4. string类对象的修改操作
operator+=:在string之后拼接:单个字符 c格式的字符串 string对象
append:在string之后拼接:单个字符 c格式的字符串 string对象
push_back(char ch):往string尾部插入ch
void TestString06() { string s("hello"); s.push_back(' '); //拼接c格式的字符串 s += "world"; //拼接string对象 string ss("!!"); s += ss; s += '$'; } void TestString07() { string s("hello"); s.append(1, ' '); //等价的 s.push_back(' ') || s+='' s.append("world"); //s+="world"; string ss("!!!"); s.append(ss); //s+=ss; string str("123456"); s.append(str, 1, 3); //将str内部234拼接到s中 } void TestString08() { string s("hello"); //在0号位置插入3个 'A' s.insert(0, 3, 'A'); s.insert(0, "bit"); string ss("BBBB"); s.insert(7, ss); s.insert(s.begin(), ss.begin(), ss.end()); }
erase:删除
void TestString09()
{
string s("hello world");
//从0号位置开始删除六个
s.erase(0, 6);
//删除从begin开始+2位置的字符
s.erase(s.begin() + 2);
s.erase(s.begin(), s.end()); //s.clear()
}
借助push_back方法演示string的扩容机制
在vs2019中:string是按照1.5倍的方式进行扩容
在linux中:SGI版本STL的string是按照2倍方式进行扩容
上述代码还存在缺陷:效率很低
原因:扩容是需要---》申请新空间 拷贝元素 释放旧空间 虽然元素不断增多 拷贝成本增大
优化:
void TestString10()
{
string s;
s.reserve(300);
size_t cap = s.capacity();
for (size_t i = 0; i < 300; ++i)
{
//s+='A';
s.push_back('A');
if (cap != s.capacity())
{
cap = s.capacity();
cout << cap << endl;
}
}
}
atoi:int atoi(const char* str ) 转换整形的数字
c_str():返回string对象中存储字符串的那块空间的首地址
void TestString15()
{
string s("12345");
int ret=atoi(s.c_str());
cout << ret << endl;
}
find:
//需求:把字符串中1替换成0
void TestString16()
{
string s("1234523471136");
size_t pos = 0;
while (true)
{
pos=s.find('1', pos = 0); //表示从pos位置开始查找
if (pos == string::npos)
{
break;
}
s[pos] = '0';
pos += 1;
}
cout << s << endl;
}
rfind:
substr:substr(pos=0,n=npos) :从pos的位置 开始,截取n个字符 。如果n没有传递,表示从pos位置开始一直截取到末尾。如果pos和n都没传递,将字符串整个进行截取。
//需求:获取文件的后缀---->文件名中最后一个点之后的内容
void TestString18()
{
string s("1234.txt.cpp");
size_t pos = s.rfind('.')+1;
string posFix = s.substr(pos);
cout << posFix << endl;
}
有多行单词,获取每行单词中最后一个单词的长度,每行中单词和单词之间以空格隔开
void GetLastWordLenght()
{
string s;
while (getline(cin,s)) //注意:cin以空包字符为分割的,空格,tab,回车
//genline(cin,s):获取一整行字符串 字符串中如果包含空格等空白字符也可以接收
{
size_t start = s.rfind(' ') + 1;
string word = s.substr(start);
cout << word.size() << endl;
}
}
例题:
字符串相加:
给定两个字符串形式的非负整数 num1
和num2
,计算它们的和并同样以字符串形式返回。
你不能使用任何內建的用于处理大整数的库(比如 BigInteger
), 也不能直接将输入的字符串转换为整数形式。
示例 1:
输入:num1 = "11", num2 = "123" 输出:"134"
示例 2:
输入:num1 = "456", num2 = "77" 输出:"533"
示例 3:
输入:num1 = "0", num2 = "0" 输出:"0"
class Solution { public: string addStrings(string num1, string num2) { //1.找出位数最多的 int LSize = num1.size(); int RSize = num2.size(); if (RSize > LSize) { num1.swap(num2); swap(LSize, RSize); } //num1的位数 一定大于 num2的位数 //2.设置保存结果的字符串 string ret(LSize+1, '0'); //模拟实现加法 int step = 0; for (int idx1 = LSize - 1, idx2 = RSize - 1; idx1 >= 0; idx1--, idx2--) { //获取左操作数中的每一位 int val = num1[idx1] - '0'; //字符就会变成数字 //如果num2中有数字 if (idx2 >= 0) { val += num2[idx2] - '0'; } val += step; //考虑是否进位 step = 0; if (val >= 10) { step = 1; val -= 10; } ret[idx1 + 1] += val; } if (step == 1) ret[0] += 1; else ret.erase(ret.begin()); return ret; } };