字符串算法(string_algorithm)
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
作者 | stingliang
來源 | urlify.cn/veaARn
format
作用
格式化輸出對象,可以不改變流輸出狀態(tài)實(shí)現(xiàn)類似于printf()的輸出
頭文件
#include <boost/format.hpp>
using namespace boost;
簡單的例子
//第一種用法
cout << format("%s:%d+%d=%d\n") %"sum" %1 %2 %(1+2);
//第二種用法
format fmt("(%1% + %2%) * %2% = %3%\n");
fmt %2 %5;
fmt %((2+5)*5);
cout << fmt.str();
運(yùn)行結(jié)果:
sum:1+2=3
(2 + 5) * 5 = 35
說明:
第一種用法使用了和printf類似的語法結(jié)構(gòu),不必贅述
第二種用法,先實(shí)例化format對象確定輸出格式,使用%N%指示參數(shù)位置
format對象操作
//返回格式化后的字符串
formatobj.str();
//返回已格式化的字符串長度
formatobj.size();
//清空格式和內(nèi)容
formatobj.parse();
//只清除格式
formatobj.clear();
格式化規(guī)則
%05d:輸出寬度為5的整數(shù),不足位用0填充
%-8.3f:輸出左對齊,寬度總為8,小數(shù)位為3的浮點(diǎn)數(shù)
% 10s:輸出10位的字符串,不足用空格填充
05X:輸出寬度為5的大寫16進(jìn)制整數(shù),不足填充0
%|spec|:將格式化選項(xiàng)包含進(jìn)兩個(gè)豎線之間,更好的區(qū)分格式化選項(xiàng)和普通字符
%N%:標(biāo)記弟N個(gè)參數(shù),相當(dāng)于占位符,不帶任何其他的格式化選項(xiàng)
format fmt1("%05d\t%-8.3f\t% 10s\t%05X\n");
cout << fmt1 %62 %2.236 %"123456789" %48;
format fmt2("%|05d|\t%|-8.3f|\t%| 10s|\t%|05X|\n");
cout << fmt2 %62 %2.236 %"123456789" %48;
執(zhí)行結(jié)果:
00062 2.236 123456789 00030
00062 2.236 123456789 00030
lexical_cast
功能
對字符串進(jìn)行“字面值”的轉(zhuǎn)換,對字符串與整數(shù)/浮點(diǎn)數(shù)之間進(jìn)行轉(zhuǎn)換
需要包含的頭文件
#inlude <boost/lexical_cast.hpp>
using namespace boost;
聲明
//標(biāo)準(zhǔn)形式,轉(zhuǎn)換數(shù)字和字符串
template <typename Target,typename Source>
inline Target lexical_cast(const Source &arg);
//轉(zhuǎn)換C字符串
template <typename Target>
inline Target lexical_cast(const char * chars,std::size_t count)
使用
在模板參數(shù)里指定轉(zhuǎn)換的目標(biāo)類型即可
//string -> int
int x = lexical_cast<int> ("100");
// string -> float
float pai = lexical_cast<float> ("3.14159e5");
//int -> string
string str = lexical_cast<string> (456);
//float -> string
string str = lexical_cast<string> (0.618);
//hex -> string
string str = lexical_cast<string> (0x10);
【注意事項(xiàng)】
該模板智能轉(zhuǎn)換字面值,如果出現(xiàn)不合理的轉(zhuǎn)換,例如“hello”轉(zhuǎn)int類型,則會報(bào)錯(正常人應(yīng)該不會這么干)
錯誤處理
當(dāng)lexical_cast無法執(zhí)行轉(zhuǎn)換操作時(shí)會拋出異常bad_lexical_cast,它是std::bad_cast的派生類
傳統(tǒng)保護(hù)辦法
在使用lexical_cast時(shí)應(yīng)該使用try_catch來保護(hù)代碼
try
{
cout <<lexical_cast<int>("0x100");
}
catch(bad_lexical_cast& e)
{
cout << "error: \n" << e.what() << endl;
}
//運(yùn)行結(jié)果
error:
bad lexical cast:source type value could not be interpreted as target
已有庫的保護(hù)辦法
需要使用命名空間:boost::conversion
函數(shù):
boost::conversion::try_lexical_cast(typeRaw,typeTarget);
返回值為bool表示是否轉(zhuǎn)換成功
【技巧:驗(yàn)證數(shù)字字符串的合法性(用于驗(yàn)證用戶輸入的有效性)】
實(shí)現(xiàn)一個(gè)模板類
template<typename T>
bool num_valid(const char* str)
{
T tmp;
return conversion::try_lexical_convert(str,tmp) //嘗試轉(zhuǎn)換數(shù)字
}
//用法
assert(num_valid<double>("3.14"));
assert(!num_valid<int>("3.14"));
assert(num_valid<int>("65535"));
轉(zhuǎn)換要求
lexical_cast對轉(zhuǎn)換對象有一定要求
轉(zhuǎn)換的起點(diǎn)對象是可流輸出的(可以用“<<”)
【注意事項(xiàng)】對于重載了“<<”操作符的自定義類型也可以使用它
轉(zhuǎn)換的終點(diǎn)對象是可流輸入的(可以用“>>”)
轉(zhuǎn)換的終點(diǎn)對象是可默認(rèn)構(gòu)造的、可拷貝構(gòu)造的
最常用的搭檔:int,double,string等POD類型
C++標(biāo)準(zhǔn)轉(zhuǎn)換函數(shù)
//字符串轉(zhuǎn)換為數(shù)字
int stoi(const string& str,size_t *idx = 0,int base = 10);
long stol(const string& str,size_t *idx = 0,int base = 10);
long long stoll(const string& str,size_t *idx = 0,int base = 10);
float stof(const string& str,size_t *idx = 0);
double stod(const string& str,size_t *idx = 0);
//數(shù)字轉(zhuǎn)換為string
string to_string(Type val);
【注意事項(xiàng)】必須以空格或數(shù)字開頭,否則報(bào)錯
和lexical_cast的比較:
優(yōu)點(diǎn):
無需寫模板參數(shù)
允許出現(xiàn)非數(shù)字字符(忽略起始空格,遇到無法轉(zhuǎn)換的字符終止)
缺點(diǎn):
不支持對自定義類型的轉(zhuǎn)換
string_algo
功能
提供了強(qiáng)大的字符串處理能力,如查找、訪問、基本的字符串處理
頭文件和命名空間
#include <boost/algorithm/string.hpp>
using namespace boost;
用法
【注意事項(xiàng)】不僅可以用在string上(在這里string被看作是vector<char>),也可以用于部分其他容器,例如(vector<T>)
大小寫轉(zhuǎn)換
string str("Hello");
//轉(zhuǎn)向大寫
cout << to_upper(str) << endl; //這種方式會改變源數(shù)據(jù)
cout << to_upper_copy(str) << endl; //這種方法返回一個(gè)轉(zhuǎn)換后的拷貝對象
//轉(zhuǎn)向小寫
cout << to_lower(str) <<endl;
cout << to_lower_copy(str) << endl;
判斷式(算法)
lexicographical_compare:根據(jù)字典順序檢測一個(gè)字符串是否小于另一個(gè)字符串starts_with:檢測字符串是否以另一個(gè)字符串為前綴ends_with:檢測字符串是否以另一個(gè)字符串為后綴contains:檢測字符串是否包含另一個(gè)字符串equals:檢測兩個(gè)字符串是否相等all:檢測字符串是否滿足指定的判斷式
【注意事項(xiàng)】
除了all以外都有一個(gè)i前綴的版本,表示大小寫無關(guān)
這些函數(shù)都不變動字符串
用法示例
string str("Power Bomb");
assert(iends_with(str,"bomb")); //大小寫無關(guān)檢測后綴
assert(!ends_with(str,"bomb")); //大小寫敏感檢測后綴
assert(starts_with(str,"Pow")); //檢測前綴
assert(contains(str,"er")); //測試包含關(guān)系
string str2 = to_lower_copy(str); //轉(zhuǎn)換成小寫
assert(iequals(str,str2)); //大小寫無關(guān)判斷相等
assert(ilexicographical_compare(str,str3)); //大小寫無關(guān)字符串比較
assert(all(str2.substr(0,5),is_lower())); //檢測字符串均小寫
分類
提供一組分類函數(shù),用于檢測字符串是否符合某種特性,主要搭配其他算法使用,如上一節(jié)的all
is_space:字符是否為空格或制表符(tab)is_alnum:字符是否為字母和數(shù)字字符is_alpha:字符是否為字母is_cntrl:字符是否為控制字符is_digit:字符是否為十進(jìn)制數(shù)字is_graph:字符是否為圖形字符is_lower:字符是否為小寫字符is_print:字符是否為可打印字符is_punct:字符是否為標(biāo)點(diǎn)符號字符is_upper:字符是否為大寫字符is_xdigit:字符是否為十六進(jìn)制數(shù)字is_any_of:字符是否是參數(shù)字符序列中的任意字符if_from_range:字符是否位于指定區(qū)間內(nèi),即from<=ch<=to
需要注意的是這些函數(shù)并不真正地檢測字符,而是返回一個(gè)類型為detail::is_classifiedF的函數(shù)對象,這個(gè)函數(shù)對象的operator()才是真正的分類函數(shù)(因此,這些函數(shù)都屬于工廠函數(shù))。
修剪
提供三個(gè)算法,刪去字符串開頭結(jié)尾的空格,提供_copy后綴和_if后綴
trim_left:刪除左邊的空格trim_right:刪除右邊的空格trim:刪除兩邊的空格
用法示例
format fmt("|%s|\n");
string str = " samus aran ";
cout << fmt % trim_copy(str); //刪除兩端的空格
cout << fmt % trim_left_copy(str); //刪除左邊的空格
trim_right(str); //原地刪除
cout << fmt % str;
string str2 = "2020 Happy new Year!!!";
cout << fmt % trim_left_copy_if(str2,is_digit()); //刪除左端的數(shù)字
cout << fmt % trim_right_copy_if(str2,is_punct()); //刪除右邊的標(biāo)點(diǎn)
//刪除兩端的標(biāo)點(diǎn)、數(shù)字和空格
cout << fmt % trim_copy_if(str2,is_punct() || is_digit() || is_space());
執(zhí)行結(jié)果
|samus aran|
|samus aran |
| samus aran|
| Happy new Year!!!|
|2020 Happy new Year|
|Happy new Year|
查找
提供的查找算法如下:
find_first:查找字符串在輸入中第一次出現(xiàn)的位置find_last:查找字符串在輸入中最后一次出現(xiàn)的位置find_nth:查找字符串在輸入中的第N次(從0開始計(jì)數(shù))出現(xiàn)的位置find_head:取一個(gè)字符串開頭N個(gè)字符的子串,相當(dāng)于substr(0,n)find_tail:取一個(gè)字符串末尾N個(gè)字符的子串
【注意事項(xiàng)】
這些算法的返回值是iterator_range,在概念上類似于std::pair,包裝了兩個(gè)迭代器,可以用begin()和end()訪問。提供了i前綴的用法。
用法示例
format fmt("|%s|.pos = %d\n");
string str = "Long long ago , there was a king.";
iterator_range<string::iterator> rge; //迭代器區(qū)間
rge = find_first(str,"long"); //找第一次出現(xiàn)
cout << fmt % rge % (rge.begin() - str.begin());
rge = ifind_first(str,"long"); //大小寫無關(guān)第一次出現(xiàn)
cout << fmt % rge % (rge.begin() - str.begin());
rge = find_nth(str,"ng",2); //找第三次出現(xiàn)
cout << fmt % rge % (rge.begin() - str.begin());
rge = find_head(str,4); //取前4個(gè)字符
cout << fmt % rge % (rge.begin() - str.begin());
rge = find_tail(str,5); //取末5個(gè)字符
cout << fmt % rge % (rge.begin() - str.begin());
rge = find_first(str,"samus"); //找不到
assert(rge.empty() && !rge);
執(zhí)行結(jié)果
|long|.pos = 5
|Long|.pos = 0
|ng|.pos = 30
|Long|.pos = 0
|king.|.pos = 28
替換與刪除
替換、刪除操作與查找算法非常接近,是在查找到結(jié)果后再對字符串進(jìn)行處理,具體如下:
replace/erase_first:替換/刪除字符串在輸入中的第一次出現(xiàn)
replace/erase_last:替換/刪除字符串在輸入中的最后一次出現(xiàn)
replace/erase_nth:替換/刪除字符串在輸入中的第n次出現(xiàn)
replace/erase_all:替換/刪除字符串在輸入中的所有出現(xiàn)
replace/erase_head:替換/刪除輸入的開頭
replace/erase_tail:替換/刪除輸入的末尾
前8個(gè)算法每個(gè)都有前綴i、后綴_copy的組合,有四個(gè)版本,后4個(gè)則只有后綴_copy的兩個(gè)版本
示例代碼如下:
// 替換和刪除
string str_2 = "Samus beat the monster.\n";
// replace
cout << replace_first_copy(str_2, "Samus", "samus");
replace_last(str_2, "beat", "kill");
cout << str_2;
replace_tail(str_2, 9, "ridley.\n");
cout << str_2;
// delete
cout << ierase_all_copy(str_2, "samus ");
cout << replace_nth_copy(str_2, "l", 1, "L");
cout << erase_tail_copy(str_2, 8);
運(yùn)行結(jié)果:
samus beat the monster.
Samus kill the monster.
Samus kill the ridley.
kill the ridley.
Samus kilL the ridley.
Samus kill the
分割
string_algo提供了兩個(gè)字符串分割算法:find_all(雖然它的名稱含有find,但因?yàn)槠涔δ芏粴w類為分割算法)和split,可以使用某種策略把字符串分割成若干部分,并將分割后的字符串拷貝存入指定的容器。
分割算法對容器類型的要求是必須能夠持有查找到結(jié)果的拷貝或引用,因此容器的元素類型必須是string或iterator_range<string::iterator>,容器則可以是vector、list、deque等標(biāo)準(zhǔn)容器。
find_all算法類似于普通的查找算法,它搜索所有匹配的字符串,將其加入容器,有一個(gè)忽略大小寫的前綴i版本。
split算法使用判斷式Pred來確定分割的依據(jù),如果字符ch滿足判斷式Pred(Pred(ch)==true),那么它就是一個(gè)分割符,將字符串從這里分割。
還有第三個(gè)參數(shù)eCompress可以取值為token_compress_on或token_compress_off,如果值為前者,那么當(dāng)兩個(gè)分隔符連續(xù)出現(xiàn)時(shí),它們將被視為一個(gè)分隔符,如果值為后者則兩個(gè)連續(xù)的分隔符標(biāo)記了一個(gè)空字符串。參數(shù)eCompress的默認(rèn)取值為token_compress_off。
string str = "Samus, Link.Zelda::Mario-Luigi+zelda";
deque<string> d;
// 大小寫無關(guān)查找
ifind_all(d, str, "zELDA");
assert(d.size() == 2);
for(auto x: d)
{
cout << "[" << x << "]";
}
cout << endl;
// 存儲range對象
list<iterator_range<string::iterator>> l;
split(l, str, is_any_of(",.:-+")); // 使用標(biāo)點(diǎn)分割
for(auto x: l)
{
cout << "[" << x << "]";
}
cout << endl;
l.clear();
split(l, str, is_any_of(",.:-+"), token_compress_on);
for(auto x: l)
{
cout << "[" << x << "]";
}
cout << endl;
程序運(yùn)行結(jié)果如下:
[Zelda][zelda]
[Samus][ Link][Zelda][][Mario][Luigi][zelda]
[Samus][ Link][Zelda][Mario][Luigi][zelda]
合并
合并算法join是分割算法的逆運(yùn)算,它把存儲在容器中的字符串連接成一個(gè)新的字符串,并且可以指定連接的分隔符。
join還有一個(gè)后綴_if的版本,它接收一個(gè)判斷式,只有滿足判斷式的字符串才能合并。
vector<string> v = list_of("Samus")("Link")("Zelda")("Mario");
cout << join(v, "+") << endl;
cout << join_if(
v, "**",
[](string_ref s)
{
return contains(s, "a");
}
) << endl;
程序首先使用assign庫向vector添加了4個(gè)字符串,然后用+合并它們。隨后的join_if算法使用lambda表達(dá)式定義了一個(gè)簡單的謂詞,它包裝了算法contains,判斷字符串是否包含字符a。
程序運(yùn)行結(jié)果如下:
Samus+Link+Zelda+Mario
Samus**Zelda**Mario
string_ref
功能
一種輕量級的string,持有string類型的引用
頭文件
#include <boost/utility/string_ref.hpp>
using namespace boost;
類摘要
template<typename charT,typename traits>
class basic_string_ref
{
public:
//和std::string有著幾乎一樣的接口
private:
const charT* ptr_; //字符串指針
std::size_t len_; //字符串長度
};
不拷貝字符串,所以不分配內(nèi)存,使用兩個(gè)成員變量表示字符串
用法
【注意事項(xiàng)】只能像std::string&一樣去獲取其內(nèi)容,但不能修改其本身
1、構(gòu)造
//通過標(biāo)準(zhǔn)字符數(shù)組構(gòu)造普通string,有拷貝成本
const char* ch = "hello";
string str(ch);
//字符數(shù)組構(gòu)造,無成本
string_ref s1(ch);
//標(biāo)準(zhǔn)字符串構(gòu)造,無成本
string_ref s2(str);
可以像使用普通string一樣使用string_ref(除了修改)
2、用在哪
用于代替string&作為函數(shù)參數(shù)和返回值,可以完全避免字符串拷貝代價(jià)


