总言
string类的各种常见接口使用介绍。
文章目录
- 总言
- 1、整体介绍
- 2、常用各种接口介绍
- 2.1、string类对象的常见构造:string::string
- 2.2、析构、赋值运算符重载
- 2.3、string中元素访问及遍历相关
- 2.4、迭代器
- 2.5、元素插入提取、修改搜索相关
- 2.6、与扩容相关
- 2.7、string类非成员函数
- 2.8、与数值转换相关的函数
- 2.9、 相关习题
- Fin、共勉。
1、整体介绍
可以看到这些函数接口在<string>
这个头文件中。
基础介绍:
1、string
是表示字符串的字符串类
2、该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
3、 string
在底层实际是:basic_string
模板类的别名:
typedef basic_string<char, char_traits, allocator> string;
4、 不能操作多字节或者变长字符的序列。
5、在使用string
类时,必须包含#include
头文件以及using namespace std;
2、常用各种接口介绍
2.1、string类对象的常见构造:string::string
2.1.1、总览
string类对象的常见构造
函数名称 | 功能说明 |
---|---|
string() (重点) | 构造空的string类对象,即空字符串 |
string(const char* s) (重点) | 用C-string来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string&s) (重点) | 拷贝构造函数 |
相关链接:std::string::string
2.1.2、构造空的string类
string();
演示代码如下:
#include<string>
int main(void)
{
string s1;//无参的
string s2("hello!");//带参的
return 0;
}
演示结果如下:
此处说明string兼容C ,因此虽然是无参构造,但其结尾实际上是有"\0
" 的。需要注意,这里的\0
并不算在有效字符内(可看到其size
值为0)。
2.1.3、用C-string来构造string类
string (const char* s);
演示代码如下:
#include<string>
int main(void)
{
string s1;//无参的
string s2("hello!");//带参的
return 0;
}
演示结果如下:
关于此处的另一种写法:涉及隐式类型转换。
演示代码如下:
void test_string3()
{
string s1("hello string!");
string s2 = "hello string!";//构造+拷贝构造->优化->直接构造
cout << s1 << endl;
cout << s2 << endl;
}
int main(void)
{
test_string3();
return 0;
}
演示结果如下:
2.1.4、用n个字符c构造string类
string (size_t n, char c);
演示代码如下:
void test_string3()
{
string s1(5, 'c');
cout << s1 << endl;
}
int main(void)
{
test_string3();
return 0;
}
演示结果如下:
2.1.5、string类拷贝构造
拷贝构造,此处实际上是涉及深拷贝,关于深拷贝的内容将在后续模拟实现strng类中讲解。
void test_string2()
{
string s1("hello string!");
string s2(s1);
cout << s1 << endl;
cout << s2 << endl;
}
int main(void)
{
test_string2();
return 0;
}
演示结果如下:
2.1.6、部分拷贝构造函数
注意此处有个size_t len = npos
,为默认的缺省参数。相关链接
演示代码如下:
void test_string2()
{
string s1("hello string!");
string s2(s1);
string s3(s1, 6);
string s4(s1, 6, 3);
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
}
int main(void)
{
test_string2();
return 0;
}
演示结果如下:
2.2、析构、赋值运算符重载
2.2.1、析构:string::~string
2.2.2、赋值运算符重载:string::operator=
演示代码如下:
void test_string3()
{
string s1("hello string!");
//这是拷贝构造的写法:发生隐式类型转换
string s2 = "hello string!";//构造+拷贝构造->优化->直接构造
string s3;
//这是赋值运算符重载:
s3 = s1;
cout << s3 << endl;
s3 = "example";
cout << s3 << endl;
s3 = "c";
cout << s3 << endl;
}
int main(void)
{
test_string3();
return 0;
}
演示结果如下:
2.3、string中元素访问及遍历相关
本小节涉及的string类成员函数:
函数名称 | 功能说明 |
---|---|
string::length (重点) | 返回字符串有效字符长度 |
string::size (重点) | 返回字符串有效字符长度 |
string::operator[] (重点) | 获取字符串的字符 |
string::at | 获取字符串中的字符 |
string::back | 访问最后一个字符 |
string::front | 访问第一个字符 |
2.3.1、string::operator[] ,返回pos位置的字符,const string类对象调用
1)、概述
char& operator[] (size_t pos);
const char& operator[] (size_t pos) const;
细节解释:
1、此处有两个重载,一个是char&
、一个是const char&
,分别对应两种不同的类。
2、该运算符重载作用:①访问某个位置的字串;②和其它函数结合,能达到遍历字符串的目的。
3、使用引用返回的原因:①通过[ ]
访问到对应字符,要能支持可读可写,因此需要引用返回而非传值返回(后者只是临时拷贝)。②引用返回可减少拷贝(次要原因)。
4、访问时需要注意越界问题。
2)、运用举例
演示一:对单字符
void test_string3()
{
string s1("hello world!");
cout << s1[2] << endl;//访问单字符
s1[2] = 'y';//修改字符
cout << s1 << endl;
}
int main(void)
{
test_string3();
return 0;
}
演示结果如下:
演示二:对字符串遍历
void test_string3()
{
string s1("hello world!");
cout << s1 << endl;
//遍历s1,每个字符+1:
for (size_t i = 0; i < s1.size(); ++i)
{
s1[i]++ ;
}
cout << s1 << endl;
const string s2("hello world!");
//const不能被修改,但仍然能遍历:
for (size_t i = 0; i < s2.size(); ++ i)
{
cout << s2[i] << " ";
}
cout << endl;
}
int main(void)
{
test_string3();
return 0;
}
演示结果如下:
2.3.2、string::size、string::length
size_t length() const;
size_t size() const;
注意事项:
1、此处使用size还是length结果都一样,只是一个发展先后问题。一般而言,为与其它SQL相同,我们推荐使用size。
演示代码如下:
void test_string3()
{
string s1("hello world!");
cout << s1 << endl;
//遍历s1,每个字符+1:
for (size_t i = 0; i < s1.length(); ++i)
{
s1[i]++ ;
}
cout << s1 << endl;
const string s2("hello world!");
//const不能被修改,但仍然能遍历:
for (size_t i = 0; i < s2.size(); ++ i)
{
cout << s2[i] << " ";
}
cout << endl;
}
int main(void)
{
test_string3();
return 0;
}
演示结果如下:
2.3.3、string::at、string::back、string::front
注意事项:string::at
类似于operator[]
,但其是成员函数,失败后抛异常。
事实上以下两个函数可以用operator[]
替代。
2.3.4、习题练习: 仅仅反转字母
#include<ctype.h>
class Solution {
public:
string reverseOnlyLetters(string s) {
size_t left=0,right=s.size()-1;
while(left<right)
{
while((left<right) && !isalpha(s[left]))
left++;
while((left<right) && !isalpha(s[right]))
right++;
swap(s[left],s[right]);
left++;
right--;
}
return s;
}
};
2.4、迭代器
在C++中,迭代器(Iterator)是一种用于遍历容器(如数组、向量、列表、集合等)中元素的对象。迭代器提供了一种通用的方式来访问和操作容器中的元素,而不需要关心容器底层的具体实现细节。
2.4.1、正向迭代器介绍iterator:string::begin、string::end
1)、是什么
迭代器,单词iterator
,其包含在对应的类中,用法和指针类似,其有可能是指针,也有可能不是指针。
string::iterator 变量名
vector<int>::iterator 变量名
list<int>::iterator 变量名
1、使用迭代器时要注意对应的类,这里举例了string、vector、list,后两者属于模板故使用时加上了类型,但它们的基本格式一致。
2)、正向迭代器介绍与使用
相关链接:begin、end
iterator begin();
iterator end();
//可以看到iterator类型:a random access iterator to char (convertible to const_iterator)
相关示意图:注意string::end
指向的是最后一个字符的下一个位置,即指向\0
。整体而言达成一个左闭右开的区间:[begin,end)
。
使用举例如下:
void test_string4()
{
string s("hello string!");
string::iterator it = s.begin();//定义一个迭代器命名为it变量,让其指向string类中begin位置
while (it != s.end())//遍历,并打印string类中元素
{
cout << *it << " ";
++it;
}
cout << endl;
}
int main(void)
{
test_string4();
return 0;
}
演示结果如下:可看到使用迭代器,我们将string类中的数据都一一遍历并打印。
3)、为什么要学习迭代器?
1、对于SQL,迭代器iterator
是所有容器通用的访问方式,使用法类似,由于这种高度统一性,学会并理解一个,我们就能快速上手其它sql的迭代器。
2、从使用角度来讲,迭代器也是一种封装结构,我们使用它时并未了解其底层实现。但实际上不同SQL中,迭代器实现可能不同。
3、一般情况下,string
不喜欢使用iterator
,因为operate[]
更好用。同理,vector
也不喜欢使用iterator
,因为operate[]
更好用。这两者具有的特性都是物理地址空间连续,而对于list、map、set
等一些物理地址空间不连续的sql,要访问相应数据,则需要使用迭代器。
以下为迭代器在list
中的用法,此处list
是带头双向链表:
#include<list>
void test_string4()
{
list<int> lt(10, 1);
list <int>::iterator lit = lt.begin();
while (lit != lt.end())
{
cout << *lit << " ";
++lit;
}
cout << endl;
}
以下为演示结果:
4、一个问题说明:lit != lt.end()
、it != s.end()
,可以看到这里我们在判断结束条件时,使用的是!=
,而非<
。那么可不可以使用小于号?
回答:对于string、vector这样物理地址空间连续的情况,可以使用<
,而对于其它物理地址空间不连续的情况,使用<
不能达到要求。
2.4.2、反向迭代器reverse_iterator
reverse_iterator rbegin();
reverse_iterator rend();
//可以看到二者类型为reverse_iterator:reverse_iterator<iterator>
示意图:可以看到rbegin、rend
和begin、end
大同小异,其使用方法类同。
演示代码如下:
void test_string4()
{
string s1("hello string!");
string::reverse_iterator rit = s1.rbegin();
while (rit != s1.rend())
{
cout << *rit << " ";
++rit;//需要注意这里仍旧是++;
}
cout << endl;
}
int main(void)
{
test_string4();
return 0;
}
演示结果如下:我们的字符串是倒置打印的。
2.4.3、const迭代器:const_iterator、const_reverse_iterator
1)、基本介绍:
在上述两者中,我们看到string除了实现了普通迭代器,还对被const修饰的对象给予了其相应的const迭代器。
用法与之前,区别只在于不能修改元素(可读不可写)。
const正向:
const_iterator begin() const;
const_iterator end() const;
const反向:
const_reverse_iterator rbegin() const;
const_reverse_iterator rend() const;
实际使用时,由于其类型名称太长,我们可以用auto
自动推演类型。
演示代码如下:
void test_string4(const string& s)
{
string::const_iterator it = s.begin();//正向
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
string::const_reverse_iterator rit = s.rbegin();//反向
while (rit != s.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
}
int main(void)
{
string s("hello string!");
test_string4(s);
return 0;
}
演示结果如下:
2.4.4、范围for与迭代器的关系
实际上,范围for的底层就是迭代器,我们写了范围for,编译器会对它进行替换处理。
void test()
{
string s("hello");
string::iterator it = s.begin();
while (it != s.end())
{
(*it)++;
cout << *it << " ";
++it;
}
cout << endl;
for (auto& ch : s)
{
ch++;
cout << ch << " ";
}
cout << endl;
cout << s << endl;
}
void test()
{
list<int> lt(10, 1);
list<int>::iterator lit = lt.begin();
while (lit != lt.end())
{
cout << *lit << " ";
++lit;
}
cout << endl;
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
2.5、元素插入提取、修改搜索相关
2.5.1、string::push_back、string::append
1)、string::push_back
基本讲解
说明: 我们可以使用该函数对string进行单字符尾插。相关链接:string::push_back
void push_back (char c);
演示代码如下:
void test_string1()
{
string s1("hello string!");
s1.push_back(' ');
s1.push_back('v');
s1.push_back('i');
s1.push_back('m');
cout << s1 << endl;
}
int main(void)
{
test_string1();
return 0;
}
演示结果如下:
2)、string::append
基本讲解
说明: push_back只能解决单字符,若要尾插字符串,则可使用该函数。相关链接:string::append
演示一:对C字符串
string& append (const char* s);
代码如下:
void test_string_1()
{
string str("hello ");
str.append("world!");
cout << str << endl;
}
演示结果如下:
演示二:对迭代器
template <class InputIterator>
string& append (InputIterator first, InputIterator last);
append函数还可以使用迭代器进行追加,演示效果如下:
void test_string_2()
{
string s1("hello string!");
string s2("welcome to the world!");
cout << s1 << "\n" << s2 << "\n" << endl;
s1.append(s2.begin(), s2.end());
cout << s1 << endl;
s1.append(++s2.begin(), --s2.end());
cout << s1 << endl;
}
其意义所在:我们只需要改变begin、end,就可以追加任意区间段。(注意,这里是英文字符,若是中文字符,由于中文一般占两个字节,直接++、–效果不同,但逻辑含义一致)
关于这里的迭代器更多的意义需要学到后面才能认知到。
2.5.2、string::operator+=
1)、string::operator+=
基本讲解
比起string::push_back
、string::append
,我们更常用string::operator+=
(前两者是函数,后者是运算符重载),其上述对单字符、对字符串,我们都可以用该函数做到。相关链接:string::operator+=
演示代码如下:
void test_string_4()
{
string s1("hello string!");
s1 += " welcome";//对字符串
string s2(" the world");
s1 += s2;//对string类
s1 += '!';//对字符
cout << s1 << endl;
}
演示结果如下:
当然,上述代码也能用下述的+n
、-n
来实现:
s1.append(s2.begin()+3, s2.end()-3);
cout << s1 << endl;
2.5.3、习题: 字符串相加
解析:此题的实际背景是实现大整数运算。因为int、unsigned int等都有其范围,对于一些庞大的数值,可以将其转化为字符串形式,但这样一来对其的运算比如加减乘除、按位域异或等需要重新思考。
2.5.3.1、头插写法
在insert中不使用迭代器的情况
class Solution {
public:
string addStrings(string num1, string num2) {
int end1=num1.size()-1;
int end2=num2.size()-1;
string num;
int next =0;
while(end1>=0 || end2>=0)
{
//防止一个结束,另一个未结束:
int val1=end1>=0?num1[end1]-'0':0;
int val2=end2>=0?num2[end2]-'0':0;
//当前位数相加结果:
int val=val1+val2+next;
next=val>9?1:0;
//在新的string类中插入数值:
//头插:
num.insert(0,1,'0'+(val%10));
--end1;
--end2;
}
//处理next进位
if(next)
num.insert(0,1,'1');
return num;
}
};
在insert中使用迭代器的情况
class Solution {
public:
string addStrings(string num1, string num2) {
int end1=num1.size()-1;
int end2=num2.size()-1;
string num;
int next =0;
while(end1>=0 || end2>=0)
{
//防止一个结束,另一个未结束:
int val1=end1>=0?num1[end1]-'0':0;
int val2=end2>=0?num2[end2]-'0':0;
//当前位数相加结果:
int val=val1+val2+next;
next=val>9?1:0;
//在新的string类中插入数值:
//头插:
num.insert(num.begin(),'0'+(val%10));
--end1;
--end2;
}
//处理next进位
if(next)
num.insert(num.begin(),'1');
return num;
}
};
2.5.3.2、尾插写法
由于头插效率极低,因此此处我们一般建议尾插处理:
class Solution {
public:
string addStrings(string num1, string num2) {
int end1=num1.size()-1;
int end2=num2.size()-1;
string num;
int next =0;
while(end1>=0 || end2>=0)
{
//防止一个结束,另一个未结束:
int val1=end1>=0?num1[end1]-'0':0;
int val2=end2>=0?num2[end2]-'0':0;
//当前位数相加结果:
int val=val1+val2+next;
next=val>9?1:0;
//在新的string类中插入数值:
//尾插:
num+=('0'+(val%10));
--end1;
--end2;
}
//处理next进位
if(next)
// num.insert(num.begin(),'1');
num+='1';
//字符串逆置:使用了算法库
reverse(num.begin(),num.end());
return num;
}
};
2.5.3.3、相关涉及函数:string::insert、std::reverse
insert将在后续介绍:
reverse该函数在头文件中:
template <class BidirectionalIterator>
void reverse (BidirectionalIterator first, BidirectionalIterator last);
2.5.4、string::insert、string::assign、string::erase
2.5.4.1、插入字符串:string::insert
1)、string::insert
基本讲解
相关链接:string::insert
2)、使用演示
习题演示一:要求在下述字符串中空白位置插入20%
void test_string1()
{
string str("wo lai le");
//要求:将上述字符串空白位置插入20%
for (int i = 0; i < str.size();++i)
{
if (str[i] == ' ')
str.insert(i, "20%");
}
cout << str << endl;
}
思考:这样写法有没有什么问题?
回答:insert头插,挪动了数据位置,而i
仍旧指向pos
位置,因此++i
后再次符合条件时仍旧遇到原先的' '
(空格),会导致在此处死循环无限插入。
解决方案一:
void test_string1()
{
string str("wo lai le");
for (int i = 0; i < str.size();)
{
if (str[i] == ' ')
{
str.insert(i, "20%");
i += 4;//要跳过已经访问到的空格
}
else
{
++i;//将++i单独放置
}
}
cout << str << endl;
}
解决方案二:
void test_string1()
{
string str("wo lai le");
for (int i = 0; i < str.size();i++)
{
if (str[i] == ' ')
{
str.insert(i, "20%");
i += 3;
}
}
cout << str << endl;
}
2.5.4.2、从字符串中删除字符:string::erase
1)、string::erase
基本讲解
习题演示二:要求在下述字符串中空白位置插入20%,在此基础上删除空白字符或则替换空白字符呢?
我们可以使用下列函数:erase
string& erase (size_t pos = 0, size_t len = npos);
erase这里给了三个函数重载,注意第一个函数的缺省参数。当我们传入的实参len<size
、len>=size
时,消除的字符串长度不同。
上述问题演示如下:
void test_string1()
{
string str("wo lai le");
for (size_t i = 0; i < str.size(); ++i)
{
if (str[i] == ' ')
{
str.insert(i, "20%");
i += 3;
}
}
cout << str << endl;
for (size_t i = 0; i < str.size(); ++i)
{
if (str[i] == ' ')
{
str.erase(i, 1);
}
}
cout << str << endl;
}
2)、方法改进:以空间换时间
上述这种方法其时间复杂度不太行,此处提供一种以空间换时间的方法:
void test_string1()
{
string str("wo lai le");
string newstr;
for (size_t i = 0; i < str.size(); i++)
{
if (str[i] != ' ')
newstr += str[i];
else
{
newstr += "20%";
}
}
cout << newstr << endl;
}
事实上,上述此题解法中,也可以用replace实现。
2.5.5、返回C字符串:string::c_str
1)、基本讲解
将string类转换为对应的C字符串,一般常用于C\C++多文件中。以下为使用说明。(实际应用举例:网络基础套接字那部分常涉及该函数。)
2)、使用说明
2.5.6、string::find、string::refind、string::substr
1)、基本讲解:
string::find
:正序,在类中找首次出现的对应内容(字符、字符串、子类)
注意点:
1、注意这些参数的缺省值各自的含义。比如,size_t pos = 0
,表示若不给值,则从首位址开始找。
2、注意其函数返回值。如下,若找到,则返回对应位置下标,找不到,则返回string::npos
。因此,我们使用时,谨慎起见可加入条件判断。
The position of the first character of the first match.
If no matches were found, the function returns string::npos.
string::refind
:查找字符串中最后一次出现的内容(find的逆序查找)
说明:rfind用法和find大同小异,size_t pos = npos
告诉我们默认从末尾元素找起,找不到仍旧返回string::npos
。
string::substr
:获取子串
string substr (size_t pos = 0, size_t len = npos) const;
说明:从pos位置(默认从0开始)处开始获取长度为len的子类,若不给确切n值,则len取到字符尾部。
2)、使用演示:
场景要求:给定一个文件名,打印其后缀
void test_string1()
{
//要求:取得该文件名称的后缀
string filename("test.cpp");
//步骤:
size_t pos = filename.find('.');
if (pos != string::npos)
{
//若给定第二参数,需要注意减法含义
string suff1 = filename.substr(pos, filename.size() - pos);
cout << suff1 << endl;
//若不给第二参数,需要注意默认npos的含义
string suff2 = filename.substr(pos);
cout << suff2 << endl;
}
}
当前情景下我们是通过正序遍历去找'.'
,实际上当文件有多个后缀名时,比如test.cpp.tar.zip
。此时我们需要的是该文件的真实后置,即.zip
。那么当前写法就会出问题,对此,提出了rfind。
void test_string1()
{
//要求:取得该文件名称的后缀
string filename("test.cpp.tar.zip");
//步骤:
size_t pos = filename.rfind('.');
if (pos != string::npos)
{
//若给定第二参数,需要注意减法含义
string suff1 = filename.substr(pos, filename.size() - pos);
cout << suff1 << endl;
//若不给第二参数,需要注意默认npos的含义
string suff2 = filename.substr(pos);
cout << suff2 << endl;
}
}
3)、实际场景演示:取网址分割域名(网络协议)
问题说明:给你一串网址,要求分割其每部分(协议、域名、路径)
string url1 = "https://cplusplus.com/reference/string/string/rfind/";
string url2 = "https://www.ncbi.nlm.nih.gov/nuccore/NM_005368.3";
string url3 = "ftp://ftp.cs.umd.edu/pub/skipLists/skiplists.pdf";
思路如下:
特点1:://
,协议与域名的区分界限
特点2:://
后,首个/
前的这一串,即为域名。
写法如下:
void DearUrl(const string& url)
{
//取协议
size_t pos1 = url.find("://");//从头开始找.//分隔符
if (pos1 == string::npos)//检查find所取值是否存在
{
cout << "非法url" << endl;
return;
}
//取得https该段字符串,需要注意substr的参数输入含义
string protocol = url.substr(0, pos1);
cout << protocol << endl;
//取域名
size_t pos2 = url.find('/', pos1 + 3);//要将://跳过
//需要注意此处参数的含义,第二参数中pos2-(pos1+3)
string domain = url.substr(pos1 + 3, pos2 - pos1 - 3);
cout << domain << endl;
//取资源位置
string uri = url.substr(pos2 + 1);
cout << uri << endl;
cout << endl;
}
void test_string3()
{
string url1 = "https://cplusplus.com/reference/string/string/rfind/";
string url2 = "https://www.ncbi.nlm.nih.gov/nuccore/NM_005368.3";
string url3 = "ftp://ftp.cs.umd.edu/pub/skipLists/skiplists.pdf";
DearUrl(url1);
DearUrl(url2);
DearUrl(url3);
}
2.6、与扩容相关
总述:
2.6.1、string::max_size、string::capacity
1)、基本讲解
演示代码如下:
void test_string2()
{
string s1("hello string!");
cout << s1.max_size() << endl;//相同机器下固定值,实际中一般不太用到它,没有太大的意义。
cout << s1.capacity() << endl;
}
演示结果如下:
2)、扩容机制演示:
演示代码如下:
void test_string2()
{
string s = "abcde";
size_t sz = s.capacity();
cout << "capacity now: " << sz << '\n';
cout << "making s grow:\n";
for (int i = 0; i < 100; ++i)//插入100个数据
{
s.push_back('c');
if (sz != s.capacity())
{//当新旧容量不一致时,说明该对象进行了扩容
sz = s.capacity();//那么我们更新一下sz记录的数据
cout << "capacity changed: " << sz << '\n';//并将其显示出来
}
}
}
int main(void)
{
test_string2();
return 0;
}
演示结果如下:
2.6.2、重新为stirng类申请容量:string::reserve
1)、基本讲解
说明:为了避免频繁扩容(注意与reverse区别),在知道预计的容量空间时可以使用该函数。
演示代码如下:
void test_string2()
{
string s = "abcde";
s.reserve(1000);//
size_t sz = s.capacity();
cout << "capacity now: " << sz << '\n';
cout << "making s grow:\n";
for (int i = 0; i < 100; ++i)//插入100个数据
{
s.push_back('c');
if (sz != s.capacity())
{//当新旧容量不一致时,说明该对象进行了扩容
sz = s.capacity();//那么我们更新一下sz记录的数据
cout << "capacity changed: " << sz << '\n';//并将其显示出来
}
}
}
演示结果如下:
2.6.3、string::resize
1)、基本讲解
演示代码如下:
若给两个参数其会开空间并初始化
void test_string2()
{
string s = "abcde";
//s.reserve(1000);
//s.resize(1000);
s.resize(1000,'h');
size_t sz = s.capacity();
cout << "capacity now: " << sz << '\n';
cout << "making s grow:\n";
for (int i = 0; i < 100; ++i)//插入100个数据
{
s.push_back('c');
if (sz != s.capacity())
{//当新旧容量不一致时,说明该对象进行了扩容
sz = s.capacity();//那么我们更新一下sz记录的数据
cout << "capacity changed: " << sz << '\n';//并将其显示出来
}
}
}
演示结果如下:
2.7、string类非成员函数
总览:
2.7.1、流插入流提取(输入输出运算符重载)
1)、概述与运用
string类中也对流插入和流提取运算符进行了重载。
相关链接:流提取、流插入
ostream& operator<< (ostream& os, const string& str);
istream& operator>> (istream& is, string& str);
使用举例:
#include<string>
void test_string1()
{
string s1;
string s2("hello!");
cin >> s1;
cout << s1 << endl;
cout << s2 << endl;
}
int main(void)
{
test_string1();
return 0;
}
2.7.2、getline (以例题形式介绍:字符串最后一个单词的长度)
1)、问题引入:字符串最后一个单词的长度
题源
总体思路:反向查询到第一次出现空格的位置,往后至\0即得最后一个字单词。
错误演示:
#include <iostream>
#include<string>
using namespace std;
int main(void)
{
string str;
cin >> str;//使用cin导致错误
size_t pos=str.rfind(' ');
if(pos!=string::npos)
{ //size是最后一个字符的下一个位置:起点为1,pos是下标位置,起点为0
cout<< str.size()-pos-1;//size-(pos+1)
}
else//需要注意只有单个单词的情况,该场景下rfind取不到空格。
{
cout<< str.size();
}
}
细节说明:需要注意cin、scanf等使用情况。二者在缓冲区取到的数据是以空格或换行符区分的。
cin>>str;
此处出错的原因是:我们输入的英文句子里本身就有空格,cin会把它们当成多次值输入时分割,只取空格或换行前的数据记录在一个变量中,剩下的(输入缓冲区里)被当作下一次变量值。
因此需要使用getline。
正确演示:getline
#include <iostream>
#include<string>
using namespace std;
int main(void)
{
string str;
getline(cin,str);
size_t pos=str.rfind(' ');
if(pos!=string::npos)
{
cout<< str.size()-pos-1;
}
else
{
cout<< str.size();
}
}
2.8、与数值转换相关的函数
2.8.1、总览
2.9、 相关习题
2.9.1、字符串中的第一个唯一字符
class Solution {
public:
int firstUniqChar(string s) {
int CountArr[26]={0};//定义拥有26个元素的数组,用于统计string类中s中字母出现次数。
for(auto ch:s)//通过范围for来遍历
{
CountArr[ch-'a']++;
}
for(int i=0;i<s.size();i++)//遍历string类,依次通过映射关系来判断其中字母是否唯一
{
if(CountArr[s[i]-'a']==1)
return i;
}
return -1;
}
};
2.9.2、验证回文串
class Solution {
public:
bool isPalindrome(string s) {
//范围for:大写转小写
for(auto& ch :s)
{
if(ch>='A' && ch<='Z')
ch+=32;
}
int begin=0;int end=s.size()-1;
while(begin<end)
{
while(begin<end && !isalnum(s[begin]))
++begin;
while(begin<end && !isalnum(s[end]))
--end;
if(s[begin]!=s[end])
return false;
else
{
begin++;
end--;
}
}
return true;
}
};