9.5 额外的string操作
string提供了非常多的函数供我们使用,其中大部分函数都和C风格字符串,字符数据,以及使用下标代替迭代器有关。
9.5.1 构造string的其他方法
除了之前的初始化方式,string还可以使用字符数组以及使用字符串并指定字符串的范围进行初始化。
注意,字符数组初始化给定计数值,因为在字符数组中,默认是没有结束符的。
对于使用字符串进行初始化,并指定范围。
string str(str1,起始位置,计数值)
计数值默认为str.size()-其实位置,也就是如果计数值没有写,则从起始位置截至str1的尾部。
如果计数值+起始位置超过了str1的size,最多截取到str的结尾处。但是起始位置,不能大于str的size,这会导致未定义的行为。
截取字符串
str.substr(pos,n)从pos位开始,截取n个字符,n默认位str.size()-pos,即使n超过了str.size()-pos,也只截取到str的尾部。这和初始化时,截图字符串是一样的属性。
练习
假定这个vector<char>不包含结束符
9.4.1
vector<char> cvec = {'h','e','l','l','o',',','w','o','r','l','d'};
//得到一个指向类内元素的指针
//因为没有加结束符,所以如果不显式的指定截取的范围,将导致未定义的行为
string str(cvec.data(),cvec.size());
cout<<str<<endl;
9.42
思路:直接使用100个空格初始化字符串,使用一个下标来标记新输入的字符存到第几个位置。
//因为会反复的存入一百次,为了避免内存空间的反复分配,直接使用一个空字符,进行初始化,并指定个数为100
//构造的方式太多了,记错了,是先传入元素个数,再传入初始化的值
//string str1(' ',100); 错误示范
//其实和vec初始化时提供元素个数,以及初始值是一样的
string str1(100,' ');
//定义一个变量,存储当前str1的为空的下标
string::size_type cur_index = 0;
char word;
while (cin>>word) {
str1[cur_index] = word;
++cur_index;
}
//打印时截取部分字符
cout<< str1.substr(0,cur_index)<<endl;
9.5.2 改变string的其他方法
之前说的insert和erase是通用的,除此之外,string类定义了大量的insert和erase,replace,assign的重载函数。
这一时半会是根本记不清的。
我们可以稍微总结一下,这些重载的函数。
如果insert、erase,传入的是位置,不是迭代器,那么返回的是字符串的引用,而不是迭代器。
除此之外,string还定义了append和replace两个函数,append用来在末尾添加字符串,注意push_back()只能添加单个字符。
replace(),用来将字符字串替换为另一个字符字串,两个子串的大小,不要求一样。
关于string的操作,如下表:
实在是有点多,记不住没关系,随时查表。
练习
9.43
可以看到非常的复杂。。。
void replace_old_to_new( string s, const string & oldVal, const string &newVal) {
//两个指针
auto begin = s.begin();
auto end = s.begin();
while(begin!=s.end()&&(s.end()-begin)>=oldVal.size()){
//在begin没有越界的情况下,把++end
++end;
if (*begin==oldVal[0]) {
//判断后续的字符是否相等
bool is_equal = true;
string::size_type target_cur_index = 1;
//防止迭代器和下标越界
while (end!=s.end()&&target_cur_index!=oldVal.size()) {
if (*end!=oldVal[target_cur_index]) {
is_equal = false;
break;
}
++target_cur_index;
++end;
}
//如果成功匹配,target_cur_index必然等于target_str.size()
if (target_cur_index!=oldVal.size()) {
is_equal = false;
}
if (is_equal) {
//erase返回删除元素之后那个元素的迭代器
begin = s.erase(begin,end);
//cout << s << endl;
//insert,如果使用迭代器版本,则传入的必须是字符,所以不能使用迭代器传值
//所以这里需要获取当前的begin的下标
string::size_type cur_index = 0;
auto temp_iter = s.begin();
while (temp_iter!=begin) {
++cur_index;
++temp_iter;
}
//下标版本的insert返回的是s的副本,不是插入的元素的迭代器
s.insert(cur_index,newVal);
//因为之前的迭代器很可能失效了,所以重新获取迭代器
begin = s.begin() + cur_index+newVal.size();
end = begin;
//++end;
}
else {
//target_cur_index = 1;
++begin;
end = begin;
//++end;
}
/*target_cur_index == 1;*/
//cout<<"123"<<endl;
}
else {
++begin;
end = begin;
//++end;
}
}
cout << s << endl;
}
测试用例
string s = " i it's tho tho tho very good ";
//string s = "tho i good";
//string s = "i good tho";
string oldVal = "tho";
string newVal = "thought";
replace_old_to_new(s,oldVal,newVal);
9.44
void replace_old_to_new(string s, const string & oldVal, const string &newVal) {
string::size_type begin = 0,end=0;
while (begin!=s.size()&&(s.size()-begin)>=oldVal.size()) {
end = begin;
++end;
if (s[begin]==oldVal[0]) {
string::size_type target_cur_index = 1;
bool is_equal = true;
while (end!=s.size()&&target_cur_index!=oldVal.size()) {
if (s[end]!=oldVal[target_cur_index]) {
is_equal = false;
break;
}
++end;
++target_cur_index;
}
if (target_cur_index!=oldVal.size()) {
is_equal = false;
}
if (is_equal) {
//返回的是引用,这里容易犯错误,把begin和end都传入
s.replace(begin,oldVal.size(),newVal);
begin = begin + newVal.size();
}
else {
++begin;
//end = begin;
}
}
else {
++begin;
//end = begin;
}
//s.replace(begin,end,newVal);
}
cout << s << endl;
}
3.45
这里的i没用size_type是因为size_type是无符号类型,永远都不会小于0
string get_name(string name,const string& pre,const string &last) {
for (int i = pre.size() - 1; i >=0 ; --i) {
name.insert(name.begin(),pre[i]);
}
name.append(last);
return name;
}
3.46
string get_name(string name,const string& pre,const string &last) {
name.insert(0,pre);
name.insert(name.size(),last);
return name;
}
string的搜索操作
字符串的查找有以下的操作,一个有6个函数,每个函数有4个重载版本。
这些搜索操作都是大小写敏感的
重载的形式可以简要的记为。
传入字符,传入string对象,传入C风格字符串,传入字符数组。,他们都可以传入添加pos参数,表示从字符串的第几个位置开始搜索,但是传入字符数组时,因为字符数组没有结束符,所以需要传入匹配的长度n。
str.find_first_of(args),表示的是在str搜索到第一个在args中存在的字符的位置。
而str.find(args)是搜索整体。
一个常用的字符串,代码设计模式
string::size_type pos=0;
while((pos=str.find_first_of(target_str,pos))!=string::npos){
//todo
++pos;
}
记住,赋值语句返回的是左侧的运算对象
练习
9.47
void find_all_number_v1(const string& str) {
string::size_type pos=0;
string target_str = "0123456789";
//pos = str.find_first_of(target_str, pos);
//cout << pos << endl;
while ((pos=str.find_first_of(target_str,pos))!=string::npos) {
cout<<str[pos]<<endl;
++pos;
}
}
void find_all_number_v2(const string& str) {
string::size_type pos = 0;
//这里考虑到只输出数字,因此ascii码包含的256个元素除了数字外都要算进去
string target_str(246,' ');
string::size_type cur_pos = 0;
for (int i = 0; i < 256;++i) {
if (i<'0'||i>'9') {
target_str[cur_pos] = i;
++cur_pos;
}
}
while ((pos = str.find_first_not_of(target_str, pos)) != string::npos) {
cout << str[pos] << endl;
++pos;
}
}
void find_all_case_v1(const string& str) {
string::size_type pos = 0;
string target_str(52,' ');
string::size_type cur_pos = 0;
for (int i = 0; i < 256;++i) {
if ((i>='a'&&i<='z')||(i>='A'&&i<='Z')) {
target_str[cur_pos] = i;
++cur_pos;
}
}
//pos = str.find_first_of(target_str, pos);
//cout << pos << endl;
while ((pos = str.find_first_of(target_str, pos)) != string::npos) {
cout << str[pos] << endl;
++pos;
}
}
9.48
返回的值为string::npos,是string做能存储的最大长度。
9.49
思路:
这个题目可以简化为在01字符串中,寻找最长的0字符子串。
寻找最长的字符串,所以我们需要一个变量记录最长的字符串子串。
需要字符子串,所以肯定需要两个位置变量记录子串的位置。
在这里我把前面的位置start_pos设置为npos。
把后一个位置pos设置为0.
然后使用循环开始检测字符。
在循环中要把start_pos的值设置为pos的值,再把pos的值+1;
但是我们不能直接把start_pos赋值为pos,因为我们必须考虑第一次find时,staret_pos的初始值为npos。需要判断第一次find,是否存在符合标准的子串。
该循环可以得到在字符串中间的字符子串。
但是输入的str,可能不会进入该循环,也可能pos==npos而跳出循环。
这两种情况,我们的代码都没有考虑。
所以需要针对这两种情况做特殊判断。
1.不会进入循环,不进入循环表示str为空,或者str中没有任何复合标准的输出,所以可以直接输出str
2.pos==npos而跳出循环,这回出现两种情况,(1)start_pos==str.size()-1,(2)start_pos<str.size()-1
对于(1)即表示当pos==npos时,后续没有子串
对于(2) 截取对应子串即可,截取位置为start_pos+1,长度为str.size()-start_pos
void func_1(const string& str) {
//所有的词
string target = "bdfghijklpqt";
string max_str = "";
string::size_type pos = 0;
string::size_type start_pos = string::npos;
int up_and_down_count = 0;
//只有在字符串中出现了两个上或者两个下,这代码才成立
//
while ((pos=str.find_first_of(target,pos))!=string::npos) {
/*string temp_str = "";*/
++up_and_down_count;
//该部分代码用于判断pos和start_pos同时不为npos时,如果截取字符串
if (start_pos != string::npos && (pos - start_pos - 1) > 0) {
string temp_str(str, start_pos + 1, pos - start_pos - 1);
if (temp_str.size() > max_str.size()) {
max_str = temp_str;
}
}
//因为把start_pos设置为了npos
//所以在第一find的时候,需要判断find的位置
if (start_pos == string::npos) {
start_pos = 0;
if (pos - start_pos > 1) {
string temp_str(str, start_pos, pos - start_pos);
if (temp_str.size() > max_str.size()) {
max_str = temp_str;
}
temp_str = "";
}
}
start_pos = pos;
++pos;
}
//如果start_pos==npos那么循环肯定没有进入
if (start_pos==string::npos) {
max_str = str;
}//find匹配到最后,或者到最后没有匹配到
//匹配到最后那就意味着不用比较
//没有匹配到最后意味着至少最后一个数为小矮子
else if (pos==string::npos&&(start_pos!=str.size()-1)) {
string temp_str(str, start_pos + 1, str.size() - start_pos);
if (temp_str.size() > max_str.size()) {
max_str = temp_str;
}
}
cout << max_str << endl;
}
测试用例:
func_1("aaaaa");
func_1("baaaa");
func_1("aaaab");
func_1("baaab");
func_1("baabaabaaabaaab");
func_1("baabaabaaabaaabaaaaa");
func_1("aaaaaaaabaabaabaaabaaabaaaaa");
9.5.4 compare函数
之前的关系运算符,需要同一种容器类型,才可以进行比较,但是string经常和C风格字符串互用,所以C++标准库提供了和C风格字符串比较的函数。
9.5.5 数值转换
C++标准库给我们提供了将数值转化为string类型,以及将string类型转化为数值的函数。
数值转化为string类型就一个函数to_string();
而将字符串转化为数值,则根据对应的数值内置类型, 有多种,如stoi,stod,
在转化时要求第一个非空字符为+,-,数字或者’.’、0x,大白话来说就是,第一个字符一定要复合数值的标准,不管这个数值时,int,float还是16进制。
在转化字符转数字时,会解析到无法解析的位置为止。
stoi("123asb");//得到123
stoi("123.123")//得到123
cout<<std::stoi("ddd123")<<endl;//报错
练习
9.50
vector<string> int_vec= {"123","321","233","+123","-1232"};
int sum = 0;
for (const auto& item:int_vec) {
sum+= std::stoi(item);
}
cout << sum << endl;
9.51
思路就是
按照输入日期的特殊字符,来截取年月日。
因为stoi(str)如果传入的str无法转换为整型,会报错所以捕获异常,如果报错,那么就执行下一种格式。
我只写了一种格式的转换,但是剩下的格式可以依葫芦画瓢写出来。
class My_Date {
public:
My_Date(const string& date_str);
bool is_format1(const string&);
static vector<string> month_format_1;
static vector<string> month_format_2;
std::ostream& print(std::ostream& out) {
out << year << "-" << month << "-" << day;
return out;
}
private:
unsigned year=0;
unsigned month = 0;
unsigned day = 0;
int is_month_format(const string& date) {
int is_month = -1;
int month = 0;
for (const auto&item:month_format_1) {
if (item==date) {
is_month = month;
break;
}
++month;
}
month = 0;
for (const auto&item:month_format_2) {
if (item==date) {
is_month = month;
break;
}
++month;
}
return is_month;
}
};
bool My_Date::is_format1(const string& date_str) {
bool is_right = true;
try
{
string::size_type pos = 0,last_pos=0;
pos = date_str.find(' ', pos);
string month(date_str,0,pos);
int _month= is_month_format(month);
if (_month!=-1) {
_month+=1;
}
else {
throw std::exception("month error");
}
last_pos = pos;
pos = date_str.find(',',pos);
string str_day(date_str,last_pos+1,pos-last_pos-1);
int _day = std::stoi(str_day);
last_pos = pos;
string str_year(date_str,last_pos+1);
int _year = std::stoi(str_year);
this->year = _year;
this->month = _month;
this->day = _day;
}
catch (const std::exception&)
{
is_right = false;
}
return is_right;
}
vector<string> My_Date::month_format_1 = {
"January","February","March","April","May","June","July","August","September"
,"October","November","December"};
vector<string> My_Date::month_format_2 = {
"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"
};
My_Date::My_Date(const string& date_str) {
//适配三种格式
//去除头部和尾部的空格
auto head_space_pos = date_str.find_first_not_of(' ');
auto tail_space_pos = date_str.find_last_not_of(' ');
if (head_space_pos!=tail_space_pos) {
string new_str(date_str, head_space_pos, tail_space_pos - head_space_pos + 1);
if (is_format1(new_str)) {
//不用写什么
}
}
}
测试用例
//My_Date date(" January 1,1900 ");
My_Date date("xx123");
date.print(cout);