之前很长的一段时间里,无论写什么程序,我都是使用C++,因为C++非常强大,从底层到上层都可以搞。底层方面C++对C兼容、支持内联汇编,可以开发嵌入式程序、驱动程序、操作系统,上层方面C++支持面向对象,有非常丰富的程序库,足以开发出任何程序。C++使用QT、Android NDK等工具可以开发手机应用,使用CGICC等工具可以开发Web程序。但用C++来开发手机应用和Web程序并不是个好选择,也没有多少人这样做。C++什么都可以搞,但不意味着什么都可以方便的搞。选择合适的语言工具来开发相应的程序,发挥出每种语言在各个领域的优势,并让各种语言分工合作才是王道。
刚开始踏入编程这一行的时候,我选择的是C/C++语言作为入门语言,因为对底层的东西比较感兴趣,并且打算将来搞嵌入式、驱动程序、虚拟机、操作系统或服务器方面的开发。在使用C/C++开发程序的过程中,经常与系统API打交道,甚至突破系统API的限制实现了操作系统没有公开提供一些功能,感觉非常爽快。后来有一段时间用C#重写我的一些C++程序的时候,由于.NET里没有提供我想要的那些功能(毕竟C#是微软用来和JAVA竞争的语言,.NET里封装的最多的还是那些用于上层应用开发的工具库),需要大量的调用系统API,本来在C++下调用系统API一句话就能搞定的东西,在C#下需要声明一大堆东西,还需要处理类型转换和指针的问题,结果就是代码写起来非常恶心,从那以后就对C#没有什么好感。不过C#给我印象较好的一点就是上层工具库很丰富,调用简单。之前在写一些XX管理系统的作业的时候,用winform,哗哗哗,各种控件往上拖,改一改属性,关联一下数据库,基本代码都没写,数据库中的数据就以报表形式显示在界面上了。不过我对XX管理系统之类的企业应用开发和Web程序开发之类的上层应用开发兴趣不大,所以就没有继续深入学习C#了。
话说回来,虽然C/C++在底层方面开发的能力很强,但它并不是一门开发效率高的语言,毕竟运行效率与开发效率不可兼得,没有什么东西能兼顾方方面面做到十全十美,凡事有得必有失。这样一来,在写一些比较上层的功能的时候,就感觉很累,很吃力,所以一直想找一门语言与C/C++配合工作,发挥C/C++在底层的优势并能提高开发效率。于是我便打开最近的编程语言排行榜,从上至下看一看选择什么语言作为我的第二编程语言比较好。首先是JAVA和C#,由于对移动应用开发、企业应用开发、Web开发兴趣不大,所以否决掉了,并且好像也没有多少领域需要JAVA与C/C++合作或C#与C/C++合作。然后是Objective-C,由于不准备搞移动应用开发,当然也包括IOS的移动应用开发,所以否决掉了。接下来是Python、Javascript、PHP、VB.NET、VB,VB就不说了,VB.NET的话不如搞C#了,PHP和Javascript主要用于Web开发方面,所以都否决掉了,那排名前10的语言就只剩Python了。10名以后的语言由于用的人不是很多(不火),或者是领域特定语言,或是由于我孤陋寡闻不认识,所以就都否决掉了。
粗略了解了一下Python,是一门脚本语言,开发效率比C#、JAVA还高,并且能和C/C++很好的结合在一起,那当然是最佳之选了。之前就听很多人推荐Python,一直以为不就是一门脚本语言嘛,脚本语言那么多,Python能牛到哪去,而且看了下Python写的代码,发现语法与C的语法有不少差别,感觉怪怪的,就没有去过多了解。后来找了几本书学了下,并且动手写了写代码,顿时大吃一惊,原来程序也可以写得这么简洁方便。
马上动手用Python写了一个从Web上爬取数据的小程序,再和以前用C++写过的同样功能的程序对比了一下,顿时倒吸一口凉气,裤裆一下子就湿了——吓尿了。在此贴出这段Python代码,只用了10多行就实现了从爱密码网上爬取每天最新发布的共享迅雷会员账号密码。
import urllib.request
import re
def get_member():
response = urllib.request.urlopen("http://521xunlei.com/portal.php")
html = str(response.read(), 'gbk')
result = re.search(r'', html)
if result is None:
return None
url = r'http://521xunlei.com/' + result.group(1)
response = urllib.request.urlopen(url)
html = str(response.read(), 'gbk')
pattern = re.compile(r'(?:账号共享|迅雷会员账号分享|迅雷号|迅雷账号|迅雷共享号|迅雷会员账号|共享账号)([a-zA-Z0-9]+?:[12]).*?(?:分享密码|密码分享|首发密码)(.+?)(?:
)')
result = pattern.findall(html)
if len(result) == 0:
return None
else:
return result
我之前也用C++写过Web爬虫,可谓是费了很大的功夫。首先是需要一个基于HTTP协议访问网站的库,C++标准库与Boost库中都没有。到网上找一找,有个开源跨平台的libcurl可用,不过和大多数C/C++的第三方库一样,这些库在VC编译器上编译总是会有各种问题。折腾了大半天好不容易编译成功了,看一下文档,全英文的,而且不是很详细,而且libcurl的API似乎也不支持Unicode,总之用起来各种不顺心。后来改用windows提供的Wininet和WinHttp,虽然用起来感觉好了点,但毕竟是C语言的API,而且没有提供易用的简化接口,发送一个GET请求都得写十几行代码。后来花了几天时间把WinHttp封装成了一个类,提供简单易用的接口来发送GET/POST请求,用起来才感觉方便了点。可是网页的HTML文本获取到以后,如何从中提取数据。刚开始想到的是HTML解析器,尝试使用第三方的HTML解析库和微软提供的MSHTML,遇到的各种恶心事情,就不再提了,在另外一篇文章中已经提过了。为了使用MSHTML,可谓是把基本的COM技术和ATL都学了,花了多长时间就不说了。再后来,又花了好几天吧MSHTML的基本功能封装成了一个类,可以像javascript那样用简短的语句读取HTML元素的内容。总结一下,为了用C++写Web爬虫,总共写了两个类分别封装了WinHTTP和MSHTML,还有一些方便易用的转换字符集的函数和小工具,总共写了3000多行代码。做这些准备工作,查阅了很多资料,学了多门技术,花了很长很长的时间。然后才可以比较愉快的写Web爬虫了,不过这个时候已经很疲惫了…
后来看到别人用Python结合正则表达式写Web爬虫,自己也跟着试了下,果然很简单很过瘾。之所以写起来很爽,主要有三点:1.Python语法简洁,代码写出来很短小2.Python标准库很强大,各种各样的模块都有,尤其是Web访问、文本处理方面更是它的强项 3.对于定向的Web爬虫,其实不用上HTML解析器,使用正则表达式也能达到目的,而且所需要的代码量也小,另外正则表达式中的分组捕获功能相当好用,直接一次性的就把整个HTML文本中想要的数据捕获出来了。
其实Python让我最惊喜的是它的标准库提供了很多东西,功能比较全面,我想要的东西都有了,而且这些库调用起来很简单,只需import一下就行了,第三方库也只需easy_install一下就可简单安装起来。C/C++的第三方库虽然更多更丰富,但是编译、整合比较麻烦,尤其是在Windows平台上的VC编译器中编译,十有八九要蹦出一大堆编译错误,这对于我们这样的新手来说非常头疼,简直是一场灾难。而且各种库都喜欢各自搞一套数据类型,比如STL/MFC/QT等各种库都各自有各自的string类型,还有字符集问题,有的库不支持Unicode,有的又只支持UTF8等等,想要把各种库整合到自己的程序中很麻烦。Python中则不需要管这些恶心的事情,整合各种库的恶心的工作已经被别人包办了,你只需要伸手拿过来用即可。
之前就说到,没有什么语言是万能,Python也不是搞什么都好搞,比如程序GUI。上面的10几行代码虽然实现了迅雷账号的爬取,但如果需要给程序加一个图形界面。用Python就没有那么简单易用了,Python的大多数GUI库都是调用其它语言的GUI库,比如Python也可以调用Win32 API和MFC来构建GUI,但肯定没有原生的C/C++调用起来方便。并且Python似乎也没有简单易用的可视化GUI设计工具。再加上我刚接触Python,也不太熟悉Python的GUI开发,于是决定用我熟悉的MFC构建GUI,然后让C++调用上面的10几行Python代码,完成一个“迅雷账号获取器”。顺便也了解一下C++和Python的交互方式。
程序代码就不贴了,这篇文章主要是讲使用Python的感受。完成了C++调用Python的程序后,我发现了一个让人兴奋的事情。那就是MFC写好图形界面后,那个EXE以后完全不需要改动了。MFC程序只是调用Python的一个函数,函数返回一个账号密码数组,MFC程序将这些账号密码显示在列表框中。只要保证Python提供给MFC程序的这个接口不变,那么MFC程序永远都不需要修改,不需要重新编译。由于获取到的迅雷账号肯定是一个账号和一个密码构成的数组,所以可以保证这个接口不变。如果以后“爱密码”网站改版了,采集数据的代码失效了(这种事很容易发生),或者是不再在“爱密码”网上采集数据,而是改为到其它网站上采集数据,只需要修改Python代码即可,不会影响到MFC程序,不需要改动MFC程序。这就是模块化编程的优点。不过模块这个东西,很多人认为一般是一个DLL。比如这个程序,如果用C++实现数据采集功能并封装到一个DLL里给MFC程序调用,以后如果需要改动采集数据的代码,只需重新编译DLL即可,不会影响到MFC写的EXE。其实脚本语言也可以作模块,称为脚本模块。脚本模块的优势更明显,修改的时候用文本编辑器打开改一改保存一下就OK了。用户在使用你的软件的时候,如果你的软件需要更新,更新文本化的脚本模块比更新二进制的DLL模块会方便很多,也不需要注意二进制兼容问题。
说到C++与Python的交互,最基础的做法是调用Python虚拟机提供的C语言API。因为Python虚拟机本来就是C语言写的,官方也提供了丰富的API,使得C/C++可以访问Python中的几乎所有东西。但是这组API调用起来并不方便。比如要调用上面的Python代码中的get_member()方法获取迅雷账号,get_member()的返回值是一个list,这个list的每个元素是一个tuple,每个tuple中保存着两个字符串,一个是账号一个是密码。调用的代码如下:
PyObject *module_name = Py_BuildValue("s", "thunder");
PyObject *module = PyImport_Import(module_name);
PyObject *function_dict = PyModule_GetDict(module);
PyObject *function = PyDict_GetItemString(function_dict, "get_member");
PyObject *result_list = PyObject_CallObject(function, NULL);
int result_num = PyList_Size(result_list);
for (int i = 0; i < result_num; i++)
{
PyObject *member_tuple = PyList_GetItem(result_list, i);
wcout << PyUnicode_AsUnicode(PyTuple_GetItem(member_tuple, 0)) << L"\t"
<< PyUnicode_AsUnicode(PyTuple_GetItem(member_tuple, 1)) << L"\n";
}
Py_DecRef(module);
可见调用起来是很麻烦的,还要注意资源释放问题。如果换成Python调用Python的话是非常简洁的(好像是废话):
import thunder
result = thunder.get_member()
for iter in result:
print(iter[0] + '\t' + iter[1] + '\n')
不过一个让人兴奋的事情就是Boost.Python改变了这一现状,Boost.Python使得无论是C++调用Python还是Python调用C++都方便了很多,使用Boost.Python调用如下:
object result = import("thunder").attr("get_member")();
for (int i = 0; i < len(result); i++)
{
cout << string(extract(result[i][0])) << "\t"
<< string(extract(result[i][1])) << "\n";
}
可以看出Boost.Python对Python函数get_member()的调用,一行代码就完成了,并且使用了智能指针技术,不需要管资源释放问题。代码写出来简洁了很多,其实Boost里很多库都使得C++代码写起来更简洁。这得益于C++强大模板技术(被称为编译时的多态),以及Boost的那帮开发者牛逼的技巧,把模板玩得生龙活虎、出神入化,如果你读过Boost的源代码,一定会觉得非常佩服。C++本来是一门强类型的静态语言,但是Boost借助模板、运算符重载技术,使得你在使用Boost.Python之类的一些库的时候,感觉C++好像变成了一门弱类型的动态语言。比如Boost.Python中的object包装了Python中的一切“东西“,一个object对象可以是Python中的一个模块、一个Python对象、一个函数、一个字符串、一个整型变量、一个list、一个tuple或一个dict等等任何类型的实例。一切类型都弱化了,只有一种类型:object。并且Boost重载了object的operator()和operator[],使得一个object对象可以当数组、当list、当tuple、当dict用,也可以当一个函数调用,而在编译时并不关心它到底是不是一个数组、是不是一个list、是不是一个函数,一切都放到了运行时才决定。并且使用起来在语法上也接近脚本语言,非常简单易用。这真是一大神器啊,借助Boost.Python,C++与Python的合作开发方便极了。