一个工具、一个库和一部小说

Visual Studio 2010发布有几个月了,但是中文版一直到5月底才有。软件一拿到手,我就迫不及待地安装,想一睹Visual C++ 2010的风采。

对于Visual C++,我比较关心的几个问题有:一、对新的C++ 0x支持如何?二、能否顺利编译和使用Boost?说到底,这两个问题差不多是一个问题,因为大家都知道Boost和C++0x的关系,C++0x中的很多特性,其实都是从Boost中来的。

首先来说Visual C++对C++0x的新特性的支持,主要有四点:一、auto关键字的新意义,有了该特性,我们就不用再在使用STL 的iterator时写一长串代码了,编译器可以自动推断其类型;二、static_assert关键字,该特性不稀奇,Boost里面早就有了;三、右值引用,这个很好,主要解决了移动语意和完美转发的问题,在Visual Studio的欢迎页中链接了一篇文档就是讲的这个东西,还有一篇文档讲了我们在设计类时,怎么编写移动构造函数和移动赋值运算符,值得一看。(这里的移动指的是move,和copy相对,可不是指中国移动哦)四、lambda表达式,这个也是Boost中早就有的功能。

毕竟C++0x标准还没有发布,Visual C++支持到这一步,已经很不错了,还差的几个方面是:Concepts、可变模板参数、多线程内存模型。。。说实话,具体还有多少我也说不清楚。

至于纳入Visual C++的标准库的东西,还是只有tr1,这在Visual Studio 2008时代已经有了,没什么新意。要想找激情,还是去深度探索Boost吧。安装好Visual Studio 2010后,我马上就下载了最近的Boost,按照文档的说明进行安装,安装非常的顺利。

