1. 程式人生 > >boost字串處理(下)

boost字串處理(下)

四、正則表示式庫 Boost.Regex 可以應用正則表示式於C++。正則表示式大大減輕了搜尋特定模式字串的負擔,在很多語言中都是強大的功能。雖然現在C++仍然需要以 Boost C++庫的形式提供這一功能,但是在將來正則表示式將進入C++標準庫。 Boost Regex庫有望包括在下一版的 C++ 標準中。

    Boost.Regex庫中兩個最重要的類是boost::regex和boost::smatch,它們都在 boost/regex.hpp檔案中定義。前者用於定義一個正則表示式,而後者可以儲存搜尋結果。

    以下將要介紹 Boost.Regex 庫中提供的三個搜尋正則表示式的函式。

  1. #include <boost/regex.hpp>    
  2. #include <locale>    
  3. #include <iostream>    
  4. int main()   
  5. {   
  6.   std::locale::global(std::locale("German"));   
  7.   std::string s = "Boris Sch?ling";   
  8.   boost::regex expr("\\w+\\s\\w+");   
  9.   std::cout << boost::regex_match(s, expr) << std::endl;   
  10. }   

   函式 boost::regex_match() 用於字串與正則表示式的比較。 在整個字串匹配正則表示式時其返回值為 true 。

   函式 boost::regex_search() 可用於在字串中搜索正則表示式。

  1. #include <boost/regex.hpp>    
  2. #include <locale>    
  3. #include <iostream>    
  4. int main()   
  5. {   
  6.   std::locale::global(std::locale("German"));   
  7.   std::string s = "Boris Sch?ling"
    ;   
  8.   boost::regex expr("(\\w+)\\s(\\w+)");   
  9.   boost::smatch what;   
  10.   if (boost::regex_search(s, what, expr))   
  11.   {   
  12.     std::cout << what[0] << std::endl;   
  13.     std::cout << what[1] << " " << what[2] << std::endl;   
  14.   }   
  15. }   

    函式 boost::regex_search() 可以接受一個型別為 boost::smatch 的引用的引數用於儲存結果。 函式 boost::regex_search() 只用於分類的搜尋, 本例實際上返回了兩個結果, 它們是基於正則表示式的分組。

    儲存結果的類 boost::smatch 事實上是持有型別為 boost::sub_match 的元素的容器, 可以通過與類 std::vector 相似的介面訪問。 例如, 元素可以通過操作符 operator[]() 訪問。

    另一方面,類boost::sub_match將迭代器儲存在對應於正則表示式分組的位置。 因為它繼承自類std::pair,迭代器引用的子串可以使用 first 和 second 訪問。如果像上面的例子那樣,只把子串寫入標準輸出流,那麼通過過載操作符 << 就可以直接做到這一點,那麼並不需要訪問迭代器。

    請注意結果儲存在迭代器中而boost::sub_match類並不複製它們, 這說明它們只是在被迭代器引用的相關字串存在時才可以訪問。

    另外,還需要注意容器boost::smatch 的第一個元素儲存的引用是指向匹配正則表示式的整個字串的,匹配第一組的第一個子串由索引 1 訪問。

