• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            巢穴

            about:blank

            2011年3月15日 #

            linux內(nèi)核情景分析筆記-存儲(chǔ)管理

            第2章 存儲(chǔ)管理
            LINUX頁(yè)式管理
            PGD          PMD          PT        PTE
            頁(yè)表目標(biāo)     中間目錄     頁(yè)表     頁(yè)表項(xiàng)

            LINUX在32位地址下采取二層映射
            #define PGDIR_SHIFT 22
            #define PTRS_PER_PGD 1024

            #define PMD_SHIFT 22
            #define PTRS_PER_PMD 1

            #define PTRS_PER_PTE 1024
            根據(jù)以上宏定義,PMD被完美的架空了,而相當(dāng)于采取了二層映射

            其中PGD用了線性地址的最高10位 與  MMU 對(duì)應(yīng)
            線性地址的中間10位是所對(duì)應(yīng)的PTE在PT中的索引
            剩下的最低12位則是頁(yè)中的偏移量

            虛擬地址 = 段基地址:段偏移量
                                        16位      32位
            更準(zhǔn)確的講是段選擇子了吧

            在LINUX中段基地址 = 0(下面的____KERNEL_CS等),所以可以認(rèn)為線性地址與虛擬地址總是相等的,但其本質(zhì)不是一個(gè)東西


            0xC0000000-0xFFFFFFFF為內(nèi)核占用
            0x0-0xBFFFFFFF為用戶控件


            內(nèi)核的虛擬內(nèi)存為簡(jiǎn)單的線性映射
            #__PAGE_OFFSET (0xC0000000)
            #define PAGE_OFFSET  ((unsigned long) __PAGE_OFFSET)
            #define __pa(x) ((unsigned long)(x) - PAGE_OFFSET)
            #define __va(x) ((void *)((unsigned long)(x) +PAGE_OFFSET)

            __pa是從虛擬地址轉(zhuǎn)換成物理地址
            __va是從物理地址轉(zhuǎn)換成虛擬地址


            在GDT中有4個(gè)段描述符
            其索引是2-5
            分別是
            __KERNEL_CS 內(nèi)核代碼段
            __KERNEL_DS 內(nèi)核數(shù)據(jù)段
            __USER_CS 用戶代碼段
            __USER_DS 用戶數(shù)據(jù)段

            #define start_thread(regs,new_eip,new_esp) do {\
             __asm__("movl %0,%%fs;movl %0,%%gs"::"r"(0)); \
             set_fs(USER_DS);
             regs->xds = __USER_DS; \
             regs->xes = __USER_DS; \
             regs->xss = __USER_DS; \
             regs->xcs = __USER_CS; \
             regs->eip = new_eip;   \
             regs->esp = new_esp;   \

            }while(0)

            通過(guò)這段宏可以看出,LINUX沒(méi)用段式存儲(chǔ),雖然它也走了這個(gè)流程

             

            MMU的流程 MMU使用物理地址

            頁(yè)式映射
            從REG CR3拿PGD的地址
            找到頁(yè)面目錄,線性地址中的高10位為索引,找到頁(yè)面目錄項(xiàng),從中拿高20位作為頁(yè)面表的索引,頁(yè)面表與4k字節(jié)邊界對(duì)齊,CPU自動(dòng)補(bǔ)充前12位為0得到頁(yè)面表地址。

            然后拿線性地址的中間10位,得到頁(yè)面表中的索引,拿到頁(yè)面表項(xiàng),頁(yè)面表項(xiàng)的高20位在低位補(bǔ)充12個(gè)0,再加上線性地址的低12位組成物理地址。


            mm_struct 任務(wù)相關(guān)的虛擬內(nèi)存
            vm_area_struct 一段虛擬內(nèi)存的抽象,也可以理解為段
            mm_struct中擁有vm_area_struct的指針
            在vm_area_struct多的時(shí)候使用avl樹(shù)來(lái)存儲(chǔ)
            mem_map_t  物理頁(yè)表
            zone_struct 物理內(nèi)存的區(qū)結(jié)構(gòu),zone_struct把物理內(nèi)存分成了幾個(gè)部分
            ZONE_DMA 0 供DMA使用
            ZONE_NORMAL 普通使用
            ZONE_HIGHMEN 高段內(nèi)存,內(nèi)核映射不到

            物理內(nèi)存之間區(qū)的劃分并不是強(qiáng)制的,如果某一個(gè)區(qū)已經(jīng)沒(méi)有內(nèi)存可用,是可以去別的區(qū)拿內(nèi)存的

            其實(shí)一直對(duì)內(nèi)核的尋址有些疑問(wèn)
            不過(guò)剛剛似乎想通了
            內(nèi)核會(huì)做預(yù)映射,把PGD第768項(xiàng)以后的都做映射,也就是1G的空間
            而這種映射應(yīng)該是滿足__pa()宏,即線性地址與物理地址是線性映射的。
            所以最終__pa()宏被用作在內(nèi)核代碼中顯性的獲得某個(gè)線性地址所對(duì)應(yīng)的物理地址
            而MMU負(fù)責(zé)把一個(gè)線性地址隱式的轉(zhuǎn)成了物理地址,而這已轉(zhuǎn)換與內(nèi)核代碼無(wú)關(guān)。
            不知這樣理解是否正確?

            今天只看到了這里
            待續(xù)……

            說(shuō)起來(lái)把這么個(gè)東西放到首頁(yè)很不好意思,主要目的是希望有看到的人幫我指正一下我所認(rèn)知的錯(cuò)誤或者解惑。謝謝啦:)

            posted @ 2011-03-15 17:47 Vincent 閱讀(1663) | 評(píng)論 (0)編輯 收藏

            2011年3月3日 #

            關(guān)于CppUnit的使用?

                首先,我是初學(xué)者,從無(wú)使用CppUnit的經(jīng)驗(yàn)。

                項(xiàng)目進(jìn)展到后期,想提高穩(wěn)定性,在這個(gè)時(shí)候是否適合加入單元測(cè)試呢?
                找了幾篇文章看了看,似乎談單元測(cè)試就不得不談TDD,但我看到的絕大多數(shù)人的觀點(diǎn)是舍TDD,留單元測(cè)試
                好了,暫且不談TDD,只談單元測(cè)試,其中我看到一篇Blog的觀點(diǎn)是,使用單元測(cè)試,來(lái)逐步重構(gòu)代碼,其論點(diǎn)就是真正適合單元測(cè)試的代碼,能夠更符合SOLID原則。
                 這個(gè)看法是我目前比較認(rèn)同的。
                 另外看到一種使用方式是,只對(duì)算法類和一些基礎(chǔ)類,進(jìn)行單元測(cè)試。對(duì)于這種使用方式,我實(shí)不知使用CppUnit這種現(xiàn)成的較為自動(dòng)化的單元測(cè)試的框架意義在哪。
                最后一個(gè)問(wèn)題就是,如果我認(rèn)為的前一種觀點(diǎn)是正確的,那在我們的這個(gè)項(xiàng)目進(jìn)行到的這個(gè)階段,想要提高穩(wěn)定性去做單元測(cè)試,是需要大量重構(gòu)的,這樣一個(gè)量會(huì)不會(huì)反而會(huì)影響穩(wěn)定性?


                 歡迎大家隨意發(fā)表自己的觀點(diǎn)哈,我只是想多了解一下。以便做出認(rèn)為自己更適合的判斷。

            posted @ 2011-03-03 09:56 Vincent 閱讀(2380) | 評(píng)論 (11)編輯 收藏

            2011年2月15日 #

            關(guān)于lua的coroutine

            本質(zhì)是串行,沒(méi)有同步開(kāi)銷
            雖然在用戶態(tài)下,但是還是需要維護(hù)coroutine的狀態(tài),
            也就是也會(huì)擁有自己的上下文切換的開(kāi)銷
            更像是用戶態(tài)下實(shí)現(xiàn)的線程,但搶占式的線程不論切換粒度再大或再小,都會(huì)有不可預(yù)知的行為,所要做同步,而coroutine是協(xié)作式的任務(wù),
            由自己交把自己的權(quán)利交出去,同步問(wèn)題自然不用考慮了
            自己小記一下

            posted @ 2011-02-15 11:01 Vincent 閱讀(351) | 評(píng)論 (0)編輯 收藏

            Lisp的本質(zhì)(The Nature of Lisp)(轉(zhuǎn))

                                   Lisp的本質(zhì)(The Nature of Lisp)

                                         作者 Slava Akhmechet
                                         譯者 Alec Jang

                          出處: http://www.defmacro.org/ramblings/lisp.html


            簡(jiǎn)介

            最初在web的某些角落偶然看到有人贊美Lisp時(shí), 我那時(shí)已經(jīng)是一個(gè)頗有經(jīng)驗(yàn)的程序員。
            在我的履歷上, 掌握的語(yǔ)言范圍相當(dāng)廣泛, 象C++, Java, C#主流語(yǔ)言等等都不在話下,
            我覺(jué)得我差不多知道所有的有關(guān)編程語(yǔ)言的事情。對(duì)待編程語(yǔ)言的問(wèn)題上, 我覺(jué)得自己不
            太會(huì)遇到什么大問(wèn)題。其實(shí)我大錯(cuò)特錯(cuò)了。

            我試著學(xué)了一下Lisp, 結(jié)果馬上就撞了墻。我被那些范例代碼嚇壞了。我想很多初次接觸
            Lisp語(yǔ)言的人, 一定也有過(guò)類似的感受。Lisp的語(yǔ)法太次了。一個(gè)語(yǔ)言的發(fā)明人, 居然不
            肯用心弄出一套漂亮的語(yǔ)法, 那誰(shuí)還會(huì)愿意學(xué)它。反正, 我是確確實(shí)實(shí)被那些難看的無(wú)數(shù)
            的括號(hào)搞蒙了。

            回過(guò)神來(lái)之后, 我和Lisp社區(qū)的那伙人交談, 訴說(shuō)我的沮喪心情。結(jié)果, 立馬就有一大套
            理論砸過(guò)來(lái), 這套理論在Lisp社區(qū)處處可見(jiàn), 幾成慣例。比如說(shuō): Lisp的括號(hào)只是表面現(xiàn)
            象; Lisp的代碼和數(shù)據(jù)的表達(dá)方式?jīng)]有差別, 而且比XML語(yǔ)法高明許多, 所以有無(wú)窮的好
            處; Lisp有強(qiáng)大無(wú)比的元語(yǔ)言能力, 程序員可以寫(xiě)出自我維護(hù)的代碼; Lisp可以創(chuàng)造出針
            對(duì)特定應(yīng)用的語(yǔ)言子集; Lisp的運(yùn)行時(shí)和編譯時(shí)沒(méi)有明確的分界; 等等, 等等, 等等。這
            么長(zhǎng)的贊美詞雖然看起來(lái)相當(dāng)動(dòng)人, 不過(guò)對(duì)我毫無(wú)意義。沒(méi)人能給我演示這些東西是如何
            應(yīng)用的, 因?yàn)檫@些東西一般來(lái)說(shuō)只有在大型系統(tǒng)才會(huì)用到。我爭(zhēng)辯說(shuō), 這些東西傳統(tǒng)語(yǔ)言
            一樣辦得到。在和別人爭(zhēng)論了數(shù)個(gè)小時(shí)之后, 我最終還是放棄了學(xué)Lisp的念頭。為什么要
            花費(fèi)幾個(gè)月的時(shí)間學(xué)習(xí)語(yǔ)法這么難看的語(yǔ)言呢? 這種語(yǔ)言的概念這么晦澀, 又沒(méi)什么好懂
            的例子。也許這語(yǔ)言不是該我這樣的人學(xué)的。

            幾個(gè)月來(lái), 我承受著這些Lisp辯護(hù)士對(duì)我心靈的重壓。我一度陷入了困惑。我認(rèn)識(shí)一些絕
            頂聰明的人, 我對(duì)他們相當(dāng)尊敬, 我看到他們對(duì)Lisp的贊美達(dá)到了宗教般的高度。這就是
            說(shuō), Lisp中一定有某種神秘的東西存在, 我不能忍受自己對(duì)此的無(wú)知, 好奇心和求知欲最
            終不可遏制。我于是咬緊牙關(guān)埋頭學(xué)習(xí)Lisp, 經(jīng)過(guò)幾個(gè)月的時(shí)間費(fèi)勁心力的練習(xí), 終于,
            我看到了那無(wú)窮無(wú)盡的泉水的源頭。在經(jīng)過(guò)脫胎換骨的磨練之后, 在經(jīng)過(guò)七重地獄的煎熬
            之后, 終于, 我明白了。

            頓悟在突然之間來(lái)臨。曾經(jīng)許多次, 我聽(tīng)到別人引用雷蒙德(譯者注: 論文<<大教堂和市
            集>>的作者, 著名的黑客社區(qū)理論家)的話: "Lisp語(yǔ)言值得學(xué)習(xí)。當(dāng)你學(xué)會(huì)Lisp之后, 你
            會(huì)擁有深刻的體驗(yàn)。就算你平常并不用Lisp編程, 它也會(huì)使你成為更加優(yōu)秀的程序員"。
            過(guò)去, 我根本不懂這些話的含義, 我也不相信這是真的。可是現(xiàn)在我懂得了。這些話蘊(yùn)含
            的真理遠(yuǎn)遠(yuǎn)超過(guò)我過(guò)去的想像。我內(nèi)心體會(huì)到一種神圣的情感, 一瞬間的頓悟, 幾乎使我
            對(duì)電腦科學(xué)的觀念發(fā)生了根本的改變。

            頓悟的那一刻, 我成了Lisp的崇拜者。我體驗(yàn)到了宗教大師的感受: 一定要把我的知識(shí)傳
            布開(kāi)來(lái), 至少要讓10個(gè)迷失的靈魂得到拯救。按照通常的辦法, 我把這些道理(就是剛開(kāi)
            始別人砸過(guò)來(lái)的那一套, 不過(guò)現(xiàn)在我明白了真實(shí)的含義)告訴旁人。結(jié)果太令人失望了,
            只有少數(shù)幾個(gè)人在我堅(jiān)持之下, 發(fā)生了一點(diǎn)興趣, 但是僅僅看了幾眼Lisp代碼, 他們就退
            卻了。照這樣的辦法, 也許費(fèi)數(shù)年功夫能造就了幾個(gè)Lisp迷, 但我覺(jué)得這樣的結(jié)果太差強(qiáng)
            人意了, 我得想一套有更好的辦法。

            我深入地思考了這個(gè)問(wèn)題。是不是Lisp有什么很艱深的東西, 令得那么多老練的程序員都
            不能領(lǐng)會(huì)? 不是, 沒(méi)有任何絕對(duì)艱深的東西。因?yàn)槲夷芘? 我相信其他人也一定能。那
            么問(wèn)題出在那里? 后來(lái)我終于找到了答案。我的結(jié)論就是, 凡是教人學(xué)高級(jí)概念, 一定要
            從他已經(jīng)懂得的東西開(kāi)始。如果學(xué)習(xí)過(guò)程很有趣, 學(xué)習(xí)的內(nèi)容表達(dá)得很恰當(dāng), 新概念就會(huì)
            變得相當(dāng)直觀。這就是我的答案。所謂元編程, 所謂數(shù)據(jù)和代碼形式合一, 所謂自修改代
            碼, 所謂特定應(yīng)用的子語(yǔ)言, 所有這些概念根本就是同族概念, 彼此互為解釋, 肯定越講
            越不明白。還是從實(shí)際的例子出發(fā)最有用。

            我把我的想法說(shuō)給Lisp程序員聽(tīng), 遭到了他們的反對(duì)。"這些東西本身當(dāng)然不可能用熟悉
            的知識(shí)來(lái)解釋, 這些概念完全與眾不同, 你不可能在別人已有的經(jīng)驗(yàn)里找到類似的東西",
            可是我認(rèn)為這些都是遁詞。他們又反問(wèn)我, "你自己為啥不試一下?" 好吧, 我來(lái)試一下。
            這篇文章就是我嘗試的結(jié)果。我要用熟悉的直觀的方法來(lái)解釋Lisp, 我希望有勇氣的人讀
            完它, 拿杯飲料, 深呼吸一下, 準(zhǔn)備被搞得暈頭轉(zhuǎn)向。來(lái)吧, 愿你獲得大能。

            重新審視XML

            千里之行始于足下。讓我們的第一步從XML開(kāi)始。可是XML已經(jīng)說(shuō)得更多的了, 還能有什么
            新意思可說(shuō)呢? 有的。XML自身雖然談?wù)劜簧嫌腥? 但是XML和Lisp的關(guān)系卻相當(dāng)有趣。
            XML和Lisp的概念有著驚人的相似之處。XML是我們通向理解Lisp的橋梁。好吧, 我們且把
            XML當(dāng)作活馬醫(yī)。讓我們拿好手杖, 對(duì)XML的無(wú)人涉及的荒原地帶作一番探險(xiǎn)。我們要從一
            個(gè)全新的視角來(lái)考察這個(gè)題目。

            表面上看, XML是一種標(biāo)準(zhǔn)化語(yǔ)法, 它以適合人閱讀的格式來(lái)表達(dá)任意的層次化數(shù)據(jù)
            (hirearchical data)。象任務(wù)表(to-do list), 網(wǎng)頁(yè), 病歷, 汽車保險(xiǎn)單, 配置文件等
            等, 都是XML用武的地方。比如我們拿任務(wù)表做例子:

            <todo name="housework">
                <item priority="high">Clean the house.</item>
                <item priority="medium">Wash the dishes.</item>
                <item priority="medium">Buy more soap.</item>
            </todo>

            解析這段數(shù)據(jù)時(shí)會(huì)發(fā)生什么情況? 解析之后的數(shù)據(jù)在內(nèi)存中怎樣表示? 顯然, 用樹(shù)來(lái)表示
            這種層次化數(shù)據(jù)是很恰當(dāng)?shù)摹Uf(shuō)到底, XML這種比較容易閱讀的數(shù)據(jù)格式, 就是樹(shù)型結(jié)構(gòu)
            數(shù)據(jù)經(jīng)過(guò)序列化之后的結(jié)果。任何可以用樹(shù)來(lái)表示的數(shù)據(jù), 同樣可以用XML來(lái)表示, 反之
            亦然。希望你能懂得這一點(diǎn), 這對(duì)下面的內(nèi)容極其重要。

            再進(jìn)一步。還有什么類型的數(shù)據(jù)也常用樹(shù)來(lái)表示? 無(wú)疑列表(list)也是一種。上過(guò)編譯課
            吧? 還模模糊糊記得一點(diǎn)吧? 源代碼在解析之后也是用樹(shù)結(jié)構(gòu)來(lái)存放的, 任何編譯程序都
            會(huì)把源代碼解析成一棵抽象語(yǔ)法樹(shù), 這樣的表示法很恰當(dāng), 因?yàn)樵创a就是層次結(jié)構(gòu)的:
            函數(shù)包含參數(shù)和代碼塊, 代碼快包含表達(dá)式和語(yǔ)句, 語(yǔ)句包含變量和運(yùn)算符等等。

            我們已經(jīng)知道, 任何樹(shù)結(jié)構(gòu)都可以輕而易舉的寫(xiě)成XML, 而任何代碼都會(huì)解析成樹(shù), 因此,
            任何代碼都可以轉(zhuǎn)換成XML, 對(duì)不對(duì)? 我舉個(gè)例子, 請(qǐng)看下面的函數(shù):

            int add(int arg1, int arg2)
            {
                return arg1+arg2;
            }

            能把這個(gè)函數(shù)變成對(duì)等的XML格式嗎? 當(dāng)然可以。我們可以用很多種方式做到, 下面是其
            中的一種, 十分簡(jiǎn)單:

            <define-function return-type="int" name="add">
                <arguments>
                    <argument type="int">arg1</argument>
                    <argument type="int">arg2</argument>
                </arguments>
                <body>
                    <return>
                        <add value1="arg1" value2="arg2" />
                    </return>
                </body>
            </define>

            這個(gè)例子非常簡(jiǎn)單, 用哪種語(yǔ)言來(lái)做都不會(huì)有太大問(wèn)題。我們可以把任何程序碼轉(zhuǎn)成XML,
            也可以把XML轉(zhuǎn)回到原來(lái)的程序碼。我們可以寫(xiě)一個(gè)轉(zhuǎn)換器, 把Java代碼轉(zhuǎn)成XML, 另一個(gè)
            轉(zhuǎn)換器把XML轉(zhuǎn)回到Java。一樣的道理, 這種手段也可以用來(lái)對(duì)付C++(這樣做跟發(fā)瘋差不
            多么。可是的確有人在做, 看看GCC-XML(http://www.gccxml.org)就知道了)。進(jìn)一步說(shuō),
            凡是有相同語(yǔ)言特性而語(yǔ)法不同的語(yǔ)言, 都可以把XML當(dāng)作中介來(lái)互相轉(zhuǎn)換代碼。實(shí)際上
            幾乎所有的主流語(yǔ)言都在一定程度上滿足這個(gè)條件。我們可以把XML作為一種中間表示法,
            在兩種語(yǔ)言之間互相譯碼。比方說(shuō), 我們可以用Java2XML把Java代碼轉(zhuǎn)換成XML, 然后用
            XML2CPP再把XML轉(zhuǎn)換成C++代碼, 運(yùn)氣好的話, 就是說(shuō), 如果我們小心避免使用那些C++不
            具備的Java特性的話, 我們可以得到完好的C++程序。這辦法怎么樣, 漂亮吧?

            這一切充分說(shuō)明, 我們可以把XML作為源代碼的通用存儲(chǔ)方式, 其實(shí)我們能夠產(chǎn)生一整套
            使用統(tǒng)一語(yǔ)法的程序語(yǔ)言, 也能寫(xiě)出轉(zhuǎn)換器, 把已有代碼轉(zhuǎn)換成XML格式。如果真的采納
            這種辦法, 各種語(yǔ)言的編譯器就用不著自己寫(xiě)語(yǔ)法解析了, 它們可以直接用XML的語(yǔ)法解
            析來(lái)直接生成抽象語(yǔ)法樹(shù)。

            說(shuō)到這里你該問(wèn)了, 我們研究了這半天XML, 這和Lisp有什么關(guān)系呢? 畢竟XML出來(lái)之時(shí),
            Lisp早已經(jīng)問(wèn)世三十年了。這里我可以保證, 你馬上就會(huì)明白。不過(guò)在繼續(xù)解釋之前, 我
            們先做一個(gè)小小的思維練習(xí)。看一下上面這個(gè)XML版本的add函數(shù)例子, 你怎樣給它分類,
            是代碼還是數(shù)據(jù)? 不用太多考慮都能明白, 把它分到哪一類都講得通。它是XML, 它是標(biāo)
            準(zhǔn)格式的數(shù)據(jù)。我們也知道, 它可以通過(guò)內(nèi)存中的樹(shù)結(jié)構(gòu)來(lái)生成(GCC-XML做的就是這個(gè)事
            情)。它保存在不可執(zhí)行的文件中。我們可以把它解析成樹(shù)節(jié)點(diǎn), 然后做任意的轉(zhuǎn)換。顯
            而易見(jiàn), 它是數(shù)據(jù)。不過(guò)且慢, 雖然它語(yǔ)法有點(diǎn)陌生, 可它又確確實(shí)實(shí)是一個(gè)add函數(shù),
            對(duì)吧?  一旦經(jīng)過(guò)解析, 它就可以拿給編譯器編譯執(zhí)行。我們可以輕而易舉寫(xiě)出這個(gè)XML
            代碼解釋器, 并且直接運(yùn)行它。或者我們也可以把它譯成Java或C++代碼, 然后再編譯運(yùn)
            行。所以說(shuō), 它也是代碼。

            我們說(shuō)到那里了? 不錯(cuò), 我們已經(jīng)發(fā)現(xiàn)了一個(gè)有趣的關(guān)鍵之點(diǎn)。過(guò)去被認(rèn)為很難解的概念
            已經(jīng)非常直觀非常簡(jiǎn)單的顯現(xiàn)出來(lái)。代碼也是數(shù)據(jù), 并且從來(lái)都是如此。這聽(tīng)起來(lái)瘋瘋癲
            癲的, 實(shí)際上卻是必然之事。我許諾過(guò)會(huì)以一種全新的方式來(lái)解釋Lisp, 我要重申我的許
            諾。但是我們此刻還沒(méi)有到預(yù)定的地方, 所以還是先繼續(xù)上邊的討論。

            剛才我說(shuō)過(guò), 我們可以非常簡(jiǎn)單地實(shí)現(xiàn)XML版的add函數(shù)解釋器, 這聽(tīng)起來(lái)好像不過(guò)是說(shuō)說(shuō)
            而已。誰(shuí)真的會(huì)動(dòng)手做一下呢? 未必有多少人會(huì)認(rèn)真對(duì)待這件事。隨便說(shuō)說(shuō), 并不打算真
            的去做, 這樣的事情你在生活中恐怕也遇到吧。你明白我這樣說(shuō)的意思吧, 我說(shuō)的有沒(méi)有
            打動(dòng)你? 有哇, 那好, 我們繼續(xù)。

            重新審視Ant

            我們現(xiàn)在已經(jīng)來(lái)到了月亮背光的那一面, 先別忙著離開(kāi)。再探索一下, 看看我們還能發(fā)現(xiàn)
            什么東西。閉上眼睛, 想一想2000年冬天的那個(gè)雨夜, 一個(gè)名叫James Duncan Davidson
            的杰出的程序員正在研究Tomcat的servlet容器。那時(shí), 他正小心地保存好剛修改過(guò)的文
            件, 然后執(zhí)行make。結(jié)果冒出了一大堆錯(cuò)誤, 顯然有什么東西搞錯(cuò)了。經(jīng)過(guò)仔細(xì)檢查, 他
            想, 難道是因?yàn)閠ab前面加了個(gè)空格而導(dǎo)致命令不能執(zhí)行嗎? 確實(shí)如此。老是這樣, 他真
            的受夠了。烏云背后的月亮給了他啟示, 他創(chuàng)建了一個(gè)新的Java項(xiàng)目, 然后寫(xiě)了一個(gè)簡(jiǎn)單
            但是十分有用的工具, 這個(gè)工具巧妙地利用了Java屬性文件中的信息來(lái)構(gòu)造工程, 現(xiàn)在
            James可以寫(xiě)makefile的替代品, 它能起到相同的作用, 而形式更加優(yōu)美, 也不用擔(dān)心有
            makefile那樣可恨的空格問(wèn)題。這個(gè)工具能夠自動(dòng)解釋屬性文件, 然后采取正確的動(dòng)作來(lái)
            編譯工程。真是簡(jiǎn)單而優(yōu)美。

            (作者注: 我不認(rèn)識(shí)James, James也不認(rèn)識(shí)我, 這個(gè)故事是根據(jù)網(wǎng)上關(guān)于Ant歷史的帖子
            虛構(gòu)的)

            使用Ant構(gòu)造Tomcat之后幾個(gè)月, 他越來(lái)越感到Java的屬性文件不足以表達(dá)復(fù)雜的構(gòu)造指
            令。文件需要檢出, 拷貝, 編譯, 發(fā)到另外一臺(tái)機(jī)器, 進(jìn)行單元測(cè)試。要是出錯(cuò), 就發(fā)郵
            件給相關(guān)人員, 要是成功, 就繼續(xù)在盡可能高層的卷(volumn)上執(zhí)行構(gòu)造。追蹤到最后,
            卷要回復(fù)到最初的水平上。確實(shí), Java的屬性文件不夠用了, James需要更有彈性的解決
            方案。他不想自己寫(xiě)解析器(因?yàn)樗M幸粋€(gè)具有工業(yè)標(biāo)準(zhǔn)的方案)。XML看起來(lái)是個(gè)
            不錯(cuò)的選擇。他花了幾天工夫把Ant移植到XML,于是,一件偉大的工具誕生了。

            Ant是怎樣工作的?原理非常簡(jiǎn)單。Ant把包含有構(gòu)造命令的XML文件(算代碼還是算數(shù)據(jù),
            你自己想吧),交給一個(gè)Java程序來(lái)解析每一個(gè)元素,實(shí)際情況比我說(shuō)的還要簡(jiǎn)單得多。
            一個(gè)簡(jiǎn)單的XML指令會(huì)導(dǎo)致具有相同名字的Java類裝入,并執(zhí)行其代碼。

                <copy todir="../new/dir">
                    <fileset dir="src_dir" />
                </copy>

            這段文字的含義是把源目錄復(fù)制到目標(biāo)目錄,Ant會(huì)找到一個(gè)"copy"任務(wù)(實(shí)際上就是一個(gè)
            Java類), 通過(guò)調(diào)用Java的方法來(lái)設(shè)置適當(dāng)參數(shù)(todir和fileset),然后執(zhí)行這個(gè)任務(wù)。
            Ant帶有一組核心類, 可以由用戶任意擴(kuò)展, 只要遵守若干約定就可以。Ant找到這些類,
            每當(dāng)遇到XML元素有同樣的名字, 就執(zhí)行相應(yīng)的代碼。過(guò)程非常簡(jiǎn)單。Ant做到了我們前面
            所說(shuō)的東西: 它是一個(gè)語(yǔ)言解釋器, 以XML作為語(yǔ)法, 把XML元素轉(zhuǎn)譯為適當(dāng)?shù)腏ava指令。
            我們可以寫(xiě)一個(gè)"add"任務(wù), 然后, 當(dāng)發(fā)現(xiàn)XML中有add描述的時(shí)候, 就執(zhí)行這個(gè)add任務(wù)。
            由于Ant是非常流行的項(xiàng)目, 前面展示的策略就顯得更為明智。畢竟, 這個(gè)工具每天差不
            多有幾千家公司在使用。

            到目前為之, 我還沒(méi)有說(shuō)Ant在解析XML時(shí)所遇到困難。你也不用麻煩去它的網(wǎng)站上去找答
            案了, 不會(huì)找到有價(jià)值的東西。至少對(duì)我們這個(gè)論題來(lái)說(shuō)是如此。我們還是繼續(xù)下一步討
            論吧。我們答案就在那里。

            為什么是XML

            有時(shí)候正確的決策并非完全出于深思熟慮。我不知道James選擇XML是否出于深思熟慮。也
            許僅僅是個(gè)下意識(shí)的決定。至少?gòu)腏ames在Ant網(wǎng)站上發(fā)表的文章看起來(lái), 他所說(shuō)的理由完
            全是似是而非。他的主要理由是移植性和擴(kuò)展性, 在Ant案例上, 我看不出這兩條有什么
            幫助。使用XML而不是Java代碼, 到底有什么好處? 為什么不寫(xiě)一組Java類, 提供api來(lái)滿
            足基本任務(wù)(拷貝目錄, 編譯等等), 然后在Java里直接調(diào)用這些代碼? 這樣做仍然可以保
            證移植性, 擴(kuò)展性也是毫無(wú)疑問(wèn)的。而且語(yǔ)法也更為熟悉, 看著順眼。那為什么要用 XML
            呢? 有什么更好的理由嗎?

            有的。雖然我不確定James是否確實(shí)意識(shí)到了。在語(yǔ)義的可構(gòu)造性方面, XML的彈性是Java
            望塵莫及的。我不想用高深莫測(cè)的名詞來(lái)嚇唬你, 其中的道理相當(dāng)簡(jiǎn)單, 解釋起來(lái)并不費(fèi)
            很多功夫。好, 做好預(yù)備動(dòng)作, 我們馬上就要朝向頓悟的時(shí)刻做奮力一躍。

            上面的那個(gè)copy的例子, 用Java代碼怎樣實(shí)現(xiàn)呢? 我們可以這樣做:

                CopyTask copy = new CopyTask();
                Fileset fileset = new Fileset();

                fileset.setDir("src_dir");
                copy.setToDir("../new/dir");
                copy.setFileset(fileset);

                copy.excute();

            這個(gè)代碼看起來(lái)和XML的那個(gè)很相似, 只是稍微長(zhǎng)一點(diǎn)。差別在那里? 差別在于XML構(gòu)造了
            一個(gè)特殊的copy動(dòng)詞, 如果我們硬要用Java來(lái)寫(xiě)的話, 應(yīng)該是這個(gè)樣子:

                copy("../new/dir");
                {
                    fileset("src_dir");
                }

            看到差別了嗎? 以上代碼(如果可以在Java中用的化), 是一個(gè)特殊的copy算符, 有點(diǎn)像
            for循環(huán)或者Java5中的foreach循環(huán)。如果我們有一個(gè)轉(zhuǎn)換器, 可以把XML轉(zhuǎn)換到Java, 大
            概就會(huì)得到上面這段事實(shí)上不可以執(zhí)行的代碼。因?yàn)镴ava的技術(shù)規(guī)范是定死的, 我們沒(méi)有
            辦法在程序里改變它。我們可以增加包, 增加類, 增加方法, 但是我們沒(méi)辦法增加算符,
            而對(duì)于XML, 我們顯然可以任由自己增加這樣的東西。對(duì)于XML的語(yǔ)法樹(shù)來(lái)說(shuō), 只要原意,
            我們可以任意增加任何元素, 因此等于我們可以任意增加算符。如果你還不太明白的話,
            看下面這個(gè)例子, 加入我們要給Java引入一個(gè)unless算符:

                unless(someObject.canFly())
                {
                    someObject.transportByGround():
                }

            在上面的兩個(gè)例子中, 我們打算給Java語(yǔ)法擴(kuò)展兩個(gè)算符, 成組拷貝文件算符和條件算符
            unless, 我們要想做到這一點(diǎn), 就必須修改Java編譯器能夠接受的抽象語(yǔ)法樹(shù), 顯然我們
            無(wú)法用Java標(biāo)準(zhǔn)的功能來(lái)實(shí)現(xiàn)它。但是在XML中我們可以輕而易舉地做到。我們的解析器
            根據(jù) XML元素, 生成抽象語(yǔ)法樹(shù), 由此生成算符, 所以, 我們可以任意引入任何算符。

            對(duì)于復(fù)雜的算符來(lái)說(shuō), 這樣做的好處顯而易見(jiàn)。比如, 用特定的算符來(lái)做檢出源碼, 編譯
            文件, 單元測(cè)試, 發(fā)送郵件等任務(wù), 想想看有多么美妙。對(duì)于特定的題目, 比如說(shuō)構(gòu)造軟
            件項(xiàng)目, 這些算符的使用可以大幅減低少代碼的數(shù)量。增加代碼的清晰程度和可重用性。
            解釋性的XML可以很容易的達(dá)到這個(gè)目標(biāo)。XML是存儲(chǔ)層次化數(shù)據(jù)的簡(jiǎn)單數(shù)據(jù)文件, 而在
            Java中, 由于層次結(jié)構(gòu)是定死的(你很快就會(huì)看到, Lisp的情況與此截然不同), 我們就沒(méi)
            法達(dá)到上述目標(biāo)。也許這正是Ant的成功之處呢。

            你可以注意一下最近Java和C#的變化(尤其是C#3.0的技術(shù)規(guī)范), C#把常用的功能抽象出
            來(lái), 作為算符增加到C#中。C#新增加的query算符就是一個(gè)例子。它用的還是傳統(tǒng)的作法:
            C#的設(shè)計(jì)者修改抽象語(yǔ)法樹(shù), 然后增加對(duì)應(yīng)的實(shí)現(xiàn)。如果程序員自己也能修改抽象語(yǔ)法樹(shù)
            該有多好! 那樣我們就可以構(gòu)造用于特定問(wèn)題的子語(yǔ)言(比如說(shuō)就像Ant這種用于構(gòu)造項(xiàng)目
            的語(yǔ)言), 你能想到別的例子嗎? 再思考一下這個(gè)概念。不過(guò)也不必思考太甚, 我們待會(huì)
            還會(huì)回到這個(gè)題目。那時(shí)候就會(huì)更加清晰。

            離Lisp越來(lái)越近

            我們先把算符的事情放一放, 考慮一下Ant設(shè)計(jì)局限之外的東西。我早先說(shuō)過(guò), Ant可以通
            過(guò)寫(xiě)Java類來(lái)擴(kuò)展。Ant解析器會(huì)根據(jù)名字來(lái)匹配XML元素和Java類, 一旦找到匹配, 就執(zhí)
            行相應(yīng)任務(wù)。為什么不用Ant自己來(lái)擴(kuò)展Ant呢? 畢竟核心任務(wù)要包含很多傳統(tǒng)語(yǔ)言的結(jié)構(gòu)
            (例如"if"), 如果Ant自身就能提供構(gòu)造任務(wù)的能力(而不是依賴java類), 我們就可以得
            到更高的移植性。我們將會(huì)依賴一組核心任務(wù)(如果你原意, 也不妨把它稱作標(biāo)準(zhǔn)庫(kù)), 而
            不用管有沒(méi)有Java 環(huán)境了。這組核心任務(wù)可以用任何方式來(lái)實(shí)現(xiàn), 而其他任務(wù)建筑在這
            組核心任務(wù)之上, 那樣的話, Ant就會(huì)成為通用的, 可擴(kuò)展的, 基于XML的編程語(yǔ)言。考慮
            下面這種代碼的可能性:

                <task name="Test">
                    <echo message="Hello World" />
                </task>
                <Test />

            如果XML支持"task"的創(chuàng)建, 上面這段代碼就會(huì)輸出"Hello World!". 實(shí)際上, 我們可以
            用Java寫(xiě)個(gè)"task"任務(wù), 然后用Ant-XML來(lái)擴(kuò)展它。Ant可以在簡(jiǎn)單原語(yǔ)的基礎(chǔ)上寫(xiě)出更復(fù)
            雜的原語(yǔ), 就像其他編程語(yǔ)言常用的作法一樣。這也就是我們一開(kāi)始提到的基于XML的編
            程語(yǔ)言。這樣做用處不大(你知道為甚么嗎?), 但是真的很酷。

            再看一回我們剛才說(shuō)的Task任務(wù)。祝賀你呀, 你在看Lisp代碼!!! 我說(shuō)什么? 一點(diǎn)都不像
            Lisp嗎? 沒(méi)關(guān)系, 我們?cè)俳o它收拾一下。

            比XML更好

            前面一節(jié)說(shuō)過(guò), Ant自我擴(kuò)展沒(méi)什么大用, 原因在于XML很煩瑣。對(duì)于數(shù)據(jù)來(lái)說(shuō), 這個(gè)問(wèn)題
            還不太大, 但如果代碼很煩瑣的話, 光是打字上的麻煩就足以抵消它的好處。你寫(xiě)過(guò)Ant
            的腳本嗎? 我寫(xiě)過(guò), 當(dāng)腳本達(dá)到一定復(fù)雜度的時(shí)候, XML非常讓人厭煩。想想看吧, 為了
            寫(xiě)結(jié)束標(biāo)簽, 每個(gè)詞都得打兩遍, 不發(fā)瘋算好的!

            為了解決這個(gè)問(wèn)題, 我們應(yīng)當(dāng)簡(jiǎn)化寫(xiě)法。須知, XML僅僅是一種表達(dá)層次化數(shù)據(jù)的方式。
            我們并不是一定要使用尖括號(hào)才能得到樹(shù)的序列化結(jié)果。我們完全可以采用其他的格式。
            其中的一種(剛好就是Lisp所采用的)格式, 叫做s表達(dá)式。s表達(dá)式要做的和XML一樣, 但
            它的好處是寫(xiě)法更簡(jiǎn)單, 簡(jiǎn)單的寫(xiě)法更適合代碼輸入。后面我會(huì)詳細(xì)講s表達(dá)式。這之前
            我要清理一下XML的東西。考慮一下關(guān)于拷貝文件的例子:

                <copy toDir="../new/dir">
                    <fileset dir="src_dir">
                </copy>

            想想看在內(nèi)存里面, 這段代碼的解析樹(shù)在內(nèi)存會(huì)是什么樣子? 會(huì)有一個(gè)"copy"節(jié)點(diǎn), 其下
            有一個(gè) "fileset"節(jié)點(diǎn), 但是屬性在哪里呢? 它怎樣表達(dá)呢? 如果你以前用過(guò)XML, 并且
            弄不清楚該用元素還是該用屬性, 你不用感到孤單, 別人一樣糊涂著呢。沒(méi)人真的搞得清
            楚。這個(gè)選擇與其說(shuō)是基于技術(shù)的理由, 還不如說(shuō)是閉著眼瞎摸。從概念上來(lái)講, 屬性也
            是一種元素, 任何屬性能做的, 元素一樣做得到。XML引入屬性的理由, 其實(shí)就是為了讓
            XML寫(xiě)法不那么冗長(zhǎng)。比如我們看個(gè)例子:

                <copy>
                    <toDir>../new/dir</toDir>
                    <fileset>
                        <dir>src_dir</dir>
                    </fileset>
                </copy>

            兩下比較, 內(nèi)容的信息量完全一樣, 用屬性可以減少打字?jǐn)?shù)量。如果XML沒(méi)有屬性的話,
            光是打字就夠把人搞瘋掉。

            說(shuō)完了屬性的問(wèn)題, 我們?cè)賮?lái)看一看s表達(dá)式。之所以繞這么個(gè)彎, 是因?yàn)閟表達(dá)式?jīng)]有屬
            性的概念。因?yàn)閟表達(dá)式非常簡(jiǎn)練, 根本沒(méi)有必要引入屬性。我們?cè)诎裍ML轉(zhuǎn)換成s表達(dá)式
            的時(shí)候, 心里應(yīng)該記住這一點(diǎn)。看個(gè)例子, 上面的代碼譯成s表達(dá)式是這樣的:

                (copy
                    (todir "../new/dir")
                    (fileset (dir "src_dir")))

            仔細(xì)看看這個(gè)例子, 差別在哪里? 尖括號(hào)改成了圓括號(hào), 每個(gè)元素原來(lái)是有一對(duì)括號(hào)標(biāo)記
            包圍的, 現(xiàn)在取消了后一個(gè)(就是帶斜杠的那個(gè))括號(hào)標(biāo)記。表示元素的結(jié)束只需要一個(gè)")"
            就可以了。不錯(cuò), 差別就是這些。這兩種表達(dá)方式的轉(zhuǎn)換, 非常自然, 也非常簡(jiǎn)單。s表
            達(dá)式打起字來(lái), 也省事得多。第一次看s表達(dá)式(Lisp)時(shí), 括號(hào)很煩人是吧? 現(xiàn)在我們明
            白了背后的道理, 一下子就變得容易多了。至少, 比XML要好的多。用s表達(dá)式寫(xiě)代碼, 不
            單是實(shí)用, 而且也很讓人愉快。s表達(dá)式具有XML的一切好處, 這些好處是我們剛剛探討過(guò)
            的。現(xiàn)在我們看看更加Lisp風(fēng)格的task例子:

                (task (name "Test")
                    (echo (message "Hellow World!")))
                (Test)

            用Lisp的行話來(lái)講, s表達(dá)式稱為表(list)。對(duì)于上面的例子, 如果我們寫(xiě)的時(shí)候不加換
            行, 用逗號(hào)來(lái)代替空格, 那么這個(gè)表達(dá)式看起來(lái)就非常像一個(gè)元素列表, 其中又嵌套著其
            他標(biāo)記。

                (task, (name, "test"), (echo, (message, "Hello World!")))

            XML自然也可以用這樣的風(fēng)格來(lái)寫(xiě)。當(dāng)然上面這句并不是一般意義上的元素表。它實(shí)際上
            是一個(gè)樹(shù)。這和XML的作用是一樣的。稱它為列表, 希望你不會(huì)感到迷惑, 因?yàn)榍短妆砗?br>樹(shù)實(shí)際上是一碼事。Lisp的字面意思就是表處理(list processing), 其實(shí)也可以稱為樹(shù)
            處理, 這和處理XML節(jié)點(diǎn)沒(méi)有什么不同。

            經(jīng)受這一番折磨以后, 現(xiàn)在我們終于相當(dāng)接近Lisp了, Lisp的括號(hào)的神秘本質(zhì)(就像許多
            Lisp狂熱分子認(rèn)為的)逐漸顯現(xiàn)出來(lái)。現(xiàn)在我們繼續(xù)研究其他內(nèi)容。

            重新審視C語(yǔ)言的宏

            到了這里, 對(duì)XML的討論你大概都聽(tīng)累了, 我都講累了。我們先停一停, 把樹(shù), s表達(dá)式,
            Ant這些東西先放一放, 我們來(lái)說(shuō)說(shuō)C的預(yù)處理器。一定有人問(wèn)了, 我們的話題和C有什么
            關(guān)系? 我們已經(jīng)知道了很多關(guān)于元編程的事情, 也探討過(guò)專門寫(xiě)代碼的代碼。理解這問(wèn)題
            有一定難度, 因?yàn)橄嚓P(guān)討論文章所使用的編程語(yǔ)言, 都是你們不熟悉的。但是如果只論概
            念的話, 就相對(duì)要簡(jiǎn)單一些。我相信, 如果以C語(yǔ)言做例子來(lái)討論元編程, 理解起來(lái)一定
            會(huì)容易得多。好, 我們接著看。

            一個(gè)問(wèn)題是, 為什么要用代碼來(lái)寫(xiě)代碼呢? 在實(shí)際的編程中, 怎樣做到這一點(diǎn)呢? 到底元
            編程是什么意思? 你大概已經(jīng)聽(tīng)說(shuō)過(guò)這些問(wèn)題的答案, 但是并不懂得其中緣由。為了揭示
            背后的真理, 我們來(lái)看一下一個(gè)簡(jiǎn)單的數(shù)據(jù)庫(kù)查詢問(wèn)題。這種題目我們都做過(guò)。比方說(shuō),
            直接在程序碼里到處寫(xiě)SQL語(yǔ)句來(lái)修改表(table)里的數(shù)據(jù), 寫(xiě)多了就非常煩人。即便用
            C#3.0的LINQ, 仍然不減其痛苦。寫(xiě)一個(gè)完整的SQL查詢(盡管語(yǔ)法很優(yōu)美)來(lái)修改某人的地
            址, 或者查找某人的名字, 絕對(duì)是件令程序員倍感乏味的事情, 那么我們?cè)撛鯓觼?lái)解決這
            個(gè)問(wèn)題? 答案就是: 使用數(shù)據(jù)訪問(wèn)層。

            概念挺簡(jiǎn)單, 其要點(diǎn)是把數(shù)據(jù)訪問(wèn)的內(nèi)容(至少是那些比較瑣碎的部分)抽象出來(lái), 用類來(lái)
            映射數(shù)據(jù)庫(kù)的表, 然后用訪問(wèn)對(duì)象屬性訪問(wèn)器(accessor)的辦法來(lái)間接實(shí)現(xiàn)查詢。這樣就
            極大地簡(jiǎn)化了開(kāi)發(fā)工作量。我們用訪問(wèn)對(duì)象的方法(或者屬性賦值, 這要視你選用的語(yǔ)言
            而定)來(lái)代替寫(xiě)SQL查詢語(yǔ)句。凡是用過(guò)這種方法的人, 都知道這很節(jié)省時(shí)間。當(dāng)然, 如果
            你要親自寫(xiě)這樣一個(gè)抽象層, 那可是要花非常多的時(shí)間的--你要寫(xiě)一組類來(lái)映射表, 把屬
            性訪問(wèn)轉(zhuǎn)換為SQL查詢, 這個(gè)活相當(dāng)耗費(fèi)精力。用手工來(lái)做顯然是很不明智的。但是一旦
            你有了方案和模板, 實(shí)際上就沒(méi)有多少東西需要思考的。你只需要按照同樣的模板一次又
            一次重復(fù)編寫(xiě)相似代碼就可以了。事實(shí)上很多人已經(jīng)發(fā)現(xiàn)了更好的方法, 有一些工具可以
            幫助你連接數(shù)據(jù)庫(kù), 抓取數(shù)據(jù)庫(kù)結(jié)構(gòu)定義(schema), 按照預(yù)定義的或者用戶定制的模板來(lái)
            自動(dòng)編寫(xiě)代碼。

            如果你用過(guò)這種工具, 你肯定會(huì)對(duì)它的神奇效果深為折服。往往只需要鼠標(biāo)點(diǎn)擊數(shù)次, 就
            可以連接到數(shù)據(jù)庫(kù), 產(chǎn)生數(shù)據(jù)訪問(wèn)源碼, 然后把文件加入到你的工程里面, 十幾分鐘的工
            作, 按照往常手工方式來(lái)作的話, 也許需要數(shù)百個(gè)小時(shí)人工(man-hours)才能完成。可是,
            如果你的數(shù)據(jù)庫(kù)結(jié)構(gòu)定義后來(lái)改變了怎么辦? 那樣的話, 你只需把這個(gè)過(guò)程重復(fù)一遍就可
            以了。甚至有一些工具能自動(dòng)完成這項(xiàng)變動(dòng)工作。你只要把它作為工程構(gòu)造的一部分, 每
            次編譯工程的時(shí)候, 數(shù)據(jù)庫(kù)部分也會(huì)自動(dòng)地重新構(gòu)造。這真的太棒了。你要做的事情基本
            上減到了0。如果數(shù)據(jù)庫(kù)結(jié)構(gòu)定義發(fā)生了改變, 并在編譯時(shí)自動(dòng)更新了數(shù)據(jù)訪問(wèn)層的代碼,
            那么程序中任何使用過(guò)時(shí)的舊代碼的地方, 都會(huì)引發(fā)編譯錯(cuò)誤。

            數(shù)據(jù)訪問(wèn)層是個(gè)很好的例子, 這樣的例子還有好多。從GUI樣板代碼, WEB代碼, COM和
            CORBA存根, 以及MFC和ATL等等。在這些地方, 都是有好多相似代碼多次重復(fù)。既然這些
            代碼有可能自動(dòng)編寫(xiě), 而程序員時(shí)間又遠(yuǎn)遠(yuǎn)比CPU時(shí)間昂貴, 當(dāng)然就產(chǎn)生了好多工具來(lái)自
            動(dòng)生成樣板代碼。這些工具的本質(zhì)是什么呢? 它們實(shí)際上就是制造程序的程序。它們有一
            個(gè)神秘的名字, 叫做元編程。所謂元編程的本義, 就是如此。

            元編程本來(lái)可以用到無(wú)數(shù)多的地方, 但實(shí)際上使用的次數(shù)卻沒(méi)有那么多。歸根結(jié)底, 我們
            心里還是在盤(pán)算, 假設(shè)重復(fù)代碼用拷貝粘貼的話, 大概要重復(fù)6,7次, 對(duì)于這樣的工作量,
            值得專門建立一套生成工具嗎? 當(dāng)然不值得。數(shù)據(jù)訪問(wèn)層和COM存根往往需要重用數(shù)百次,
            甚至上千次, 所以用工具生成是最好的辦法。而那些僅僅是重復(fù)幾次十幾次的代碼, 是沒(méi)
            有必要專門做工具的。不必要的時(shí)候也去開(kāi)發(fā)代碼生成工具, 那就顯然過(guò)度估計(jì)了代碼生
            成的好處。當(dāng)然, 如果創(chuàng)建這類工具足夠簡(jiǎn)單的話, 還是應(yīng)當(dāng)盡量多用, 因?yàn)檫@樣做必然
            會(huì)節(jié)省時(shí)間。現(xiàn)在來(lái)看一下有沒(méi)有合理的辦法來(lái)達(dá)到這個(gè)目的。

            現(xiàn)在, C預(yù)處理器要派上用場(chǎng)了。我們都用過(guò)C/C++的預(yù)處理器, 我們用它執(zhí)行簡(jiǎn)單的編譯
            指令, 來(lái)產(chǎn)生簡(jiǎn)單的代碼變換(比方說(shuō), 設(shè)置調(diào)試代碼開(kāi)關(guān)), 看一個(gè)例子:

                #define triple(X) X+X+X

            這一行的作用是什么? 這是一個(gè)簡(jiǎn)單的預(yù)編譯指令, 它把程序中的triple(X)替換稱為
            X+X+X。例如, 把所有的triple(5)都換成5+5+5, 然后再交給編譯器編譯。這就是一個(gè)簡(jiǎn)
            單的代碼生成的例子。要是C的預(yù)處理器再?gòu)?qiáng)大一點(diǎn), 要是能夠允許連接數(shù)據(jù)庫(kù), 要是能
            多一些其他簡(jiǎn)單的機(jī)制, 我們就可以在我們程序的內(nèi)部開(kāi)發(fā)自己的數(shù)據(jù)訪問(wèn)層。下面這個(gè)
            例子, 是一個(gè)假想的對(duì)C宏的擴(kuò)展:

                #get-db-schema("127.0.0.1")
                #iterate-through-tables
                #for-each-table
                    class #table-name
                        {
                        };
                #end-for-each

            我們連接數(shù)據(jù)庫(kù)結(jié)構(gòu)定義, 遍歷數(shù)據(jù)表, 然后對(duì)每個(gè)表創(chuàng)建一個(gè)類, 只消幾行代碼就完成
            了這個(gè)工作。這樣每次編譯工程的時(shí)候, 這些類都會(huì)根據(jù)數(shù)據(jù)庫(kù)的定義同步更新。顯而易
            見(jiàn), 我們不費(fèi)吹灰之力就在程序內(nèi)部建立了一個(gè)完整的數(shù)據(jù)訪問(wèn)層, 根本用不著任何外部
            工具。當(dāng)然這種作法有一個(gè)缺點(diǎn), 那就是我們得學(xué)習(xí)一套新的"編譯時(shí)語(yǔ)言", 另一個(gè)缺點(diǎn)
            就是根本不存在這么一個(gè)高級(jí)版的C預(yù)處理器。需要做復(fù)雜代碼生成的時(shí)候, 這個(gè)語(yǔ)言(譯
            者注: 這里指預(yù)處理指令, 即作者所說(shuō)的"編譯時(shí)語(yǔ)言")本身也一定會(huì)變得相當(dāng)復(fù)雜。它
            必須支持足夠多的庫(kù)和語(yǔ)言結(jié)構(gòu)。比如說(shuō)我們想要生成的代碼要依賴某些ftp服務(wù)器上的
            文件, 預(yù)處理器就得支持ftp訪問(wèn), 僅僅因?yàn)檫@個(gè)任務(wù)而不得不創(chuàng)造和學(xué)習(xí)一門新的語(yǔ)言,
            真是有點(diǎn)讓人惡心(事實(shí)上已經(jīng)存在著有此能力的語(yǔ)言, 這樣做就更顯荒謬)。我們不妨再
            靈活一點(diǎn), 為什么不直接用 C/C++自己作為自己的預(yù)處理語(yǔ)言呢?  這樣子的話, 我們可
            以發(fā)揮語(yǔ)言的強(qiáng)大能力, 要學(xué)的新東西也只不過(guò)是幾個(gè)簡(jiǎn)單的指示字 , 這些指示字用來(lái)
            區(qū)別編譯時(shí)代碼和運(yùn)行時(shí)代碼。

                <%
                    cout<<"Enter a number: ";
                    cin>>n;
                %>
                for(int i=0;i< <% n %>;i++)
                {
                    cout<<"hello"<<endl;
                }

            你明白了嗎? 在<%和%>標(biāo)記之間的代碼是在編譯時(shí)運(yùn)行的, 標(biāo)記之外的其他代碼都是普通
            代碼。編譯程序時(shí), 系統(tǒng)會(huì)提示你輸入一個(gè)數(shù), 這個(gè)數(shù)在后面的循環(huán)中會(huì)用到。而for循
            環(huán)的代碼會(huì)被編譯。假定你在編譯時(shí)輸入5, for循環(huán)的代碼將會(huì)是:

                for(int i=0;i<5; i++)
                {
                    cout<<"hello"<<endl;
                }

            又簡(jiǎn)單又有效率, 也不需要另外的預(yù)處理語(yǔ)言。我們可以在編譯時(shí)就充分發(fā)揮宿主語(yǔ)言(
            此處是C/C++)的強(qiáng)大能力, 我們可以很容易地在編譯時(shí)連接數(shù)據(jù)庫(kù), 建立數(shù)據(jù)訪問(wèn)層, 就
            像JSP或者ASP創(chuàng)建網(wǎng)頁(yè)那樣。我們也用不著專門的窗口工具來(lái)另外建立工程。我們可以在
            代碼中立即加入必要的工具。我們也用不著顧慮建立這種工具是不是值得, 因?yàn)檫@太容易
            了, 太簡(jiǎn)單了。這樣子不知可以節(jié)省多少時(shí)間啊。

            你好, Lisp

            到此刻為止, 我們所知的關(guān)于Lisp的指示可以總結(jié)為一句話: Lisp是一個(gè)可執(zhí)行的語(yǔ)法更
            優(yōu)美的XML, 但我們還沒(méi)有說(shuō)Lisp是怎樣做到這一點(diǎn)的, 現(xiàn)在開(kāi)始補(bǔ)上這個(gè)話題。

            Lisp有豐富的內(nèi)置數(shù)據(jù)類型, 其中的整數(shù)和字符串和其他語(yǔ)言沒(méi)什么分別。像71或者
            "hello"這樣的值, 含義也和C++或者Java這樣的語(yǔ)言大體相同。真正有意思的三種類型是
            符號(hào)(symbol), 表和函數(shù)。這一章的剩余部分, 我都會(huì)用來(lái)介紹這幾種類型, 還要介紹
            Lisp環(huán)境是怎樣編譯和運(yùn)行源碼的。這個(gè)過(guò)程用Lisp的術(shù)語(yǔ)來(lái)說(shuō)通常叫做求值。通讀這一
            節(jié)內(nèi)容, 對(duì)于透徹理解元編程的真正潛力, 以及代碼和數(shù)據(jù)的同一性, 和面向領(lǐng)域語(yǔ)言的
            觀念, 都極其重要。萬(wàn)勿等閑視之。我會(huì)盡量講得生動(dòng)有趣一些, 也希望你能獲得一些
            啟發(fā)。那好, 我們先講符號(hào)。

            大體上, 符號(hào)相當(dāng)于C++或Java語(yǔ)言中的標(biāo)志符, 它的名字可以用來(lái)訪問(wèn)變量值(例如
            currentTime, arrayCount, n, 等等), 差別在于, Lisp中的符號(hào)更加基本。在C++或
            Java里面, 變量名只能用字母和下劃線的組合, 而Lisp的符號(hào)則非常有包容性, 比如, 加
            號(hào)(+)就是一個(gè)合法的符號(hào), 其他的像-, =, hello-world, *等等都可以是符號(hào)名。符號(hào)
            名的命名規(guī)則可以在網(wǎng)上查到。你可以給這些符號(hào)任意賦值, 我們這里先用偽碼來(lái)說(shuō)明這
            一點(diǎn)。假定函數(shù)set是給變量賦值(就像等號(hào)=在C++和Java里的作用), 下面是我們的例子:

                set(test, 5)            // 符號(hào)test的值為5
                set(=, 5)               // 符號(hào)=的值為5
                set(test, "hello")      // 符號(hào)test的值為字符串"hello"
                set(test, =)            // 此時(shí)符號(hào)=的值為5, 所以test的也為5
                set(*, "hello")         // 符號(hào)*的值為"hello"

            好像有什么不對(duì)的地方? 假定我們對(duì)*賦給整數(shù)或者字符串值, 那做乘法時(shí)怎么辦? 不管
            怎么說(shuō), *總是乘法呀? 答案簡(jiǎn)單極了。Lisp中函數(shù)的角色十分特殊, 函數(shù)也是一種數(shù)據(jù)
            類型, 就像整數(shù)和字符串一樣, 因此可以把它賦值給符號(hào)。乘法函數(shù)Lisp的內(nèi)置函數(shù), 默
            認(rèn)賦給*, 你可以把其他函數(shù)賦值給*, 那樣*就不代表乘法了。你也可以把這函數(shù)的值存
            到另外的變量里。我們?cè)儆脗未a來(lái)說(shuō)明一下:

                *(3,4)          // 3乘4, 結(jié)果是12
                set(temp, *)    // 把*的值, 也就是乘法函數(shù), 賦值給temp
                set(*, 3)       // 把3賦予*
                *(3,4)          // 錯(cuò)誤的表達(dá)式, *不再是乘法, 而是數(shù)值3
                temp(3,4)       // temp是乘法函數(shù), 所以此表達(dá)式的值為3乘4等于12
                set(*, temp)    // 再次把乘法函數(shù)賦予*
                *(3,4)          // 3乘4等于12

            再古怪一點(diǎn), 把減號(hào)的值賦給加號(hào):

                set(+, -)       // 減號(hào)(-)是內(nèi)置的減法函數(shù)
                +(5, 4)         // 加號(hào)(+)現(xiàn)在是代表減法函數(shù), 結(jié)果是5減4等于1

            這只是舉例子, 我還沒(méi)有詳細(xì)講函數(shù)。Lisp中的函數(shù)是一種數(shù)據(jù)類型, 和整數(shù), 字符串,
            符號(hào)等等一樣。一個(gè)函數(shù)并不必然有一個(gè)名字, 這和C++或者Java語(yǔ)言的情形很不相同。
            在這里函數(shù)自己代表自己。事實(shí)上它是一個(gè)指向代碼塊的指針, 附帶有一些其他信息(例
            如一組參數(shù)變量)。只有在把函數(shù)賦予其他符號(hào)時(shí), 它才具有了名字, 就像把一個(gè)數(shù)值或
            字符串賦予變量一樣的道理。你可以用一個(gè)內(nèi)置的專門用于創(chuàng)建函數(shù)的函數(shù)來(lái)創(chuàng)建函數(shù),
            然后把它賦值給符號(hào)fn, 用偽碼來(lái)表示就是:

                fn [a]
                {
                    return *(a, 2);
                }

            這段代碼返回一個(gè)具有一個(gè)參數(shù)的函數(shù), 函數(shù)的功能是計(jì)算參數(shù)乘2的結(jié)果。這個(gè)函數(shù)還
            沒(méi)有名字, 你可以把此函數(shù)賦值給別的符號(hào):

                set(times-two, fn [a] {return *(a, 2)})

            我們現(xiàn)在可以這樣調(diào)用這個(gè)函數(shù):

                time-two(5)         // 返回10

            我們先跳過(guò)符號(hào)和函數(shù), 講一講表。什么是表? 你也許已經(jīng)聽(tīng)過(guò)好多相關(guān)的說(shuō)法。表, 一
            言以蔽之, 就是把類似XML那樣的數(shù)據(jù)塊, 用s表達(dá)式來(lái)表示。表用一對(duì)括號(hào)括住, 表中元
            素以空格分隔, 表可以嵌套。例如(這回我們用真正的Lisp語(yǔ)法, 注意用分號(hào)表示注釋):

                ()                      ; 空表
                (1)                     ; 含一個(gè)元素的表
                (1 "test")              ; 兩元素表, 一個(gè)元素是整數(shù)1, 另一個(gè)是字符串
                (test "hello")          ; 兩元素表, 一個(gè)元素是符號(hào), 另一個(gè)是字符串
                (test (1 2) "hello")    ; 三元素表, 一個(gè)符號(hào)test, 一個(gè)含有兩個(gè)元素1和2的
                                        ; 表, 最后一個(gè)元素是字符串

            當(dāng)Lisp系統(tǒng)遇到這樣的表時(shí), 它所做的, 和Ant處理XML數(shù)據(jù)所做的, 非常相似, 那就是試
            圖執(zhí)行它們。其實(shí), Lisp源碼就是特定的一種表, 好比Ant源碼是一種特定的XML一樣。
            Lisp執(zhí)行表的順序是這樣的, 表的第一個(gè)元素當(dāng)作函數(shù), 其他元素當(dāng)作函數(shù)的參數(shù)。如果
            其中某個(gè)參數(shù)也是表, 那就按照同樣的原則對(duì)這個(gè)表求值, 結(jié)果再傳遞給最初的函數(shù)作為
            參數(shù)。這就是基本原則。我們看一下真正的代碼:

                (* 3 4)                 ; 相當(dāng)于前面列舉過(guò)的偽碼*(3,4), 即計(jì)算3乘4
                (times-two 5)           ; 返回10, times-two按照前面的定義是求參數(shù)的2倍
                (3 4)                   ; 錯(cuò)誤, 3不是函數(shù)
                (time-two)              ; 錯(cuò)誤, times-two要求一個(gè)參數(shù)
                (times-two 3 4)         ; 錯(cuò)誤, times-two只要求一個(gè)參數(shù)
                (set + -)               ; 把減法函數(shù)賦予符號(hào)+
                (+ 5 4)                 ; 依據(jù)上一句的結(jié)果, 此時(shí)+表示減法, 所以返回1
                (* 3 (+ 2 2))           ; 2+2的結(jié)果是4, 再乘3, 結(jié)果是12

            上述的例子中, 所有的表都是當(dāng)作代碼來(lái)處理的。怎樣把表當(dāng)作數(shù)據(jù)來(lái)處理呢? 同樣的,
            設(shè)想一下, Ant是把XML數(shù)據(jù)當(dāng)作自己的參數(shù)。在Lisp中, 我們給表加一個(gè)前綴'來(lái)表示數(shù)
            據(jù)。

                (set test '(1 2))       ; test的值為兩元素表
                (set test (1 2))        ; 錯(cuò)誤, 1不是函數(shù)
                (set test '(* 3 4))     ; test的值是三元素表, 三個(gè)元素分別是*, 3, 4

            我們可以用一個(gè)內(nèi)置的函數(shù)head來(lái)返回表的第一個(gè)元素, tail函數(shù)來(lái)返回剩余元素組成的
            表。

                (head '(* 3 4))         ; 返回符號(hào)*
                (tail '(* 3 4))         ; 返回表(3 4)
                (head (tal '(* 3 4)))   ; 返回3
                (head test)             ; 返回*

            你可以把Lisp的內(nèi)置函數(shù)想像成Ant的任務(wù)。差別在于, 我們不用在另外的語(yǔ)言中擴(kuò)展
            Lisp(雖然完全可以做得到), 我們可以用Lisp自己來(lái)擴(kuò)展自己, 就像上面舉的times-two
            函數(shù)的例子。Lisp的內(nèi)置函數(shù)集十分精簡(jiǎn), 只包含了十分必要的部分。剩下的函數(shù)都是作
            為標(biāo)準(zhǔn)庫(kù)來(lái)實(shí)現(xiàn)的。

            Lisp宏

            我們已經(jīng)看到, 元編程在一個(gè)類似jsp的模板引擎方面的應(yīng)用。我們通過(guò)簡(jiǎn)單的字符串處
            理來(lái)生成代碼。但是我們可以做的更好。我們先提一個(gè)問(wèn)題, 怎樣寫(xiě)一個(gè)工具, 通過(guò)查找
            目錄結(jié)構(gòu)中的源文件來(lái)自動(dòng)生成Ant腳本。

            用字符串處理的方式生成Ant腳本是一種簡(jiǎn)單的方式。當(dāng)然, 還有一種更加抽象, 表達(dá)能
            力更強(qiáng), 擴(kuò)展性更好的方式, 就是利用XML庫(kù)在內(nèi)存中直接生成XML節(jié)點(diǎn), 這樣的話內(nèi)存中
            的節(jié)點(diǎn)就可以自動(dòng)序列化成為字符串。不僅如此, 我們的工具還可以分析這些節(jié)點(diǎn), 對(duì)已
            有的XML文件做變換。通過(guò)直接處理XML節(jié)點(diǎn)。我們可以超越字符串處理, 使用更高層次的
            概念, 因此我們的工作就會(huì)做的更快更好。

            我們當(dāng)然可以直接用Ant自身來(lái)處理XML變換和制作代碼生成工具。或者我們也可以用Lisp
            來(lái)做這項(xiàng)工作。正像我們以前所知的, 表是Lisp內(nèi)置的數(shù)據(jù)結(jié)構(gòu), Lisp含有大量的工具來(lái)
            快速有效的操作表(head和tail是最簡(jiǎn)單的兩個(gè))。而且, Lisp沒(méi)有語(yǔ)義約束, 你可以構(gòu)造
            任何數(shù)據(jù)結(jié)構(gòu), 只要你原意。

            Lisp通過(guò)宏(macro)來(lái)做元編程。我們寫(xiě)一組宏來(lái)把任務(wù)列表(to-do list)轉(zhuǎn)換為專用領(lǐng)
            域語(yǔ)言。

            回想一下上面to-do list的例子, 其XML的數(shù)據(jù)格式是這樣的:

                <todo name = "housework">
                    <item priority = "high">Clean the hose</item>
                    <item priority = "medium">Wash the dishes</item>
                    <item priority = "medium">Buy more soap</item>
                </todo>

            相應(yīng)的s表達(dá)式是這樣的:

                (todo "housework"
                    (item (priority high) "Clean the house")
                    (item (priority medium) "Wash the dishes")
                    (item (priority medium) "Buy more soap"))

            假設(shè)我們要寫(xiě)一個(gè)任務(wù)表的管理程序, 把任務(wù)表數(shù)據(jù)存到一組文件里, 當(dāng)程序啟動(dòng)時(shí), 從
            文件讀取這些數(shù)據(jù)并顯示給用戶。在別的語(yǔ)言里(比如說(shuō)Java), 這個(gè)任務(wù)該怎么做? 我們
            會(huì)解析XML文件, 從中得出任務(wù)表數(shù)據(jù), 然后寫(xiě)代碼遍歷XML樹(shù), 再轉(zhuǎn)換為Java的數(shù)據(jù)結(jié)構(gòu)
            (老實(shí)講, 在Java里解析XML真不是件輕松的事情), 最后再把數(shù)據(jù)展示給用戶。現(xiàn)在如果
            用Lisp, 該怎么做?

            假定要用同樣思路的化, 我們大概會(huì)用Lisp庫(kù)來(lái)解析XML。XML對(duì)我們來(lái)說(shuō)就是一個(gè)Lisp
            的表(s表達(dá)式), 我們可以遍歷這個(gè)表, 然后把相關(guān)數(shù)據(jù)提交給用戶。可是, 既然我們用
            Lisp, 就根本沒(méi)有必要再用XML格式保存數(shù)據(jù), 直接用s表達(dá)式就好了, 這樣就沒(méi)有必要做
            轉(zhuǎn)換了。我們也用不著專門的解析庫(kù), Lisp可以直接在內(nèi)存里處理s表達(dá)式。注意, Lisp
            編譯器和.net編譯器一樣, 對(duì)Lisp程序來(lái)說(shuō), 在運(yùn)行時(shí)總是隨時(shí)可用的。

            但是還有更好的辦法。我們甚至不用寫(xiě)表達(dá)式來(lái)存儲(chǔ)數(shù)據(jù), 我們可以寫(xiě)宏, 把數(shù)據(jù)當(dāng)作代
            碼來(lái)處理。那該怎么做呢? 真的簡(jiǎn)單。回想一下, Lisp的函數(shù)調(diào)用格式:

                (function-name arg1 arg2 arg3)

            其中每個(gè)參數(shù)都是s表達(dá)式, 求值以后, 傳遞給函數(shù)。如果我們用(+ 4 5)來(lái)代替arg1,
            那么, 程序會(huì)先求出結(jié)果, 就是9, 然后把9傳遞給函數(shù)。宏的工作方式和函數(shù)類似。主要
            的差別是, 宏的參數(shù)在代入時(shí)不求值。

                (macro-name (+ 4 5))

            這里, (+ 4 5)作為一個(gè)表傳遞給宏, 然后宏就可以任意處理這個(gè)表, 當(dāng)然也可以對(duì)它求
            值。宏的返回值是一個(gè)表, 然后有程序作為代碼來(lái)執(zhí)行。宏所占的位置, 就被替換為這個(gè)
            結(jié)果代碼。我們可以定義一個(gè)宏把數(shù)據(jù)替換為任意代碼, 比方說(shuō), 替換為顯示數(shù)據(jù)給用戶
            的代碼。

            這和元編程, 以及我們要做的任務(wù)表程序有什么關(guān)系呢? 實(shí)際上, 編譯器會(huì)替我們工作,
            調(diào)用相應(yīng)的宏。我們所要做的, 僅僅是創(chuàng)建一個(gè)把數(shù)據(jù)轉(zhuǎn)換為適當(dāng)代碼的宏。

            例如, 上面曾經(jīng)將過(guò)的C的求三次方的宏, 用Lisp來(lái)寫(xiě)是這樣子:

                (defmacro triple (x)
                    `(+ ~x ~x ~x))

            (譯注: 在Common Lisp中, 此處的單引號(hào)應(yīng)當(dāng)是反單引號(hào), 意思是對(duì)表不求值, 但可以對(duì)
            表中某元素求值, 記號(hào)~表示對(duì)元素x求值, 這個(gè)求值記號(hào)在Common Lisp中應(yīng)當(dāng)是逗號(hào)。
            反單引號(hào)和單引號(hào)的區(qū)別是, 單引號(hào)標(biāo)識(shí)的表, 其中的元素都不求值。這里作者所用的記
            號(hào)是自己發(fā)明的一種Lisp方言Blaise, 和common lisp略有不同, 事實(shí)上, 發(fā)明方言是
            lisp高手獨(dú)有的樂(lè)趣, 很多狂熱分子都熱衷這樣做。比如Paul Graham就發(fā)明了ARC, 許多
            記號(hào)比傳統(tǒng)的Lisp簡(jiǎn)潔得多, 顯得比較現(xiàn)代)

            單引號(hào)的用處是禁止對(duì)表求值。每次程序中出現(xiàn)triple的時(shí)候,

                (triple 4)

            都會(huì)被替換成:

                (+ 4 4 4)

            我們可以為任務(wù)表程序?qū)懸粋€(gè)宏, 把任務(wù)數(shù)據(jù)轉(zhuǎn)換為可執(zhí)行碼, 然后執(zhí)行。假定我們的輸
            出是在控制臺(tái):

                (defmacro item (priority note)
                    `(block
                        (print stdout tab "Prority: " ~(head (tail priority)) endl)
                        (print stdout tab "Note: " ~note endl endl)))

            我們創(chuàng)造了一個(gè)非常小的有限的語(yǔ)言來(lái)管理嵌在Lisp中的任務(wù)表。這個(gè)語(yǔ)言只用來(lái)解決特
            定領(lǐng)域的問(wèn)題, 通常稱之為DSLs(特定領(lǐng)域語(yǔ)言, 或?qū)S妙I(lǐng)域語(yǔ)言)。

            特定領(lǐng)域語(yǔ)言

            本文談到了兩個(gè)特定領(lǐng)域語(yǔ)言, 一個(gè)是Ant, 處理軟件構(gòu)造。一個(gè)是沒(méi)起名字的, 用于處
            理任務(wù)表。兩者的差別在于, Ant是用XML, XML解析器, 以及Java語(yǔ)言合在一起構(gòu)造出來(lái)
            的。而我們的迷你語(yǔ)言則完全內(nèi)嵌在Lisp中, 只消幾分鐘就做出來(lái)了。

            我們已經(jīng)說(shuō)過(guò)了DSL的好處, 這也就是Ant用XML而不直接用Java的原因。如果使用Lisp,
            我們可以任意創(chuàng)建DSL, 只要我們需要。我們可以創(chuàng)建用于網(wǎng)站程序的DSL, 可以寫(xiě)多用戶
            游戲, 做固定收益貿(mào)易(fixed income trade), 解決蛋白質(zhì)折疊問(wèn)題, 處理事務(wù)問(wèn)題, 等
            等。我們可以把這些疊放在一起, 造出一個(gè)語(yǔ)言, 專門解決基于網(wǎng)絡(luò)的貿(mào)易程序, 既有網(wǎng)
            絡(luò)語(yǔ)言的優(yōu)勢(shì), 又有貿(mào)易語(yǔ)言的好處。每天我們都會(huì)收獲這種方法帶給我們的益處, 遠(yuǎn)遠(yuǎn)
            超過(guò)Ant所能給予我們的。

            用DSL解決問(wèn)題, 做出的程序精簡(jiǎn), 易于維護(hù), 富有彈性。在Java里面, 我們可以用類來(lái)
            處理問(wèn)題。這兩種方法的差別在于, Lisp使我們達(dá)到了一個(gè)更高層次的抽象, 我們不再受
            語(yǔ)言解析器本身的限制, 比較一下用Java庫(kù)直接寫(xiě)的構(gòu)造腳本和用Ant寫(xiě)的構(gòu)造腳本其間
            的差別。同樣的, 比較一下你以前所做的工作, 你就會(huì)明白Lisp帶來(lái)的好處。

            接下來(lái)

            學(xué)習(xí)Lisp就像戰(zhàn)爭(zhēng)中爭(zhēng)奪山頭。盡管在電腦科學(xué)領(lǐng)域, Lisp已經(jīng)算是一門古老的語(yǔ)言, 直
            到現(xiàn)在仍然很少有人真的明白該怎樣給初學(xué)者講授Lisp。盡管Lisp老手們盡了很大努力,
            今天新手學(xué)習(xí)Lisp仍然是困難重重。好在現(xiàn)在事情正在發(fā)生變化, Lisp的資源正在迅速增
            加, 隨著時(shí)間推移, Lisp將會(huì)越來(lái)越受關(guān)注。

            Lisp使人超越平庸, 走到前沿。學(xué)會(huì)Lisp意味著你能找到更好的工作, 因?yàn)槁斆鞯墓椭鲿?huì)
            被你與眾不同的洞察力所打動(dòng)。學(xué)會(huì)Lisp也可能意味著明天你可能會(huì)被解雇, 因?yàn)槟憧偸?br>強(qiáng)調(diào), 如果公司所有軟件都用Lisp寫(xiě), 公司將會(huì)如何卓越, 而這些話你的同事會(huì)聽(tīng)煩的。
            Lisp值得努力學(xué)習(xí)嗎? 那些已經(jīng)學(xué)會(huì)Lisp的人都說(shuō)值得, 當(dāng)然, 這取決于你的判斷。

            你的看法呢?

            這篇文章寫(xiě)寫(xiě)停停, 用了幾個(gè)月才最終完成。如果你覺(jué)得有趣, 或者有什么問(wèn)題, 意見(jiàn)或
            建議, 請(qǐng)給我發(fā)郵件coffeemug@gmail.com, 我會(huì)很高興收到你的反饋。

            posted @ 2011-02-15 09:29 Vincent 閱讀(813) | 評(píng)論 (3)編輯 收藏

            2010年12月16日 #

            互斥鎖與條件變量的語(yǔ)義

            互斥鎖與條件變量的語(yǔ)義


            互斥鎖,我要對(duì)一塊共享數(shù)據(jù)操作,但是我怕同時(shí)你也操作,那就亂套了,所以我要加鎖,這個(gè)時(shí)候我就開(kāi)始操作這塊共享數(shù)據(jù),而你進(jìn)不了臨界區(qū),等我操作完了,把鎖丟掉,你就可以拿到鎖進(jìn)去操作了

             

            條件變量,我要看一塊共享數(shù)據(jù)里某一個(gè)條件是否達(dá)成,我很關(guān)心這個(gè),如果我用互斥鎖,不停的進(jìn)入臨界區(qū)看條件是否達(dá)成,這簡(jiǎn)直太悲劇了,這樣一來(lái),我醒的時(shí)候會(huì)占CPU資源,但是卻干不了什么時(shí),只是頻繁的看條件是否達(dá)成,而且這對(duì)別人來(lái)說(shuō)也是一種損失,我每次加上鎖,別人就進(jìn)不了臨界區(qū)干不了事了。好吧,輪詢總是痛苦的,咱等別人通知吧,于是條件變量出現(xiàn)了,我依舊要拿個(gè)鎖,進(jìn)了臨界區(qū),看到了共享數(shù)據(jù),發(fā)現(xiàn),咦,條件還不到,于是我就調(diào)用pthread_cond_wait(),先把鎖丟了,好讓別人可以去對(duì)共享數(shù)據(jù)做操作,然后呢?然后我就睡了,直到特定的條件發(fā)生,別人修改完了共享數(shù)據(jù),給我發(fā)了個(gè)消息,我又重新拿到了鎖,繼續(xù)干俺要干的事情了……

             

            posted @ 2010-12-16 15:35 Vincent 閱讀(849) | 評(píng)論 (0)編輯 收藏

            線程內(nèi)幕

            一.
            在主線程中調(diào)用
            (1)pthread_create( &thread_a, NULL, thread_function, NULL);
            (2)pthread_create( &thread_b, NULL, thread_function, NULL);
            (3)pthread_create( &thread_c, NULL, thread_function, NULL);

             


            在段2處,線程b可以認(rèn)為線程a已經(jīng)存在
            但是在段2執(zhí)行完以后,主線程并不知道線程a和線程b誰(shuí)先執(zhí)行,并不能在這里做線程a先于線程b執(zhí)行的假設(shè)
            因?yàn)榫€程的時(shí)間片分配在這里是未知的

             

            二.
            myglobal=myglobal+1;
            myglobal是全局變量,多個(gè)線程同時(shí)在做累加的工作
            是否應(yīng)該為myglobal=myglobal+1;加鎖呢?
            肯定是應(yīng)該加鎖
            首先我們并不知道m(xù)yglobal=myglobal+1;又或是++ myglobal;能否被編譯成一條匯編指令
            就算如此++ myglobal被編譯成了原子操作
            但考慮到多核處理器,其原子操作可能在多CPU上同時(shí)處理
            其結(jié)果仍然是不可預(yù)估的


            以上內(nèi)容轉(zhuǎn)述自http://www.ibm.com/developerworks/cn/linux/thread/posix_thread2/index.html

            posted @ 2010-12-16 14:37 Vincent 閱讀(485) | 評(píng)論 (0)編輯 收藏

            第4章

            UNIX網(wǎng)絡(luò)編程筆記

            做下筆記,以供不時(shí)之需:)

            4

             

             

            socket(int family,               int type,                      int protocol);

            非負(fù)描述字-成功,-1-出錯(cuò)

                        協(xié)議族                   套接口類型                              協(xié)議類型常量值

                        AF_INET  IPv4協(xié)議 SOCK_STREAM 字節(jié)流           IPPROTO_TCP  TCP傳輸協(xié)議

                        AF_INET6 IPv6協(xié)議    SOCK_DGRAM  數(shù)據(jù)報(bào)              IPPROTO_UDP  UDP傳輸協(xié)議

                       AF_LOCAL UNIX域協(xié)議  SOCK_SEQPACKET有序分組          IPPROTO_SCTP SCTP傳輸協(xié)議

                        AF_ROUTE 路由套接口  SOCK_RAW    原始套接口

                        AF_KEY   密鑰套接口

             

             

             

            protocol可以設(shè)為0,以選擇所給定的familytype組合的系統(tǒng)缺省值

             

             

            connect(int sockfd,           const struct sockaddr *servaddr,    socklen_t addrlen);

                     要連接的套接口描述字         指向描述連接地址的套接口地址的結(jié)構(gòu)指針     指向套接口地址的結(jié)構(gòu)大小        

            0-成功,-1-失敗

             

             

            ETIMEDOUT錯(cuò)誤,客戶端未收到SYN分節(jié)的響應(yīng)

            4.4BSD內(nèi)核中SYN分節(jié)會(huì)在首次發(fā)出后的6秒,24秒后再發(fā)出SYN分節(jié),如果75秒后仍無(wú)響應(yīng),返回錯(cuò)誤(TCPv2 p828)

             

            ECONNERFUSED錯(cuò)誤,客戶端收到RST分節(jié)時(shí)就返回錯(cuò)誤

            其中產(chǎn)生RST的三個(gè)條件是:

            1.目的地為某端口的SYN到達(dá),然而該端口上沒(méi)有正在監(jiān)聽(tīng)的服務(wù)器

            2.TCP想取消一個(gè)已有連接

            3.TCP接受到一個(gè)根本不存在的連接上的分節(jié)(TCPv1 246-250)

             

            EHOSTUNREACHENETUNREACH錯(cuò)誤

            在發(fā)出SYN分節(jié)的中間某個(gè)路由器引發(fā)了目的地不可達(dá)的ICMP錯(cuò)誤,客戶主機(jī)保存該消息,但仍然繼續(xù)發(fā)送SYN,直到75秒后,如果仍未有回應(yīng),則把錯(cuò)誤消息返回給進(jìn)程。

            以下情況也會(huì)產(chǎn)生此類錯(cuò)誤

            1.本地系統(tǒng)的轉(zhuǎn)發(fā)表,根本沒(méi)有到達(dá)遠(yuǎn)地系統(tǒng)的路徑

            2.connect調(diào)用根本不等待就返回(非阻塞?)

             

            :connect失敗則該套接口不再可用,必須關(guān)閉。

             

             

            bind(int sockfd,                  const struct sockaddr *myaddr,               socklen_t addrlen);

            0-成功,-1失敗

                 要綁定的套接口描述字       指向描述要綁定的套接口地址的結(jié)構(gòu)指針         指向套接口地址的結(jié)構(gòu)大小

             

            如果一個(gè)TCP客戶端或服務(wù)器未曾調(diào)用bind捆綁一個(gè)端口,當(dāng)調(diào)用connectlisten時(shí),內(nèi)核會(huì)為相應(yīng)的套接口選擇一個(gè)臨時(shí)端口

            進(jìn)程可把特定IP地址捆綁到它的套接口上,這個(gè)特定IP必須是主機(jī)的網(wǎng)絡(luò)接口之一。對(duì)于TCP客戶,這就限定了套接口只接受目的地為這個(gè)特定IP的客戶連接。

            TCP客戶通過(guò)不把IP捆綁到它的套接口上,內(nèi)核會(huì)根據(jù)所用外出網(wǎng)絡(luò)接口來(lái)選擇源IP地址,而所用外出的接口則取決于到達(dá)服務(wù)器所需的路徑。(TCPv2 p737)

            如果TCP服務(wù)器沒(méi)有把IP地址捆綁到它的套接口上,內(nèi)核就把客戶發(fā)送的SYN的宿IP地址作為服務(wù)器的源地址。(TCPv2 p943)

             

            EADDRINUSE錯(cuò)誤 地址已使用

             

             

             

            listen(   int sockfd ,                         int backlog);

            0-成功,-1失敗

                  要轉(zhuǎn)換成被動(dòng)的套接口     排隊(duì)的最大已連接個(gè)數(shù)

             

             

            1.未完成連接隊(duì)列

            處于SYN_RCVD狀態(tài)的套接口隊(duì)列

            2.已完成連接隊(duì)列

            處于ESTABLISHED狀態(tài)的套接口隊(duì)列

             

            在隊(duì)列滿時(shí),服務(wù)器如果收到SYN分節(jié),會(huì)忽略掉,因?yàn)殛?duì)列滿是暫時(shí)性的,忽略掉可以以期待客戶端的再次連接。而如果返回RST分節(jié),會(huì)時(shí)客戶端放棄連接。

             

            在三次握手完成之后,但在服務(wù)器調(diào)用accept之前到達(dá)的數(shù)據(jù),應(yīng)有服務(wù)器TCP排隊(duì),最大數(shù)據(jù)量為相應(yīng)的已連接套接口的接受緩沖區(qū)大小。



            Int accept(        int sockfd,                  struct sockaddr* cliaddr,                             socklen_t* addrlen);

            非負(fù)描述字-成功,-1-失敗

                                        被動(dòng)的監(jiān)聽(tīng)套接口  對(duì)端的套接口地址的結(jié)構(gòu)         對(duì)端的套接口地址的結(jié)構(gòu)大小

             

             

            Int close(int sockfd);

            0-       成功,-1-失敗

            要關(guān)閉的套接口

             

            缺省行為是修改套接口標(biāo)記為已關(guān)閉,函數(shù)會(huì)立即返回,此時(shí)該套接口描述字已不能再由進(jìn)程使用。而TCP將嘗試發(fā)送已排隊(duì)等待發(fā)送到對(duì)端的任何數(shù)據(jù),發(fā)送完畢后開(kāi)始正常的TCP連接終止序列。

             

            SO_LINGER套接口選項(xiàng)可以改變此缺省行為。

            posted @ 2010-12-16 11:39 Vincent 閱讀(353) | 評(píng)論 (0)編輯 收藏

            2010年12月12日 #

            懂得

            第一次周六周日在加班
            第一次擔(dān)起責(zé)任
            第一次怕自己不行
            第一次有那么一點(diǎn)理解到底什么是主程
            這一天僅作紀(jì)念:)

            posted @ 2010-12-12 21:55 Vincent 閱讀(356) | 評(píng)論 (0)編輯 收藏

            2010年10月19日 #

            游戲服務(wù)端程序員群

            群號(hào):16043631 已經(jīng)250人了 還差250
            幫朋友宣傳一下:)

            posted @ 2010-10-19 16:35 Vincent 閱讀(1819) | 評(píng)論 (0)編輯 收藏

            2010年10月14日 #

            [轉(zhuǎn)]《深度探索C++對(duì)象模型》讀書(shū)筆記[二]

            2002-7-6

            3.3 Data Member的存取
            1.   不管什么情況,每一個(gè)static data member只有一個(gè)實(shí)體,放在程序的data segment之中,每次程序取用static member,不管是通過(guò)operator::還是member selection operator,都會(huì)被內(nèi)部轉(zhuǎn)化為對(duì)該唯一extern實(shí)體的直接參考操作。每一個(gè)static member的存取以及與class的關(guān)聯(lián)不會(huì)導(dǎo)致任何執(zhí)行時(shí)間或空間上的額外負(fù)擔(dān)。如果有兩個(gè)classes,每一個(gè)都聲明了一個(gè)static member freeList,那么當(dāng)它們都放在程序的data segment時(shí),就會(huì)導(dǎo)致名稱沖突,編譯器的解決方法是使用name-mangling,暗中對(duì)每一個(gè)static data member編碼,以獲得一個(gè)獨(dú)一無(wú)二的程序識(shí)別代碼。

            2.   有多少個(gè)編譯器,就有多少種name-mangling做法,任何name-mangling做法都有兩個(gè)要點(diǎn):

            ü          一種算法,推導(dǎo)出獨(dú)一無(wú)二的名稱;

            ü          如果編譯系統(tǒng)或者環(huán)境工具必須和使用者交談,那些獨(dú)一無(wú)二的名稱可被輕易推導(dǎo)回原先的名稱。

            3.   取一個(gè)static data member的地址,會(huì)得到一個(gè)指向其數(shù)據(jù)類型的常量指針,而不是指向其class member的指針。

            4.   nonstatic data members直接放在每一個(gè)class object之中,除非經(jīng)過(guò)顯示的explicit或隱含的implicit class object,沒(méi)有辦法直接存取它們。只要程序員在一個(gè)member function中直接處理一個(gè)nonstatic data member,所謂implicit class object就會(huì)發(fā)生,其實(shí)質(zhì)是編譯器會(huì)為這個(gè)member function增添一個(gè)const this指針,而在函數(shù)體內(nèi)通過(guò)這個(gè)this指針來(lái)存取nontatic data member。

            5.   欲對(duì)一個(gè)nonstatic data member進(jìn)行存取操作,編譯器需要把class object的起始地址加上data member的編譯量offset,如地址&someObject.someMember等于&someobject + (&theClass::someMember – 1);指向data member的指針,其offset值總是會(huì)被加上1,這樣可以使編譯系統(tǒng)區(qū)分出一個(gè)指向class第一個(gè)data member的指針和一個(gè)沒(méi)有指向任何data member的指針。

            6.   每一個(gè)nonstatic data member的偏移量在編譯時(shí)期即可獲知,甚至如果member屬于一個(gè)單一或多重繼承體系中base class subobject也是一樣,因此其存取效率和一個(gè)C struct member或一個(gè)nonderived class的member的存取效率是一樣的。但是在虛擬繼承的情況下就另當(dāng)別論了:如果該nonstatic data member是一個(gè)virtual base class的member,并且通過(guò)指針來(lái)存取的話,在編譯時(shí)期就不會(huì)得知這個(gè)member真正的offset位置,所以這個(gè)存取操作必須延遲至執(zhí)行期,經(jīng)由一個(gè)額外的間接導(dǎo)引才能夠解決。

            2002-7-7

            3.4 “繼承”與Data Member
            1.   在C++繼承模型中,一個(gè)derived class object所表現(xiàn)出來(lái)的東西,是其自己的members加上其base classes members的總和。C++并未規(guī)定derived class members和base classes members的排列次序。不過(guò),在大部分編譯器上,除virtual base class外,base class members總是先出現(xiàn)。

            2.   一般而言,具體繼承concrete inheritance并不會(huì)增加空間或存取時(shí)間上的額外負(fù)擔(dān)。

            3.   把兩個(gè)原本獨(dú)立不相干的classes湊成一對(duì)type/subtype,并帶有繼承關(guān)系容易犯兩個(gè)錯(cuò)誤。一是可能會(huì)重復(fù)設(shè)計(jì)一些相同操作的函數(shù),一般而言,選擇某些函數(shù)做成inline函數(shù),是設(shè)計(jì)class的一個(gè)重要課題;二是把一個(gè)class分解為多層,有可能會(huì)為了表現(xiàn)class體系之抽象化,因?yàn)榫幾g器的邊界調(diào)整而膨脹所需空間。其根本原因是C++保證出現(xiàn)在derived class中的base class subobject有其完整原樣性。

            4.   C++最初問(wèn)世時(shí),許多編譯器把vptr放在class object的尾端,這樣可以保留base class C struct的對(duì)象布局。此后,某些編譯器開(kāi)始把vptr放在class object的開(kāi)始處,這樣會(huì)給多重繼承下通過(guò)指向class members之指針調(diào)用virtual function帶來(lái)一些幫助,否則,在執(zhí)行期不僅必須備妥從class object起點(diǎn)處開(kāi)始量起的offset,而且必須備妥class vptr之間的offset。

            5.   單一繼承提供了一種自然多態(tài)的形態(tài),是關(guān)于class體系中base type和derived type之間的轉(zhuǎn)換。一般來(lái)說(shuō),base class和derived class objects都是從相同的地址開(kāi)始。但若將vptr放在class object的起始處,如果base class沒(méi)有virtual function而derived class有,那么單一繼承的自然多態(tài)就會(huì)打破。此時(shí),把一個(gè)derived object轉(zhuǎn)換為其base類型就需要編譯器的介入,用以調(diào)整地址。而在既是多重繼承又是虛擬繼承的情況下,編譯器的介入則更有必要。

            6.   多重繼承的復(fù)雜度在于derived class和其上一個(gè)base class乃至上上一個(gè)base class之間的非自然關(guān)系,其主要問(wèn)題發(fā)生在derived class objects和其第二或后繼的base class objects之間的轉(zhuǎn)換。對(duì)一個(gè)多重派生對(duì)象,將其地址指定給最左端base class的指針,情況將和單一繼承相同,而第二個(gè)或后繼的base class的地址指定操作則需要修改地址,加上或減去(若是downcast)介于中間的base class subobjects的大小。C++并未要求多重繼承時(shí)derived class object中各個(gè)base class subjectes的排列次序,目前各個(gè)編譯器都是根據(jù)聲明次序來(lái)排列它們。

            7.   class內(nèi)如果內(nèi)含一個(gè)或多個(gè)virtual bass class subobjects,將被分割為兩部分:一個(gè)不變局部和一個(gè)共享局部。不變局部總是擁有固定的offset,其數(shù)據(jù)用以指定共享局部的位置,可以直接存取;而共享局部表現(xiàn)的就是virtual base class subobject,其位置會(huì)因?yàn)槊看蔚呐缮僮鞫兓豢砷g接存取。各家編譯器實(shí)現(xiàn)技術(shù)之間的差異就在于間接存取的方法不同。

            8.   一般而言,virtual base class最有效的一種運(yùn)用方式是:一個(gè)沒(méi)有任何data member的抽象class。

            2002-7-14

            3.5 對(duì)象成員的效率
            如果沒(méi)有把優(yōu)化開(kāi)關(guān)打開(kāi),就很難猜測(cè)一個(gè)程序的效率表現(xiàn),因?yàn)槌绦虼a潛在性的受到某些與編譯器有關(guān)的東西的影響。程序員如果關(guān)心效率,應(yīng)該實(shí)際測(cè)試,不要光憑推論或常識(shí)判斷或假設(shè)。優(yōu)化操作并不一定總是能夠有效運(yùn)行。

            2002-7-15

            3.6 指向Data Members的指針
            指向data members的指針可用來(lái)詳細(xì)調(diào)查class members的底層布局,可用來(lái)決定vptr是放在class的起始處還是尾端,還可用來(lái)決定class中access sections的次序。

            取一個(gè)nonstatic data member的地址,將會(huì)得到它在class的offset;而取一個(gè)static data member的地址或者取一個(gè)綁定于真正class object身上的data member的地址,將會(huì)得到該member在內(nèi)存中的真正地址。這也正是someType someClass::*和someTye *潛在的區(qū)別。

            2002-7-16

            Function語(yǔ)意學(xué) The Semantics of Function
            C++支持三種類型的member functions:static、nonstatic和virtual,每一種類型的調(diào)用方式都不同。

            4.1 Members的各種調(diào)用方式
            1.   C++的設(shè)計(jì)準(zhǔn)則之一便是nonstatic member function至少必須和一般的nonmember function有著相同的效率。編譯器內(nèi)部會(huì)將member函數(shù)實(shí)體轉(zhuǎn)換為對(duì)等的nonmember函數(shù)實(shí)體,其步驟為:

            ü          改寫(xiě)函數(shù)原型signature以安插一個(gè)額外的參數(shù)this到member function中,使得class object可以調(diào)用該函數(shù)。其中,this是const指針,若該函數(shù)為const,則反映在this上面的結(jié)果是this指向的data也為const;

            ü          將每一個(gè)對(duì)nonstatic data member的存取操作改為經(jīng)由this指針來(lái)存取;

            ü          將member function重新寫(xiě)成一個(gè)外部函數(shù),對(duì)函數(shù)名稱進(jìn)行mangling處理;

            此后,每一個(gè)函數(shù)調(diào)用操作也都必須轉(zhuǎn)換,用以提供相應(yīng)的實(shí)參。

            2.   關(guān)于虛擬函數(shù)的內(nèi)部轉(zhuǎn)換步驟:若normalize是一個(gè)virtual member function,ptr->normalize();會(huì)被內(nèi)部轉(zhuǎn)化為(*ptr->vptr[t])(ptr); 事實(shí)上,vptr名稱也會(huì)被mangled,因?yàn)榭赡艽嬖谟卸鄠€(gè)vptrs;t是vitrual table slot的索引值,關(guān)聯(lián)到normalize函數(shù);第二個(gè)ptr表示this指針。

            3.   使用class scope operator明確調(diào)用一個(gè)vitual function,或經(jīng)由一個(gè)class object調(diào)用一個(gè)vitual function其決議方式會(huì)和nontatic member function一樣!故virtual function的一個(gè)inline函數(shù)實(shí)體可被擴(kuò)展開(kāi)來(lái),因而提供極大的效率利益。

            4.   static member function的主要特征是沒(méi)有this指針,這導(dǎo)致它不能直接存取其class中的nonstatic members,不能被聲明為const、volatile或virtual,也不需要經(jīng)由class object才能調(diào)用。static member function會(huì)被提出于class聲明之外,并給予一個(gè)經(jīng)過(guò)mangled的適當(dāng)名稱。如果取一個(gè)static member function的地址,得到的將是其在內(nèi)存中的地址,其地址類型并不是一個(gè)指向class member function的指針,而是一個(gè)nonmember函數(shù)指針。static member function的一個(gè)意想不到的好處是可以成為一個(gè)callback函數(shù),也可以成功地應(yīng)用在thread函數(shù)身上。

            2002-07-17

            4.2 Virtual Member Functions虛擬成員函數(shù)
            1.   C++中,多態(tài)polymorphism表示以一個(gè)public base class指針或reference尋址出一個(gè)derived class object。識(shí)別一個(gè)class是否支持多態(tài),唯一適當(dāng)?shù)姆椒ㄔ嚳此欠裼腥魏蝪irtual function。只要class擁有一個(gè)virtual function,它就需要一份額外的執(zhí)行期型別判斷信息。

            2.   一個(gè)class只會(huì)有一個(gè)virtual table,其中內(nèi)含對(duì)應(yīng)class object中所有的active virtual functions的函數(shù)實(shí)體的地址。這些active virtual functions包括:

            ü          一個(gè)class定義的函數(shù)實(shí)體。它會(huì)改寫(xiě)overriding一個(gè)可能存在的base class virtual function。

            ü          繼承自base class的函數(shù)實(shí)體。此時(shí)該class不改寫(xiě)base class virtual function。

            ü          一個(gè)pure_virtual_called()函數(shù)實(shí)體,它既可以扮演pure virtual function的空間保衛(wèi)者,也可以當(dāng)作執(zhí)行期異常處理函數(shù)。如果該函數(shù)被調(diào)用,通常的操作是結(jié)束程序。

            3.   每一個(gè)virtual function都被指派一個(gè)固定不變的索引值,該值在整個(gè)繼承體系中保持與特定virtual function的關(guān)聯(lián)。這樣就可以在編譯時(shí)期設(shè)定virtual function的調(diào)用。

            2002-7-20

            4.   多重繼承下,一個(gè)上層basse classes數(shù)目為n的derived class,它將內(nèi)含n-1個(gè)額外的virtual tables。其主要實(shí)體與最左端的base class共享,其中包含所有virtual functios的地址;n-1個(gè)次要實(shí)體與其它base classes有關(guān),其中只包含出現(xiàn)在對(duì)應(yīng)base class中virtual functions的地址。

            5.   在多重繼承中支持virtual function,其復(fù)雜度圍繞在第二個(gè)及后繼base class上,以及執(zhí)行期this指針調(diào)整上。第二(或后繼)base class會(huì)影響對(duì)virtual function支持的3種情況:

            ü          通過(guò)指向第二個(gè)base class的指針,調(diào)用derived class virtual function;

            ü          通過(guò)指向derived class的指針,調(diào)用第二個(gè)base class中一個(gè)繼承而來(lái)的virtual function;

            ü          允許virtual function函數(shù)的返回值類型有所變化,可能是base type,也可能是publicly derived type。

            6.   關(guān)于執(zhí)行期this指針調(diào)整比較有效率的解決方法是thunk。所謂thunk是一小端assembly碼,用來(lái)以適當(dāng)?shù)膐ffset值來(lái)調(diào)整this指針并跳到相應(yīng)的virtual function。thunk技術(shù)允許virtual table slot繼續(xù)內(nèi)含一個(gè)簡(jiǎn)單的指針,此時(shí)多重繼承將不需要任何空間上的額外負(fù)擔(dān)!slots中的地址可以直接指向virtual function,也可以指向一個(gè)相關(guān)的thunk。

            4.3 函數(shù)的效能
            nonmember、static member和nonstatic member function在內(nèi)部都會(huì)轉(zhuǎn)化為完全相同的形式,三者效率相同。

            2002-08-08

            4.4 指向Member Function的指針
            對(duì)一個(gè)nonstatic member function取址,得到的是該函數(shù)在內(nèi)存中的地址;而面對(duì)一個(gè)virtual function,得到的將是一個(gè)索引值。這個(gè)值是不完整的,必須被綁定于一個(gè)class object上,才能夠通過(guò)它調(diào)用函數(shù)。指向member function的指針的聲明語(yǔ)法,以及指向member selection運(yùn)算符的指針,其作用是作為this指針的空間保留者。因此,static member function的類型是函數(shù)指針,而不是指向member function的指針。

            使用一個(gè)member function指針,如果并不用于virtual function、多重繼承、virtual base class等情況的話,其成本并不比使用一個(gè)nonmember function指針要高。

            4.5 Inline Functions
            關(guān)鍵詞inline只是一項(xiàng)請(qǐng)求。如果在某個(gè)層次上,函數(shù)的執(zhí)行成本比一般的函數(shù)調(diào)用及返回機(jī)制所帶來(lái)的負(fù)荷低,那么該請(qǐng)求被接受,編譯器就用一個(gè)表達(dá)式合理地將函數(shù)擴(kuò)展開(kāi)來(lái)。真正的inline函數(shù)擴(kuò)展操作是在函數(shù)調(diào)用的那一點(diǎn)上。在inline擴(kuò)展期間,每一個(gè)形式參數(shù)會(huì)被對(duì)應(yīng)的實(shí)際參數(shù)所取代,inline函數(shù)中的每一個(gè)局部變量都必須被放在函數(shù)調(diào)用的一個(gè)封閉區(qū)段中,并擁有一個(gè)獨(dú)一無(wú)二的名稱。這會(huì)帶來(lái)參數(shù)的求值操作以及臨時(shí)性對(duì)象的管理。

            2002-08-11

            構(gòu)造、解構(gòu)、拷貝語(yǔ)意學(xué)  Semantics of Construction, Destruction, and Copy
            1.   一般而言,class的data member應(yīng)該被初始化,而且只在constructor中或其它member functions中初始化,其它任何操作都將破壞其封裝性質(zhì),使其維護(hù)和修改更加困難。

            2.   可以定義并調(diào)用invoke一個(gè)pure virtual function,但它只能被靜態(tài)調(diào)用,不能經(jīng)由虛擬機(jī)制調(diào)用。每一個(gè)derived class destructor會(huì)被編譯器加以擴(kuò)展,靜態(tài)調(diào)用每一個(gè)virtual base class以及上一層base class的destructor。因此,不管base class的virtual destructor是否聲明為pure,它必須被定義。

            5.1 無(wú)繼承情況下的對(duì)象構(gòu)造
            C++ Standard要求編譯器盡量延遲nontrivial members的實(shí)際合成操作,直到真正遇到其使用場(chǎng)所為止。

            5.2 繼承體系下的對(duì)象構(gòu)造
            一般而言,繼承體系下編譯器對(duì)constructor所作的擴(kuò)充操作以及次序大約如下:

            ü          所有virtual base class constructors必須從左到右、從深到淺被調(diào)用:如果class被列于member initialization list中,那么任何明確指定的參數(shù)都必須傳遞過(guò)去,否則如果class有一個(gè)default constructor,也應(yīng)該調(diào)用它;class中的每一個(gè)virtual base class subobject的偏移量offset必須在執(zhí)行期可被存取;如果class object是最底層most-derived的class,其constructors可能被調(diào)用,某些用以支持這個(gè)行為的機(jī)制必須被方進(jìn)來(lái)。

            ü          以base class的聲明次序調(diào)用上一層base class constructors:如果base class被列于member initialization list中,那么任何明確指定的參數(shù)都必須傳遞過(guò)去,否則若它有default constructor或default memberwise copy constructor,那么就調(diào)用它;如果base class是多重繼承下的第二或后繼的base class,那么this指針必須有所調(diào)整。

            ü          如果class object有virtual table pointer(s),它(們)必須被設(shè)定初值,指向適當(dāng)?shù)膙irtual table(s)。

            ü          如果有一個(gè)member沒(méi)有出現(xiàn)在member initialization list中,但它有default constructor,調(diào)用之。

            ü          將member initialization list中的data members的初始化操作以members的聲明次序放進(jìn)constructor的函數(shù)本身。

            2002-8-18

            5.3對(duì)象復(fù)制語(yǔ)意學(xué) Object Copy Semantics
            1.   只有在默認(rèn)行為所導(dǎo)致的語(yǔ)意不安全或者不正確以致發(fā)生別名化aliasing或者內(nèi)存泄漏memory leak時(shí),才需要設(shè)計(jì)一個(gè)copy assignment operator。否則,程序反倒會(huì)執(zhí)行得較慢。

            2.   如果僅僅是為了把NRV優(yōu)化開(kāi)關(guān)打開(kāi)而提供一個(gè)copy constructor,那么就沒(méi)有必要一定要提供一個(gè)copy assignment operator。

            3.   copy assignment operator有一個(gè)非正交情況,那就是它缺乏一個(gè)平行于member initialization list的member assignment list。調(diào)用base class的copy assignment operator示例:

            Point::operator = (p3d); 或 (*(Point*)this) = p3d; 或 (Point &)(*this) = p3d;

            4.   事實(shí)上,copy assignment operator在虛擬繼承情況下行為不佳,需要小心設(shè)計(jì)和說(shuō)明。許多編譯器甚至并不嘗試取得正確的語(yǔ)意,它們?cè)诿恳粋€(gè)中間的copy assignment operator中調(diào)用每一個(gè)base class instance,于是造成virtual base copy assignment operator的多個(gè)實(shí)體被調(diào)用。建議盡可能不要允許一個(gè)virtual base class的拷貝操作,并不要在任何virtual base class中聲明data member。

            5.5解構(gòu)語(yǔ)意學(xué) Semantics of Destruction
            如果class沒(méi)有定義destructor,那么只有在其內(nèi)帶的member object或base class擁有destructor時(shí),編譯器才會(huì)自動(dòng)合成出一個(gè)destructor。一個(gè)由程序員定義的destructor被擴(kuò)展的方式類似constructors被擴(kuò)展的方式,只是順序相反:

            ü          destructor的函數(shù)本體首先被執(zhí)行;

            ü          如果class擁有member class objects,而后者擁有destructors,那么它們將以聲明的相反順序而調(diào)用;

            ü          如果object內(nèi)帶一個(gè)vptr,則現(xiàn)在被重新設(shè)定以指向適當(dāng)base class之virtual table;

            ü          如果有任何直接的nonvirtual base classes擁有destructor,它們將以聲明的相反順序而調(diào)用;

            ü          如果有任何virtual base classes擁有destructor,而前面討論的這個(gè)class是most-derived class,那么它們會(huì)以原先構(gòu)造順序的相反順序被調(diào)用。

            2002-8-19

            執(zhí)行期語(yǔ)意學(xué) Runtime Semantics
            6.1對(duì)象的構(gòu)造和解構(gòu)
            1.   一般而言,constructor和destructor的安插都如你所預(yù)期。但如果一個(gè)區(qū)段或函數(shù)中有一個(gè)以上的離開(kāi)點(diǎn),情況就會(huì)復(fù)雜一些,destructor會(huì)放在每一個(gè)離開(kāi)點(diǎn)之前。通常,我們要求將object盡可能放在使用它的那個(gè)程序區(qū)附近,這樣做可以節(jié)省不必要的對(duì)象產(chǎn)生和銷毀操作。

            2.   C++程序中所有的global objects都被放置在程序的data segment中,如果不明確指定初值,object所配置的內(nèi)存內(nèi)容將為0(C并不自動(dòng)設(shè)定初值)。如果global object有constructor和destructor的話,我們說(shuō)它需要靜態(tài)的初始化和內(nèi)存釋放操作。

            2002-8-20

            3.   virtual base class的subobject在每個(gè)derived class中的位置可能會(huì)變動(dòng),不能在編譯時(shí)期確定。以一個(gè)derived class的pointer或reference來(lái)存取virtual base class subobject,是一種nonconstant expression,必須在執(zhí)行期方可評(píng)估求值。

            4.   使用靜態(tài)初始化的object有一些缺點(diǎn)。其一,無(wú)法放入try區(qū)段,任何throw操作必將觸發(fā)exception handling library的默認(rèn)函數(shù)terminate();其二,程序員必須為控制“需要跨越模塊做靜態(tài)初始化”objects的依賴順序而產(chǎn)生的復(fù)雜度付出代價(jià)。建議根本就不要使用那些需要靜態(tài)初始化的global objects。

            5.   新的C++標(biāo)準(zhǔn)要求編譯單位中的static local class objects必須在相應(yīng)函數(shù)第一次被調(diào)用時(shí)才被構(gòu)造,而且必須以相反的次序銷毀。由于這些objects是在需要時(shí)才被構(gòu)造,因此編譯時(shí)期無(wú)法預(yù)期其集合和順序。為支持新標(biāo)準(zhǔn),可能要對(duì)被產(chǎn)生出來(lái)的static local class objects保持一個(gè)執(zhí)行期鏈表。

            2003-8-1

            6.   對(duì)于對(duì)象數(shù)組定義,晚近的編譯器一般會(huì)提供兩個(gè)函數(shù),分別用于處理沒(méi)有virtual base class的class,以及內(nèi)帶virtual base class的class ,它們通常被稱為vec_new、vec_vnew。前者類型通常為:

            void* vec_new(                                                   // 初始化程序員未提供初值的連續(xù)元素

                       void *array,                                               // 數(shù)組起始地址若為0,則動(dòng)態(tài)分配

                       size_t elem_size,                                       // 每一個(gè)class object的大小

                       int elem_count,                                         // 數(shù)組中的元素?cái)?shù)目

                       void (*constructor) (void *),                    // class的default constructor指針

                       void (*destructor) (void *, char)               // class的destructor指針,以0填入

            ); 如果程序員提供帶有默認(rèn)參數(shù)值的default constructor,編譯器要做特殊處理,以傳入默認(rèn)參數(shù)值!

            對(duì)應(yīng)銷毀數(shù)組的兩個(gè)函數(shù)分別為vec_delete、vec_vdelete。前者類型通常為:

            void* vec_delete(

                       void *array,                                               // 數(shù)組起始地址

                       size_t elem_size,                                       // 每一個(gè)class object的大小

                       int elem_count,                                         // 數(shù)組中的元素?cái)?shù)目

                       void (*destructor) (void *, char)               // class的destructor指針

            );

            6.2 new和delete運(yùn)算符
                     注意區(qū)分operator new和new operator!前者負(fù)責(zé)分配內(nèi)存;后者先調(diào)用前者分配內(nèi)存,然后調(diào)用constructor以實(shí)施初始化。

            《深度探索C++對(duì)象模型》讀書(shū)筆記

             

            本文來(lái)自CSDN博客,轉(zhuǎn)載請(qǐng)標(biāo)明出處:http://blog.csdn.net/xjtuse_mal/archive/2007/03/01/1517809.aspx

            posted @ 2010-10-14 10:36 Vincent 閱讀(419) | 評(píng)論 (0)編輯 收藏

            僅列出標(biāo)題  下一頁(yè)
            无码人妻少妇久久中文字幕蜜桃| 嫩草影院久久99| 伊人久久大香线蕉av一区| 亚洲午夜久久久久久噜噜噜| 99久久国产综合精品麻豆| 久久最新精品国产| 中文精品久久久久人妻不卡| 94久久国产乱子伦精品免费| 久久精品中文字幕大胸| jizzjizz国产精品久久| 中文字幕久久精品 | 性做久久久久久久久久久| 国内精品人妻无码久久久影院导航 | 2020久久精品国产免费| 国产福利电影一区二区三区久久老子无码午夜伦不 | 无码人妻久久一区二区三区蜜桃| 久久久久高潮毛片免费全部播放| 国内精品久久久久久久影视麻豆 | 99久久免费国产特黄| 一本一道久久a久久精品综合| 国内精品久久久久影院免费| 久久久精品国产免大香伊 | 久久乐国产综合亚洲精品| 国产福利电影一区二区三区久久久久成人精品综合 | 欧美黑人激情性久久| 久久精品无码一区二区三区日韩| 亚洲国产精品无码久久SM| 亚洲精品99久久久久中文字幕| 精品久久久久久| 久久国产精品99久久久久久老狼 | 无码人妻少妇久久中文字幕 | 久久精品卫校国产小美女| 日本精品久久久久影院日本| 日韩精品国产自在久久现线拍 | 久久99国产综合精品| 色偷偷久久一区二区三区| 国产一区二区久久久| 麻豆av久久av盛宴av| 中文字幕热久久久久久久| 国产成人无码精品久久久性色| 精品伊人久久大线蕉色首页|