一個游戲程序員的學習資料
想起寫這篇文章是在看侯杰先生的《深入淺出MFC》時,突然覺得自己在大學這幾年關于游戲編程方面還算是有些心得,因此寫出這篇小文,介紹我眼中的游戲程序員的書單與源代碼參考。一則是作為自己今后兩年學習目標的備忘錄,二來沒準對別人也有點參考價值。我的原則是只寫自己研究過或準備研究的資料,所以內容無疑會帶上強烈的個人喜好色彩,比如對網絡,數據庫等重要方面完全沒有涉及。因為自己主要對三維圖形引擎,人工智能算法,腳本系統,反外掛(反反外掛? ^-^)等方面感興趣。這學期電腦都沒聯網了,在岳麓山閉關修煉中(^-^),連這篇文章都得在學校圖書館電子閱覽室(電影放映室?)上傳,內容很多憑記憶寫出,如有誤差敬請訂正。程序員應該在理論學習與實踐編程中反復迭代,所以學習資料是一回事,須知盡信書不如無書。
一、書籍:
算法與數據結構:
《數據結構(C語言版)》——嚴蔚敏、吳偉民 清華出版社
我覺得其配套習題集甚至比原書更有價值,每個較難的題都值得做一下。
《Introduction to Algorithms》第二版 中文名《算法導論》
關于算法的標準學習教材與工程參考手冊,在去年CSDN網站上其翻譯版竟然評為年度二十大技術暢銷書,同時《程序員》雜志上開設了“算法擂臺”欄目,這些溯源固本的舉動,不由得使人對中國現今浮躁不堪的所謂“IT”業又產生了一線希望。這本厚厚的書,幸虧打折我才買得起。雖然厚達千頁,但其英文通俗曉暢,內容深入淺出,可見經典之作往往比一般水準的書還耐讀。還能找到MIT的視頻教程,第一節課那個老教授嘻皮笑臉的,后面就是一長發助教上課了。
《C語言名題精選百則 技巧篇》——冼鏡光 機械工業出版社
作者花費一年時間搜集了各種常見C程序段的極具技巧性的編程法,其內容都是大有來頭的,而且給出了詳細的參考資料。如一個普通的Fibonacci數就給出了非遞歸解、快速算法、擴充算法等,步步深入,直至幾無油水可榨。對于視速度如生命,連一個普通的浮點數轉化為整數都另辟蹊徑以減少CPU cycle的游戲程序員,怎可不看?
《計算機算法基礎(第二版)》—— 佘祥宣等 華中科大出版社
我看到幾個學校的研究生拿它作教材(研究生才開算法,太開玩笑了吧)。這本書薄是薄了點,用作者的話來說,倒也“精辟”。其實此書是《Fundamentals of Computer Algorithms》的縮寫版,不過原書出版太久了,反正我是沒找到。
《The Art of Computer Programming》Volume 1-3
作者Donald E. Knuth是我心目中與馮.諾依曼、Dijkstra、Shannon并列的四位大師。這本書作者從讀大學本科時開始寫,一直寫到博士時,十年磨一劍,足見其下足了功夫??勺鳛橛嬎銠C技術的核心——算法與數據結構的終極參考手冊。創新處也頗多,譬如常見的Shell排序他在書中提出可用(3i-1)/2的間隔,這使其稍快于O(n1. 5)。當然這套書描述高度數學化,為此恐怕一般的人(我?)最好還得先看一本數學預備書《Concrete Mathematics》(直譯為混凝土數學?^-^)再說。可惜的是這套書才出到第三卷,并沒有覆蓋全部常見的算法內容。不過好在對于游戲程序員來說,越常見的算法用得越多,這也不算是什么要命的損失。
《STL源碼剖析》—— 侯捷 華中科大出版社
侯捷不用介紹了,華人技術作家中的旗艦,說其有世界級水準也不為過。這本書我以為是C++與數據結構的葵花寶典(欲練此功,必先自宮)。也就是說,不下幾層地獄很難看懂,因為它要求的預備知識太多了,如STL、數據結構、泛型編程、內存管理都要很扎實(為此是不是還要看看有內存管理設計模式之稱的《Small Memory Software》這本書呢?),但是一旦看懂,真會是所向披靡。
《Data Structures for Game Programmers》
每個數據結構的例程都是一個小游戲,還用SDL庫實現了一個算法演示系統。雖然內容失之于淺,但起碼讓人了解了數據結構在游戲中的作用。
其實游戲程序并不比其它程序特殊,甚至要求基本功更加扎實,所以花時間做一些看似與實際應用不甚相干的習題,對今后的工作是大有裨益的。而且有些應用很廣的算法,如常被人津津樂道的A*算法及其變種,牽涉到圖的檢索周游與分枝-限界法,恐怕還得讀一些艱深的論文才能充分明白運用,如Donald E. Knuth的《An analysis of alpha-beta cutoffs》。其實還有不少此類的好書,如《Data Structures and Algorithms in C++》、《Programming Pearls》、《More Programming Pearls》(算法珠璣)等,我卻以為要先看嚴謹一點的著作,再看內容隨筆一點的書。
匯編:
《IBM-PC 匯編語言程序設計》第二版
國內經典教材。
《The Art of Assembly Language》
這本書足有1600頁,噢!
C語言:
《The C Programming Language》第二版
雖然篇幅短小,但每個例程都很經典。(我們老師開始拿它作教材,后面換為譚小強的C語言書,理由為:例子盡是些文本處理。我就納了悶了,難道現代的計算機程序不是將大量時間消耗在字符串與文本的處理上嗎?)
C++:
學過C語言,再學C++,先看這本《C++ Primer》的縮寫版:
《Essential C++》
對C++有個入門了解,再看
《C++ Common Knowledge: Essential Intermediate Programming》
就不會有什么重要的知識點完全不知所措了,接下來是
《The C++ Standard Library : A Tutorial and Reference》
標準庫,當然主要是標準模板庫的標準學習參考手冊,然后最好平時邊寫程序邊參悟。
《Effective C++》等
我是說書名以形容詞 + C++的那些書,計有七八本,慢慢看吧,羅馬不是一日建成的。
(《Essential C++》、《Effective C++》、《More Effective C++》、《Accelerated C++》、《Effective STL》、《Exceptional C++》、《More Exceptional C++》、《Imperfect C++》,雖然書名格式相似,但每一本都絕非馬虎之作。)
誰說C++程序比C程序要慢?那就請看下面:
《The Design and Evolution of C++》
知其過去才能知其未來,才能應用。
《Inside the C++ Object Model》
揭露C++的編譯器模型。
《Efficient C++ Performance Programming Techniques》
當算法優化已到極致,在運用匯編之前,最后還可看看此書,有時高級和低階都能做成相同的事情。
還有兩本特別的書:
《Modern C++ Design : Generic Programming and Design Patterns Applied》
作者想把設計模式和泛型編程結合起來,并寫了個嘗試提供一切的Loki庫來實作,不過其觀點并未得到C++社區的普遍響應。盡管如此,本書仍稱得上思想前沿性與技術實用性結合的典范。
《C++ Template Metaprogramming》
把編譯器當作計算器?本書介紹了Boost庫的MPL模板元編程庫。當然提到Boost庫,對于游戲程序員不能不提到其中的Graph庫,有《The Boost Graph Library》一書可看。還有其中Python庫,號稱國內首款商業三維圖形引擎的起點引擎就用了Boost-Python庫。說實話我覺得起點引擎還是蠻不錯的,那個自制的三維編輯器雖然界面簡陋,但功能還算蠻完善,給游戲學院用作教學內容也不錯。另有一個號稱中國首款自主研發的全套網游解決方案。我看到它那個三維編輯器,心想這不就是國外一個叫freeworld3D的編輯器嗎?雖然有點偏門,但我以前還較勁嘗試破解過呢。還把英文界面漢化了,大概用exescope這樣的資源修改軟件就能搞定吧。我又心想為什么要找freeworld3D這個功能并不太強大的編輯器呢?僅僅是因為它便宜到幾十美金?它唯一特別一點的地方就是支持導出OGRE圖形引擎的場景格式,這樣一想不由得使人對它圖形引擎的“自主”性也產生懷疑了。這樣的“自主”研發真讓人汗顏,只要中國還沒封sourceforge這個網站(據說以前和freeBSD網站一起被封過?),國人就能“自主”研發。
有人還會推薦《C++ Primer》《Thinking in C++》《The C++ Programming Language》等書吧,誠然這些書也很好,但我總覺得它們太大部頭了。還不如多花點時間看看國外好的源代碼。
Windows編程
Operating System Concepts第五版
國內有些操作系統的教程其實就是它的縮寫版。
《Windows 95 System Programming Secrets》
深入剖析了Windows操作系統的種種種種,有人愛看《Linux內核完全注釋》,有人愛看《自己動手寫操作系統》這樣煽情的書,但我想作為商業的操作系統,把Windows內核剖析到這地步也高山仰止了。
《Programming Applications for Microsoft Windows》第四版
先進程線程,再虛存管理,再動態鏈接庫,最多講到消息機制。作者在序言中說:“我不講什么ActiveX, COM等等,因為當你了解了這些基礎后,那些東西很快就會明白!”可以作為《Programming Windows》的先修課。
計算機體系:
《Computer Systems : A Programmer’s Perspective》
和《The Art of Computer Programming》在我心中是計算機史上兩本稱得上偉大的書,計算機組成原理,操作系統,匯編,編譯原理,計算機網絡等等課程匯成這本千頁的大書,因為計算機在作者眼中就是一個整體。
開源閱讀:
《Code Reading : The Open Source Perspective》
張大千臨摹了幾百張明代石濤的山水,畫出的畫以假亂真,后來他去敦煌潛心臨摹幾年,回來畫風大變,終成大家。程序員其實有40%的時間是在讀別人的源代碼,侯捷先生說:“源碼面前,了無秘密”,又說“天下大事,必作于細”,可以與他的《上窮碧落下黃泉,源碼追蹤經驗談》參看。
MFC:
《深入淺出MFC》
我實在以為沒有看過侯捷先生的《深入淺出MFC》的人多半不會懂得MFC編程。其實我是打算用一年多的時間寫一個給游戲美工用的三維編輯器,順便作為畢業設計。圖形庫就用MFC吧,反正也沒得選擇。如果要用wxWidgets無非是獵奇而已,還不是MFC的翻版,當然它跨平臺了。就象阻擊手對自己槍械的零件了如指掌一樣,要想用MFC寫出非玩具程序的人一定要了解其內部構造。還有一本書叫《MFC深入淺出》,并不是同一本。
IDE:
《Microsoft Visual Studio 2005 Unleashed》
工欲善其事,必先利其器。當然我認為與其用形如Source Insight、Slick Edit、Code Visualizer之類的代碼閱讀器、圖形化工具,還不如用自己的大腦。但如果你嫌打源代碼慢的話,可以用Visual AssistX。如果嫌老是寫重復相似的代碼的話,可以用Code Smith。單元測試可以用CppUnit,Boost庫中的測試框架也不錯。有心情可以吧Visual Studio外接Intel的Compiler,內嵌STLport,但不是大工程,性能分析沒必要動不動就用下VTune吧。
程序員之路:
《游戲之旅——我的編程感悟》
云風大哥。在我心目中游戲程序員國外首推卡馬克,國內首推云風。也許過兩年我會到網易當云風大哥的助理程序員吧。It’s my dream.(^-^)他寫這本書的時候本著只有透徹理解的東西才寫出來,因此內容不會很酷新,但是相信我,每讀一遍都有新的收獲,主要還不是知識上的,因為知識是學無止境的,授人以魚不如授人以漁,精神上的啟迪才是長久的。誠如經典游戲《仙劍奇俠傳》的主力程序員兼美術指導姚壯憲(人稱姚仙)在序言中所說的“云風得到的只是一些稿費,而整個中國民族游戲產業得到的將是一次知識的推動”,此言不虛矣。
《編程高手箴言》
梁肇新是豪杰超級解霸的作者,本來每個合格的程序員(Programmer , 而非Coder)都應該掌握的東西,現在變成了編程高手的獨家箴言。不知是作者的幸運還是中國IT業的悲哀。知識點還是講得蠻多的,不過對MFC的地位頗有微詞。我實在認為MFC的名聲就是那些不懂得用它的人搞臭的。不過作者的牢騷也情有可原,每個具有創造力的程序員都應該不太喜歡framework。
《Masters of DOOM: How Two Guys Created an Empire and Transformed Pop Culture》中文名《DOOM啟世錄》
卡馬克,羅洛斯,這些游戲史上如雷貫耳的名字。(現在卡馬克已專注于火箭制造上,羅洛斯則攜妻回鄉隱居)要不是沒上過大學的卡馬克和圖形學大師亞伯拉罕的功勛,可能到現在游戲中還不知三維為何物。勿庸置疑,在計算機界歷史是英雄們所推動的。這本書真實的記錄了這些塵世英雄的所為所思。
作為程序員的我對這幾本策劃與美工的書也產生了濃厚興趣,以前搞過一兩年的3DS MAX插件編程,覺得用maxscript還是好過MaxSDK,畢竟游戲開發中所多的是模型場景數據的導入導出,大可不必大動干戈。
策劃:
《Creating Emotion in Games : The Craft and Art of Emotioneering》
在壯麗煊目的宏偉三維世界背后,在殘酷的殺戮,動人心魄的情節背后,我們還需要什么來抓住玩家的心?答對了,就是emotion.真正打動人心的,才是深入骨髓的。
《Ultimate Game Design : Building Game Worlds》
從名字可以看出,寫給關卡設計師的,特別是講室外自然場景的構建頗有可取之處。
《Developing Online Games : An Insider’s Guide》
就象名為反模式的書講軟件團隊運營一樣,這本書講商業運作多過技術。一個歷經艱難,現在盛大的游戲程序員,翻譯了這本書。
美工:
《Digital Cinematography & Directing》
數字攝影導演術,每當你在3DS MAX或者Maya等三維創作軟件中擺放攝影機,設計其運動軌跡時,你可曾想過你也站在導演的位置上了?
《The Animator’s Survival Kit》
看著這本講卡通角色運動規律的書,一邊產生溫習《貓和老鼠》的念頭,一邊繼續對前不久新聞聯播中關于中國產生了某計算機自動卡通動畫生成軟件報道的蔑視,這條報道稱此舉可大大加快中國卡通動畫的產量。我且不從技術上探討其是否是在放衛星(其實我知道得很清楚,前文已表,本人搞過一兩年的卡通動畫輔助軟件編程),但計算機機械生成的動畫怎可代替人類充滿靈性的創作?
《The Dark Side of Game Texturing》
用Photoshop制作材質貼圖,還真有些學問。
三維圖形學:
搞三維圖形學首先還是要扎扎實實的先看解析幾何、線性代數、計算幾何的教材,后面的習題一個都不能少。國內數學書還是蠻好的。蘇步青大師的《計算幾何》稱得上具有世界級水準,可惜中國CAD的宏圖被盜版給擊垮了?,F在是我們接過接力棒的時候了。It’s time!
《Computer Graphics Geometrical Tools》
《計算機圖形學幾何工具算法詳解》算法很多,紕漏處也不少。
《3D Math Primer for Graphics and Game Development》
淺易,可作為三維數學的“速食“。
《Mathematics for 3D Game Programming & Computer Graphics》第二版
比上面那本深入一些,證明推理的數學氣也濃一些,可作為專業的數學書與編程實踐一個過渡的橋梁吧。內容涉獵也廣,射線追蹤,光照計算,可視裁剪,碰撞檢測,多邊形技術,陰影算法,剛體物理,流體水波,數值方法,曲線曲面,還真夠豐富。
《Vector Game Math Processors》
想學MMX,SSE嗎,那就看它吧,不過從基礎講起的,要耐心哦。
DirectX:
《Introduction to 3D Game Programming with DirectX 9.0》
DirectX入門的龍書,作者自己寫的簡單示例框架,后面我干脆用State模式,把所有例子綁到一塊兒去了。
《Beginning Direct3D Game Programming》
作者取得律師學位后變成了游戲程序員,真是怪也哉。本書雖定位為入門級書,內容頗有獨特可取之處。它用到的示例框架是DXSDK Sample Framework,而不是現在通行的DXUT。要想編譯有兩種辦法吧,一是自己改寫成用DXUT的。二是找舊的Sample Framework。我又懶得為了一個示例框架下載整個早期版本的DirectX,后面在Nvidia SDK 9.5中發現了。
《Advanced Animation with DirectX》
DirectX高級動畫技術。骨骼系統,漸變關鍵幀動畫,偶人技術,表情變形,粒子系統,布料柔體,動態材質,不一而足。我常常在想,從三維創作軟件導出的種種效果,變成一堆text或binary,先加密壓縮打包再解包解壓解密,再用游戲程序重建一個Lite動畫系統,游戲程序員也真是辛苦。
OpenGL:
《NeHe OpenGL Tutorials》
雖是網絡教程,不比正式的書遜,本來學OpenGL就不過是看百來條C函數文檔的工夫吧,如果圖形學基礎知識扎實的話。
《OpenGL Shading Language》
OpenGL支持最新顯卡技術要靠修修補補的插件擴展,所以還要配合
《Nvidia OpenGL Extension Specifications》
來看為上。
《Focus on 3D Models》
《Focus on 3D Terrain Programming》
《Focus on Curves and Surfaces》
顧名思義,三本專論,雖然都很不深,但要對未知三維模型格式作反向工程前,研讀Geomipmapping地形算法論文前,CAD前,還是要看看它們為上,如果沒從別處得過到基礎的話。
腳本:
先看
《Game Scripting Mastery》
等自己了解了虛擬機的構造,可以設計出簡單的腳本解釋執行系統了。
再去查Python , Lua ,Ruby的手冊吧,會事半半功倍倍的。
《Programming Role Playing Games with DirectX 8.0》
一邊教學一邊用DirectX寫出了一個GameCore庫,初具引擎稚形。
《Isometric Game Programming with DirectX 7.0》
三維也是建立在二維的基礎上,這就是這本書現在還值得看的原因。
《Visual C++網絡游戲建模與實現》
聯眾的程序員寫的,功力很扎實,講棋牌類游戲編程,特別講了UML建模和Rotional Rose。
《Object-Oriented Game Development》
套用某人的話:“I like this book.”
Shader:
要入門可先看
《Shaders for Game Programmers and Artists》
講在RenderMonkey中用HLSL高級著色語言寫Shader.
再看
《Direct3D ShaderX : Vertex and Pixel Shander Tips and Tricks》
用匯編著色語言,純銀赤金。
三大寶庫:
《Game Programming Gems》
我只見到1-6本,據說第7、8本也出來了?附帶的源代碼常有bug,不過瑕不掩瑜,這套世界頂級游戲程序員每年一度的技術文集,涉及游戲開發的各個方面,我覺得富有開發經驗的人更能在其中找到共鳴。
《Graphics Gems》全五本
圖形學編程Bible,看了這套書你會明白計算機領域的科學家和工程師區別之所在??茖W家總是說,這個東西在理論上可行。工程師會說,要使問題在logN的時限內解決我只能忍痛割愛,舍繁趨簡。
《GPU Gems》出了二本
Nvidia公司召集圖形學Gurus寫的,等到看懂的那一天,我也有心情跑去Siggraph國際圖形學大會上投文章碰運氣。
游戲引擎編程:
《3D Game Engine Programming》
是ZFXEngine引擎的設計思路闡釋,很平實,冇太多驚喜。
《3D Game Engine Design》
數學物理的理論知識講解較多,本來這樣就夠了,還能期待更多嗎?
人工智能:
《AI Techniques for Game Programming》
講遺傳算法,人工神經網絡,主要用到位數組,圖算法。書的原型是根據作者發表到GameDev.net論壇上的內容整理出來的,還比較切中實際。
《AI Game Programming Wisdom》
相當于AI編程的Gems。
《PC游戲編程(人機博弈)》
以象棋程序為藍本,介紹了很多種搜索算法,除了常見的極大極小值算法及其改進--負極大值算法,還有深度優先搜索以外。更提供了多種改進算法,如:Alpha-Beta,Fail-soft alpha-beta,Aspiration Search, Minimal Window Search,Zobrist Hash,Iterative Deepening,History Heuristic,Killer Heuristic,SSS*,DUAL*,MFD and more.琳瑯滿目,實屬難得。
反外掛:
《加密與解密(第二版)》 看雪論壇站長 段鋼
破解序列號與反外掛有關系么?不過,世上哪兩件事情之間又沒有關系呢?
《UML Distilled》 Martin Fowler
很多人直到看了這本書才真正學懂UML。
Martin Fowler是真正的大師,從早期的分析模式,到這本UML精粹,革命性的重構都是他提出的,后來又寫了企業模式一書?,F在領導一個軟件開發咨詢公司,去年JavaOne中國大會他作為專家來華了吧。個人網站:MartinFowler.com
設計模式三劍客:
《Design Patterns Elements of Reusable Object-Oriented Software》
《Design Patterns Explained》
《Head First Design Patterns》
重構三板斧:
《Refactoring : Improving the Design of Existing Code》
《Refactoring to Patterns》
《Refactoring Workbook》
軟件工程:
《Extreme Programming Explained : Embrace Change》第二版
其中Simplicity的Value真是振聾發聵,這就是我什么都喜歡輕量級的原因。
《Agile Software Development Principles,Patterns,and Practices》
敏捷真是炒得夠火的,連企業都有敏捷一說,不過大師是不會這么advertising的。
《Code Complete》第二版
名著。
數學:
《數學,確定性的喪失》M.克萊因
原來數學也只不過是人類的發明與臆造,用不著供入神殿,想起歷史上那么多不食人間煙火的科學家(多半是數學家),自以為發現了宇宙運作的奧秘,是時候走下神壇了。
物理:
《普通物理學》第一冊 += 《Physics for Game Developers》
物理我想就到此為此吧,再復雜我可要用Newton Engine,ODE了,等待物理卡PPU普及的那天,就可充分發揮PhysX的功效了,看過最新的《細胞分裂》游戲Demo演示,成千上萬個Box瘋狂Collide,骨灰級玩家該一邊摸錢包一邊流口水了。
二、開源代碼:
Irrlicht
著名的鬼火引擎,從兩年前第一眼看到它,這個輕量級的三維圖形引擎,就喜歡上了它。源代碼優雅,高效,且不故弄玄虛。值得每個C++程序員一讀,并不限于圖形編程者。它的周邊中也有不少輕量級的東西。如Lightfeather擴展引擎,ICE、IrrlichtRPG、IrrWizard.還有IrrEdit、IrrKlang、IrrXML可用。(可能是為了效率原因,很多開源作者往往喜歡自己寫XML解析庫,如以上的IrrXML庫,即使有現成的tinyXML庫可用。這真會讓tomcat里面塞Axis,Axis里面塞JUDDI,弄得像俄羅斯套娃玩具的Java Web Service Coder們汗顏。)
OGRE
排名第一的開源圖形引擎,當然規模是很大的,周邊也很多。除了以C#寫就的OgreStudio ,ofusion嵌入3DS MAX作為WYSWYG式的三維編輯器也是棒棒的,特別是其幾個場景、地形插件值得研究。以至于《Pro OGRE 3D Programming》一書專論其用法。搜狐的《天龍八部》游戲就是以其作為圖形引擎,當然還另外開發了引擎插塊啦。我早知道OGRE開發組中有一個中國人謝程序員,他以前做了很多年的傳統軟件編程。有一次天龍八部游戲的圖形模塊的出錯信息中包含了一串某程序員的工作目錄,有一個文件夾名即是謝程序員的英文名,我據此推斷謝程序員即是搜狐北京的主程??磥碇袊鴮﹂_源事業還是有所貢獻的嘛,王開源哥哥的努力看來不會白費?。?span>^-^)不過我偵測的手法也有些像網站數據庫爆庫了,非君子之所為作。
RakNet
基于UDI的網絡庫,竟還支持聲音傳輸,以后和OpenVision結合起來做個視聊程序試試。
Blender
聲譽最盛的開源三維動畫軟件,竟還帶一個游戲引擎。雖然操作以快捷鍵驅動,也就是說要背上百來個快捷鍵才能熟練使用。但是作為從商業代碼變為開源之作,威脅三維商業巨頭的輕騎兵,歷經十年錘煉,代碼達百萬行,此代碼只應天上有,人間哪得幾回看,怎可不作為長期的源碼參考?
風魂
二維圖形庫。云風大哥的成名之作。雖然不代表其最高水平(最高水平作為商業代碼保存在廣州網易互動的SVN里呢),但是也可以一仰風采了。
圣劍英雄傳
二維RPG。幾個作者已成為成都錦天的主力程序員。錦天的老總從一百萬發家,三年時間身價過億,也是一代梟雄了。這份代碼作為幾年前的學生作品也算可以了,因為一個工程講究的是四平八穩,并不一定要哪個模塊多么出彩。反正我是沒有時間寫這么一個東東,連個美工都找不到,只能整天想著破解別人的資源(^-^)。
Boost
C++準標準庫,我想更多的時候可以參考學習其源代碼。
Yake
我遇到的最好的輕量級游戲框架了。在以前把一個工程中的圖形引擎從Irrlicht換成OGRE的嘗試中,遇到了它。OGRE的周邊工程在我看來都很庸腫,沒有完善文檔的情況下看起來和Linux內核差不多。不過這個Yake引擎倒是很喜歡。它以一個FSM有限狀態機作為實時程序的調度核心,然后每個模塊:物理、圖形、網絡、腳本、GUI、輸入等等都提供一個接口,接口之下再提供到每種具體開源引擎的接口,然后再接具體引擎。通過這樣層層抽象,此時你是接Newton Engine,ODE還是PysX都可以;是接OGRE,Crystal Space還是Irrlicht都可以;是接RakNet還是LibCurl都可以;是接Python,Lua還是Ruby都可以,是接CEGUI還是others,是接OIS還是others(呵呵,記不起來others)都可以。所以Yake本質上不是OGRE的周邊。雖然用Neoengine的人都倒向了它,但是現在版本還很早。特別是我認為,學習研究時一定要有這種抽象之抽象,接口之接口的東西把思維從具體的綁定打開,而開發時抽象要有限度的,就像蔡學鏞在《Java夜未眠》中講的,面向對象用得過濫也會得OOOO癥(面向對象過敏強迫癥)。
Quake Doom系列
據說很經典,卡馬克這種開源的黑客精神就值得贊許。把商業源代碼放出來,走自己的創新之路,讓別人追去吧。不過Quake與Unreal引擎的三維編輯器是現在所有編輯器的鼻祖,看來要好好看看了。
Nvidia SDK 9.X
三維圖形編程的大寶庫,這些Diret3D與OpenGL的示例程序都是用來展示其最新的顯卡技術的。硬件廠商往往對軟件產品不甚在意,源代碼給你看,東西給你用去吧,學完了還得買我的硬件。Intel的編譯器,PhysX物理引擎大概也都是這樣。Havok會把它的Havok物理引擎免費給別人用嗎?別說試用版,連個Demo都看不到。所以這套SDK的內容可比MS DirectX SDK里面那些入門級的示例酷多了,反正我是如獲至寶,三月不知愁滋味。不過顯卡要so-so哦。我的GeForce 6600有兩三個跑不過去,差強人意。
三、網站:
www.CSDN.net
程序員大本營吧,軟文與“新技術秀”討厭了點,blog和社區是精華之所在。
www.GameRes.com
游戲程序員基地,文檔庫中還有點東西。投稿的接收者Seabug與圣劍英雄傳的主程Seabug會是同一個人嗎?一個在成都錦天擔當技術重擔的高手還有時間維護網站嗎?我不得而知。
“何苦做游戲”網站
名字很個性,站長也是歷盡幾年前產業發展初期的艱難才出此名字。
www.66rpg.com
二維游戲圖片資源很多,站長柳柳主推的RPGMaker 軟件也可以玩一玩吧,但對于專業開發者來說不可當真。
www.GameDev.net
論壇中有不少熱心的國外高手在活動。
www.SourceForge.net
不用說了,世界最大的開源代碼庫,入金山怎可空手而返?看到國外那些學生項目動不動就像模像樣的。(DirectX的稚形就是英國的學生項目,在學校還被判為不合格。)
www.koders.com
源代碼搜索引擎,支持正則表達式,google Lab中也有。當你某種功能寫不出來時,可以看一下開源代碼怎么寫的,當然不過是僅供參考,開源代碼未必都有產品級的強度。說到google,可看《Google Power Tools Bible》一書,你會發現,google的眾多產品原來也有這么多使用門道。
這篇小文足足寫了一天半的時間,不由得使我對侯捷一樣的技術作家長期伏案辛勤勞作深深敬佩了??磥韺τ跁蛘哕浖?,都應該尊重作者或者programmer的才智勞動
posted @
2008-12-26 00:29 kzhang 閱讀(1536) |
評論 (2) |
編輯 收藏
主題索引:
一、剖析C++標準庫智能指針(std::auto_ptr)
1.Do you Smart Pointer?
2.std::auto_ptr的設計原理
3.std::auto_ptr高級使用指南
4.你是否覺得std::auto_ptr還不夠完美?
二、C++條件,尋找構造更強大的智能指針(Smart Pointer)的
策略
1.支持引用記數的多種設計策略
2.支持處理多種資源
3.支持Subclassing
4.支持多線程條件下,線程安全的多種設計策略
5.其它多種特殊要求下,再構造
三、Generic Programming基礎技術和Smart Pointer
1.回首處理資源中的Traits技術
2.回首多線程支持的設計
四、COM實現中,Smart Pointer設計原理
五、著名C++庫(標準和非標準)中的Smart Pointer現狀
---------------------------------------------------------------------
一、剖析C++標準庫智能指針(std::auto_ptr)
1.Do you Smart Pointer?
Smart Pointer,中文名:智能指針, 舶來品?
不可否認,資源泄露(resource leak)曾經是C++程序的一大噩夢.垃圾回收
機制(Garbage Collection)一時頗受注目.然而垃圾自動回收機制并不能
滿足內存管理的即時性和可視性,往往使高傲的程序設計者感到不自在.
況且,C++實現沒有引入這種機制.在探索中,C++程序員創造了鋒利的
"Smart Pointer".一定程度上,解決了資源泄露問題.
也許,經常的,你會寫這樣的代碼:
//x擬為class:
// class x{
// public:
// int m_Idata;
// public:
// x(int m_PARAMin):m_Idata(m_PARAMin){}
// void print(){ cout<<m_Idata<<endl; }
// .....
// }
//
void fook(){
x* m_PTRx = new A(m_PARAMin);
m_PTRx->DoSomething(); //#2
delete m_PTRx;
}
是的,這里可能沒什么問題.可在復雜、N行、m_PTRclassobj所指對象生命周
期要求較長的情況下,你能保證你不會忘記delete m_PTRclassobj嗎?生活中,
我們往往不應該有太多的口頭保證,我們需要做些真正有用的東西.還有一個
更敏感的問題:異常.假如在#2方法執行期異常發生,函數執行終止,那么new
出的對象就會泄露.于是,你可能會說:那么就捕獲異常來保證安全性好了.
你寫這樣的程式:
void fook(){
A* m_PTRx = new A(m_PARAMin);
try{
m_PTRx->DoSomething();
}
catch(..){
delete m_PTRx;
throw;
}
delete m_PTRx;
}
哦!天哪!想象一下,你的系統,是否會象專為捕獲異常而設計的.
一天,有人給你建議:"用Smart Pointer,那很安全.".你可以這樣重寫你的程序:
void fook(){
auto_ptr<x> m_SMPTRx(new x(m_PARAMin));
m_SMPTRx->DoSomething();
}
OK!你不太相信.不用delete嗎?
是的.不用整天提心吊膽的問自己:"我全部delete了嗎?",而且比你的delete
策略更安全.
然后,還有人告訴你,可以這樣用呢:
ok1.
auto_ptr<x> m_SMPTR1(new x(m_PARAMin));
auto_ptr<x> m_SMPTR2(m_SMPTR1); //#2
May be you can code #2 like this :
auto_ptr<x> m_SMPTR2;
m_SMPTR2 = m_SMPTR1;
ok2.
auto_ptr<int> m_SMPTR1(new int(32));
ok3.
auto_ptr<int> m_SMPTR1;
m_SMPTR1 = auto_ptr<int>(new int(100));
也可以:
auto_ptr<int> m_SMPTR1(auto_ptr<int>(new int(100)));
ok4.
auto_ptr<x> m_SMPTR1(new x(m_PARAMin));
m_SMPTR1.reset(new x(m_PARAMin1));
ok5.
auto_ptr<x> m_SMPTR1(new x(m_PARAMin));
auto_ptr<x> m_SMPTR2(m_SMPTR.release());
cout<<(*m_SMPTR2).m_Idata<<endl;
ok6.
auto_ptr<int> fook(){
return auto<int>(new int(100));
}
ok7.............and so on
但不可這樣用:
no1.
char* chrarray = new char[100];
strcpy(chrarray,"I am programming.");
auto_ptr<char*> m_SMPTRchrptr(chrarray);
//auto_ptr并不可幫你管理數組資源
no2.
vector<auto_ptr<x>> m_VECsmptr;
m_VECsmptr.push_back(auto_ptr<int>(new int(100)));
//auto_ptr并不適合STL內容.
no3.
const auto_ptr<x> m_SMPTR1(new x(100));
auto_ptr<x> m_SMPTR(new x(200));
no4.
x m_OBJx(300);
auto_ptr<x> m_SMPTR(&m_OBJx);
no5
x* m_PTR = new x(100);
auto_ptr<x> m_SMPTR = m_pTR;
no6..........and so on
預先提及所有權的問題,以便下面帶著疑問剖析代碼?
power1.
auto_ptr<x> m_SMPTR1(new x(100));
auto_ptr<x> m_SMPTR2 = m_SMPTR1;
m_SMPTR2->print();
//輸出:100.
m_SMPTR1->print();
//!! 非法的.
power2.
auto_ptr<x> m_SMPTR(new x(100));
auto_ptr<x> returnfun(auto_ptr<x> m_SMPTRin){
return m_SMPTRin;
}
auto_ptr<x> = returnfun(m_SMPTR); //#5
//在上面的#5中,我要告訴你對象所有權轉移了兩次.
//什么叫對象所有權呢?
2. std::auto_ptr的設計原理
上面的一片正確用法,它們在干些什么?
一片非法,它們犯了什么罪?
一片什么所有權轉移,它的內部機智是什么?
哦!一頭霧水?下面我們就來剖析其實現機制.
基礎知識:
a.智能指針的關鍵技術:在于構造棧上對象的生命期控制
堆上構造的對象的生命期.因為在智能指針的內部,存儲
著堆對象的指針,而且在構析函數中調用delete行為.
大致機構如下:
x* m_PTRx = new x(100);//#1
template<typename T>
auto_ptr{
private:
T* m_PTR;//維護指向堆對象的指針,在auto_ptr定位后
.... //它應該指向#1構造的對象,即擁有所有權.
~auto(){ delete m_PTR; }
....
}
b.所有權轉移之說
上面曾有一非法的程式片段如下:
auto_ptr<x> m_SMPTR1(new x(100));
auto_ptr<x> m_SMPTR2 = m_SMPTR1;
m_SMPTR2->print();
//輸出:100.
m_SMPTR1->print();
//!! 非法的.
按常理來說,m_SMPTR->print();怎么是非法的呢?
那是因為本來,m_SMPTR1維護指向new x(100)的指針,
可是m_SMPTR2 = m_SMPTR1;auto_ptr內部機制使得m_SMPTR1將對象的地址
傳給m_SMPTR2,而將自己的對象指針置為0.
那么自然m_SMPTR->print();失敗.
這里程序設計者要負明顯的職責的.
那么auto_ptr為什么采取這樣的策略:保證所有權的單一性.
亦保證了系統安全性.
如果多個有全權的auto_ptr維護一個對象,那么在你消除一個
auto_ptr時,將導致多個auto_ptr的潛在危險.
下面我們以SGI-STL的auto_ptr設計為樣本(去掉了無關分析的宏),來剖析其原理.
#1 template <class _Tp> class auto_ptr {
#2 private:
#3 _Tp* _M_ptr; //定義將維護堆對象的指針
#4 public:
#5 typedef _Tp element_type; //相關類型定義
#6 explicit auto_ptr(_Tp* __p = 0) __STL_NOTHROW : _M_ptr(__p) {}
#7 auto_ptr(auto_ptr& __a) __STL_NOTHROW : _M_ptr(__a.release()) {}
#8 template <class _Tp1> auto_ptr(auto_ptr<_Tp1>& __a) __STL_NOTHROW
: _M_ptr(__a.release()) {}
//#6、#7、#8是auto_ptr構造函數的三個版本.
//#6注釋:傳入對象的指針,構造auto_ptr.explicit關鍵字:禁止隱式轉換.
// 這就是ok2正確,而no5(隱式轉換)錯誤的原因.
//#7注釋:拷貝構造函數.
// 傳入auto_ptr實例,構造auto_ptr. ok1、ok3使用了這個構造式.
// 它是一個很關鍵的構造函數,在具體情況下,我們再分析
//#8注釋:auto_ptr的模板成員,可在繼承對象重載的基礎上,實現特殊功能.
//
// 舉例:
// class A{ public:
// virtual void fook(){cout<<"I am programming"<<endl;
// /*..........*/ };
// class B : public A {
// virtual void fook(){ cout<<"I am working"<<endl;
// /*...........*/ };
// auto_ptr<A> m_SMPTRa(new A(33));//實質:
// auto_ptr<B> m_SMPTRb(m_SMPTRa); //基類的指針可以賦給派生類的指針
//
// auto_ptr<B> m_SMPTRb(new B(44));//實質:
// auto_ptr<A> m_SMPTRa(m_SMPTRb); //派生類的指針不可賦給基類的指針
//
// auto_ptr<A> m_SMPTRa(new B(33)); // ok!
// m_SMPTRa->fook()將調用派生類B的fook()
// m_SMPTRa->A::fook()將調用基類A的fook()
//
// auto_ptr<B> m_SMPTRb(new A(33)); // wrong!
//
//
#9 auto_ptr& operator=(auto_ptr& __a) __STL_NOTHROW {
#10 if (&__a != this) { delete _M_ptr; _M_ptr = __a.release(); }
#11 return *this;
#12 }
#13 template <class _Tp1>
#14 auto_ptr& operator=(auto_ptr<_Tp1>& __a) __STL_NOTHROW {
#15 if (__a.get() != this->get()) { delete _M_ptr; _M_ptr = __a.release(); }
#16 return *this;
#16 }
//
// #9~~#16 兩個版本的指派函數.
// delete _M_ptr; 在指派前,銷毀原維護的對象.
// _a.release() ; release操作,詳細代碼參見#20~~#23.
// 用于*this獲得被指派對象,
// 且將原維護auto_ptr置空.
// no3使用了第一種指派.
// 而權限轉移正是_a.release()的結果.
#17 ~auto_ptr() __STL_NOTHROW { delete _M_ptr; }
//構析函數.消除對象.注意這里對對象的要求!
#17 _Tp& operator*() const __STL_NOTHROW { return *_M_ptr; }
#18 _Tp* operator->() const __STL_NOTHROW { return _M_ptr; }
#19 _Tp* get() const __STL_NOTHROW { return _M_ptr; }
//
// 操作符重載.
// #17注釋:提領操作(dereference),獲得對象. 見ok5用法.
// #18注釋:成員運算符重載,返回對象指針.
// #19注釋:普通成員函數.作用同于重載->運算符
//
#20 _Tp* release() __STL_NOTHROW {
#21 _Tp* __tmp = _M_ptr;
#22 _M_ptr = 0;
#23 return __tmp; }
//上面已經詳解
#24 void reset(_Tp* __p = 0) __STL_NOTHROW {
#25 delete _M_ptr;
#26 _M_ptr = __p; }
//
//傳入對象指針,改變auto_ptr維護的對象
// 且迫使auto_ptr消除原來維護的對象
// 見ok3用法.
// According to the C++ standard, these conversions are required. Most
// present-day compilers, however, do not enforce that requirement---and,
// in fact, most present-day compilers do not support the language
// features that these conversions rely on.
//下面這片段用于類型轉化,目前沒有任何編譯器支持
//具體技術細節不訴.
#ifdef __SGI_STL_USE_AUTO_PTR_CONVERSIONS
#27 private:
#28 template<class _Tp1>
#29 struct auto_ptr_ref { _Tp1* _M_ptr; auto_ptr_ref(_Tp1* __p) : _M_ptr(__p) {}
};
#30 public:
#31 auto_ptr(auto_ptr_ref<_Tp> __ref) __STL_NOTHROW
: _M_ptr(__ref._M_ptr) {}
#32 template <class _Tp1>
#33 operator auto_ptr_ref<_Tp1>() __STL_NOTHROW
#34 { return auto_ptr_ref<_Tp>(this->release()); }
#35 template <class _Tp1> operator auto_ptr<_Tp1>() __STL_NOTHROW
#36 { return auto_ptr<_Tp1>(this->release()); }
#37 #endif /* __SGI_STL_USE_AUTO_PTR_CONVERSIONS */
#38 };
OK!就是這樣了.
正如上面原理介紹處敘說,
你需要正視兩大特性:
1.構造棧對象的生命期控制堆上構造的對象的生命期
2.通過release來保證auto_ptr對對象的獨權.
在我們對源碼分析的基礎上,重點看看:
no系列錯誤在何處?
no1.
我們看到構析函數template<class _Tp>
~auto_ptr() _STL_NOTHROW
{ delete _M_ptr; }
所以它不能維護數組,
維護數組需要操作:delete[] _M_ptr;
no2.
先提部分vector和auto_ptr代碼:
a.提auto_ptr代碼
auto_ptr(auto_ptr& __a) __STL_NOTHROW : _M_ptr(__a.release()) {}
b.提vector代碼
Part1:
void push_back(const _Tp& __x) {
if (_M_finish != _M_end_of_storage) {
construct(_M_finish, __x);
++_M_finish;
}
else
_M_insert_aux(end(), __x);
}
Part2:
template <class _T1, class _T2>
inline void construct(_T1* __p,
//++++++++++++++++++++++++++++++++
// const _T2& __value) { +
//++++++++++++++++++++++++++++++++
// new (__p) _T1(__value); +
//++++++++++++++++++++++++++++++++
}
Part3.
template <class _Tp, class _Alloc>
void
vector<_Tp, _Alloc>::_M_insert_aux
(iterator __position,
//++++++++++++++++++++++++++++++++
// const _Tp& __x) ++
//++++++++++++++++++++++++++++++++
{
if (_M_finish != _M_end_of_storage) {
construct(_M_finish, *(_M_finish - 1));
++_M_finish;
//++++++++++++++++++++++++++++++++
// _Tp __x_copy = __x; +
//++++++++++++++++++++++++++++++++
copy_backward(__position, _M_finish - 2, _M_finish - 1);
*__position = __x_copy;
}
else {
const size_type __old_size = size();
const size_type __len = __old_size != 0 ? 2 * __old_size : 1;
iterator __new_start = _M_allocate(__len);
iterator __new_finish = __new_start;
__STL_TRY {
__new_finish = uninitialized_copy
(_M_start, __position, __new_start);
construct(__new_finish, __x);
++__new_finish;
__new_finish = uninitialized_copy
(__position, _M_finish, __new_finish);
}
__STL_UNWIND((destroy(__new_start,__new_finish),
_M_deallocate(__new_start,__len)));
destroy(begin(), end());
_M_deallocate(_M_start, _M_end_of_storage - _M_start);
_M_start = __new_start;
_M_finish = __new_finish;
_M_end_of_storage = __new_start + __len;
}
}
從提取的vector代碼,Part1可看出,push_back的操作行為.
兵分兩路,可是再向下看,你會發現,無一例外,都
通過const _Tp& 進行拷貝行為,那么從auto_ptr提出的片段就
派上用場了.
可你知道的,auto_ptr總是堅持對對象的獨權.那必須修改
原來維護的對象,而vector行為要求const _Tp&,這樣自然會產生
問題.一般編譯器是可以發覺這種錯誤的.
其實,STL所有的容器類都采用const _Tp&策略.
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 看了sutter和Josuttis的兩篇文章中,都提及: +
+ STL容器不支持auto_ptr原因在于copy的對象只是獲得所有權的對象, +
+ 這種對象不符合STL的要求.可是本人總感覺即時不是真正的復制對象,+
+ 但我用vector<auto_ptr<x> >的目的就在于維護對象,并不在乎 +
+ 所謂的完全對象.而且我用自己寫的Smart Pointer配合STL容器工作, +
+ 很正常.那需要注意的僅僅是const問題. +
+ +
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
no3.
這個也是auto_ptr隱含的所有權問題引起的.
const auto_ptr不允許修改.
隨便提及:const對象不代表對象一點不可以改變.
在兩種const語義下,都有方法修改對象或對象內部指針維護的對象
或其它資源.
no4.
再看auto_ptr的構析函數.
delete不可以消除棧上資源.
no5.
依賴傳入對象指針的構造函數被聲明為explicit,禁止隱式轉換.
3.auto_ptr高級使用指南
a.類成員auto_ptr,禁止構造函數以構建"完全對象"
Programme1:
struct Structx{
int m_Idata;
char m_CHRdata;
/* and so on */
};
出于對象編程的理念,
我們將Structx打造成包裹類:
class StructWrapper{
private:
Structx* m_STRTxptr;
public:
StructWrapper():m_STRTxptr(new Structx){}
~StructWrapper(){delete m_SMRTxptr; }
public:
void Soperator1(){ /* 針對Structx對象的特性操作 */}
void Soperator2(){ /* 針對Structx對象的特性操作 */}
/* and so on */
};
Programme2:
class StructWrapper{
private:
auto_ptr<Structx> m_SMPTRx;
public:
StructWrapper():m_SMPTRAx(new Structx){}
public:
void Soperator1(){ /* 針對Structx對象的特性操作 */}
void Soperator2(){ /* 針對Structx對象的特性操作 */}
/* and so on */
};
Programme3:
StructWrapper::StructWrapper(const StructWrapper& other)
: M_SMPTRx(new Struct(*other.m_SMPTRx)) { }
StructWrapper& StructWrapper::operator=(const StructWrapper &other){
*m_SMPTRx = *other.m_SMPTRx;
};
處于對構建于堆中的對象(new Structx)智能維護的需要.
我們將programme1改造為programme2:
不錯,對象是可以智能維護了.
對于包裹類(StructWrapper)你是否會有這樣的構造或指派操作:
StructWrapper m_SMPTRWrapper2(m_SMPTRWrapper1);
StructWrapper mSMPTRWrapper2 = m_SMPTRWrapper1;
那么請注意:
當你坦然的來一個:M_SMPTRWrapper1->Soperator1();的時候,
系統崩潰了.
不必驚訝,所有權還是所有權問題.
問一下自己:當programme2默認拷貝構造函數作用時,又調用了auto_ptr的
默認構造函數,那么auto_ptr所有的默認行為都遵循獨權策略.對,就這樣.
m_SMPTRWrapper1的對象所有權轉移給了m_SMPTRWrapper2.
M_SMPTRWrapper1->Soperator1();那么操作變成了在NULL上的.
哦!系統不崩潰才怪.
那么你需要想,programme3那樣利用auto_ptr的提領操作符自己的
構造"完全對象".
b.利用const關鍵字,防止不經意的權限轉移
從上面的敘述,你可看出,所有權轉移到處可以釀成大禍.
而對于一般應用來說,獨權又是很好的安全性策略.
那么我們就用const來修飾auto_ptr,禁止不經意的錯誤.
當然上面提及:并不代表auto_ptr是不可修改的.
處于需要,從兩種const語義,你都可實現修改.
然,你還希望在函數傳入傳出auto_ptr那么你可傳遞auto_ptr的引用,
那就萬無一失了: void fook(const auto_ptr<x>& m_PARAMin);
在返回后賦予其它時,使用引用是不行的.你得用指針.
因為引用無論作為lvalue還是rvaluev,都會調用構造或指派函數.
4.你是否覺得std::auto_ptr還不夠完美
在實踐中,std::auto_ptr能滿足你的需求嗎?
Andrei Alexandrescu在一篇文章中,提及:有關Smart Pointer的技術就像
巫術.Smart Pointer作為C++垃圾回收機制的核心,它必須足夠強大的、具有工業強度和安全性.
但為了可一勞永逸我們還需要披荊斬棘繼續探索.
下面在需求層面上,我們思索一下我們的智能指針還需要些什么?
a. std::auto_ptr 能夠處理數組嗎?我們可以用智能指針來管理其它的資源嗎?
譬如一個線程句柄、一個文件句柄 and so on !
b. 對于我們的對象真的永遠實行獨權政策嗎?
c. Our 智能指針還需要在繼承和虛擬層面上發揮威力 !
d. 往往,需要擴展Our 智能指針的功能成員函數來滿足動態的需要 !
e. 也許,你需要的還很多.
---------------------------------------------------------------
[下續]
二、C++條件,尋找構造更強大的智能指針(Smart Pointer)的
策略
1.支持引用記數的多種設計策略
2.支持處理多種資源
3.支持Subclassing
4.支持多線程條件下,線程安全的多種設計策略
5.其它多種特殊要求下,再構造
三、Generic Programming基礎技術和Smart Pointer
1.回首處理資源中的Traits技術
2.回首多線程支持的設計
四、COM實現中,Smart Pointer設計原理
五、著名C++庫(標準和非標準)中的Smart Pointer現狀
-----------------------------------------------------------
--------------------------------------------------------------
鄭重聲明:
允許復制、修改、傳遞或其它行為
但不準用于任何商業用途.
寫于 20/3/2003
最后修改: 20/3/2003
By RedStar81
81_RedStar@163.com
-------------------------------------------------------------
posted @
2008-09-22 00:37 kzhang 閱讀(2687) |
評論 (1) |
編輯 收藏