boost字符串處理(上)

一、概述

    最近工作又開始忙了,額外學習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的對象,並使用與全局區域設置相同的屬性初始化。如下:

  1. #include <locale>    
  2. #include <iostream>    
  3.   
  4. int main()   
  5. {   
  6.   std::locale loc;   
  7.   std::cout << loc.name() << std::endl;   
  8. }  

    以上程序在iostream中輸出C,這就是基本區域設置的名稱,它包括了 C 語言編寫的程序中默認使用的描述。這也是每個 C++ 應用的默認全局區域設置,它包括了美式文化中使用的描述。如貨幣符號使用美元符號,基字符爲英文句號,日期中的月份用英語書寫。全局區域設置可以使用類std::locale中的靜態函數global()改變。

  1. #include <locale>    
  2. #include <iostream>    
  3.   
  4. int main()   
  5. {   
  6.   std::locale::global(std::locale("German"));   
  7.   std::locale loc;   
  8.   std::cout << loc.name() << std::endl;   
  9. }  

    靜態函數global接收類型爲std::locale的對象作爲唯一的參數,此類的另一個版本的構造函數接受類型爲const char*的字符串,可以爲一個特別的文化創建區域設置對象。然而,除了C區域設置相應地命名爲 "C" 之外,其他區域設置的名字並沒有標準化,這就依賴於接受區域設置名字的C++標準庫。VS 2008的語言字符串文檔指出,可以使用語言字符串 "German" 選擇定義爲德國文化。

    上面程序的輸出是German_Germany.1252。指定語言字符串爲 "German" 等於選擇了德國文化作爲主要語言和子語言,這裏選擇了字符映射1252。以此類推,如果想指定與德國文化不同的子語言設置,例如瑞士語,需要使用不同的語言字符串。

  1. #include <locale>    
  2. #include <iostream>    
  3.   
  4. int main()   
  5. {   
  6.   std::locale::global(std::locale("German_Switzerland"));   
  7.   std::locale loc;   
  8.   std::cout << loc.name() << std::endl;   
  9. }   

