Boost C++的正則表達式庫Boost.Regex
Boost.Regex庫中兩個最重要的類是boost::regex和boost::smatch,它們都在 boost/regex.hpp文件中定義。前者用於定義一個正則表達式,而後者可以保存搜索結果。
以下將要介紹 Boost.Regex 庫中提供的三個搜索正則表達式的函數。
- #include <boost/regex.hpp>
- #include <locale>
- #include <iostream>
- int main()
- {
- std::locale::global(std::locale("German"));
- std::string s = "Boris Sch?ling";
- boost::regex expr("\\w+\\s\\w+");
- std::cout << boost::regex_match(s, expr) << std::endl;
- }
函數 boost::regex_match() 用於字符串與正則表達式的比較。 在整個字符串匹配正則表達式時其返回值爲 true 。
函數 boost::regex_search() 可用於在字符串中搜索正則表達式。
- #include <boost/regex.hpp>
- #include <locale>
- #include <iostream>
- int main()
- {
- std::locale::global(std::locale("German"));
- std::string s = "Boris Sch?ling";
- boost::regex expr("(\\w+)\\s(\\w+)");
- boost::smatch what;
- if (boost::regex_search(s, what, expr))
- {
- std::cout << what[0] << std::endl;
- std::cout << what[1] << " " << what[2] << std::endl;
- }
- }
函數 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()。
- #include <boost/regex.hpp>
- #include <locale>
- #include <iostream>
- int main()
- {
- std::locale::global(std::locale("German"));
- std::string s = " Boris Sch?ling ";
- boost::regex expr("\\s");
- std::string fmt("_");
- std::cout << boost::regex_replace(s, expr, fmt) << std::endl;
- }
除了待搜索的字符串和正則表達式之外,boost::regex_replace()函數還需要一個格式參數,它決定了子串、匹配正則表達式的分組如何被替換。如果正則表達式不包含任何分組,相關子串將被用給定的格式一個個地被替換。這樣上面程序輸出的結果爲 _Boris_Sch?ling_。
boost::regex_replace()函數總是在整個字符串中搜索正則表達式,所以這個程序實際上將三處空格都替換爲下劃線。
- #include <boost/regex.hpp>
- #include <locale>
- #include <iostream>
- int main()
- {
- std::locale::global(std::locale("German"));
- std::string s = "Boris Sch?ling";
- boost::regex expr("(\\w+)\\s(\\w+)");
- std::string fmt("\\2 \\1");
- std::cout << boost::regex_replace(s, expr, fmt) << std::endl;
- }
格式參數可以訪問由正則表達式分組的子串,這個例子正是使用了這項技術,交換了姓、名的位置,於是結果顯示爲 Sch?ling Boris 。
需要注意的是,對於正則表達式和格式有不同的標準。 這三個函數都可以接受一個額外的參數,用於選擇具體的標準。 也可以指定是否以某一具體格式解釋特殊字符或者替代匹配正則表達式的整個字符串。
- #include <boost/regex.hpp>
- #include <locale>
- #include <iostream>
- int main()
- {
- std::locale::global(std::locale("German"));
- std::string s = "Boris Sch?ling";
- boost::regex expr("(\\w+)\\s(\\w+)");
- std::string fmt("\\2 \\1");
- std::cout << boost::regex_replace(s, expr, fmt, boost::regex_constants::format_literal) << std::endl;
- }
此程序將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 庫可以在指定某個字符爲分隔符後,遍歷字符串的部分表達式。
- #include <boost/tokenizer.hpp>
- #include <string>
- #include <iostream>
- int main()
- {
- typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
- std::string s = "Boost C++ libraries";
- tokenizer tok(s);
- for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)
- std::cout << *it << std::endl;
- }
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對象。以下例子正是這樣做的:
- #include <boost/tokenizer.hpp>
- #include <string>
- #include <iostream>
- int main()
- {
- typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
- std::string s = "Boost C++ libraries";
- boost::char_separator<char> sep(" ");
- tokenizer tok(s, sep);
- for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)
- std::cout << *it << std::endl;
- }
類 boost::char_separator 的構造函數可以接受三個參數, 只有第一個是必須的,它描述了需要隱藏的分隔符。 在本例中, 空格仍然被視爲分隔符。
第二個參數指定了需要顯示的分隔符。 在不提供此參數的情況下,將不顯示任何分隔符。 執行程序,會顯示 Boost 、 C++ 和 libraries 。
如果將加號作爲第二個參數,此例的結果將和上一個例子相同。
- #include <boost/tokenizer.hpp>
- #include <string>
- #include <iostream>
- int main()
- {
- typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
- std::string s = "Boost C++ libraries";
- boost::char_separator<char> sep(" ", "+");
- tokenizer tok(s, sep);
- for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)
- std::cout << *it << std::endl;
- }
第三個參數決定了是否顯示空的部分表達式。 如果連續找到兩個分隔符,他們之間的部分表達式將爲空。在默認情況下,這些空表達式是不會顯示的。第三個參數可以改變默認的行爲。
- #include <boost/tokenizer.hpp>
- #include <string>
- #include <iostream>
- int main()
- {
- typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
- std::string s = "Boost C++ libraries";
- boost::char_separator<char> sep(" ", "+", boost::keep_empty_tokens);
- tokenizer tok(s, sep);
- for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)
- std::cout << *it << std::endl;
- }
執行以上程序,會顯示另外兩個的空表達式。 其中第一個是在兩個加號中間的而第二個是加號和之後的空格之間的。
詞彙分割器也可用於不同的字符串類型。
- #include <boost/tokenizer.hpp>
- #include <string>
- #include <iostream>
- int main()
- {
- typedef boost::tokenizer<boost::char_separator<wchar_t>, std::wstring::const_iterator, std::wstring> tokenizer;
- std::wstring s = L"Boost C++ libraries";
- boost::char_separator<wchar_t> sep(L" ");
- tokenizer tok(s, sep);
- for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)
- std::wcout << *it << std::endl;
- }
這個例子遍歷了一個類型爲 std::wstring 的字符串。 爲了使用這個類型的字符串,必須使用另外的模板參數初始化詞彙分割器,對 boost::char_separator 類也是如此,他們都需要參數 wchar_t 初始化。
除了 boost::char_separator 類之外, Boost.Tokenizer 還提供了另外兩個類以識別部分表達式。
- #include <boost/tokenizer.hpp>
- #include <string>
- #include <iostream>
- int main()
- {
- typedef boost::tokenizer<boost::escaped_list_separator<char> > tokenizer;
- std::string s = "Boost,\"C++ libraries\"";
- tokenizer tok(s);
- for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)
- std::cout << *it << std::endl;
- }
boost::escaped_list_separator 類用於讀取由逗號分隔的多個值,這個格式的文件通常稱爲 CSV (comma separated values,逗號分隔文件),它甚至還可以處理雙引號以及轉義序列。所以本例的輸出爲 Boost 和 C++ libraries 。
另一個是 boost::offset_separator 類,必須用實例說明。 這個類的對象必須作爲第二個參數傳遞給 boost::tokenizer 類的構造函數。
- #include <boost/tokenizer.hpp>
- #include <string>
- #include <iostream>
- int main()
- {
- typedef boost::tokenizer<boost::offset_separator> tokenizer;
- std::string s = "Boost C++ libraries";
- int offsets[] = { 5, 5, 9 };
- boost::offset_separator sep(offsets, offsets + 3);
- tokenizer tok(s, sep);
- for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)
- std::cout << *it << std::endl;
- }
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 的構造函數的參數也是一個字符串,它由控制格式的特殊字符組成。 實際數據通過操作符 % 相連,在輸出中替代特殊字符,如下例所示。
- #include <boost/format.hpp>
- #include <iostream>
- int main()
- {
- std::cout << boost::format("%1%.%2%.%3%") % 16 % 9 % 2008 << std::endl;
- }
Boost.Format 類使用置於兩個百分號之間的數字作爲佔位符,佔位符稍後通過 % 操作符與實際數據連接。 以上程序使用數字16、9 和 2009 組成一個日期字符串,以 16.9.2008的格式輸出。 如果要月份出現在日期之前,即美式表示,只需交換佔位符即可。
- #include <boost/format.hpp>
- #include <iostream>
- int main()
- {
- std::cout << boost::format("%2%/%1%/%3%") % 16 % 9 % 2008 << std::endl;
- }
現在程序顯示的結果變成 9/16/2008 。
如果要使用C++ 操作器格式化數據,Boost.Format 庫提供了函數 boost::io::group() 。
- #include <boost/format.hpp>
- #include <iostream>
- int main()
- {
- std::cout << boost::format("%1% %2% %1%") % boost::io::group(std::showpos, 99) % 100 << std::endl;
- }
本例的結果顯示爲 +99 100 +99 。 因爲操作器 std::showpos() 通過 boost::io::group() 與數字 99 連接,所以只要顯示 99 , 在它前面就會自動加上加號。
如果需要加號僅在 99 第一次輸出時顯示, 則需要改造格式化佔位符。
- #include <boost/format.hpp>
- #include <iostream>
- int main()
- {
- std::cout << boost::format("%|1{1}| %2% %1%") % 99 % 100 << std::endl;
- }
爲了將輸出格式改爲 +99 100 99 ,不但需要將數據的引用符號由 1$ 變爲 1% ,還需要在其兩側各添加一個附加的管道符號,即將佔位符 %1% 替換爲 %|1$+|。
請注意,雖然一般對數據的引用不是必須的,但是所有佔位符一定要同時設置爲指定貨非指定。 以下例子在執行時會出現錯誤,因爲它給第二個和第三個佔位符設置了引用但是卻忽略了第一個。
- #include <boost/format.hpp>
- #include <iostream>
- int main()
- {
- try
- {
- std::cout << boost::format("%|+| %2% %1%") % 99 % 100 << std::endl;
- }
- catch (boost::io::format_error &ex)
- {
- std::cout << ex.what() << std::endl;
- }
- }
此程序拋出了類型爲 boost::io::format_error 的異常。 嚴格地說,Boost.Format 庫拋出的異常爲 boost::io::bad_format_string。 但是由於所有的異常類都繼承自 boost::io::format_error 類,捕捉此類型的異常會輕鬆一些。
以下例子演示了不引用數據的方法。
- #include <boost/format.hpp>
- #include <iostream>
- int main()
- {
- std::cout << boost::format("%|+| %|| %||") % 99 % 100 % 99 << std::endl;
- }
第二、第三個佔位符的管道符號可以被安全地省略,因爲在這種情況下,他們並不指定格式。這樣的語法看起來很像 std::printf ()的那種。
- #include <boost/format.hpp>
- #include <iostream>
- int main()
- {
- std::cout << boost::format("%+d %d %d") % 99 % 100 % 99 << std::endl;
- }
雖然這看起來就像 std::printf() ,但是 Boost.Format 庫有類型安全的優點。 格式化字符串中字母 'd' 的使用並不表示輸出數字,而是表示 boost::format 類所使用的內部流對象上的 std::dec() 操作器,它可以使用某些對 std::printf() 函數無意義的格式字符串,如果使用 std::printf() 會導致程序在運行時崩潰。
- #include <boost/format.hpp>
- #include <iostream>
- int main()
- {
- std::cout << boost::format("%+s %s %s") % 99 % 100 % 99 << std::endl;
- }
儘管在 std::printf() 函數中,字母 's' 只用於表示類型爲 const char* 的字符串,然而以上程序卻能正常運行。 因爲在 Boost.Format 庫中,這並不代表強制爲字符串,它會結合適當的操作器,調整內部流的操作模式。 所以即使在這種情況下,在內部流中加入數字也是沒問題的