一、概述
最近工作又開始忙了,額外學習boost的機會也變少了很多,再加上在使用Boost時出現了很多編譯錯誤的問題,讓寫文章的過程變得不可預測了。但我還是很期待這一部分,這是在平時應用中最常見的,也是boost的看家本領了,將會著重介紹。在標準 C++ 中,用於處理字串的是std::string 類,它提供很多字串操作,包括尋找指定字元或子串的函數。儘管
std::string囊括了百餘函數,是標準C++中最為臃腫的類之一,但卻仍不能滿足很多開發人員在日常工作中的需要。例如, Java中提供的可以將字串轉換到大寫字母的函數,std::string就沒有相應的功能。Boost C++ 庫試圖彌補這一缺憾。
二、地區設定
進入正題之前,需要先看一下地區設定的問題,本章中提到的很多函數都需要一個附加的地區設定參數。地區設定在標準 C++ 中封裝了文化習俗相關的內容,包括貨幣符號、日期時間格式、分隔整數部分與分數部分的符號(基數符)以及多於三個數字時的分隔字元(千位符)。
在字串處理方面,地區設定和特定文化中對字元次序以及特殊字元的描述有關。例如,字母表中是否含有變異母音字母以及其在字母表中的位置都由語言文化決定。如果一個函數用於將字串轉換為大寫形式,那麼其實施步驟取決於具體的地區設定。在德語中,字母'ä' 顯然要轉換為'Ä',然而在其他語言中並不一定。
使用類std::string時地區設定可以忽略, 因為它的函數均不依賴於特定語言。 然而在本章中為了使用 Boost C++ 庫, 地區設定的知識是必不可少的。C++標準中在 locale 檔案中定義了類 std::locale 。每個 C++ 程式自動擁有一個此類的執行個體,即不能直接存取的全域地區設定。如果要訪問它,需要使用預設建構函式構造類std::locale的對象,並使用與全域地區設定相同的屬性初始化。如下:
#include <locale> #include <iostream> int main() { std::locale loc; std::cout << loc.name() << std::endl; }
以上程式在iostream中輸出C,這就是基本地區設定的名稱,它包括了 C 語言編寫的程式中預設使用的描述。這也是每個 C++ 應用的預設全域地區設定,它包括了美式文化中使用的描述。如貨幣符號使用貨幣符號,基字元為英文句號,日期中的月份用英語書寫。全域地區設定可以使用類std::locale中的靜態函數global()改變。
#include <locale> #include <iostream> int main() { std::locale::global(std::locale("German")); std::locale loc; std::cout << loc.name() << std::endl; }
靜態函數global接收類型為std::locale的對象作為唯一的參數,此類的另一個版本的建構函式接受類型為const char*的字串,可以為一個特別的文化建立地區設定對象。然而,除了C地區設定相應地命名為 "C" 之外,其他地區設定的名字並沒有標準化,這就依賴於接受地區設定名字的C++標準庫。VS 2008的語言字串文檔指出,可以使用語言字串
"German" 選擇定義為德國文化。
上面程式的輸出是German_Germany.1252。指定語言字串為 "German" 等於選擇了德國文化作為主要語言和子語言,這裡選擇了字元對應表1252。以此類推,如果想指定與德國文化不同的子語言設定,例如瑞士語,需要使用不同的語言字串。
#include <locale> #include <iostream> int main() { std::locale::global(std::locale("German_Switzerland")); std::locale loc; std::cout << loc.name() << std::endl; }
現在程式會輸出 German_Switzerland.1252 。
在初步理解了地區設定以及如何更改全域設定後,下面的例子說明了地區設定如何影響字串操作。
#include <locale> #include <iostream> #include <cstring> int main() { std::cout << std::strcoll("ä", "z") << std::endl; std::locale::global(std::locale("German")); std::cout << std::strcoll("ä", "z") << std::endl; }
本例使用了定義在檔案cstring中的函數 std::strcoll() ,該函數用於按照字典順序比較第一個字串是否小於第二個。也就是兩個字串中哪一個在字典中靠前(鬱悶了,VC中居然不讓輸入ä,自動變成了’?’)。執行程式,得到結果為1和-1。雖然函數的參數是一樣的, 卻得到了不同的結果。 原因很簡單,在第一次調用函數 std::strcoll() 時,使用了全域 C
地區設定; 而在第二次調用時,全域地區設定更改為德國文化。 從輸出中可以看出,在這兩種地區設定中,字元'ä'和'z'的次序是不同的。
很多C 函數以及 C++ 流都與地區設定有關。儘管類 std::string 中的函數是與地區設定獨立工作的, 但是以下各節中提到的函數並不是這樣。 所以,在本章中還會多次提到地區設定的相關內容。
三、字串演算法庫 Boost.StringAlgorithms
Boost C++字串演算法庫提供了很多字元操作函數,操作的字串類型可以為std:;string、std::wstring或任何其他模板類std::basic_string的執行個體。使用時需包含標頭檔boost/algorithm/string.hpp,這個庫中很多函數都可以接受類型為std::local的對象作為附加的選擇性參數,若未設定會使用預設的全域地區設定。先看下這個德國區的:
#include <boost/algorithm/string.hpp> #include <locale> #include <iostream> #include <clocale> int main() { std::setlocale(LC_ALL, "German"); std::string s = "Boris Schäling"; std::cout << boost::algorithm::to_upper_copy(s) << std::endl; std::cout << boost::algorithm::to_upper_copy(s, std::locale("German")) << std::endl; }
函數to_upper_copy用於轉換一個字串為大寫,它返迴轉換後的字串。上面代碼第一次調用時使用的是預設全域地區設定, 第二次調用時則明確將地區設定為德國文化。顯然後者的轉換是正確的, 因為小寫字母 'ä' 對應的大寫形式 'Ä' 是存在的。而在C地區設定中, ä' 是一個未知字元所以不能轉換。為了能得到正確結果,必須明確傳遞正確的地區設定參數或者在調用 boost::algorithm::to_upper_copy()
之前改變全域地區設定。可以注意到,程式使用了定義在標頭檔 clocale 中的函數 std::setlocale() 為 C 函數進列區域設定, 因為 std::cout 使用 C 函數在螢幕上顯示資訊。 在設定了正確的地區後,才可以正確顯示 'ä' 和 'Ä' 等母音字母。另外,程式中的setlocale函數可以用std::locale::global代替,同為全域地區設定操作。
Boost.StringAlgorithms 庫還提供了幾個從字串中刪除單獨字母的函數, 可以明確指定在哪裡刪除,如何刪除。例如,可以使用函數boost::algorithm::erase_all_copy()從整個字串中刪除特定的某個字元,若想只在此字元首次出現時刪除,可以使用函數 boost::algorithm::erase_first_copy()。如果要在字串頭部或尾部刪除若干字元,可以使用函數boost::algorithm::erase_head_copy()和boost::algorithm::erase_tail_copy():
#include <boost/algorithm/string.hpp> #include <locale> #include <iostream> int main() { std::locale::global(std::locale("German")); std::string s = "Boris Schäling"; boost::iterator_range<std::string::iterator> r = boost::algorithm::find_first(s, "Boris"); std::cout << r << std::endl; r = boost::algorithm::find_first(s, "xyz"); std::cout << r << std::endl; }
以下各個不同函數boost::algorithm::find_first()、boost::algorithm::find_last()、 boost::algorithm::find_nth()、boost::algorithm::find_head()以及boost::algorithm::find_tail()可以用於在字串中尋找子串。
上面的程式還用到了一個boost::iterator_range,這個迭代器是所有這些函數的傳回型別。此類起源於Boost C++的Boost.Range庫,它在迭代器的概念上定義了“範圍”。因為操作符<<由boost::iterator_range類重載而來,單個搜尋演算法的結果可以直接寫入標準輸出資料流。以上程式將Boris作為第一個結果輸出而第二個結果為空白字串。
#include <boost/algorithm/string.hpp> #include <locale> #include <iostream> #include <vector> int main() { std::locale::global(std::locale("German")); std::vector<std::string> v; v.push_back("Boris"); v.push_back("Schäling"); std::cout << boost::algorithm::join(v, " ") << std::endl; }
函數boost::algorithm::join()接受一個字串的容器作為第一個參數,根據第二個參數將這些字串串連起來。相應地這個例子會輸出Boris Schäling。
#include <boost/algorithm/string.hpp> #include <locale> #include <iostream> int main() { std::locale::global(std::locale("German")); std::string s = "Boris Schäling"; std::cout << boost::algorithm::replace_first_copy(s, "B", "D") << std::endl; std::cout << boost::algorithm::replace_nth_copy(s, "B", 0, "D") << std::endl; std::cout << boost::algorithm::replace_last_copy(s, "B", "D") << std::endl; std::cout << boost::algorithm::replace_all_copy(s, "B", "D") << std::endl; std::cout << boost::algorithm::replace_head_copy(s, 5, "Doris") << std::endl; std::cout << boost::algorithm::replace_tail_copy(s, 8, "Becker") << std::endl; }
Boost.StringAlgorithms 庫不但提供了尋找子串或刪除字母的函數, 而且提供了使用字串替代子串的函數,包括 boost::algorithm::replace_first_copy(), boost::algorithm::replace_nth_copy(), boost::algorithm::replace_last_copy(), boost::algorithm::replace_all_copy(),
boost::algorithm::replace_head_copy() 以及 boost::algorithm::replace_tail_copy() 等等。 它們的使用方法同尋找和刪除函數是差不多一樣的,所不同的是還需要一個替代字串作為附加參數。
#include <boost/algorithm/string.hpp> #include <locale> #include <iostream> int main() { std::locale::global(std::locale("German")); std::string s = "\t Boris Schäling \t"; std::cout << "." << boost::algorithm::trim_left_copy(s) << "." << std::endl; std::cout << "." <<boost::algorithm::trim_right_copy(s) << "." << std::endl; std::cout << "." <<boost::algorithm::trim_copy(s) << "." << std::endl; }
可以使用修剪函數 boost::algorithm::trim_left_copy(), boost::algorithm::trim_right_copy() 以及 boost::algorithm::trim_copy() 等自動去除字串中的空格或者字串的結束符。什麼字元是空格取決於全域地區設定。
Boost.StringAlgorithms庫的函數可以接受一個附加的謂詞參數,以決定函數作用於字串的哪些字元。謂詞版本的修剪函數相應地被命名為boost::algorithm::trim_left_copy_if(), boost::algorithm::trim_right_copy_if()和boost::algorithm::trim_copy_if()。
#include <boost/algorithm/string.hpp> #include <locale> #include <iostream> int main() { std::locale::global(std::locale("German")); std::string s = "--Boris Schäling--"; std::cout << "." << boost::algorithm::trim_left_copy_if(s, boost::algorithm::is_any_of("-")) << "." << std::endl; std::cout << "." <<boost::algorithm::trim_right_copy_if(s, boost::algorithm::is_any_of("-")) << "." << std::endl; std::cout << "." <<boost::algorithm::trim_copy_if(s, boost::algorithm::is_any_of("-")) << "." << std::endl; }
以上程式調用了一個輔助函數boost::algorithm::is_any_of(),它用於產生謂詞以驗證作為參數傳入的字元是否在給定的字串中存在。使用函數boost::algorithm::is_any_of後,正如例子中做的那樣,修剪字串的字元被指定為連字號。Boost.StringAlgorithms類也提供了眾多返回通用謂詞的輔助函數。
#include <boost/algorithm/string.hpp> #include <locale> #include <iostream> int main() { std::locale::global(std::locale("German")); std::string s = "123456789Boris Schäling123456789"; std::cout << "." << boost::algorithm::trim_left_copy_if(s, boost::algorithm::is_digit()) << "." << std::endl; std::cout << "." <<boost::algorithm::trim_right_copy_if(s, boost::algorithm::is_digit()) << "." << std::endl; std::cout << "." <<boost::algorithm::trim_copy_if(s, boost::algorithm::is_digit()) << "." << std::endl; }
函數boost::algorithm::is_digit()返回的謂詞在字元為數字時返回布爾值true。檢查字元是否為大寫或小寫輔助函數分別是boost::algorithm::is_upper()和boost::algorithm::is_lower()。所有這些函數都預設使用全域地區設定,除非在參數中指定其他地區設定。
除了檢驗單獨字元的謂詞之外,Boost.StringAlgorithms庫還提供了處理字串的函數。
#include <boost/algorithm/string.hpp> #include <locale> #include <iostream> int main() { std::locale::global(std::locale("German")); std::string s = "Boris Schäling"; std::cout << boost::algorithm::starts_with(s, "Boris") << std::endl; std::cout << boost::algorithm::ends_with(s, "Schäling") << std::endl; std::cout << boost::algorithm::contains(s, "is") << std::endl; std::cout << boost::algorithm::lexicographical_compare(s, "Boris") << std::endl; }
函數boost::algorithm::starts_with()、boost::algorithm::ends_with、boost::algorithm::contains和boost::algorithm::lexicographical_compare()均可以比較兩個字串。
下面再介紹一個字串切割函數。
#include <boost/algorithm/string.hpp> #include <locale> #include <iostream> #include <vector> int main() { std::locale::global(std::locale("German")); std::string s = "Boris Schäling"; std::vector<std::string> v; boost::algorithm::split(v, s, boost::algorithm::is_space()); std::cout << v.size() << std::endl; }
在給定分界符後,使用函數 boost::algorithm::split() 可以將一個字串拆分為一個字串容器。 它需要給定一個謂詞作為第三個參數以判斷應該在字串的哪個位置分割。 這個例子使用了輔助函數 boost::algorithm::is_space() 建立一個謂詞,在每個空白字元處分割字串。
本節中許多函數都有忽略字串大小寫版本, 這些版本一般都有與原函數相似的名稱,所相差的只是以'i'.開頭。例如,與函數 boost::algorithm::erase_all_copy() 相對應的是函數 boost::algorithm::ierase_all_copy()。
最後,值得注意的是類Boost.StringAlgorithms中許多函數都支援Regex。以下程式使用函數boost::algorithm::find_regex()搜尋Regex。
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string/regex.hpp> #include <locale> #include <iostream> int main() { std::locale::global(std::locale("German")); std::string s = "Boris Schäling"; boost::iterator_range<std::string::iterator> r = boost::algorithm::find_regex(s, boost::regex("\\w\\s\\w")); std::cout << r << std::endl; }
為了使用Regex,此程式使用了Boost C++庫中的boost::regex,這將在下一節介紹。