Boost.Regex 提供的第三個函式是 boost::regex_replace()。

  1. #include <boost/regex.hpp>    
  2. #include <locale>    
  3. #include <iostream>    
  4. int main()   
  5. {   
  6.   std::locale::global(std::locale("German"));   
  7.   std::string s = " Boris Sch?ling ";   
  8.   boost::regex expr("\\s");   
  9.   std::string fmt("_");   
  10.   std::cout << boost::regex_replace(s, expr, fmt) << std::endl;   
  11. }   

    除了待搜尋的字串和正則表示式之外,boost::regex_replace()函式還需要一個格式引數,它決定了子串、匹配正則表示式的分組如何被替換。如果正則表示式不包含任何分組,相關子串將被用給定的格式一個個地被替換。這樣上面程式輸出的結果為 _Boris_Sch?ling_。

    boost::regex_replace()函式總是在整個字串中搜索正則表示式,所以這個程式實際上將三處空格都替換為下劃線。

  1. #include <boost/regex.hpp>    
  2. #include <locale>    
  3. #include <iostream>    
  4. int main()   
  5. {   
  6.   std::locale::global(std::locale("German"));   
  7.   std::string s = "Boris Sch?ling";   
  8.   boost::regex expr("(\\w+)\\s(\\w+)");   
  9.   std::string fmt("\\2 \\1");   
  10.   std::cout << boost::regex_replace(s, expr, fmt) << std::endl;   
  11. }   

    格式引數可以訪問由正則表示式分組的子串,這個例子正是使用了這項技術,交換了姓、名的位置,於是結果顯示為 Sch?ling Boris 。

    需要注意的是,對於正則表示式和格式有不同的標準。 這三個函式都可以接受一個額外的引數,用於選擇具體的標準。 也可以指定是否以某一具體格式解釋特殊字元或者替代匹配正則表示式的整個字串。

  1. #include <boost/regex.hpp>    
  2. #include <locale>    
  3. #include <iostream>    
  4. int main()   
  5. {   
  6.   std::locale::global(std::locale("German"));   
  7.   std::string s = "Boris Sch?ling";   
  8.   boost::regex expr("(\\w+)\\s(\\w+)");   
  9.   std::string fmt("\\2 \\1");   
  10.   std::cout << boost::regex_replace(s, expr, fmt, boost::regex_constants::format_literal) << std::endl;   
  11. }   

    此程式將boost::regex_constants::format_literal標誌作為第四引數傳遞給函式 boost::regex_replace(),從而抑制了格式引數中對特殊字元的處理。 因為整個字串匹配正則表示式,所以本例中經格式引數替換的到達的輸出結果為 \2 \1。

    正如上一節末指出的那樣,正則表示式可以和 Boost.StringAlgorithms 庫結合使用。它通過 Boost.Regex 庫提供函式如 boost::algorithm::find_regex() 、 boost::algorithm::replace_regex() 、 boost::algorithm::erase_regex() 以及 boost::algorithm::split_regex() 等等。由於 Boost.Regex 庫很有可能成為即將到來的下一版 C++ 標準的一部分,脫離 Boost.StringAlgorithms 庫,熟練地使用正則表示式是個明智的選擇。

