天天看點

boost之路 五 字元串處理



在标準 C++ 中,用于處理字元串的是 ​

​std::string​

​​ 類,它提供很多字元串操作,包括查找指定字元或子串的函數。 盡管 ​

​std::string​

​​ 囊括了百餘函數,是标準 C++ 中最為臃腫的類之一,然而卻并不能滿足很多開發者在日常工作中的需要。 例如, Java 和 .Net 提供了可以将字元串轉換到大寫字母的函數,而 ​

​std::string​

​ 就沒有相應的功能。 Boost C++ 庫試圖彌補這一缺憾。

5.2. 區域設定

在進入正題之前,有必要先審視下區域設定的問題,本章中提到的很多函數都需要一個附加的區域設定參數。

區域設定在标準 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;

}

  • ​​下載下傳源代碼​​

以上程式在标準輸出流輸出 ​

​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++ 标準庫。 在使用 Visual Studio 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()​

​ ,這個函數用于按照字典順序比較第一個字元串是否小于第二個。 換言之,它會判斷這兩個字元串中哪一個在字典中靠前。

執行程式,得到結果為 ​

​1​

​​ 和 ​

​ -1​

​​ 。 雖然函數的參數是一樣的, 卻得到了不同的結果。 原因很簡單,在第一次調用函數 ​

​std::strcoll()​

​ 時,使用了全局 C 區域設定; 而在第二次調用時,全局區域設定更改為德國文化。 從輸出中可以看出,在這兩種區域設定中,字元 'ä' 和 'z' 的次序是不同的。

很多 C 函數以及 C++ 流都與區域設定有關。 盡管類 ​

​std::string​

​ 中的函數是與區域設定獨立工作的, 但是以下各節中提到的函數并不是這樣。 是以,在本章中還會多次提到區域設定的相關内容。

5.3. 字元串算法庫 Boost.StringAlgorithms

Boost C++ 字元串算法庫 ​​ Boost.StringAlgorithms​​​ 提供了很多字元串操作函數。 字元串的類型可以是 ​

​std::string​

​​, ​

​std::wstring​

​​ 或任何其他模闆類 ​

​std::basic_string​

​ 的執行個體。

這些函數分類别在不同的頭檔案定義。 例如,大小寫轉換函數定義在檔案 ​

​boost/algorithm/string/case_conv.hpp​

​​ 中。 因為 Boost.StringAlgorithms 類中包括超過20個類别和相同數目的頭檔案, 為了友善起見,頭檔案 ​

​boost/algorithm/string.hpp​

​ 包括了所有其他的頭檔案。 後面所有例子都會使用這個頭檔案。

正如上節提到的那樣, Boost.StringAlgorithms 庫中許多函數 都可以接受一個類型為 ​

​std::locale​

​ 的對象作為附加參數。 而此參數是可選的,如果不設定将使用預設全局區域設定。

#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; 
}      
  • ​​下載下傳源代碼​​

函數 ​

​boost::algorithm::to_upper_copy()​

​​ 用于 轉換一個字元串為大寫形式,自然也有提供相反功能的函數 —— ​

​boost::algorithm::to_lower_copy()​

​​ 把字元串轉換為小寫形式。 這兩個函數都傳回轉換過的字元串作為結果。 如果作為參數傳入的字元串自身需要被轉換為大(小)寫形式,可以使用函數 ​

​boost::algorithm::to_upper()​

​​ 或 ​

​ boost::algorithm::to_lower ()​

​。

上面的例子使用函數 ​

​boost::algorithm::to_upper_copy()​

​ 把字元串 "Boris Schäling" 轉換為大寫形式。 第一次調用時使用的是預設全局區域設定, 第二次調用時則明确将區域設定為德國文化。

顯然後者的轉換是正确的, 因為小寫字母 'ä' 對應的大寫形式 'Ä' 是存在的。 而在 C 區域設定中, 'ä' 是一個未知字元是以不能轉換。 為了能得到正确結果,必須明确傳遞正确的區域設定參數或者在調用 ​

​boost::algorithm::to_upper_copy()​

​ 之前改變全局區域設定。

可以注意到,程式使用了定義在頭檔案 ​

​clocale​

​​ 中的函數 ​

​ std::setlocale()​

​ 為 C 函數進行區域設定, 因為 std::cout

#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::to_upper_copy(s) << std::endl; 
  std::cout << boost::algorithm::to_upper_copy(s, std::locale("German")) << std::endl; 
}      
  • ​​下載下傳源代碼​​

上述程式将全局區域設定設為德國文化,這使得對函數 ​

​boost::algorithm::to_upper_copy()​

​ 的調用 可以将 'ä' 轉換為 'Ä' 。

注意到本例并沒有調用函數 ​

​std::setlocale()​

​​ 。 使用函數 ​

​ std::locale::global()​

​​ 設定全局區域設定後, 也自動進行了 C 區域設定。 實際上,C++ 程式幾乎總是使用函數 ​

​ std::locale::global()​

​​ 進行全局區域設定, 而不是像前面的例子那樣使用函數 ​

​std::setlocale()​

​ 。

#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::erase_first_copy(s, "i") << std::endl; 
  std::cout << boost::algorithm::erase_nth_copy(s, "i", 0) << std::endl; 
  std::cout << boost::algorithm::erase_last_copy(s, "i") << std::endl; 
  std::cout << boost::algorithm::erase_all_copy(s, "i") << std::endl; 
  std::cout << boost::algorithm::erase_head_copy(s, 5) << std::endl; 
  std::cout << boost::algorithm::erase_tail_copy(s, 8) << std::endl; 
}      
  • ​​下載下傳源代碼​​

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 中許多函數都支援正規表達式。 以下程式使用函數 ​

​ boost::algorithm::find_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; 
}      
  • ​​下載下傳源代碼​​

為了使用正規表達式,此程式使用了Boost C++ 庫中的 ​

​boost::regex​

​ , 這将在下一節介紹。

5.4. 正規表達式庫 Boost.Regex

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

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 庫,熟練地使用正規表達式是個明智的選擇。

5.5. 詞彙分割器庫 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​

​ 。

5.6. 格式化輸出庫 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$+| %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; 
}      
  • ​​下載下傳源代碼​​