四、正则表达式库 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 库,熟练地使用正则表达式是个明智的选择。
五、 词汇分割器库 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 库中,这并不代表强制为字符串,它会结合适当的操作器,调整内部流的操作模式。 所以即使在这种情况下,在内部流中加入数字也是没问题的