目录
上期我们学习了string类的一些基本操作,本期我们将学习string中的一些进阶操作。
通过正向迭代器:begin+ end
begin:获取第一个字符的迭代器?
end:获取最后一个字符下一个位置的迭代器
代码如下:?
void stringTest1()
{
string s1("hello yjd");
string::iterator it1 = s1.begin();
while (it1 != s1.end())
{
cout << *it1 << " ";
it1++;
}
cout << endl;
string::iterator it2 = s1.begin();
while (it2 != s1.end())
{
cout <<++(*it2) << " ";
it2++;
}
}
?结果如下:
我们发现,通过正向迭代器我们可以实现字符串元素的遍历和修改。那么迭代器究竟是什么呢?
?图示如下:
解析:其实我们可以将迭代器类型iterator看成是字符指针类型,begin用来返回字符串中第一个字符的地址,end用来返回字符串中最后一个字符的地址的下一个地址,将返回的字符地址传给了迭代器变量(字符指针),此时迭代器变量自然就可以访问字符数组中的元素。?
通过反向迭代器:rbegin + rend
rbegin:获取最后一个一个字符的迭代器?
rend:获取最后一个字符的前一个位置的迭代器
代码如下:
void stringTest2()
{
string s1("hello yjd");
string::reverse_iterator it1 = s1.rbegin();
while (it1 != s1.rend())
{
cout << *it1 << "";
it1++;
}
}
截图如下:
反向迭代器的使用与正向迭代器是相反的,即逆序访问,但是它们的底层原理还是相同的。???
注意:迭代器除过普通迭代器之外还有常量迭代器,我们在使用时,如果是访问普通字符串对象那么就是用普通迭代器,如果是访问const字符串对象,那么就要使用const迭代器。
范围for: C++11支持更简洁的范围for的新遍历方式
代码如下:?
void stringTest3()
{
string s1("hello yjd");
//遍历数组
for (auto e :s1 )
{
cout << e << " ";
}
cout << endl;
//如果想改变字符数组的值,就应该将将传值改为传引用
for (auto& e : s1)
{
cout << ++e<< " ";
}
}
截图如下:
reserve:申请空间,但是不进行初始化。
resize:为字符串申请空间,并且初始化为字符'\0',也可以用指定的字符进行初始化,如果申请的空间小于原本字符串的长度,则只保留原有字符串的部分元素。
1.代码如下:
string s1("hello yjd");
截图如下:
2.代码如下:
s1.reserve(50);
?截图如下:
我们发现,只是增加了字符串的容量,但是字符串的有效字符的个数是没有变化的。?
3.代码如下:
s1.resize(50);
截图如下:
申请了空间,并且对这些空间进行了初始化,如果没有给定初始值,就默认初始化为'\0'。
4.代码如下
s1.resize(50, 'x');
?申请了空间,并且对这些空间进行了初始化,初始化为了给定的字符。?
?5.代码如下:
s1.resize(5);
截图如下:
我们发现,只要申请的空间小于原有的字符串的有效字符,那么就会让原有字符串的字符个数成为申请的空间的大小。
总结:reserve和resize都用于申请空间都可以改变字符串的容量,reserve申请空间不初始化,所以不改变字符串有效字符的个数,只改变容量capacity,但是resize申请空间时进行了初始化,所以不仅改变了容量capacity,还改变了字符串有效字符的个数。因为字符串是有默认的容量的,如果resize申请的空间小于原有的字符串的长度,那么此时只改变有效字符的个数, 并不改变容量capcity。?
find + npos:从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置,没有给定pos位置就从字符串头开始找。
rfind+npos:从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置,如果没有给定pos位置,就从字符串最后一个元素的位置开始找。
substr:在str中从pos位置开始,截取n个字符,然后将其返回,如果没有指定,默认从当前位置往后截取所有元素
注意:这里的nps是string类中的一个静态变量为无符号正型-1,所以也就是一个42亿九千万的一个很大的数,因为一个字符串不可能这么长,所以如果返回的值是-1,就代表这个字符找不到。且因为npos是string类中的静态变量,所以我们在类外进行访问时必须加上类作用域限定符。
情景1:我们设置了一个文件的名称为"file.txt",如何求文件的后缀名呢??
代码如下:
void stringTest5()
{
string file("file.txt");
size_t pos=file.find('.');
if (pos != string::npos)
{
cout << file.substr(pos)<<endl;
}
}
我们用了两个函数find找到了.的位置,然后从当前位置开始,求出当前位置和当前位置后的所有字符,这些字符组成了文件的后缀。?
截图如下:?
?
情景二.如果一个文件的名称现在为"file.txt.zip",怎样去求这个文件的后缀名称呢?
代码如下:
void stringTest5()
{
string file("file.txt.zip");
size_t pos=file.rfind('.');
if (pos != string::npos)
{
cout << file.substr(pos)<<endl;
}
}
不管一个文件名有多少疑似后缀的后缀,最后一个后缀一定是文件的后缀,所以我们可以从字符串的后面开始找到第一个.,这个.以及这个点之后的字符就组成了这个文件的文件后缀。
截图如下:
insert:插入操作
代码如下:
void stringTest6()
{
string s1 = "hello yjd";
//头插
s1.insert(s1.begin(), 'h');
cout << s1 << endl;
s1.insert(0, "hello");
cout << s1 << endl;
//在头部位置插入两个y
s1.insert(0, 2, 'y');
cout << s1 << endl;
//中间某个位置插入
string s2 = "hello yjd";
s2.insert(2, "nihao");
cout << s2 << endl;
//尾部插入
string s3= "hello yjd";
s3 += 'y';
cout << s3 << endl;
s3 += "hahahaha";
cout << s3 << endl;
}
截图如下:
?注意:我们不支持头插和中间插入字符或者字符串,因为string类本质上就是一个数组,如果我们在头部和中间插入,就会移动整个数组,复杂度太高,消耗太大,所以一般情况下只建议使用尾插。?
erase:删除操作
代码如下:
void stringTest7()
{
string s1("hello yjd");
//头部删除
s1.erase(0, 2);
cout << s1 << endl;
//中间某一位置删除
s1.erase(2, 1);
cout << s1 << endl;
//尾部删除
s1.erase(s1.size() - 1, 1);
cout << s1 << endl;
//没有给定删除的个数,默认全部删除
s1.erase(0);
cout << s1 << endl;
}
截图如下:
注意:我们不建议头删和中间删除,因为头删和中间删除我们同样会移动整个数组,复杂度太高了,所以我们一般只建议尾插。
好了,到了这里string类的所有基本操作和进阶操作我们已经全部学习完成了,下期我们将开始进行string类的模拟实现。
本期内容到此结束^_^