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);