0 摘要
一直流传这么一个说法,想成为高手,一定要多读高手写的源代码。哪些代码是好材料呢?C++标准库的源代码?不,如果您读过,就会发现:要么是各种实现独有的表达方式让人摸不着头脑,要么是恐怖的代码风格(如到处是下划线)憋得人难受。Boost库的代码则相当清晰,注释合理,命名规范,绝对是适合阅读的典范。同时,Boost内容广泛,数值计算、泛型编程、元编程、平台API……不妨从容选择自己感兴趣的部分,细细品味。
在本文中,我们将会介绍了Boost库的下载与安装,并将体验Boost库中一个非常简单实用的组件lexcial_cast。
1 Boost简介
Boost是什么?一套开放源代码、高度可移植的C++库。
谁发起的?C++标准委员会库工作组。所以,质量保证,不怕遇到假冒伪劣产品。
有些什么呢?瞧瞧:
- 正则表达式,可以与POSIX API和Perl语言处理正则表达式的功能相媲美,而且还能支持各种字符类型(如char、wchar_t,甚至还可以是自定义字符类型);
- 多线程,想了很久的跨平台多线程库了;
- 数据结构“图”,再加上即将加入标准的hash_set、hash_map、hash_multiset、hash_multimap等等(事实上不少STL实作,如SGI STL,已经支持以上数据结构),C++对数据结构的支持已近完备;
- python,没错,对Python语言的支持;
- 智能指针,与std::auto_ptr一起善加使用,可杜绝内存泄露,效率更不可和垃圾收集机制GC同日而语;
- 更有循环冗余的CRC、可轻松定义返回多个值函数的元组tuple、可容纳不同类型值的any、对标准库各方面的补充……
- 还在迅速扩大中,部分内容有望进入C++标准库……
2 Boost下载和Boost安装
去哪下载Boost呢?英文 http://www.boost.org ( 1),中文 http://boost.c-view.org,可以找到一个.zip或.tar.gz格式的压缩包。下载完毕后,解压到某个目录,比如boost_1_26_0,里面一般有这么几个子目录:boost、libs、more、people、status、tools,看看没问题就行了。
如果Boost更新时您懒得去下载整个压缩包,只希望更新发生变动的文件;或者您是一位跟我一样的Boost Fans,希望跟踪Boost的最新变化,不妨使用CVS方式。首先得有一个CVS客户端软件,比如CvsGui或http://sourceforge.net/projects/cvsgui/提供的WinCVS、gCVS和MacCVS,分别适用于Windows、Linux和MacOS平台。下载、安装、启动三步曲。
如果您习惯于传统CVS的命令行模式,那么可在Admin→Command Line...→Command line settings中输入下面一行2:
cvs -z3 -d:pserver:anonymous@cvs.boost.sourceforge.net:/cvsroot/boost checkout boost
如果您偏好GUI模式,请选择Admin→Preferences...,在General的Enter CVS ROOT中填写:
anonymous@cvs.boost.sourceforge.net:/cvsroot/boost
然后设置编译器。以Windows常用集成环境为例。Microsoft Visual C++ 6.0,可在工具→选择→目录处把Boost的路径(如前面的boost_1_26_0)添加到Include Files搜索路径中。而对于Borland C++ Builder 5.0,则是在Project→Options→Directories/Conditionals→Include Path中添加Boost的路径。还有一种比较常用的Dev-C++ 4.0(内置GNU C++,可从http://www.bloodshed.net处免费下载),可在Options→Compile Options→Directories→C++ include files处添加Boost的路径即可。其他IDE类似。至于命令行方式,则需在编译时对相应的头文件路径参数(Borland C++ Compiler、GNU C++是-I,VC++的cl是/I)给出Boost路径。
做到这一步,恭喜您,大部分Boost库就可以用了。
为什么不是全部?首先,目前还没有一个能完全符合C++标准的编译器,所以Boost库中的组件或多或少不可用,详细信息请看Boost网站上“编译器支持情况(Compiler Status)”一文。另外,有些库需要Build相应的lib或dll文件。不过这样的库很少,主要是由于平台相关性的原因,如处理正则表达式的 regex库、支持python语言的python库等,而建构库的过程相当烦琐,需要使用Jam工具(可以简单提一下:在 tools/build/jam_src/builds目录下有三个文件win32-borlandc.mk、win32-gcc.mk、win32- visualc.mk,分别是适用于Windows平台下的Borland C++ Compiler、GNU C++和Visual C++的mak文件。如果在Unix平台,则应使用tools/build/Makefile。用命令行工具make或nmake来做出Jam执行文件,然后再用Jam来建构库,详细内容可见Boost.Build文档)。我个人的建议是,不用急着去建构lib或dll。真的需要使用这些库时,再make 随库提供的mak文件即可。虽然Boost.Jam也许是Boost库未来发展的方向,不过毕竟绝大部分库都无须建构,可以直接使用。
3 Boost组件lexical_cast
这次我们先挑个简单实用的Boost组件,看看Boost能给我们带来怎样的便利。
3.1 字符串→数值
在优快云论坛上经常看到询问如何在字符串类型和数值类型间进行转换的问题,也看到了许多不同的答案。下面先讨论一下从字符串类型到数值类型的转换。
- 如何将字符串"123"转换为int类型整数123?答案是,用标准C的库函数atoi;
- 如果要转换为long类型呢?标准C的库函数atol;
- 如何将"123.12"转换为double类型呢?标准C的库函数atod;
- 如果要转换为long double类型呢?标准C的库函数atold;
- ……
后来有朋友开始使用标准库中的string类,问这个如何转换为数值?有朋友答曰,请先转换为const char*。我很佩服作答者有数学家的思维:把陌生的问题转化成熟悉的问题。(曾经有一则笑话,好事者问数学家:知道如何烧水吗?答:知道。把水壶加满水,点火烧。又问:如果水壶里已经有水了呢?答:先倒掉,就转化为我熟悉的问题了……)
不,不,这样是C的做法,不是C++。那么,C++该怎么做呢?使用Boost Conversion Library所提供的函数lexical_cast(需要引入头文件boost/lexical_cast.hpp)无疑是最简单方便的。如:
#include <boost/lexical_cast.hpp> #include <iostream> int main() { using boost::lexical_cast; int a = lexical_cast<int>("123"); double b = lexical_cast<double>("123.12"); std::cout<<a<<std::endl std::cout<<b<<std::endl; return 0; }
3.2 数值→字符串
那么从数值类型到字符串类型呢?
用itoa?不对吧,标准C/C++里根本没有这个函数。即使在Windows平台下某些编译器提供了该函数3,没有任何移植性不说,还只能解决int类型(也许其他函数还可以解决long、unsigned long等类型),浮点类型又怎么办?当然,办法还是有,那就是:sprintf。
char s[100]; sprintf(s, "%f", 123.123456);
#include <boost/lexical_cast.hpp> #include <string> #include <iostream> int main() { using std::string; const double d = 123.12; string s = boost::lexical_cast<string>(d); std::cout<<s<<std::endl; return 0; }
3.3 异常
如果转换失败,则会有异常bad_lexical_cast抛出。该异常类是标准异常类bad_cast的子类。
#include <boost/lexical_cast.hpp> #include <iostream> int main() { using std::cout; using std::endl; int i; try{ i = boost::lexical_cast<int>("abcd"); } catch(boost::bad_lexical_cast& e) { cout<<e.what()<<endl; return 1; } cout<<i<<endl; return 0; }
3.4 注意事项
lexical_cast依赖于字符流std::stringstream(会自动引入头文件 4),其原理相当简单:把源类型读入到字符流中,再写到目标类型中,就大功告成。例如
int d = boost::lexical_cast<int>("123");
int d; std::stringstream s; s<<"123"; s>>d;
- 由于Visual C++ 6的本地化(locale)部分实现有问题,因此如果使用了非默认的locale,可能会莫名其妙地抛出异常。当然,一般情况下我们并不需要去改变默认的locale,所以问题不是很大。
- 输入数据必须“完整”地转换,否则抛出bad_lexical_cast异常。例如
int i = boost::lexical_cast<int>("123.123"); // this will throw
- 浮点数的精度问题。
std::string s = boost::lexical_cast<std::string>(123.1234567);
#include <boost/limits.hpp> //... template<typename Target, typename Source> Target lexical_cast(Source arg) { //... Target result; interpreter.precision(std::numeric_limits<Source>::digits10); if( !(interpreter << arg) || !(interpreter >> result) || !(interpreter >> std::ws).eof()) //... }
4 小结
我们已经体验了boost::lexcial_cast。当然,lexical_cast不仅仅局限于字符串类型与数值类型之间的转换:可在任意可输出到stringstream的类型和任意可从stringstream输入的类型间转换。这次的了解尽管很粗略,不过毕竟我们已经“走进Boost”,而不仅仅是“走近”。以后,我们可以自行领略Boost的动人之处啦。
5 注释
[1] 如果您访问Boost英文网站出现DNS错误,不妨试试 http://64.226.201.52/。
[2] 请参考Boost文档中的“下载与安装说明(Boost Download and Installation)”部分。
[3] Borland C++ Builder提供了itoa,而Microsoft Visual C++提供了一个功能相同的函数,不过名字是_itoa。
[4] 有些不符合标准的标准库实现中,字符流类名是strstream,在头文件 中。而标准规定的是stringstream,在头文件 中。
[5] 请参考http://groups.yahoo.com/group/boost/message/15023的讨论。
[6] 非常感谢Andrew Koenig和Bjarne Stroustrup两位的指教和帮助。最开始我的想法是,指定最大精度,加入interpreter.precision(15)之类的语句,然而又担心移植性的问题。Andrew Koenig先生给出了非常明确的解释:You are quite correct that 15 is not portable across all floating-point implementations. However, it is portable across all implementations that support IEEE floating-point arithmetic, which is most computers that are in common use today. If you want to do better than that, you might consider using numeric_limits ::digits10, which is the number of significant base-10 digits that can be accurately represented in a double.(中文大意是,诚然,15并非可移植到所有浮点实现中,但对于支持IEEE浮点运算的实现来说,则的确是可移植的,而且,这也是现今绝大部分计算机所使用的。如果想做得更好一点,则可以考虑使用numeric_limits ::digits10,就能表示出10进制下double能精确表达的位数。)