字串演算法(string_algorithm)

stingliang發表於2021-06-01

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&作為函式引數和返回值,可以完全避免字串拷貝代價

相關文章