字符串算法(string_algorithm)


format

作用

格式化輸出對象,可以不改變流輸出狀態實現類似於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();

運行結果:

sum:1+2=3
(2 + 5) * 5 = 35

說明:

  1. 第一種用法使用了和printf類似的語法結構,不必贅述
  2. 第二種用法,先實例化format對象確定輸出格式,使用%N%指示參數位置

format對象操作

//返回格式化后的字符串
formatobj.str();
//返回已格式化的字符串長度
formatobj.size();
//清空格式和內容
formatobj.parse();
//只清除格式
formatobj.clear();

格式化規則

  • %05d:輸出寬度為5的整數,不足位用0填充
  • %-8.3f:輸出左對齊,寬度總為8,小數位為3的浮點數
  • % 10s:輸出10位的字符串,不足用空格填充
  • 05X:輸出寬度為5的大寫16進制整數,不足填充0
  • %|spec|:將格式化選項包含進兩個豎線之間,更好的區分格式化選項和普通字符
  • %N%:標記弟N個參數,相當於占位符,不帶任何其他的格式化選項
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;

執行結果:

00062   2.236            123456789      00030
00062   2.236            123456789      00030

lexical_cast

功能

對字符串進行“字面值”的轉換,對字符串與整數/浮點數之間進行轉換

需要包含的頭文件

#inlude <boost/lexical_cast.hpp>
using namespace boost;

聲明

//標准形式,轉換數字和字符串
template <typename Target,typename Source>
inline Target lexical_cast(const Source &arg);

//轉換C字符串
template <typename Target>
inline Target lexical_cast(const char * chars,std::size_t count)

使用

在模板參數里指定轉換的目標類型即可

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

【注意事項】
該模板智能轉換字面值,如果出現不合理的轉換,例如“hello”轉int類型,則會報錯(正常人應該不會這么干)

錯誤處理

當lexical_cast無法執行轉換操作時會拋出異常bad_lexical_cast,它是std::bad_cast的派生類

傳統保護辦法

在使用lexical_cast時應該使用try_catch來保護代碼

try
{
    cout <<lexical_cast<int>("0x100");
}
catch(bad_lexical_cast& e)
{
    cout << "error: \n" << e.what() << endl;
}

//運行結果
error: 
bad lexical cast:source type value could not be interpreted as target

已有庫的保護辦法

需要使用命名空間:boost::conversion

函數:

boost::conversion::try_lexical_cast(typeRaw,typeTarget);

返回值為bool表示是否轉換成功

【技巧:驗證數字字符串的合法性(用於驗證用戶輸入的有效性)】

實現一個模板類

template<typename T>
bool num_valid(const char* str)
{
    T tmp;
    return conversion::try_lexical_convert(str,tmp)  //嘗試轉換數字
}

//用法
assert(num_valid<double>("3.14"));
assert(!num_valid<int>("3.14"));
assert(num_valid<int>("65535"));

轉換要求

lexical_cast對轉換對象有一定要求

  • 轉換的起點對象是可流輸出的(可以用“<<”)

    【注意事項】對於重載了“<<”操作符的自定義類型也可以使用它

  • 轉換的終點對象是可流輸入的(可以用“>>”)

  • 轉換的終點對象是可默認構造的、可拷貝構造的

最常用的搭檔:int,double,string等POD類型

C++標准轉換函數

//字符串轉換為數字
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);

//數字轉換為string
string to_string(Type val);

【注意事項】必須以空格或數字開頭,否則報錯

和lexical_cast的比較:

優點:

  • 無需寫模板參數
  • 允許出現非數字字符(忽略起始空格,遇到無法轉換的字符終止)

缺點:

  • 不支持對自定義類型的轉換

string_algo

功能

提供了強大的字符串處理能力,如查找、訪問、基本的字符串處理

頭文件和命名空間

#include <boost/algorithm/string.hpp>

using namespace boost;

用法