現在程序會輸出 German_Switzerland.1252 。

    在初步理解了區域設置以及如何更改全局設置後,下面的例子說明了區域設置如何影響字符串操作。

  1. #include <locale>    
  2. #include <iostream>    
  3. #include <cstring>    
  4.   
  5. int main()   
  6. {   
  7.   std::cout << std::strcoll("?""z") << std::endl;   
  8.   std::locale::global(std::locale("German"));   
  9.   std::cout << std::strcoll("?""z") << std::endl;   
  10. }   

    本例使用了定義在文件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的對象作爲附加的可選參數,若未設置會使用默認的全局區域設置。先看下這個德國區的:

 
  1. #include <boost/algorithm/string.hpp>    
  2. #include <locale>    
  3. #include <iostream>    
  4. #include <clocale>    
  5.   
  6. int main()   
  7. {   
  8.   std::setlocale(LC_ALL, "German");   
  9.   std::string s = "Boris Sch?ling";   
  10.   std::cout << boost::algorithm::to_upper_copy(s) << std::endl;   
  11.   std::cout << boost::algorithm::to_upper_copy(s, std::locale("German")) << std::endl;   
  12. }  

    函數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():

  1. #include <boost/algorithm/string.hpp>    
  2. #include <locale>    
  3. #include <iostream>    
  4.   
  5. int main()   
  6. {   
  7.   std::locale::global(std::locale("German"));   
  8.   std::string s = "Boris Sch?ling";   
  9.   boost::iterator_range<std::string::iterator> r = boost::algorithm::find_first(s, "Boris");   
  10.   std::cout << r << std::endl;   
  11.   r = boost::algorithm::find_first(s, "xyz");   
  12.   std::cout << r << std::endl;   
  13. }   

    以下各個不同函數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作爲第一個結果輸出而第二個結果爲空字符串。

  1. #include <boost/algorithm/string.hpp>    
  2. #include <locale>    
  3. #include <iostream>    
  4. #include <vector>    
  5.   
  6. int main()   
  7. {   
  8.   std::locale::global(std::locale("German"));   
  9.   std::vector<std::string> v;   
  10.   v.push_back("Boris");   
  11.   v.push_back("Sch?ling");   
  12.   std::cout << boost::algorithm::join(v, " ") << std::endl;   
  13. }   

    函數boost::algorithm::join()接受一個字符串的容器作爲第一個參數,根據第二個參數將這些字符串連接起來。相應地這個例子會輸出Boris Sch?ling。

  1. #include <boost/algorithm/string.hpp>    
  2. #include <locale>    
  3. #include <iostream>    
  4.   
  5. int main()   
  6. {   
  7.   std::locale::global(std::locale("German"));   
  8.   std::string s = "Boris Sch?ling";   
  9.   std::cout << boost::algorithm::replace_first_copy(s, "B""D") << std::endl;   
  10.   std::cout << boost::algorithm::replace_nth_copy(s, "B", 0, "D") << std::endl;   
  11.   std::cout << boost::algorithm::replace_last_copy(s, "B""D") << std::endl;   
  12.   std::cout << boost::algorithm::replace_all_copy(s, "B""D") << std::endl;   
  13.   std::cout << boost::algorithm::replace_head_copy(s, 5, "Doris") << std::endl;   
  14.   std::cout << boost::algorithm::replace_tail_copy(s, 8, "Becker") << std::endl;   
  15. }   

    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() 等等。 它們的使用方法同查找和刪除函數是差不多一樣的,所不同的是還需要一個替代字符串作爲附加參數。

  1. #include <boost/algorithm/string.hpp>    
  2. #include <locale>    
  3. #include <iostream>    
  4.   
  5. int main()   
  6. {   
  7.   std::locale::global(std::locale("German"));   
  8.   std::string s = "\t Boris Sch?ling \t";   
  9.   std::cout << "." << boost::algorithm::trim_left_copy(s) << "." << std::endl;   
  10.   std::cout << "." <<boost::algorithm::trim_right_copy(s) << "." << std::endl;   
  11.   std::cout << "." <<boost::algorithm::trim_copy(s) << "." << std::endl;   
  12. }   

    可以使用修剪函數 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()。

  1. #include <boost/algorithm/string.hpp>    
  2. #include <locale>    
  3. #include <iostream>    
  4.   
  5. int main()   
  6. {   
  7.   std::locale::global(std::locale("German"));   
  8.   std::string s = "--Boris Sch?ling--";   
  9.   std::cout << "." << boost::algorithm::trim_left_copy_if(s, boost::algorithm::is_any_of("-")) << "." << std::endl;   
  10.   std::cout << "." <<boost::algorithm::trim_right_copy_if(s, boost::algorithm::is_any_of("-")) << "." << std::endl;   
  11.   std::cout << "." <<boost::algorithm::trim_copy_if(s, boost::algorithm::is_any_of("-")) << "." << std::endl;   
  12. }   

    以上程序調用了一個輔助函數boost::algorithm::is_any_of(),它用於生成謂詞以驗證作爲參數傳入的字符是否在給定的字符串中存在。使用函數boost::algorithm::is_any_of後,正如例子中做的那樣,修剪字符串的字符被指定爲連字符。Boost.StringAlgorithms類也提供了衆多返回通用謂詞的輔助函數。

  1. #include <boost/algorithm/string.hpp>    
  2. #include <locale>    
  3. #include <iostream>    
  4.   
  5. int main()   
  6. {   
  7.   std::locale::global(std::locale("German"));   
  8.   std::string s = "123456789Boris Sch?ling123456789";   
  9.   std::cout << "." << boost::algorithm::trim_left_copy_if(s, boost::algorithm::is_digit()) << "." << std::endl;   
  10.   std::cout << "." <<boost::algorithm::trim_right_copy_if(s, boost::algorithm::is_digit()) << "." << std::endl;   
  11.   std::cout << "." <<boost::algorithm::trim_copy_if(s, boost::algorithm::is_digit()) << "." << std::endl;   
  12. }   

    函數boost::algorithm::is_digit()返回的謂詞在字符爲數字時返回布爾值true。檢查字符是否爲大寫或小寫的輔助函數分別是boost::algorithm::is_upper()和boost::algorithm::is_lower()。所有這些函數都默認使用全局區域設置,除非在參數中指定其他區域設置。

    除了檢驗單獨字符的謂詞之外,Boost.StringAlgorithms庫還提供了處理字符串的函數。

  1. #include <boost/algorithm/string.hpp>    
  2. #include <locale>    
  3. #include <iostream>    
  4.   
  5. int main()   
  6. {   
  7.   std::locale::global(std::locale("German"));   
  8.   std::string s = "Boris Sch?ling";   
  9.   std::cout << boost::algorithm::starts_with(s, "Boris") << std::endl;   
  10.   std::cout << boost::algorithm::ends_with(s, "Sch?ling") << std::endl;   
  11.   std::cout << boost::algorithm::contains(s, "is") << std::endl;   
  12.   std::cout << boost::algorithm::lexicographical_compare(s, "Boris") << std::endl;   
  13. }   

    函數boost::algorithm::starts_with()、boost::algorithm::ends_with、boost::algorithm::contains和boost::algorithm::lexicographical_compare()均可以比較兩個字符串。

    下面再介紹一個字符串切割函數。

  1. #include <boost/algorithm/string.hpp>    
  2. #include <locale>    
  3. #include <iostream>    
  4. #include <vector>    
  5.   
  6. int main()   
  7. {   
  8.   std::locale::global(std::locale("German"));   
  9.   std::string s = "Boris Sch?ling";   
  10.   std::vector<std::string> v;   
  11.   boost::algorithm::split(v, s, boost::algorithm::is_space());   
  12.   std::cout << v.size() << std::endl;   
  13. }   

    在給定分界符後,使用函數 boost::algorithm::split() 可以將一個字符串拆分爲一個字符串容器。 它需要給定一個謂詞作爲第三個參數以判斷應該在字符串的哪個位置分割。 這個例子使用了輔助函數 boost::algorithm::is_space() 創建一個謂詞,在每個空格字符處分割字符串。

    本節中許多函數都有忽略字符串大小寫的版本, 這些版本一般都有與原函數相似的名稱,所相差的只是以'i'.開頭。例如,與函數 boost::algorithm::erase_all_copy() 相對應的是函數 boost::algorithm::ierase_all_copy()。

    最後,值得注意的是類Boost.StringAlgorithms中許多函數都支持正則表達式。以下程序使用函數boost::algorithm::find_regex()搜索正則表達式。

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

爲了使用正則表達式,此程序使用了Boost C++庫中的boost::regex,這將在下一節介紹。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章