为了使用Visual C++ 2010和Boost,我给自己找了点事,那就是编程去提取新浪读书频道上的小说。程序进行得很顺利,只有区区150行,请看:
  1  #include  < iostream >
  2  #include  < string >
  3  #include  < boost\lexical_cast.hpp >
  4  #include  < boost\asio.hpp >
  5  #include  < boost\regex.hpp >
  6 
  7  using   namespace  std;
  8  using   namespace  boost;
  9 
 10  string  clearUp(stringstream &  input){
 11       /*
 12      下面的代码使用boost::regex库进行字符串的替换
 13       */
 14      regex patternOfTitle( " (.*)<h1>(.*)</h1>(.*) " );
 15      regex patternOfBody( " (.*)<div id=\ " contTxt\ "  class=\ " contTxt1\ " ><p>(.*)</p></div>(.*) " );
 16 
 17       string  line;
 18       string  output;
 19      smatch results;
 20       int  status  =   0 ;
 21       while (getline(input,line)){
 22           if (status  ==   0 ){ // 还没有碰到标题
 23               if (regex_match(line,results,patternOfTitle)){
 24                  output  +=  results[ 2 ];
 25                  output  +=   " \r\n " ;
 26                  status  =   1 // 处理完标题
 27              }
 28          } else { // 处理正文
 29               if (regex_match(line,results,patternOfBody)){
 30                  output  +=  results[ 2 ];
 31                  status  =   2
 32                   break ;
 33              }
 34          }
 35      }
 36       // 将output中的</p><p>替换成回车换行符
 37      regex patternToReplace( " </p><p> " );
 38       return  regex_replace(output,patternToReplace, " \r\n " );
 39  }
 40 
 41  void  getChapter(ostream &  ostream,vector < int >&  args){
 42       /*
 43      以下代码使用boost::asio库
 44      具体用法请参考boost文档
 45       */
 46      asio::io_service io_service;
 47      asio::ip::tcp::resolver resolver(io_service);
 48      asio::ip::tcp::resolver::query query( " vip.book.sina.com.cn " , " http " );
 49      asio::ip::tcp::resolver::iterator endpoint_iterator  =  resolver.resolve(query);
 50      asio::ip::tcp::resolver::iterator end;
 51      system::error_code error  =  asio::error::host_not_found;
 52      asio::ip::tcp::socket socket(io_service);
 53       while  (error  &&  endpoint_iterator  !=  end)
 54      {
 55        socket.close();
 56        socket.connect( * endpoint_iterator ++ , error);
 57      }
 58       if  (error)
 59         throw  system::system_error(error);
 60 
 61      asio::streambuf request;
 62      std::ostream request_stream( & request);
 63      
 64      request_stream  <<   " GET  "   <<   " /book/chapter_ " <<  args[ 0 <<   " _ "   <<  args[ 1 <<   " .html "   <<   "  HTTP/1.0\r\n " ;
 65      request_stream  <<   " Host:  "   <<   " vip.book.sina.com.cn "   <<   " \r\n " ;
 66      request_stream  <<   " Accept: */*\r\n " ;
 67      request_stream  <<   " Connection: close\r\n\r\n " ;
 68 
 69      asio::write(socket, request);
 70 
 71      asio::streambuf response;
 72      asio::read_until(socket, response,  " \r\n " );
 73 
 74      std::istream response_stream( & response);
 75       string  http_version;
 76      response_stream  >>  http_version;
 77      unsigned  int  status_code;
 78      response_stream  >>  status_code;
 79       string  status_message;
 80      getline(response_stream, status_message);
 81       if  ( ! response_stream  ||  http_version.substr( 0 5 !=   " HTTP/ " )
 82      {
 83        cout  <<   " Invalid response\n " ;
 84         throw  system::system_error(error);
 85      }
 86       if  (status_code  !=   200 )
 87      {
 88        ostream  <<   " Response returned with status code  "   <<  status_code  <<   " \n " ;
 89         throw  system::system_error(error);
 90      }
 91 
 92       //  读取数据,并把数据放入一个string中,这里用到std::stringstream
 93      stringstream content;
 94       while  (asio::read(socket, response,
 95            asio::transfer_at_least( 1 ), error))
 96        content  <<   &  response;
 97       if  (error  !=  asio::error::eof)
 98         throw  system::system_error(error);
 99 
100       // 调用clearUp函数,从杂乱的HTML文件中提取纯文本的小说
101      ostream  <<  clearUp(content);
102  }
103 
104  void  getBook(ostream &  ostream,vector < int >&  args){
105       /*
106      如果没有到最后一章,则执行循环
107      以下载所有章节
108       */
109       while (args[ 1 <=  args[ 2 ]){
110          getChapter(ostream,args);
111          args[ 1 ] ++ ;
112      }
113  }
114 
115  int  _tmain( int  argc, _TCHAR *  argv[])
116  {
117       /*
118      新浪读书频道的URL地址为“ http://vip.book.sina.com.cn/book/chapter_120954_83221.html ”的形式
119      其中的两个数字一个代表书的ID,一个代表章节的ID
120      所以我们的程序需要接受的参数有三个,分别为书的ID,第一章的ID和最后一章的ID,该程序自动下载从
121      第一章到最后一章的内容,并整理
122      该程序的使用方法为:
123      GetBookFromSina bookId firstChapterId lastChapterId
124       */
125 
126       /*
127      下面的程序片段使用lexical_cast库来将命令行输入的参数转换为整数
128       */
129       if (argc  !=   4 ){
130          cout  <<   " 输入不正确!正确的输入为:GetBookFromSina bookId firstChapterId lastChapterId "   <<  endl;
131           return   0 ;
132      }
133      vector < int >  args;
134       try {
135           for ( int  i = 1 ; i  <   4 ; i ++ ){
136              args.push_back(lexical_cast < int > (argv[i]));
137          }
138      } catch (bad_lexical_cast  & ){
139          cout  <<   " 输入不正确!正确的输入为:GetBookFromSina bookId firstChapterId lastChapterId "   <<  endl;
140          cout  <<   " 请确定输入的参数为整数 "   <<  endl;
141           return   0 ;
142      }
143 
144       /*
145      调用getBook函数,输入一个ostream &类型的参数和一个vector<int>&参数
146      如果输入的是cout,则输出到控制台
147      也可以把输出流输出到文件或字符串,只需要传入不同的参数即可
148 
149      之所以不让getBook函数返回字符串,而是接受一个流对象作为参数,是因为getBook中有一个循环
150      如果要返回字符串的话,需要把很多字符串连接成一个更大的字符串,影响效率
151       */
152       try {
153          getBook(cout,args);
154      } catch (system::system_error & ){
155          cout  <<   " 获取文章的过程中发生错误 "   <<  endl;
156      }
157  }
158 

下面奉上一个截图,让大家看看提取小说的效果:
一个工具、一个库和一部小说_第1张图片 

以上的代码大家不要从头开始读,要从最底下的main函数一个一个往上读。在main函数中,使用了Boost的lexical_cast将命令行的参数从字符串转化成int,如果输入的参数非法,程序就会退出。在main中调用getBook函数,也许大家觉得给getBook函数传入一个cout流对象不妥,侵入性太大,但是这确实最有效率的办法,因为如果让getBook函数返回字符串的话,那将是一个非常大的字符串,而且要在getBook里面讲一百多个字符串组装成这个大字符串,效率很低。在getBook中调用getChapter函数,在getChapter函数中使用Boost的ASIO库,从网络上读取数据后,再调用clearUp函数进行处理,在clearUp函数中,使用了Boost的Regex库。

在这个程序中,因为要不断地对字符串进行处理,所以内存的分配和效率方面就需要特别注意,我做了一些努力以尽量减少字符串的复制,但是有两个地方还是做得不过好。一个地方是从response中把数据读入到了一个stringstream中,这里发生了一次复制,response是一个streambuffer对象,如果能直接从response对象中提取数据进行处理就更好了,但是问题是,streambuffer中数据是从网络中读取的,而从网络读取数据时一次传输多少谁也不知道,其中的数据不一定是完整的,所以程序中用了一个循环。当然,肯定有办法让response中尽量包含完整的数据,只不过要更耐心地去读ASIO的文档。(我的程序使用ASIO的部分是直接从Boost ASIO的示例代码抄的,没有仔细读文档。)

另外一个不好的地方就是clearUp函数,该函数返回的时候,发生了一次字符串复制。(其实编译器会优化掉)
如果不考虑编译器优化的话,最好的办法是把clearUp函数写成下面的样子:
string &&  clearUp(stringstream &  input) {
.
.
return std::move(regex_replace(output,patternToReplace,"\r\n"));
}

这就用到了亲爱的右值引用,其实上面的代码可以不要std::move,因为regex_replace函数返回的就是一个右值。

不过可惜,上面的代码编译可以通过,但是程序运行的时候会报错。我很郁闷,也不知道为什么,希望高手指点。

我的代码是使用x64平台作为目标编译的,生成的程序在我的Windows 7 64位版本下运行良好。这进一步说明Boost库在64位的程序中使用很顺利。

友情提示一下,如果要把提取的小说保存到文件,只需要使用一个文件重定向即可,如下:
getbookfromsina  39534   23601   23783   >  天使不在线.txt

最后祝大家端午节快乐!

你可能感兴趣的:(一个工具、一个库和一部小说)