一、string类的相关函数
string实际上也就是一个管理字符的顺序表!
如果我们需要遍历一个字符串,怎么实现?
我们可以通过下标访问操作符 + size实现字符串的遍历!
int main()
{
string s1("hello world");
// 遍历一个字符串
cout << s1 << endl;
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i];
}
return 0;
}
- '/0'不是一个有效字符,是一个标识字符串结束的特殊字符;
- size()统计不计算/0的数量;
for (size_t i = 0; i <= s1.size(); i++)
当i = s1.size()的时候, 此时已经访问到/0,但是vs下的编译器不打印;
还可以通过下标引用操作符进行内容的修改:
int main()
{
string s1("hello world");
// 遍历一个字符串
//cout << s1 << endl;
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i]++;
}
return 0;
}
这里,内置类型和自定义类型的[]实际意义不一样!
string s1("hello world");
char s3[] = "hello world";
s1[1]++;
s3[1]++;
二、迭代器
可以将iterator想象成指针,但是它不一定是指针!
- begin获取一个字符串的迭代器;
- end获取最后一个字符下一个位置的迭代器(/0);
iterator是一个像指针一样的类型!有可能是指针(字符串中是指针),有可能不是指针!
用法和指针几乎一致!
string::iterator it = s1.begin();
while (it != s1.end())
{
(*it)--;
++it;
}
可以通过修改迭代器的值,用法与指针类似;
注意点:范围for
// 范围for
// 通过范围for进行修改
for (auto& ch : s1)
{
ch++;
}
// 通过范围for进行打印
for (auto ch:s1)
{
cout << ch << " ";
}
return 0;
}
- 范围for中的ch是s1对象的每个字符的拷贝,然后操作后会自动迭代;
- 因为ch是s1对象的拷贝,因此我们需要加上引用,否则对ch的修改无法影响s1;
范围for的底层实际上就算迭代器!自动迭代实际上就算++来实现的!
范围for会调用begin和end来实现工作!
因此,一个类不支持迭代器就不会支持范围for(例如栈stack)!
任何的容器都支持迭代器,并且用法是相似的!
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(14);
vector<int>::iterator vit = v1.begin();
while (vit != v1.end())
{
cout << *vit << " " ;
vit++;
}
总结:
- iterator提供了一种统一的方式访问和修改容器的数据;
- 算法(algorithm)可以通过迭代器处理容器中的数据;
上面是采用隐式实例化,根据不同的参数调用不同的函数;在这里构成了函数重载;
如果我们想要逆向遍历一个字符串
- 可以取反向迭代器
string s1("hello world");
string::reverse_iterator rit = s1.rbegin();
while (rit != s1.rend())
{
cout << *(rit);
rit++;
}
}
- 此时,反向迭代器rbegin指向字符串的最后一个字母;
- rend指向字符串的前一个位置;
同时,有了auto可以省略的类型:
string s1("hello world");
//string::reverse_iterator rit = s1.rbegin();
auto rit = s1.rbegin();
分析下面的代码:
void Func(const string& s)
{
string::iterator it = s.begin();
while (it != s.end())
{
(*it)--;
++it;
}
}
这个代码无法编译通过!
这是因为const不能使用普通的迭代器,要使用const迭代器!(相当于权限的放大)
普通迭代器可以读可以写,但是const迭代器只能读(不能修改)!
同理,对于const rbegin来说只能读不能写!
共有上面四种迭代器;
string因为历史的原因,产生的比STL要早一些,因为之前string的长度都是length,为了和其他的SLT一致,引入size,其功能与strlen一样!
三、算法初体验
max_size在不同编译器运行的结果不一样,因此在实际使用上没有什么用处;
capacity是返回当前字符串的存储空间,但是对于不同的编译环境下capacity的数值是不一样的!
相同的代码,在vs下的capacity的值为15,在g++的编译条件下为11!(这是因为两个编译器用的STL的版本不一样!)
我们可以通过下面的代码查看vs下的扩容机制:
每次扩容大概为原来的1.5倍!
同样的代码,我们来查看Linux系统下的扩容机制:
g++下几乎为2倍扩容!
- vs的19扩容机制:每次默认给15(实际上是16,省去后面的/0),然后每次扩容为原来的1.5倍;
- g++的扩容机制:当前字符串长度为多少,即默认扩容数为多少,每次扩容到原来的2倍;
clear的作用:清除字符串的内容,将字符串改为空字符串;
分析下面的代码:
string s1("hello world");
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.clear();
cout << s1.size() << endl;
cout << s1.capacity() << endl;
可以发现vs2022的扩容机制是默认给定的capacity是字符串的长度!
且clear只是将字符串中的有效字符清除,但是容量capacity不发生改变!
同样的,Linux环境下也是将size清为0,capacity不发生改变!
四、练习题:字符串相加
. - 力扣(LeetCode)
两个整形相加,一个整型最大值为42亿多,如果我们需要相加的数字比42亿还多,可以采用字符串相加!
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)
{
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 += ('0'+ret); // 因为是从后往前算,这里是用尾插
Strret.insert(Strret.begin(),'0'+ret); // 采用头插
--end1;
--end2;
}
// 出循环后如果还需要进位
if (carry == 1)
{
//Strret +='1';
Strret.insert(Strret.begin(),'1');
}
//reverse(Strret.begin(),Strret.end());
return Strret;
}
};
但是需要注意的是,头插的效率比尾插的效率低!
tong图像 小部件