五、 詞彙分割器庫 Boost.Tokenizer

         Boost.Tokenizer 庫可以在指定某個字元為分隔符後,遍歷字串的部分表示式。

  1. #include <boost/tokenizer.hpp>    
  2. #include <string>    
  3. #include <iostream>    
  4. int main()   
  5. {   
  6.   typedef boost::tokenizer<boost::char_separator<char> > tokenizer;   
  7.   std::string s = "Boost C++ libraries";   
  8.   tokenizer tok(s);   
  9.   for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)   
  10.     std::cout << *it << std::endl;   
  11. }   

    Boost.Tokenizer 庫在 boost/tokenizer.hpp 檔案中定義了模板類 boost::tokenizer ,其模板引數為支援相關表示式的類。 上面的例子中就使用了 boost::char_separator 類作為模板引數,它將空格和標點符號視為分隔符。

    詞彙分割器必須由型別為 std::string 的字串初始化。通過使用 begin() 和 end() 方法,詞彙分割器可以像容器一樣訪問。通過使用迭代器,可以得到前述字串的部分表示式。模板引數的型別決定了如何達到部分表示式。

    因為 boost::char_separator 類預設將空格和標點符號視為分隔符,所以本例顯示的結果為 Boost、C、 +、 + 和 libraries。為了識別這些分隔符,boost::char_separator 函式呼叫了 std::isspace() 函式和 std::ispunct 函式。Boost.Tokenizer庫會區分要隱藏的分隔符和要顯示的分隔符。 在預設的情況下,空格會隱藏而標點符號會顯示出來,所以這個例子裡顯示了兩個加號。

    如果不需要將標點符號作為分隔符,可以在傳遞給詞彙分割器之前相應地初始化 boost::char_separator物件。以下例子正是這樣做的:

  1. #include <boost/tokenizer.hpp>    
  2. #include <string>    
  3. #include <iostream>    
  4. int main()   
  5. {   
  6.   typedef boost::tokenizer<boost::char_separator<char> > tokenizer;   
  7.   std::string s = "Boost C++ libraries";   
  8.   boost::char_separator<char> sep(" ");   
  9.   tokenizer tok(s, sep);   
  10.   for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)   
  11.     std::cout << *it << std::endl;   
  12. }   

    類 boost::char_separator 的建構函式可以接受三個引數, 只有第一個是必須的,它描述了需要隱藏的分隔符。 在本例中, 空格仍然被視為分隔符。

    第二個引數指定了需要顯示的分隔符。 在不提供此引數的情況下,將不顯示任何分隔符。 執行程式,會顯示 Boost 、 C++ 和 libraries 。

    如果將加號作為第二個引數,此例的結果將和上一個例子相同。

  1. #include <boost/tokenizer.hpp>    
  2. #include <string>    
  3. #include <iostream>    
  4. int main()   
  5. {   
  6.   typedef boost::tokenizer<boost::char_separator<char> > tokenizer;   
  7.   std::string s = "Boost C++ libraries";   
  8.   boost::char_separator<char> sep(" ""+");   
  9.   tokenizer tok(s, sep);   
  10.   for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)   
  11.     std::cout << *it << std::endl;   
  12. }   

    第三個引數決定了是否顯示空的部分表示式。 如果連續找到兩個分隔符,他們之間的部分表示式將為空。在預設情況下,這些空表示式是不會顯示的。第三個引數可以改變預設的行為。

  1. #include <boost/tokenizer.hpp>    
  2. #include <string>    
  3. #include <iostream>    
  4. int main()   
  5. {   
  6.   typedef boost::tokenizer<boost::char_separator<char> > tokenizer;   
  7.   std::string s = "Boost C++ libraries";   
  8.   boost::char_separator<char> sep(" ""+", boost::keep_empty_tokens);   
  9.   tokenizer tok(s, sep);   
  10.   for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)   
  11.     std::cout << *it << std::endl;   
  12. }   

    執行以上程式,會顯示另外兩個的空表示式。 其中第一個是在兩個加號中間的而第二個是加號和之後的空格之間的。

    詞彙分割器也可用於不同的字串型別。

  1. #include <boost/tokenizer.hpp>    
  2. #include <string>    
  3. #include <iostream>    
  4. int main()   
  5. {   
  6.   typedef boost::tokenizer<boost::char_separator<wchar_t>, std::wstring::const_iterator, std::wstring> tokenizer;   
  7.   std::wstring s = L"Boost C++ libraries";   
  8.   boost::char_separator<wchar_t> sep(L" ");   
  9.   tokenizer tok(s, sep);   
  10.   for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)   
  11.     std::wcout << *it << std::endl;   
  12. }   

    這個例子遍歷了一個型別為 std::wstring 的字串。 為了使用這個型別的字串,必須使用另外的模板引數初始化詞彙分割器,對 boost::char_separator 類也是如此,他們都需要引數 wchar_t 初始化。

    除了 boost::char_separator 類之外, Boost.Tokenizer 還提供了另外兩個類以識別部分表示式。

  1. #include <boost/tokenizer.hpp>    
  2. #include <string>    
  3. #include <iostream>    
  4. int main()   
  5. {   
  6.   typedef boost::tokenizer<boost::escaped_list_separator<char> > tokenizer;   
  7.   std::string s = "Boost,\"C++ libraries\"";   
  8.   tokenizer tok(s);   
  9.   for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)   
  10.     std::cout << *it << std::endl;   
  11. }   

    boost::escaped_list_separator 類用於讀取由逗號分隔的多個值,這個格式的檔案通常稱為 CSV (comma separated values,逗號分隔檔案),它甚至還可以處理雙引號以及轉義序列。所以本例的輸出為 Boost 和 C++ libraries 。

    另一個是 boost::offset_separator 類,必須用例項說明。 這個類的物件必須作為第二個引數傳遞給 boost::tokenizer 類的建構函式。

  1. #include <boost/tokenizer.hpp>    
  2. #include <string>    
  3. #include <iostream>    
  4. int main()   
  5. {   
  6.   typedef boost::tokenizer<boost::offset_separator> tokenizer;   
  7.   std::string s = "Boost C++ libraries";   
  8.   int offsets[] = { 5, 5, 9 };   
  9.   boost::offset_separator sep(offsets, offsets + 3);   
  10.   tokenizer tok(s, sep);   
  11.   for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)   
  12.     std::cout << *it << std::endl;   
  13. }   

    boost::offset_separator 指定了部分表示式應當在字串中的哪個位置結束。 以上程式制定第一個部分表示式在 5 個字元後結束,第二個字串在另 5 個字元後結束,第三個也就是最後一個字串應當在之後的 9 個字元後結束。 輸出的結果為 Boost 、  C++  和 libraries 。