【注意事項】不僅可以用在string上(在這里string被看作是vector<char>),也可以用於部分其他容器,例如(vector<T>

大小寫轉換

string str("Hello");

//轉向大寫
cout << to_upper(str) << endl;    //這種方式會改變源數據
cout << to_upper_copy(str) << endl;    //這種方法返回一個轉換后的拷貝對象

//轉向小寫
cout << to_lower(str) <<endl;
cout << to_lower_copy(str) << endl;

判斷式(算法)

  • lexicographical_compare:根據字典順序檢測一個字符串是否小於另一個字符串
  • starts_with:檢測字符串是否以另一個字符串為前綴
  • ends_with:檢測字符串是否以另一個字符串為后綴
  • contains:檢測字符串是否包含另一個字符串
  • equals:檢測兩個字符串是否相等
  • all:檢測字符串是否滿足指定的判斷式

【注意事項】

  • 除了all以外都有一個i前綴的版本,表示大小寫無關
  • 這些函數都不變動字符串

用法示例

string str("Power Bomb");

assert(iends_with(str,"bomb"));    //大小寫無關檢測后綴
assert(!ends_with(str,"bomb"));    //大小寫敏感檢測后綴

assert(starts_with(str,"Pow"));    //檢測前綴

assert(contains(str,"er"));    //測試包含關系

string str2 = to_lower_copy(str);    //轉換成小寫
assert(iequals(str,str2));    //大小寫無關判斷相等

assert(ilexicographical_compare(str,str3));    //大小寫無關字符串比較

assert(all(str2.substr(0,5),is_lower()));    //檢測字符串均小寫

分類

提供一組分類函數,用於檢測字符串是否符合某種特性,主要搭配其他算法使用,如上一節的all

  • is_space:字符是否為空格或制表符(tab)
  • is_alnum:字符是否為字母和數字字符
  • is_alpha:字符是否為字母
  • is_cntrl:字符是否為控制字符
  • is_digit:字符是否為十進制數字
  • is_graph:字符是否為圖形字符
  • is_lower:字符是否為小寫字符
  • is_print:字符是否為可打印字符
  • is_punct:字符是否為標點符號字符
  • is_upper:字符是否為大寫字符
  • is_xdigit:字符是否為十六進制數字
  • is_any_of:字符是否是參數字符序列中的任意字符
  • if_from_range:字符是否位於指定區間內,即from<=ch<=to

需要注意的是這些函數並不真正地檢測字符,而是返回一個類型為detail::is_classifiedF的函數對象,這個函數對象的operator()才是真正的分類函數(因此,這些函數都屬於工廠函數)。

修剪

提供三個算法,刪去字符串開頭結尾的空格,提供_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());    //刪除左端的數字
cout << fmt % trim_right_copy_if(str2,is_punct());    //刪除右邊的標點
//刪除兩端的標點、數字和空格
cout << fmt % trim_copy_if(str2,is_punct() || is_digit() || is_space()); 

執行結果

|samus aran|
|samus aran  |
|  samus aran|
| Happy new Year!!!|
|2020 Happy new Year|
|Happy new Year|

查找

提供的查找算法如下:

  • find_first:查找字符串在輸入中第一次出現的位置
  • find_last:查找字符串在輸入中最后一次出現的位置
  • find_nth:查找字符串在輸入中的第N次(從0開始計數)出現的位置
  • find_head:取一個字符串開頭N個字符的子串,相當於substr(0,n)
  • find_tail:取一個字符串末尾N個字符的子串

【注意事項】

這些算法的返回值是iterator_range,在概念上類似於std::pair,包裝了兩個迭代器,可以用begin()end()訪問。提供了i前綴的用法。
用法示例

format fmt("|%s|.pos = %d\n");
string str = "Long long ago , there was a king.";
iterator_range<string::iterator> rge;    //迭代器區間

rge = find_first(str,"long");    //找第一次出現
cout << fmt % rge % (rge.begin() - str.begin());

rge = ifind_first(str,"long");    //大小寫無關第一次出現
cout << fmt % rge % (rge.begin() - str.begin());

