類的概念在許多語言中出現(xiàn),很容易理解。它將數(shù)據(jù)和操作進(jìn)行封裝,以便將來的復(fù)用。
模塊,在Python可理解為對應(yīng)于一個(gè)文件。在創(chuàng)建了一個(gè)腳本文件后,定義了某些函數(shù)和變量。你在其他需要這些功能的文件中,導(dǎo)入這模塊,就可重用這些函數(shù)和變量。一般用module_name.fun_name,和module_name.var_name進(jìn)行使用。這樣的語義用法使模塊看起來很像類或者名字空間,可將module_name 理解為名字限定符。模塊名就是文件名去掉.py后綴。下面演示了一個(gè)簡單的例子:
#moduel1.py def say(word): print word #caller.py import module1 print __name__ print module1.__name__ module1.say('hello')
$ python caller.py __main__ module1 hello
例子中演示了從文件中調(diào)用模塊的方法。這里還展示了一個(gè)有趣的模塊屬性__name__,它的值由Python解釋器設(shè)定。如果腳本文件是作為主程序調(diào)用,其值就設(shè)為__main__,如果是作為模塊被其他文件導(dǎo)入,它的值就是其文件名。這個(gè)屬性非常有用,常可用來進(jìn)行模塊內(nèi)置測試使用,你會經(jīng)常在一些地方看到類似于下面的寫法,這些語句只在作為主程序調(diào)用時(shí)才被執(zhí)行。
if __name__ == '__main__': app = wxapp(0) app.MainLoop()
上面的例子中,當(dāng)module1被導(dǎo)入后,python解釋器就在當(dāng)前目錄下尋找module1.py的文件,然后再從環(huán)境變量PYTHONPATH尋找,如果這環(huán)境變量沒有設(shè)定,也不要緊,解釋器還會在安裝預(yù)先設(shè)定的的一些目錄尋找。這就是在導(dǎo)入下面這些標(biāo)準(zhǔn)模塊,一切美好事情能發(fā)生的原因。
import os import sys import threading ...
這些搜索目錄可在運(yùn)行時(shí)動(dòng)態(tài)改變,比如將module1.py不放在當(dāng)前目錄,而放在一個(gè)冷僻的角落里。這里你就需要通過某種途徑,如sys.path,來告知Python了。sys.path返回的是模塊搜索列表,通過前后的輸出對比和代碼,應(yīng)能理悟到如何增加新路徑的方法了吧。非常簡單,就是使用list的append()或insert()增加新的目錄。
#module2.py import sys import os print sys.path workpath = os.path.dirname(os.path.abspath(sys.argv[0])) sys.path.insert(0, os.path.join(workpath, 'modules')) print sys.path
$ python module2.py ['e:\\Project\\Python', 'C:\\WINDOWS\\system32\\python25.zip', ...] ['e:\\Project\\Python\\modules', 'e:\\Project\\Python', 'C:\\WINDOWS\\system32\\python25.zip', ...]
模塊能像包含函數(shù)定義一樣,可包含一些可執(zhí)行語句。這些可執(zhí)行語句通常用來進(jìn)行模塊的初始化工作。這些語句只在模塊第一次被導(dǎo)入時(shí)被執(zhí)行。這非常重要,有些人以為這些語句會多次導(dǎo)入多次執(zhí)行,其實(shí)不然。
模塊在被導(dǎo)入執(zhí)行時(shí),python解釋器為加快程序的啟動(dòng)速度,會在與模塊文件同一目錄下生成.pyc文件。我們知道python是解釋性的腳本語言,而.pyc是經(jīng)過編譯后的字節(jié)碼,這一工作會自動(dòng)完成,而無需程序員手動(dòng)執(zhí)行。
在創(chuàng)建許許多多模塊后,我們可能希望將某些功能相近的文件組織在同一文件夾下,這里就需要運(yùn)用包的概念了。包對應(yīng)于文件夾,使用包的方式跟模塊也類似,唯一需要注意的是,當(dāng)文件夾當(dāng)作包使用時(shí),文件夾需要包含__init__.py文件,主要是為了避免將文件夾名當(dāng)作普通的字符串。__init__.py的內(nèi)容可以為空,一般用來進(jìn)行包的某些初始化工作或者設(shè)置__all__值,__all__是在from package-name import *這語句使用的,全部導(dǎo)出定義過的模塊。
PO文件是Portable Object文件的簡稱,它包含需要翻譯的字符串。我們需要從源文件進(jìn)行提取。首先,對源文件test.py編輯,標(biāo)識代碼里需要翻譯的字符串內(nèi)容。我們使用_("xx")的方法,這種形式可能在許多開源源代碼中見識過。
#加載菜單欄 menubar = rc.LoadMenuBar('IDR_MENU')
這里的IDR_MENU是資源標(biāo)識ID,不需要翻譯,因此不做改變,而下面的代碼:
info.SetVersion('1.0') info.SetDescription('XRC i18n Demo')
'XRC i18n Demo'是描述性的文本,需要進(jìn)行翻譯,將需要處理為
info.SetVersion('1.0') info.SetDescription(_('XRC i18n Demo'))
接著需要生成.pot(Portable Object Template),這是po的模板文件。在將來程序可能配置成其他語種,其他語言的po文件都從它而來。為了創(chuàng)建這文件,需要用到GNU gettext工具集中的xgettext。向xgettext傳入些必要的信息,來創(chuàng)建.pot文件。
>xgetttext --output=test.pot test.py
我們將wxPython界面以XRC文件保存了,那里同樣有要翻譯的字符串需要提取。用XRCed工具將XRC生成python代碼,勾選上'Generate gettext strings'項(xiàng)即可。將源文件和XRC生成的test_xrc.py文件一起處理,生成一個(gè)test.pot。
>xgettext --output=test.pot test.py test_xrc.py
將得到的test.pot另存為test.po文件,然后進(jìn)行翻譯編輯,在這過程中文件需要使用utf-8編碼。將對應(yīng)的英文翻譯成中文,將charset更改為utf-8。
"Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: test.py:19 msgid "XRC i18n Demo" msgstr "XRC 國際化示例" ...
.pot和.po這些文件都是文本文件,主要供翻譯者使用。為了使程序在運(yùn)行時(shí)能獲取相關(guān)的翻譯的內(nèi)容,要進(jìn)行所謂的編譯過程,將文本文件轉(zhuǎn)換為二進(jìn)制文件.mo。這里用了gettext工具集中的另一程序msgfmt。
> msgfmt --output=test.mo test.po
因?yàn)閣indows下沒有像linux像有公共存儲.mo文件的目錄,保持平臺的遷移性,在應(yīng)用程序本地目錄下新建locale目錄,用來存放編譯過的.mo文件,然后將test.mo移動(dòng)至locale目錄。在完成這些步驟后,就轉(zhuǎn)入代碼方面的更改了。
原先的代碼只需要做小改動(dòng):
def OnInit(self): wx.Locale.AddCatalogLookupPathPrefix('locale') self.locale = wx.Locale(wx.LANGUAGE_CHINESE_SIMPLIFIED) self.locale.AddCatalog('test') import __builtin__ __builtin__.__dict__['_'] = wx.GetTranslation
首先,增加了新的目錄文件路徑,這將使wxPython搜索這個(gè)目錄,尋找匹配的.mo文件。接著創(chuàng)建wx.Locale對象,將其初始化為簡體中文,這將對應(yīng)于zh_CN。最后將wx.GetTranslation做了一全局映射,這樣你在其他類中,比如示例中TestFrame也能使用_('xx')調(diào)用。這樣wxPython的i18n工作就完成了,下面是翻譯前后的界面截圖。
如果你將locale目錄下的test.mo文件刪除掉,然后將test.py中的wx.LANGUAGE_CHINESE_SIMPLIFIED改為wx.LANGUAGE_CHINESE,重新運(yùn)行程序看看。發(fā)現(xiàn)界面變成了如下的繁體中文,但是菜單'檔案'下的Exit還是英文。
因?yàn)槿笔?mo文件,但又指定wx.LANGUAGE_CHINESE,wxPython運(yùn)行時(shí)使用了wxstd.mo文件。wxstd.mo有許多預(yù)編譯好的常見字符串的對應(yīng)關(guān)系,它隨wxPython發(fā)布,在wx/locale下有許多語言版本的wxstd.mo。
對于wxPython會對待查目錄"DIR"來搜索.mo文件,查找它下面的這些目錄,(DIR/LANG/LC_MESSAGES;DIR/LANG;DIR),對于哪些是待查目錄,各個(gè)系統(tǒng)下又有不同,在所有的平臺上,LC_PATH環(huán)境變量指定的目錄將成為待查目錄,在Linux下/share/locale, /usr/share/locale, /usr/lib/locale, /usr/locale /share/locale以及當(dāng)前目錄將是待查目錄。在上面我們已經(jīng)用過AddCatalogLookupPathPrefix()函數(shù),其作用就是增加自己的待查目錄。
在示例程序中,將test.mo放在locale\zh_CN\LC_MESSAGES或者locale\zh\LC_MESSAGES同樣是可行的。但是如果使用wx.LANGUAGE_CHINESE指定,則zh_CN目錄將不可行,因?yàn)樗皇翘鼗夸洠负嗴w中文,而zh目錄同樣適用。
gettext進(jìn)行國際化是開源社區(qū)的主流方案,它也提供了許多實(shí)用工具供使用。上面提到了xgettext,msgfmt,還有msginit.exe,它將根據(jù).pot文件創(chuàng)建新的.po文件,然后初始化一些元信息,像作者信息,項(xiàng)目,以及編碼等,當(dāng)然也可像上面的手工編輯。msgmerge.exe將兩個(gè).po文件進(jìn)行合并。除了使用GNU Gettext工具集,也可使用隨python發(fā)布的tool\i18n目錄下pygettext.py和msgfmt.py,它們等同于上述的兩個(gè)工具。
對于編輯.po文件,可以嘗試一下Poedit,它提供了圖形化的編輯環(huán)境,其他功能我就不清楚了。
但在找到這篇文章之前,和編寫插件的過程中,還是費(fèi)了很多功夫。最早我認(rèn)為寫插件是需要下載SDK之類的軟件,所以在Live Writer官方開發(fā)網(wǎng)站,Live Writer網(wǎng),MSDN之類的找了個(gè)遍,看見是有SDK之類字樣的下載,但弄不下來只有文檔,根本不見其什么頭文件,DLL之類的。在這花費(fèi)了很多時(shí)間,最后才發(fā)現(xiàn)WLW插件的SDK是隨WLW一起分發(fā)了,也就是WindowsLive.Writer.Api.dll之類的,這些dll 都隨WLW主程序在一個(gè)目錄中。還有一點(diǎn)是,現(xiàn)在WLW在中國是隨Live套件一起發(fā)布的,因此路徑由原來的C:\Program Files\Windows Live Writer變成了C:\Program Files\Windows Live\Writer,插件目錄為Plugin。如果在網(wǎng)上發(fā)現(xiàn)有好用的插件,只需要將其發(fā)布的插件dll扔到這個(gè)目錄就行了。
在開發(fā)中碰到圖標(biāo)資源不能成功加載,在Dflying Chen的文章中特意提到了圖標(biāo)資源需要是嵌入形式,我也按照其操作的,總以為是這里出現(xiàn)問題。后來花了一些時(shí)間,才找到總是的根源:自己在開發(fā)中更改了工程名,導(dǎo)致最后生成的程序集的名稱與后來的命名空間名稱不一致,圖標(biāo)路徑就出錯(cuò)了。C#也只是這次用一下,這些都沒有接觸到。
昨天弄完自己的“插入Pre標(biāo)記”插件后,想到自己寫博客常需要截圖,遂想再開發(fā)一個(gè)截圖工具的。最早搜到了別人調(diào)用SnagIt搜件,不好用,因?yàn)镾nagIt是商業(yè)軟件,需要注冊的。后來找到了picpick,小巧免費(fèi),非常實(shí)用。我想調(diào)用picpick的,但是在參照Insert SnagIt Screen Capture發(fā)現(xiàn)是用COM接口,而無奈picpick沒有這樣供開發(fā)使用的接口考慮,最終不可行。后來經(jīng)過一些其他的嘗試,都告失敗。最后還是搜索幫了忙,找到了Screen Capture這個(gè)插件,原來有別人已完工了。
最后附上,我用這個(gè)新插件截的圖,非常好用,只需一步:
一旦線程對象被創(chuàng)建,它的活動(dòng)需要通過調(diào)用線程的start()方法來啟動(dòng)。這方法再調(diào)用控制線程中的run方法。
一旦線程被激活,則這線程被認(rèn)為是'alive'(活動(dòng))。當(dāng)它的run()方法終止時(shí)-正常退出或拋出未處理的異常,則活動(dòng)狀態(tài)停止。isAlive()方法測試線程是否是活動(dòng)的。
一個(gè)線程能調(diào)用別的線程的join()方法。這將阻塞調(diào)用線程,直到擁有join()方法的線程的調(diào)用終止。
線程有名字。名字能傳給構(gòu)造函數(shù),通過setName()方法設(shè)置,用getName()方法獲取。
線程能被標(biāo)識為'daemon thread'(守護(hù)線程).這標(biāo)志的特點(diǎn)是當(dāng)剩下的全是守護(hù)線程時(shí),則Python程序退出。它的初始值繼承于創(chuàng)建線程。這標(biāo)志用setDaemon()方法設(shè)置,用isDaemon()獲取。
存在'main thread'(主線程),它對應(yīng)于Python程序的初始控制線程。它不是后臺線程。
有可能存在'dummy thread objects'(啞線程對象)被創(chuàng)建。這些線程對應(yīng)于'alien threads'(外部線程),它們在Python的線程模型之外被啟動(dòng),像直接從C語言代碼中啟動(dòng)。啞線程對象只有有限的功能,它們總是被認(rèn)為是活動(dòng)的,守護(hù)線程,不能使用join()方法。它們從不能被刪除,既然它無法監(jiān)測到外部線程的中止。
構(gòu)造函數(shù)能帶有關(guān)鍵字參數(shù)被調(diào)用。這些參數(shù)是:
group 應(yīng)當(dāng)為 None,為將來實(shí)現(xiàn)ThreadGroup類的擴(kuò)展而保留。
target 是被 run()方法調(diào)用的回調(diào)對象. 默認(rèn)應(yīng)為None, 意味著沒有對象被調(diào)用。
name 為線程名字。默認(rèn),形式為'Thread-N'的唯一的名字被創(chuàng)建,其中N 是比較小的十進(jìn)制數(shù)。
args是目標(biāo)調(diào)用參數(shù)的tuple,默認(rèn)為()。
kwargs是目標(biāo)調(diào)用的參數(shù)的關(guān)鍵字dictionary,默認(rèn)為{}。
如果子線程重寫了構(gòu)造函數(shù),它應(yīng)保證調(diào)用基類的構(gòu)造函數(shù)(Thread.__init__()),在線程中進(jìn)行其他工作之前。
在每個(gè)線程對象中最多被調(diào)用一次。它安排對象的run() 被調(diào)用在一單獨(dú)的控制線程中。
run()
你可能在子類重寫這方法。標(biāo)準(zhǔn)的 run()方法調(diào)用作為target傳遞給對象構(gòu)造函數(shù)的回調(diào)對象。如果存在參數(shù),一系列關(guān)鍵字參數(shù)從args和kwargs參數(shù)相應(yīng)地起作用。
當(dāng)timeout參數(shù)未被設(shè)置或者不是None
,它應(yīng)當(dāng)是浮點(diǎn)數(shù)指明以秒計(jì)的操作超時(shí)值。因?yàn)?tt>join()總是返回None
,你必須調(diào)用isAlive()來判別超時(shí)是否發(fā)生。
當(dāng)timeout 參數(shù)沒有被指定或者是None
時(shí),操作將被阻塞直至線程中止。
線程能被join()許多次。
線程不能調(diào)用自身的join(),因?yàn)檫@將會引起死鎖。
在線程啟動(dòng)之前嘗試調(diào)用join()會發(fā)生錯(cuò)誤。
這名字是只用來進(jìn)行標(biāo)識目的的字符串。它沒有其他作用。多個(gè)線程可以取同一名字。最初的名字通過構(gòu)造函數(shù)設(shè)置。
大致上,線程從 start()調(diào)用開始那點(diǎn)至它的run()方法中止返回時(shí),都被認(rèn)為是活動(dòng)的。模塊函數(shù)enumerate()返回活動(dòng)線程的列表。
初始值繼承至創(chuàng)建線程。
當(dāng)沒有活動(dòng)的非守護(hù)線程時(shí),整個(gè)Python程序退出。
命令行接口是普遍,基礎(chǔ)的人機(jī)交互接口,從命令行提取程序的運(yùn)行時(shí)選項(xiàng)的方法有很多。你可以自己編寫相對應(yīng)的完整的解析函數(shù),或許你有豐富的C語言編程經(jīng)驗(yàn),熟知getopt()函數(shù)的用法,又或許使用Python的你已經(jīng)在使用optparse庫來簡化這一工作。大家在平時(shí)不斷地談及到“不要重復(fù)造輪子”,那就需要掌握一些順手的庫,這里介紹一種C++方式來解析命令行選項(xiàng)的方法,就是使用Boost.Program_options庫。
program_options提供程序員一種方便的命令行和配置文件進(jìn)行程序選項(xiàng)設(shè)置的方法。使用program_options庫而不是你自己動(dòng)手寫相關(guān)的解析代碼,因?yàn)樗唵危暶鞒绦蜻x項(xiàng)的語法簡潔,并且?guī)熳陨硪卜浅P ⑦x項(xiàng)值轉(zhuǎn)換為適合的類型值的工作也都能自動(dòng)完成。庫有著完備的錯(cuò)誤檢查機(jī)制,如果自己手寫解析代碼時(shí),就可能會錯(cuò)過對一些出錯(cuò)情況的檢查了。最后,選項(xiàng)值不僅能從命令行獲取,也能從配置文件,甚至于環(huán)境變量中提取,而這些選擇不會增加明顯的工作量。
以下面簡單的hello程序進(jìn)行說明,默認(rèn)打印hello world,如果傳入-p選項(xiàng),就會打印出人的姓名,另外通過傳入-h選項(xiàng),可以打印出幫助選項(xiàng)。略微看一眼代碼文件和相應(yīng)的屏幕輸入輸出,然后我們再一起來看看這些是如何發(fā)生的。
//hello.cpp #include <iostream> #include <string> #include <boost/program_options.hpp> using namespace std; int main(int argc, char* argv[]) { using namespace boost::program_options; //聲明需要的選項(xiàng) options_description desc("Allowed options"); desc.add_options() ("help,h", "produce help message") ("person,p", value<string>()->default_value("world"), "who") ; variables_map vm; store(parse_command_line(argc, argv, desc), vm); notify(vm); if (vm.count("help")) { cout << desc; return 0; } cout << "Hello " << vm["person"].as<string>() << endl; return 0; }
下面是在Windows命令提示符窗口上的輸入輸出結(jié)果,其中">"表示提示符。
>hello Hello world >hello -h Allowed options: -h [ --help ] produce help message -p [ --person ] arg (=world) who >hello --person len Hello len
首先通過options_description類聲明了需要的選項(xiàng),add_options返回了定義了operator()的特殊的代理對象。這個(gè)調(diào)用看起來有點(diǎn)奇怪,其參數(shù)依次為選項(xiàng)名,選項(xiàng)值,以及選項(xiàng)的描述。注意到示例中的選項(xiàng)名為"help,h",是因?yàn)槁暶髁司哂卸踢x項(xiàng)名和長選項(xiàng)名的選項(xiàng),這跟gnu程序的命令行具有一致性。當(dāng)然你可以省略短選項(xiàng)名,但是這樣就不能用命令選項(xiàng)簡寫了。第二個(gè)選項(xiàng)的聲明,定義了選項(xiàng)值為string類型,其默認(rèn)值為world.
接下來,聲明了variables_map類的對象,它主要用來存儲選項(xiàng)值,并且能儲存任意類型的值。然后,store,parse_command_line和notify函數(shù)使vm能存儲在命令行中發(fā)現(xiàn)的選項(xiàng)。
最后我們就自由地使用這些選項(xiàng)了,variables_map類的使用就像使用std::map一樣,除了它必須用as方法去獲取值。如果as方法調(diào)用的指定類型與實(shí)際存儲的類型不同,就會有異常拋出。
具有編程的你可能有這樣的經(jīng)驗(yàn),使用cl或gcc對源文件進(jìn)行編譯時(shí),可直接將源文件名放置在命令行中,而無需什么選項(xiàng)字母,如gcc a.c之類的。prgram_options也能處理這種情況,在庫中被稱為"positional options"(位置選項(xiàng)),但這需要程序員的一點(diǎn)兒幫助才能完成。看下面的經(jīng)過對應(yīng)修改的代碼,我們無需傳入"-p"選項(xiàng),就能可指定"person"選項(xiàng)值
positional_options_description p; p.add("person", -1); store(command_line_parser(argc, argv).options(desc).positional(p).run(), vm);
>hello len Hello len
前面新增的兩行是為了說明所有的位置選項(xiàng)都應(yīng)被解釋成"person"選項(xiàng),這里還采用了command_line_parser類來解析命令行,而不是用parse_command_line函數(shù)。后者只是對前者類的簡單封裝,但是現(xiàn)在我們需要傳入一些額外的信息,所以要使用類本身。
一般來說,在命令行上指定所有選項(xiàng),對用戶來說是非常煩人的。如果有些選項(xiàng)要應(yīng)用于每次運(yùn)行,那該怎么辦呢。我們當(dāng)然希望能創(chuàng)建出帶有些常用設(shè)置的選項(xiàng)文件,跟命令行一起應(yīng)用于程序中。當(dāng)然這一切需要將命令行與配置文件中的值結(jié)合起來。比如,在命令行中指定的某些選項(xiàng)值應(yīng)該能覆蓋配置文件中的對應(yīng)值,或者將這些值組合起來。
下面的代碼段將選項(xiàng)通過文件讀取,這文件是文本格式,可用"#"表示注釋,格式如命令行中的參數(shù)一樣,選項(xiàng)=值
ifstream ifs("config.cfg"); store(parse_config_file(ifs,config),vm); notify(vm);
Boost Lambda庫是C++模板庫,以C++語言實(shí)現(xiàn)了lambda抽象.Lambda這個(gè)術(shù)語來自函數(shù)編程語言和lambda閉包理論,lambda抽象實(shí)際上定義了匿名函數(shù).了解過C#新引入的匿數(shù)函數(shù)特性或Lisp編程的人,對這些概念理解會有很大幫助.Lambda庫設(shè)計(jì)的主要?jiǎng)訖C(jī)是為STL算法提供靈活方便的定義匿名函數(shù)對象的機(jī)制.這個(gè)Lambda庫究竟是有什么用呢?代碼勝千言!看下面將STL容器中的元素打印到標(biāo)準(zhǔn)輸出上的代碼.
for_each(a.begin(), a.end(), std::cout << _1 << ' ');
表達(dá)式std::cout << _1 << ' '定義了一元函數(shù)對象.變量_1是函數(shù)的形參,是實(shí)參的占位符.每次for_each的迭代中,函數(shù)帶著實(shí)際的參數(shù)被調(diào)用,實(shí)際參數(shù)取代了占位符,然后函數(shù)體里的內(nèi)容被執(zhí)行.Lambda庫的核心就是讓你能像上面所展示的那樣,在STL算法的調(diào)用點(diǎn),定義小的匿名函數(shù)對象.
Lambda庫只由頭文件組成,這就意味著你不需要進(jìn)行任何編譯,連接,生成二進(jìn)制庫的動(dòng)作,只需要boost庫頭文件路徑包含進(jìn)你的工程中即可使用.
與現(xiàn)代的C++語言一樣,在使用時(shí)你需要聲明用到的名字空間,把下列的代碼包含在你的源文件頭:
using namespace boost::lambda;
在標(biāo)準(zhǔn)模板庫STL成為標(biāo)準(zhǔn)C++的一部分后,典型的STL算法對容器中元素的操作大都是通過函數(shù)對象(function objects)完成的.這些函數(shù)作為實(shí)參傳入STL算法.
任何C++中以函數(shù)調(diào)用語法被調(diào)用的對象都是函數(shù)對象.STL對某些常見情況預(yù)置了些函數(shù)對象.比如:plus,less,not1下面就是標(biāo)準(zhǔn)plus模板的一種可能實(shí)現(xiàn):template <class T> struct plus : public binary_function <T, T, T> { T operator()(const T& i, const T& j) const { return i + j; } };
基類binary_function<T, T, T>包含了參數(shù)和函數(shù)對象返回類型的類型定義,這樣可使得函數(shù)對象可配接.
除了上面提到的基本的函數(shù)對象外,STL還包含了binder模板,將可配接的二元函數(shù)中的某個(gè)實(shí)參固定為常量值,來創(chuàng)建一個(gè)一元函數(shù)對象.比如:class plus_1 { int _i; public: plus_1(const int& i) : _i(i) {} int operator()(const int& j) { return _i + j; } };
上面的代碼顯性地創(chuàng)建了一個(gè)函數(shù)對象,將其參數(shù)加1.這樣的功能可用plus模板與binder模板(bind1st來等效地實(shí)現(xiàn).舉例來說,下面的兩行表達(dá)式創(chuàng)建了一個(gè)函數(shù)對象,當(dāng)它被調(diào)用時(shí),將返回1與調(diào)用參數(shù)的和.
plus_1(1) bind1st(plus<int>(), 1)
plus<int>就是計(jì)算兩個(gè)數(shù)之和的函數(shù)對象.bind1st使被調(diào)用的函數(shù)對象的第一個(gè)參數(shù)綁定到常量1.作為上面函數(shù)對象的使用示例,下面的代碼就是將容器a中的元素加1后,輸出到標(biāo)準(zhǔn)輸出設(shè)備:
transform(a.begin(), a.end(), ostream_iterator<int>(cout), bind1st(plus<int>(), 1));為了使binder更加通用,STL包含了適配器(adaptors)用于函數(shù)引用與指針,以及成員函數(shù)的配接.
所有這些工具都有一個(gè)目標(biāo),就是為了能在STL算法的調(diào)用點(diǎn)有可能指定一個(gè)匿名的函數(shù),換句說,就是能夠使部分代碼片斷作為參數(shù)傳給調(diào)用算法函數(shù).但是,標(biāo)準(zhǔn)庫在這方面只做了部分工作.上面的例子說明用標(biāo)準(zhǔn)庫工具進(jìn)行匿名函數(shù)的定義還是很麻煩的.復(fù)雜的函數(shù)調(diào)用表達(dá)式,適配器,函數(shù)組合符都使理解變得困難.另外,在運(yùn)用標(biāo)準(zhǔn)庫這些方法時(shí)還有明顯的限束.比如,標(biāo)準(zhǔn)C++98中的binder只允許二元函數(shù)的一個(gè)參數(shù)被綁定,而沒有對3參數(shù),4參數(shù)的綁定.這種情況在TR1實(shí)施后,引進(jìn)了通用的binder后可能改善,對于使用MSVC的程序員,有興趣還可以查看下微軟針對VS2008發(fā)布的TR1增強(qiáng)包.
但是不管怎樣,Lambda庫提供了針對這些問題比較優(yōu)雅的解決方法:
對匿名函數(shù)以直觀的語義進(jìn)行創(chuàng)建,上面的例子可改寫成:
transform(a.begin(), a.end(), ostream_iterator<int>(cout), 1 + _1);
更直觀點(diǎn):
for_each(a.begin(), a.end(), cout << (1 + _1));
絕大部分對函數(shù)參數(shù)綁定的限制被去除,在實(shí)際C++代碼中可以綁定任意的參數(shù)
分離的函數(shù)組合操作不再需要了,函數(shù)組合被隱性地支持.
Lambda表達(dá)在函數(shù)式編程語言中很常見.在不同語言中,它們的語法有著很大不同,但是lambda表達(dá)式的基本形式是:
lambda x1...xn.e
lambda表達(dá)式定義了匿名函數(shù),并由下列的元素組成
一個(gè)簡單的lambda表達(dá)式的例子是:
(lambda x y.x+y) 2 3 = 2 + 3 = 5
在lambda表達(dá)式的C++版本中,表達(dá)式中x1...xn不需要,已預(yù)定義形式化的參數(shù).在現(xiàn)在Boost.Lambda庫中,存在三個(gè)這樣的預(yù)定義的參數(shù),叫做占位符:_1,_2,和_3.它們分別指代在lambda表達(dá)式中的第一,二,三個(gè)參數(shù).比如,下面這樣的lambda表達(dá)式:
lambda x y.x+y
C++定義的形式將會是這樣:
_1 + _2
因此在C++中的lambda表達(dá)式?jīng)]有語義上所謂的關(guān)鍵字.占位符作為運(yùn)算符使用時(shí)就隱性地意味著運(yùn)算符調(diào)用是個(gè)lambda表達(dá)式.但是只有在作為運(yùn)算符調(diào)用才是這樣.當(dāng)Lambda表達(dá)式包含函數(shù)調(diào)用,控制結(jié)構(gòu),轉(zhuǎn)換時(shí)就需要特殊的語法調(diào)用了.更為重要的是,作為函數(shù)調(diào)用是需封裝成binder函數(shù)的形式.比如,下面這個(gè)lambda表達(dá)式:
lambda x y.foo(x,y)
不應(yīng)寫成foo(_1,_2),對應(yīng)的C++結(jié)構(gòu)應(yīng)如下:
bind(foo, _1, _2)
對于這種表達(dá)式,更傾向于作為綁定表達(dá)式bind expressions
lambda表達(dá)式定義了C++的函數(shù)對象,因此,對于函數(shù)調(diào)用的形式跟其他的函數(shù)對象一樣,比如:(_1 + _2)(i, j).
性能,運(yùn)行效率,總是C++程序員關(guān)心的話題.理論上,相對于手寫循環(huán)代碼,使用STL算法和Lambda函數(shù)對象的所有運(yùn)行開銷,可以通過編譯優(yōu)化消除掉.這種優(yōu)化取決于編譯器,實(shí)際中的編譯器大都能做到.測試表明,性能會有下降,但是影響不大,對于代碼的效率和簡潔之間的權(quán)衡,只能由程序員自己做出判斷了.
Lambda庫的設(shè)計(jì)與實(shí)現(xiàn)中大量運(yùn)用了模板技術(shù),造成對于同一模板需要大量的遞歸實(shí)例化.這一因素可能使構(gòu)建復(fù)雜邏輯的lambda表達(dá)式,不是一個(gè)非常理想的做法.因?yàn)榫幾g這些表達(dá)式需要大量的內(nèi)存,從而使編譯時(shí)間變得非常慢,這在一些大型項(xiàng)目中會更加突出.還有在發(fā)生編誤錯(cuò)誤時(shí),引發(fā)的大量錯(cuò)誤信息,不能有效地指出真正錯(cuò)誤之處.最后點(diǎn),C++標(biāo)準(zhǔn)建議模板的嵌套層次不要超過17層來防止導(dǎo)致無限遞歸,而復(fù)雜的Lambda表達(dá)式模板會很容易超過這一限制.雖然大多數(shù)編譯器允許更深層次的模板嵌套,但是通常需要顯性地傳入一個(gè)命令行參數(shù)才能做到.
大多數(shù)內(nèi)容是從Boost.Lambday庫在線文檔參考翻譯而成
http://doc.trolltech.com/qq/qq13-apis.html
翻譯這篇文章的目的不是讓人了解Qt,而是讓人試著學(xué)習(xí)點(diǎn)C++編程的軟技能。我從原文中得到的一些風(fēng)格上的體會,也希望你能從中有所收獲.(譯者注)
我們在Trolltech做了大量研究來改進(jìn)Qt開發(fā)體驗(yàn).在這篇文章中,我將分享我們的一些成果,呈現(xiàn)我們在進(jìn)行Qt 4設(shè)計(jì)時(shí)所使遵循的原現(xiàn),并向你展示如何將它們應(yīng)用到你的代碼中.
設(shè)計(jì)應(yīng)用程序接口(APIs)是有難度的.它是像跟設(shè)計(jì)編程語言一樣困難的藝術(shù).要遵循許多不同的的原則,這些原則中的許多還彼此沖突.
現(xiàn)今的計(jì)算機(jī)教育過多關(guān)注于算法和數(shù)據(jù)結(jié)構(gòu),很少去關(guān)注隱藏在程序設(shè)計(jì)語言和程序框架后面的那些設(shè)計(jì)原則.這使得程序員們面對日益重要的任務(wù),創(chuàng)建可復(fù)用的組件,毫無準(zhǔn)備.
在面向?qū)ο笳Z言出現(xiàn)前,通用的可復(fù)用的代碼大都由庫提供者而不是應(yīng)用程序開發(fā)者來編寫.在Qt世界中,這種情況已發(fā)生了很大的變化.在用Qt編程其實(shí)就是在寫新的組件.典型的Qt應(yīng)用程序都存在某些自定義的組件,在整個(gè)應(yīng)用程序中被復(fù)用.相同的組件常常作為其他程序的一部分被開發(fā)出來.KDE,K桌面環(huán)境,甚至使用許多附加庫,來進(jìn)一步擴(kuò)展Qt,實(shí)現(xiàn)許多額外的類.
但是一個(gè)優(yōu)秀,高效的C++ API究竟是怎樣子呢?它的好壞取決于許多因素,比如說,手頭上的任務(wù)和特定目標(biāo)群體.優(yōu)秀的API具有很多特性,它們的一些是普遍所要期望的,另一些是針對特定問題域的.
API對于程序員就相當(dāng)于GUI對于最終用戶.API中'P'代表程序員(Programmer),而不是程序(Program),強(qiáng)調(diào)這一點(diǎn)是為了說明API是讓程序員使用的,程序員是人而不機(jī)器.
我們認(rèn)為APIs應(yīng)當(dāng)精簡而完備,具有清晰簡單的語義,直觀,易記且應(yīng)使代碼具有可讀性.
最后,請記住:不同的用戶使用API的不同部分.當(dāng)簡單地使用Qt類的實(shí)例可能有直觀性,但這有可能使用戶在閱讀完有關(guān)文檔后,才能嘗試使用其中部分功能.
通常的誤讀是越少的代碼越能使你達(dá)到編寫更好的API這一目的.請記住,代碼只寫一遍,卻要一遍又一遍地去理解閱讀它.比如:
QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical, 0, "volume");
可以會比下面的代碼更難閱讀(甚至于編寫)
QSlider *slider = new QSlider(Qt::Vertical); slider->setRange(12, 18); slider->setPageStep(3); slider->setValue(13); slider->setObjectName("volume");
布爾參數(shù)常常導(dǎo)致難以閱讀的代碼.特別地,增加某個(gè)bool參數(shù)到現(xiàn)存的函數(shù)一般都會是個(gè)錯(cuò)誤的決定.在Qt中,傳統(tǒng)的例子是repaint(),它帶有一個(gè)可選的布爾參數(shù),來指定背景是否刪除(默認(rèn)是刪除).這就導(dǎo)致了代碼會像這樣子:
widget->repaint(false);
初學(xué)者可能會按字面義理解為,"不要重繪!"
自然的想法是bool參數(shù)節(jié)省了一個(gè)函數(shù),因此減少了代碼的臃腫.事實(shí)上,這增加了代碼的臃腫,有多少Q(mào)t用戶真正知道下面這三行代碼在做什么呢?
widget->repaint(); widget->repaint(true); widget->repaint(false);
好一點(diǎn)的API代碼可能看起來像這樣:
widget->repaint(); widget->repaintWithoutErasing();
在Qt 4中,我們解決這個(gè)問題的辦法是,簡單地去除掉不刪除widget而進(jìn)行重繪的可能性.Qt 4對雙重緩沖的原生支持,會使這功能被廢棄掉.
這里有些例子:
widget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding, true); textEdit->insert("Where's Waldo?", true, true, false); QRegExp rx("moc_*.c??", false, true);
顯然的解決辦法就是將bool 參數(shù)用枚舉類型來替換.這就是我們在Qt 4中Qstring中的大小寫敏感所做的,比較下面兩個(gè)例子:
str.replace("%USER%", user, false); // Qt 3 str.replace("%USER%", user, Qt::CaseInsensitive); // Qt 4
相似的類應(yīng)該有相似的API.在某種程度上,這能用繼承來實(shí)現(xiàn),也就是運(yùn)用運(yùn)行時(shí)多態(tài)機(jī)制.但是多態(tài)也能發(fā)生在設(shè)計(jì)時(shí).比如,你將QListBox與QComboBox交換,QSlider與QSpinBox交換,你會發(fā)現(xiàn)API的相似性會使這種替換變得比較容易.這就是我們所謂的"靜態(tài)多態(tài)".
靜態(tài)多態(tài)也能使記憶API和編程模式更加容易.因而,對一組相關(guān)類的相似API有時(shí)候比為每個(gè)類設(shè)計(jì)獨(dú)特完美的API會更好.
命名有時(shí)候是設(shè)計(jì)API中最重要的事情了.某個(gè)類應(yīng)叫什么名字,某個(gè)成員函數(shù)又應(yīng)叫什么名字,都需要好好思考.?
有少許規(guī)則對所有類型的命名都適應(yīng).首先,正如我早先所提到的,不要用縮寫.甚至對用"prev"代表"previous"這樣明顯的縮寫也不會在長期中受益,因?yàn)橛脩舯仨氂涀∧男┟质强s寫.
如果連API自身都不能保持統(tǒng)一,事情自然會變得更壞.比如,Qt 3中有activatePreviousWindow()函數(shù),也有fetchPrev()函數(shù).堅(jiān)持"沒有縮寫"這條規(guī)則,會使創(chuàng)建一致的API更加簡單.
在設(shè)計(jì)類中,另一重要但是不明顯的規(guī)則是盡量保持子類中名字的簡潔易懂.在Qt 3中,這個(gè)原則并不總是被遵守.為了說明這一點(diǎn),我們舉下QToolButton的例子.如果你在Qt 3中對QToolButton調(diào)用call name(), caption(), text(), 或 textLabel()成員函數(shù)時(shí),你希望會發(fā)生什么?那就在Qt設(shè)計(jì)器中試試QToolButton吧.
為了可讀性的關(guān)系,在Qt4中name 被稱為objectName ,caption被稱為windowTitle,在QToolButton中為了使text明晰,不再有textLabel屬性.
不應(yīng)為每個(gè)不同的類尋求完美的名字,而是將類進(jìn)行分給.比如,在Qt 4中所有跟模型有關(guān)的視類的部件都用View后綴(QlistView,QTableView,QTreeView),相應(yīng)的基于部件的類用Widget后綴代替(QListWidget,QTableWidget,QTreeWidge).
當(dāng)設(shè)計(jì)枚舉時(shí),我們應(yīng)當(dāng)記住C++中(不像Java或C#),枚舉值在使用時(shí)不帶類型名.下面的例子說明了對枚舉值取太一般化的名字的危害:
namespace Qt { enum Corner { TopLeft, BottomRight, ... }; enum CaseSensitivity { Insensitive, Sensitive }; ... }; tabWidget->setCornerWidget(widget, Qt::TopLeft); str.indexOf("$(QTDIR)", Qt::Insensitive);
在上面這行中,Insensitive這個(gè)名字什么意思呢?為枚舉類型命名具有指導(dǎo)的原則是最好在每個(gè)枚舉值中重復(fù)枚舉類型的名字.
namespace Qt { enum Corner { TopLeftCorner, BottomRightCorner, ... }; enum CaseSensitivity { CaseInsensitive, CaseSensitive }; ... }; tabWidget->setCornerWidget(widget, Qt::TopLeftCorner); str.indexOf("$(QTDIR)", Qt::CaseInsensitive);
但枚舉值之間是一種"或"關(guān)系和被用作標(biāo)志位時(shí),傳統(tǒng)的解決方法是將"或"結(jié)果存為int,這樣做是類型不安全的.Qt 4提供了一模板類QFlags<T>,其中T是枚舉類型.Qt為標(biāo)志類型名稱提供了便利,你能用Qt::Alignment 來代替QFlags<Qt::AlignmentFlag>.
為了方便,我們給枚舉類型單數(shù)形式的名稱(只有當(dāng)只含一個(gè)標(biāo)志位時(shí)),給"flags"類型復(fù)數(shù)形式的名稱,比如:
enum RectangleEdge { LeftEdge, RightEdge, ... }; typedef QFlags<RectangleEdge> RectangleEdges;
在某些情況下,"flags"類型有單數(shù)形式的名稱.在這種情況下,枚舉類型以Flag后綴標(biāo)識:
enum AlignmentFlag { AlignLeft, AlignTop, ... }; typedef QFlags<AlignmentFlag> Alignment;
函數(shù)命名中的一條規(guī)則就是應(yīng)能從它的名字清楚地看出函數(shù)是否著副作用.在Qt 3中,常函數(shù)QString::simplifyWhiteSpace()就違反了這規(guī)則.即然它返回QString,而不是像它的名字所表述的那樣修改字符串. 在Qt 4中,這個(gè)函數(shù)被重命名為QString::simplified().
參數(shù)名對于程序員來說是重要的信息來源,即使它們不出現(xiàn)在調(diào)用API的代碼中.既然現(xiàn)代的IDE會在程序員編碼時(shí)顯示這些參數(shù),所以非常值得在頭文件中給這些參數(shù)取恰當(dāng)?shù)拿郑谖臋n中同樣使用相同的名字
給布爾型的getter,setter,屬性取個(gè)恰當(dāng)?shù)拿挚偸翘貏e困難.getter應(yīng)該叫checked() 或者還是叫isChecked(),取scrollBarsEnabled()還是areScrollBarEnabled()
在Qt 4中,我們對于getter的函數(shù)使用下面的指導(dǎo)原則
setter的命名可以從這推知,只要去掉is前綴,在名字前面加set前綴就可以了.比如setDown()和setScrollBarsEnabled().屬性的名字跟getter一樣,就是沒有is前綴
對于向外傳參,是使用指針,還是引用更好呢?
void getHsv(int *h, int *s, int *v) const void getHsv(int &h, int &s, int &v) const
絕大多數(shù)C++書籍都推薦無論何時(shí)都盡可能使用引用,因?yàn)閺拇蠖鄶?shù)情況來說,引用比指針有著所謂的"安全和優(yōu)雅".相比而方,在Trolltech,我們更趨向于指針,因?yàn)樗褂脩舸a更具可讀性.比較下面的代碼:
color.getHsv(&h, &s, &v); color.getHsv(h, s, v);
只有第一行代碼能更清楚地說明h,s,v在函數(shù)被調(diào)用后,其值極有可能被修改.
為了在實(shí)際代碼中說明這些概念,我們以QProgressBar在Qt3和Qt4中的比較進(jìn)行研究.在Qt 3中:
class QProgressBar : public QWidget { ... public: int totalSteps() const; int progress() const; const QString &progressString() const; bool percentageVisible() const; void setPercentageVisible(bool); void setCenterIndicator(bool on); bool centerIndicator() const; void setIndicatorFollowsStyle(bool); bool indicatorFollowsStyle() const; public slots: void reset(); virtual void setTotalSteps(int totalSteps); virtual void setProgress(int progress); void setProgress(int progress, int totalSteps); protected: virtual bool setIndicator(QString &progressStr, int progress, int totalSteps); ... };
對這個(gè)API進(jìn)行改進(jìn)的關(guān)鍵之處就是需要觀察到Qt 4中QProgressBar與QAbstractSpinBox,以及它的子類,QSpinBox,QSlider,和QDial有著相似性.解決的辦法呢?將其中的progress和totalSteps用minimun,maximum和value替換.
增加valueChanged()的信號量.增加setRange()這一方便的函數(shù).
接下來需要到progressString, percentage 和indicator實(shí)際上都指代同一東西:顯示在進(jìn)度欄上的文本.通常這一文本是一百分?jǐn)?shù),但是它能被setIndicator()設(shè)置成任何值.這里是新的API:
virtual QString text() const; void setTextVisible(bool visible); bool isTextVisible() const;
默認(rèn),這文本是百分比指示器.這可以用重新實(shí)現(xiàn)的text()進(jìn)行改變.
在Qt 3中,setCenterIndicator() 和 setIndicatorFollowsStyle()是兩個(gè)影響對齊方式的函數(shù).它們現(xiàn)在都被一個(gè)高級的函數(shù)所取代,setAlignment().
void setAlignment(Qt::Alignment alignment);
如果程序員沒有調(diào)用 setAlignment(),對齊是基于的樣式?jīng)Q定的.對于Motif樣式,文本顯示在中間,而對于其他樣式,文本是右對齊的.
這里是改進(jìn)過的QProgressBar:
class QProgressBar : public QWidget32 { ... public: void setMinimum(int minimum); int minimum() const; void setMaximum(int maximum); int maximum() const; void setRange(int minimum, int maximum); int value() const; virtual QString text() const; void setTextVisible(bool visible); bool isTextVisible() const; Qt::Alignment alignment() const; void setAlignment(Qt::Alignment alignment); public slots: void reset(); void setValue(int value); signals: void valueChanged(int value); ... };
APIs需要質(zhì)量保證.最早的版本一般都不是很好的,你必須測試它.通過調(diào)用這個(gè)API的代碼作為測試事例,來驗(yàn)證代碼具有可讀性.
另外的技巧包括讓人在沒有文檔和類文檔化(類的概述和函數(shù)說明)的情況下能夠使用這個(gè)API.
當(dāng)你陷入麻煩中時(shí),文檔化也是好的辦法找出一個(gè)合適的命名:試著為這些類,函數(shù),枚舉值標(biāo)住文檔,然后使用浮現(xiàn)在你腦中的第一個(gè)詞匯.如果你找不到精準(zhǔn)的名字去表述,那很有可能這個(gè)東西就不應(yīng)存在.如果任何辦法都失敗了,而且你確信這個(gè)概念是有用的,那就發(fā)明一個(gè)新的名字吧.最后,不管怎么說,"widget", "event", "focus", and "buddy"這些詞總會能用上一個(gè).
在網(wǎng)上可以見到某些關(guān)于Glade的教程,大都是關(guān)于Linux平臺和Glade 2的,因?yàn)樵菺lade作為快速開發(fā)工具,集成代碼生成功能,生成C文件.所以常常有初學(xué)者對網(wǎng)上某些教程所提及的"generate"(生成代碼)功能表示迷惑,在新版本的Glade-3上找不到對應(yīng)的功能.
新版本的Glade-3是對原先Glade代碼的完全重寫.一個(gè)顯著的變化就是去除了代碼生成功能.這樣做是有原因的,即然代碼生成功能不被提倡使用,而是更鼓勵(lì)使用libglade功能.但是如果你真需要代碼生成功能的話,它還是可以做為插件來提供的.另一個(gè)顯著的不同是glade-3設(shè)計(jì)用來最大化使用GObject的自省機(jī)制(GObject introspection),來使外部工具箱和部件的控制,信號和屬性的集成更加容易.
如果看過Say Hello to GTK+的話,可能感覺那樣的窗體程序太簡單了.那么現(xiàn)在讓我們借助Glade弄點(diǎn)兒復(fù)雜一點(diǎn)兒的界面吧.首先來瞧瞧Glade長什么樣,下圖就是Glade在windows下的界面.左邊的窗體的小部件選擇器,相當(dāng)于調(diào)色板.中間是主菜單,右邊的是屬性窗體.
現(xiàn)在開始創(chuàng)建一個(gè)類似于文本編輯器的圖形界面.按照上圖標(biāo)注的順序,依次添加window部件,vertical box部件,menu bar部件,text view部件和Status部件.vertical box設(shè)置三行,它是用來進(jìn)行界面布局,分割空間用,這是gtk+設(shè)計(jì)與傳統(tǒng)的windows UI設(shè)計(jì)很不同的地方.后三個(gè)部件是放置vertical box中的,最后設(shè)計(jì)完成圖形如下.保存取名為win.glade.如果你感興趣的話,可以用文件編輯器打開這個(gè)文件看看,正如所說的那樣,它是一個(gè)xml格式的文本文件.
現(xiàn)在我們設(shè)置相關(guān)的頭文件和庫文件,編輯一個(gè)glade.c文件,添加進(jìn)以下的代碼,運(yùn)行看看,會出現(xiàn)如上圖的對話框.雖然這個(gè)對話框什么都不干,但是通過Glade,我們能較為容易地設(shè)計(jì)界面,而不用通過gtk函數(shù),一個(gè)一個(gè)將控件實(shí)現(xiàn).
#include <gtk/gtk.h>
#include <glade/glade.h> int main(int argc, char* argv[]) { GladeXML *gxml; GtkWidget *window; gtk_init (&argc, &argv); gxml = glade_xml_new ("win.glade", NULL, NULL); window = glade_xml_get_widget (gxml, "hello"); g_object_unref (G_OBJECT (gxml)); gtk_widget_show (window); gtk_main (); return 0; }
在Windows開發(fā)GTK+的應(yīng)用程序,首先需要其在Windows版本下的庫文件,下載gladewin32項(xiàng)目中的gtk+-win32-devel安裝包進(jìn)行安裝.我這里使用VS2005進(jìn)行配置,因?yàn)橄鄬τ谄渌沫h(huán)境,大家對于此IDE更為熟悉.接下來我將一步步介紹我在對GTK/Glande3說Hello時(shí)碰到的問題與解決方法.
任何像我一樣急切的人,都希望用最少的代碼,看一下GTK+的世界是怎么樣的.新建一"Win32 項(xiàng)目",然后向其添加gtk.c文件.
/* file gtk.c */ #include < gtk / gtk.h > int main( int argc, char * argv[]) { GtkWidget * window; gtk_init ( & argc, & argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), " Hello World " ); gtk_widget_show (window); gtk_main(); return 0 ; }
現(xiàn)在麻煩的事情到了,設(shè)置相關(guān)的頭文件和庫文件.設(shè)置頭文件,右鍵點(diǎn)擊項(xiàng)目名,然后選擇屬性.然后在"常規(guī)"-"附加包含目錄"中設(shè)置你安裝gtk+-win32-devel時(shí)對應(yīng)的頭文件目錄.然后再設(shè)置庫文件,需要gtk-win32-2.0.lib,glib-2.0.lib,gobject-2.0.lib.
設(shè)置完好后,你就可以編譯運(yùn)行了,一個(gè)什么都不干的丑陋的windows窗體出現(xiàn)你面前了.若編譯不通過,缺少某些頭文件的話,查看出錯(cuò)信息,然后用windows查找對應(yīng)頭文件路徑,再設(shè)置.若連接出錯(cuò),很可能在設(shè)置庫文件出錯(cuò)了,同理,設(shè)置相應(yīng)的庫文件路徑.程序代碼很簡單,定義一個(gè)windows窗體指針,然后創(chuàng)建窗體和定義窗體標(biāo)題,接著就是顯示了,和gtk主循環(huán)了.
或許你發(fā)現(xiàn)了,點(diǎn)擊窗體中的"X"關(guān)閉按鈕,雖然窗體消失了,但是黑色的命令窗口還在,發(fā)現(xiàn)"任務(wù)管理器"中程序還在運(yùn)行著.對,它并沒有像你想的那樣,在點(diǎn)擊"X"關(guān)閉按鈕結(jié)束掉程序.這些是需要你告訴程序該怎么做的.我們需要在代碼中添加相應(yīng)的事件處理函數(shù). 新增加了destroy函數(shù),它就是事件處理函數(shù),在gtk也可叫做信號處理函數(shù),因?yàn)間tk中用信號來進(jìn)行事件的通知.destory函數(shù)的形參是有約定寫法的,這個(gè)大家可以參見具體的手冊.在主程序中,還需要將這一函數(shù)與具體的信號進(jìn)行關(guān)聯(lián),見第17行.
在完成這些工作后,你再點(diǎn)擊"X"按鈕時(shí),會發(fā)現(xiàn)"命令行窗口"會出現(xiàn)"按任意鍵繼續(xù)",程序是真正地退出了.
/* file gtk.c */ #include static void destroy( GtkWidget *widget, gpointer data ) { gtk_main_quit (); //退出gtk主循環(huán) } int main(int argc, char* argv[]) { GtkWidget* window; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Hello World"); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy), NULL); gtk_widget_show (window); gtk_main(); return 0; }
有些人會對這個(gè)gtk應(yīng)用程序總會伴隨著黑黑的命令行窗口感到不爽,包括我自己在內(nèi).不知道gtk程序在gnome下也是這樣,我沒有試過,不知道.現(xiàn)在我就使用windows平臺的特定方法,來讓它消失吧,就是使用WinMain做為主函數(shù).這方法或許不是最正規(guī)的做法,但我看來卻有效.如果要考慮到跨平臺的話,可以用條件編譯處理下.
/* file gtk.c */ #include#include static void destroy( GtkWidget *widget, gpointer data ) { gtk_main_quit (); //退出gtk主循環(huán) } int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { //為應(yīng)付gtk_init所需要的參數(shù) int argc=1; char* commandLine={"gtkApplication"}; char** argv = &commandLine; GtkWidget* window; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Hello World"); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy), NULL); gtk_widget_show (window); gtk_main(); return 0; }
好啦,現(xiàn)在再編譯運(yùn)行,命令行窗口沒有了.你會發(fā)現(xiàn)真正的win32窗體了.但是在開發(fā)中,我建議用gtk時(shí),還是讓命令行窗口顯示出來,因?yàn)間tk在出錯(cuò)時(shí),會把一些有用的信息打到命令行窗口中,這對你的幫助會很大.
PyGTK讓你用Python輕松創(chuàng)建具有圖形用戶界面的程序.底層的GTK+提供了各式的可視元素和功能,如果需要,你能開發(fā)在GNOME桌面系統(tǒng)運(yùn)行的功能完整的軟件.
PyGTK真正具有跨平臺性,它能不加修改地,穩(wěn)定運(yùn)行各種操作系統(tǒng)之上,如Linux,Windows,MacOS等.除了簡單易用和快速的原型開發(fā)能力外,PyGTK還有一流的處理本地化語言的獨(dú)特功能.
PyGTK是自由軟件,所以你能幾乎沒有任何限制的使用,修改,分發(fā),研究它,它是基于LGPL協(xié)議發(fā)布的.
如果你對上面提到的GTK+,也不了解的話,那允許再對它也進(jìn)行一番介紹.GTK+,用C語言開發(fā)的,具有跨平臺的GUI庫,它是GNOME桌面系統(tǒng)(如果你在用Linux,一定不陌生)和GIMP圖象編輯器的開發(fā)工具箱.它是世界上許多程序員的選擇,對他們來說,國際化的支持是必要的,而且性能也總是他們考慮的因素.與GTK同一領(lǐng)域的還有Qt庫,它是由商業(yè)公司開發(fā)的C++圖形庫,雖然它也有免費(fèi)的.
在windows平臺的安裝和開發(fā)
安裝PyGTK只需執(zhí)行下列步驟:
或許你對這些步驟還感到麻煩,或者對Python不熟悉的話,那也沒有關(guān)系,直接下載一鍵安裝包all-in-one installer,為你配置好全部運(yùn)行時(shí)環(huán)境.
看看開發(fā)環(huán)境是否配置正確,將下列代碼作為Python腳本或者在Python交互控制臺下輸入.如果正確的話,應(yīng)該有一個(gè)標(biāo)題為"Hello World"的windows的空窗口呈現(xiàn)在你面前.
如果不能運(yùn)行的話,有可能會出現(xiàn)一個(gè)不能成功加載dll的錯(cuò)誤提示,這是因?yàn)槿鄙賗conv.dll.這時(shí)需要只需從網(wǎng)上下載過來,拷貝至windows/system32目錄下即可了.
import gtk window = gtk.Window() window.set_title("Hello World") window.show_all() gtk.main()
FlexeLint for C/C++是在PC_lint在windows平臺獲得成功后,同樣由Gimpel公司開發(fā)的,以源代碼形式發(fā)布的,在Unix/Linux平臺上的靜態(tài)代碼分析工具。
本文主要介紹PC-lint的安裝與配置,因此是在windows平臺上進(jìn)行討論。
PC-lint支持幾乎所有流行的編譯器和IDE環(huán)境,可能因?yàn)槠浒l(fā)展歷史和面對專業(yè)程序員群體的原因,它是以命令行加配置文件的形式進(jìn)行使用,所以其使用習(xí)慣跟現(xiàn)在常見的windows軟件不同。
現(xiàn)以PC-lint與VS2005進(jìn)行集成來說明:
將PC-lint釋放到某一目錄下,如:D:\Program Files\pclint.將新建一std.lnt文件在主目錄中,并將添加上以下的內(nèi)容
au-sm.lnt co-msc80.lnt lib-mfc.lnt lib-stl.lnt lib-w32.lnt lib-wnt.lnt lib-atl.lnt options.lnt -si4 -sp4 -i "C:\Program Files\Microsoft Visual Studio 8\VC\include" -i "C:\Program Files\Microsoft Visual Studio 8\VC\atlmfc\include" -i "C:\Program Files\Microsoft Visual Studio 8\VC\PlatformSDK\include" -i "C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\include"
注意:-i后面為相應(yīng)的vc頭文件目錄路徑,而一系列xxx.lnt是語法配置規(guī)則,決定了按什么規(guī)則進(jìn)行檢查,以后可以根據(jù)需要進(jìn)行增減. 寫在配置文件中相應(yīng)的的xxx.lnt從lnt子目錄中,拷貝到主目錄中.(這一步很重要) 在vs2005中的工具->外部工具中,點(diǎn)擊"添加",新建一個(gè)外部工具.標(biāo)題可以任意,可取(pc_lint);命令為:D:\Program Files\pclint\LINT-NT.EXE;參數(shù)為:-i"D:\Program Files\pclint" std.lnt "$(ItemFileName)$(ItemExt)";初始目錄為:$(ItemDir),并將下面的"使用輸出窗口"勾選上. 接下來,就可以寫一段程序,在工具菜單中選擇pc_lint 來進(jìn)行檢查了.如果你編寫的程序有不符合定義的規(guī)范,則會在輸出窗口中出現(xiàn)相關(guān)的信息.