六、格式化輸出庫 Boost.Format

            Boost.Format 庫可以作為定義在檔案 cstdio 中的函式 std::printf() 的替代。 std::printf() 函式最初出現在 C 標準中,提供格式化資料輸出功能, 但是它既不是型別安全的有不能擴充套件。 因此在 C++ 應用中, Boost.Format 庫通常是資料格式化輸出的上佳之選。

    Boost.Format 庫在檔案 boost/format.hpp 中定義了類 boost::format 。 與函式 std::printf 相似的是,傳遞給() boost::format 的建構函式的引數也是一個字串,它由控制格式的特殊字元組成。 實際資料通過操作符 % 相連,在輸出中替代特殊字元,如下例所示。

  1. #include <boost/format.hpp>    
  2. #include <iostream>    
  3. int main()   
  4. {   
  5.   std::cout << boost::format("%1%.%2%.%3%") % 16 % 9 % 2008 << std::endl;   
  6. }   

    Boost.Format 類使用置於兩個百分號之間的數字作為佔位符,佔位符稍後通過 % 操作符與實際資料連線。 以上程式使用數字16、9 和 2009 組成一個日期字串,以 16.9.2008的格式輸出。 如果要月份出現在日期之前,即美式表示,只需交換佔位符即可。

  1. #include <boost/format.hpp>    
  2. #include <iostream>    
  3. int main()   
  4. {   
  5.   std::cout << boost::format("%2%/%1%/%3%") % 16 % 9 % 2008 << std::endl;   
  6. }   

現在程式顯示的結果變成 9/16/2008 。

如果要使用C++ 操作器格式化資料,Boost.Format 庫提供了函式 boost::io::group() 。

  1. #include <boost/format.hpp>    
  2. #include <iostream>    
  3. int main()   
  4. {   
  5.   std::cout << boost::format("%1% %2% %1%") % boost::io::group(std::showpos, 99) % 100 << std::endl;   
  6. }   

本例的結果顯示為 +99 100 +99 。 因為操作器 std::showpos() 通過 boost::io::group() 與數字 99 連線,所以只要顯示 99 , 在它前面就會自動加上加號。

如果需要加號僅在 99 第一次輸出時顯示, 則需要改造格式化佔位符。

  1. #include <boost/format.hpp>    
  2. #include <iostream>    
  3. int main()   
  4. {   
  5.   std::cout << boost::format("%|1{1}| %2% %1%") % 99 % 100 << std::endl;   
  6. }   

    為了將輸出格式改為 +99 100 99 ,不但需要將資料的引用符號由 1$ 變為 1% ,還需要在其兩側各新增一個附加的管道符號,即將佔位符 %1% 替換為 %|1$+|。

    請注意,雖然一般對資料的引用不是必須的,但是所有佔位符一定要同時設定為指定貨非指定。 以下例子在執行時會出現錯誤,因為它給第二個和第三個佔位符設定了引用但是卻忽略了第一個。

  1. #include <boost/format.hpp>    
  2. #include <iostream>    
  3. int main()   
  4. {   
  5.   try   
  6.   {   
  7.     std::cout << boost::format("%|+| %2% %1%") % 99 % 100 << std::endl;   
  8.   }   
  9.   catch (boost::io::format_error &ex)   
  10.   {   
  11.     std::cout << ex.what() << std::endl;   
  12.   }   
  13. }   

    此程式丟擲了型別為 boost::io::format_error 的異常。 嚴格地說,Boost.Format 庫丟擲的異常為 boost::io::bad_format_string。 但是由於所有的異常類都繼承自 boost::io::format_error 類,捕捉此型別的異常會輕鬆一些。

以下例子演示了不引用資料的方法。

  1. #include <boost/format.hpp>    
  2. #include <iostream>    
  3. int main()   
  4. {   
  5.   std::cout << boost::format("%|+| %|| %||") % 99 % 100 % 99 << std::endl;   
  6. }   

    第二、第三個佔位符的管道符號可以被安全地省略,因為在這種情況下,他們並不指定格式。這樣的語法看起來很像 std::printf ()的那種。

  1. #include <boost/format.hpp>    
  2. #include <iostream>    
  3. int main()   
  4. {   
  5.   std::cout << boost::format("%+d %d %d") % 99 % 100 % 99 << std::endl;   
  6. }   

    雖然這看起來就像 std::printf() ,但是 Boost.Format 庫有型別安全的優點。 格式化字串中字母 'd' 的使用並不表示輸出數字,而是表示 boost::format 類所使用的內部流物件上的 std::dec() 操作器,它可以使用某些對 std::printf() 函式無意義的格式字串,如果使用 std::printf() 會導致程式在執行時崩潰。

  1. #include <boost/format.hpp>    
  2. #include <iostream>    
  3. int main()   
  4. {   
  5.   std::cout << boost::format("%+s %s %s") % 99 % 100 % 99 << std::endl;   
  6. }   

    儘管在 std::printf() 函式中,字母 's' 只用於表示型別為 const char* 的字串,然而以上程式卻能正常執行。 因為在 Boost.Format 庫中,這並不代表強制為字串,它會結合適當的操作器,調整內部流的操作模式。 所以即使在這種情況下,在內部流中加入數字也是沒問題的