rge = find_nth(str,"ng",2);    //找第三次出現
cout << fmt % rge % (rge.begin() - str.begin());

rge = find_head(str,4);    //取前4個字符
cout << fmt % rge % (rge.begin() - str.begin());

rge = find_tail(str,5);    //取末5個字符
cout << fmt % rge % (rge.begin() - str.begin());

rge = find_first(str,"samus");    //找不到
assert(rge.empty() && !rge);

執行結果

|long|.pos = 5
|Long|.pos = 0
|ng|.pos = 30
|Long|.pos = 0
|king.|.pos = 28

替換與刪除

替換、刪除操作與查找算法非常接近,是在查找到結果后再對字符串進行處理,具體如下:

replace/erase_first:替換/刪除字符串在輸入中的第一次出現

replace/erase_last:替換/刪除字符串在輸入中的最后一次出現

replace/erase_nth:替換/刪除字符串在輸入中的第n次出現

replace/erase_all:替換/刪除字符串在輸入中的所有出現

replace/erase_head:替換/刪除輸入的開頭

replace/erase_tail:替換/刪除輸入的末尾

前8個算法每個都有前綴i、后綴_copy的組合,有四個版本,后4個則只有后綴_copy的兩個版本

示例代碼如下:

// 替換和刪除
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);

運行結果:

samus beat the monster.
Samus kill the monster.
Samus kill the ridley.
kill the ridley.
Samus kilL the ridley.
Samus kill the

分割

string_algo提供了兩個字符串分割算法:find_all(雖然它的名稱含有find,但因為其功能而被歸類為分割算法)和split,可以使用某種策略把字符串分割成若干部分,並將分割后的字符串拷貝存入指定的容器。

分割算法對容器類型的要求是必須能夠持有查找到結果的拷貝或引用,因此容器的元素類型必須是stringiterator_range<string::iterator>,容器則可以是vector、list、deque等標准容器。

find_all算法類似於普通的查找算法,它搜索所有匹配的字符串,將其加入容器,有一個忽略大小寫的前綴i版本。

split算法使用判斷式Pred來確定分割的依據,如果字符ch滿足判斷式Pred(Pred(ch)==true),那么它就是一個分割符,將字符串從這里分割。

還有第三個參數eCompress可以取值為token_compress_ontoken_compress_off,如果值為前者,那么當兩個分隔符連續出現時,它們將被視為一個分隔符,如果值為后者則兩個連續的分隔符標記了一個空字符串。參數eCompress的默認取值為token_compress_off

string str = "Samus, Link.Zelda::Mario-Luigi+zelda";

deque<string> d;
// 大小寫無關查找
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(",.:-+"));  // 使用標點分割
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;

程序運行結果如下:

[Zelda][zelda]
[Samus][ Link][Zelda][][Mario][Luigi][zelda]
[Samus][ Link][Zelda][Mario][Luigi][zelda]

合並

合並算法join是分割算法的逆運算,它把存儲在容器中的字符串連接成一個新的字符串,並且可以指定連接的分隔符。

join還有一個后綴_if的版本,它接收一個判斷式,只有滿足判斷式的字符串才能合並。

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個字符串,然后用+合並它們。隨后的join_if算法使用lambda表達式定義了一個簡單的謂詞,它包裝了算法contains,判斷字符串是否包含字符a。
程序運行結果如下:

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_;     //字符串長度
};

不拷貝字符串,所以不分配內存,使用兩個成員變量表示字符串

用法

【注意事項】只能像std::string&一樣去獲取其內容,但不能修改其本身

1、構造

//通過標准字符數組構造普通string,有拷貝成本
const char* ch = "hello";
string str(ch);

//字符數組構造,無成本
string_ref s1(ch);

//標准字符串構造,無成本
string_ref s2(str);

可以像使用普通string一樣使用string_ref(除了修改)

2、用在哪

用於代替string&作為函數參數和返回值,可以完全避免字符串拷貝代價


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM