• <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>
            asm, c, c++ are my all
            -- Core In Computer
            posts - 139,  comments - 123,  trackbacks - 0

            條款14: 確定基類有虛析構函數

            有時,一個類想跟蹤它有多少個對象存在。一個簡單的方法是創(chuàng)建一個靜態(tài)類成員來統(tǒng)計對象的個數。這個成員被初始化為0,在構造函數里加1,析構函數里減1。(條款m26里說明了如何把這種方法封裝起來以便很容易地添加到任何類中,“my article on counting objects”提供了對這個技術的另外一些改進)

            設想在一個軍事應用程序里,有一個表示敵人目標的類:

            class enemytarget {
            public:
            ? enemytarget() { ++numtargets; }
            ? enemytarget(const enemytarget&) { ++numtargets; }
            ? ~enemytarget() { --numtargets; }

            ? static size_t numberoftargets()
            ? { return numtargets; }

            ? virtual bool destroy();?????? // 摧毀enemytarget對象后
            ??????????????????????????????? // 返回成功

            private:
            ? static size_t numtargets;???? // 對象計數器
            };

            // 類的靜態(tài)成員要在類外定義;
            // 缺省初始化為0
            size_t enemytarget::numtargets;

            這個類不會為你贏得一份政府防御合同,它離國防部的要求相差太遠了,但它足以滿足我們這兒說明問題的需要。

            敵人的坦克是一種特殊的敵人目標,所以會很自然地想到將它抽象為一個以公有繼承方式從enemytarget派生出來的類(參見條款35及m33)。因為不但要關心敵人目標的總數,也要關心敵人坦克的總數,所以和基類一樣,在派生類里也采用了上面提到的同樣的技巧:

            class enemytank: public enemytarget {
            public:
            ? enemytank() { ++numtanks; }

            ? enemytank(const enemytank& rhs)
            ? : enemytarget(rhs)
            ? { ++numtanks; }

            ? ~enemytank() { --numtanks; }

            ? static size_t numberoftanks()
            ? { return numtanks; }

            ? virtual bool destroy();

            private:
            ? static size_t numtanks;???????? // 坦克對象計數器
            };

            (寫完以上兩個類的代碼后,你就更能夠理解條款m26對這個問題的通用解決方案了。)

            最后,假設程序的其他某處用new動態(tài)創(chuàng)建了一個enemytank對象,然后用delete刪除掉:

            enemytarget *targetptr = new enemytank;

            ...

            delete targetptr;

            到此為止所做的一切好象都很正常:兩個類在析構函數里都對構造函數所做的操作進行了清除;應用程序也顯然沒有錯誤,用new生成的對象在最后也用delete刪除了。然而這里卻有很大的問題。程序的行為是不可預測的——無法知道將會發(fā)生什么。

            c++語言標準關于這個問題的闡述非常清楚:當通過基類的指針去刪除派生類的對象,而基類又沒有虛析構函數時,結果將是不可確定的。這意味著編譯器生成的代碼將會做任何它喜歡的事:重新格式化你的硬盤,給你的老板發(fā)電子郵件,把你的程序源代碼傳真給你的對手,無論什么事都可能發(fā)生。(實際運行時經常發(fā)生的是,派生類的析構函數永遠不會被調用。在本例中,這意味著當targetptr 刪除時,enemytank的數量值不會改變,那么,敵人坦克的數量就是錯的,這對需要高度依賴精確信息的部隊來說,會造成什么后果?)

            為了避免這個問題,只需要使enemytarget的析構函數為virtual。聲明析構函數為虛就會帶來你所希望的運行良好的行為:對象內存釋放時,enemytank和enemytarget的析構函數都會被調用。

            和絕大部分基類一樣,現在enemytarget類包含一個虛函數。虛函數的目的是讓派生類去定制自己的行為(見條款36),所以幾乎所有的基類都包含虛函數。

            如果某個類不包含虛函數,那一般是表示它將不作為一個基類來使用。當一個類不準備作為基類使用時,使析構函數為虛一般是個壞主意。請看下面的例子,這個例子基于arm(“the annotated c++ reference manual”)一書的一個專題討論。

            // 一個表示2d點的類
            class point {
            public:
            ? point(short int xcoord, short int ycoord);
            ? ~point();

            private:
            ? short int x, y;
            };

            如果一個short int占16位,一個point對象將剛好適合放進一個32位的寄存器中。另外,一個point對象可以作為一個32位的數據傳給用c或fortran等其他語言寫的函數中。但如果point的析構函數為虛,情況就會改變。

            實現虛函數需要對象附帶一些額外信息,以使對象在運行時可以確定該調用哪個虛函數。對大多數編譯器來說,這個額外信息的具體形式是一個稱為vptr(虛函數表指針)的指針。vptr指向的是一個稱為vtbl(虛函數表)的函數指針數組。每個有虛函數的類都附帶有一個vtbl。當對一個對象的某個虛函數進行請求調用時,實際被調用的函數是根據指向vtbl的vptr在vtbl里找到相應的函數指針來確定的。

            虛函數實現的細節(jié)不重要(當然,如果你感興趣,可以閱讀條款m24),重要的是,如果point類包含一個虛函數,它的對象的體積將不知不覺地翻番,從2個16位的short變成了2個16位的short加上一個32位的vptr!point對象再也不能放到一個32位寄存器中去了。而且,c++中的point對象看起來再也不具有和其他語言如c中聲明的那樣相同的結構了,因為這些語言里沒有vptr。所以,用其他語言寫的函數來傳遞point也不再可能了,除非專門去為它們設計vptr,而這本身是實現的細節(jié),會導致代碼無法移植。

            所以基本的一條是,無故的聲明虛析構函數和永遠不去聲明一樣是錯誤的。實際上,很多人這樣總結:當且僅當類里包含至少一個虛函數的時候才去聲明虛析構函數。

            這是一個很好的準則,大多數情況都適用。但不幸的是,當類里沒有虛函數的時候,也會帶來非虛析構函數問題。 例如,條款13里有個實現用戶自定義數組下標上下限的類模板。假設你(不顧條款m33的建議)決定寫一個派生類模板來表示某種可以命名的數組(即每個數組有一個名字)。

            template<class t>??????????????? // 基類模板
            class array {??????????????????? // (來自條款13)
            public:
            ? array(int lowbound, int highbound);
            ? ~array();

            private:
            ? vector<t> data;
            ? size_t size;
            ? int lbound, hbound;
            };

            template<class t>
            class namedarray: public array<t> {
            public:
            ? namedarray(int lowbound, int highbound, const string& name);
            ? ...

            private:
            ? string arrayname;
            };

            如果在應用程序的某個地方你將指向namedarray類型的指針轉換成了array類型的指針,然后用delete來刪除array指針,那你就會立即掉進“不確定行為”的陷阱中。

            namedarray<int> *pna =
            ? new namedarray<int>(10, 20, "impending doom");

            array<int> *pa;

            ...


            pa = pna;??????????????? // namedarray<int>* -> array<int>*

            ...

            delete pa;?????????????? // 不確定! 實際中,pa->arrayname
            ???????????????????????? // 會造成泄漏,因為*pa的namedarray
            ???????????????????????? // 永遠不會被刪除


            現實中,這種情形出現得比你想象的要頻繁。讓一個現有的類做些什么事,然后從它派生一個類做和它相同的事,再加上一些特殊的功能,這在現實中不是不常見。namedarray沒有重定義array的任何行為——它繼承了array的所有功能而沒有進行任何修改——它只是增加了一些額外的功能。但非虛析構函數的問題依然存在(還有其他問題,參見m33)

            最后,值得指出的是,在某些類里聲明純虛析構函數很方便。純虛函數將產生抽象類——不能實例化的類(即不能創(chuàng)建此類型的對象)。有些時候,你想使一個類成為抽象類,但剛好又沒有任何純虛函數。怎么辦?因為抽象類是準備被用做基類的,基類必須要有一個虛析構函數,純虛函數會產生抽象類,所以方法很簡單:在想要成為抽象類的類里聲明一個純虛析構函數。

            這里是一個例子:

            class awov {??????????????? // awov = "abstract w/o
            ??????????????????????????? // virtuals"
            public:
            ? virtual ~awov() = 0;????? // 聲明一個純虛析構函數
            ???????????????????????????
            };

            這個類有一個純虛函數,所以它是抽象的,而且它有一個虛析構函數,所以不會產生析構函數問題。但這里還有一件事:必須提供純虛析構函數的定義:

            awov::~awov() {}?????????? // 純虛析構函數的定義

            這個定義是必需的,因為虛析構函數工作的方式是:最底層的派生類的析構函數最先被調用,然后各個基類的析構函數被調用。這就是說,即使是抽象類,編譯器也要產生對~awov的調用,所以要保證為它提供函數體。如果不這么做,鏈接器就會檢測出來,最后還是得回去把它添上。

            可以在函數里做任何事,但正如上面的例子一樣,什么事都不做也不是不常見。如果是這種情況,那很自然地會想到將析構函數聲明為內聯函數,從而避免對一個空函數的調用所產生的開銷。這是一個很好的方法,但有一件事要清楚。

            因為析構函數為虛,它的地址必須進入到類的vtbl(見條款m24)。但內聯函數不是作為獨立的函數存在的(這就是“內聯”的意思),所以必須用特殊的方法得到它們的地址。條款33對此做了全面的介紹,其基本點是:如果聲明虛析構函數為inline,將會避免調用它們時產生的開銷,但編譯器還是必然會在什么地方產生一個此函數的拷貝。

            posted @ 2006-09-24 21:07 Jerry Cat 閱讀(279) | 評論 (0)編輯 收藏
            [C++基礎]重載、覆蓋、多態(tài)與函數隱藏

            ?

            小結:

            ?

            ??????? 重載 overload 是根據函數的參數列表來選擇要調用的函數版本,而多態(tài)是根據運行時對象的實際類型來選擇要調用的虛 virtual 函數版本,多態(tài)的實現是通過派生類對基類的虛 virtual 函數進行覆蓋 override 來實現的,若派生類沒有對基類的虛 virtual 函數進行覆蓋 override 的話,則派生類會自動繼承基類的虛 virtual 函數版本,此時無論基類指針指向的對象是基類型還是派生類型,都會調用基類版本的虛 virtual 函數;如果派生類對基類的虛 virtual 函數進行覆蓋 override 的話,則會在運行時根據對象的實際類型來選擇要調用的虛 virtual 函數版本,例如基類指針指向的對象類型為派生類型,則會調用派生類的虛 virtual 函數版本,從而實現多態(tài)。

            ?

            ??????? 使用多態(tài)的本意是要我們在基類中聲明函數為 virtual ,并且是要在派生類中覆蓋 override 基類的虛 virtual 函數版本,注意,此時的函數原型與基類保持一致,即同名同參數類型;如果你在派生類中新添加函數版本,你不能通過基類指針動態(tài)調用派生類的新的函數版本,這個新的函數版本只作為派生類的一個重載版本。還是同一句話,重載只有在當前類中有效,不管你是在基類重載的,還是在派生類中重載的,兩者互不牽連。如果明白這一點的話,在例 6 、例 9 中,我們也會對其的輸出結果順利地理解。

            ?

            ??????? 重載是靜態(tài)聯編的,多態(tài)是動態(tài)聯編的。進一步解釋,重載與指針實際指向的對象類型無關,多態(tài)與指針實際指向的對象類型相關。若基類的指針調用派生類的重載版本, C++ 編繹認為是非法的, C++ 編繹器只認為基類指針只能調用基類的重載版本,重載只在當前類的名字空間作用域內有效,繼承會失去重載的特性,當然,若此時的基類指針調用的是一個虛 virtual 函數,那么它還會進行動態(tài)選擇基類的虛 virtual 函數版本還是派生類的虛 virtual 函數版本來進行具體的操作,這是通過基類指針實際指向的對象類型來做決定的,所以說重載與指針實際指向的對象類型無關,多態(tài)與指針實際指向的對象類型相關。 ?

            ?

            ??? 最后闡明一點,虛 virtual 函數同樣可以進行重載,但是重載只能是在當前自己名字空間作用域內有效 ( 請再次參考例 6)

            本文來源:http://blog.csdn.net/callzjy/archive/2004/01/04/20044.aspx


            續(xù):

            重載與覆蓋
            成員函數被重載的特征:
            (1)相同的范圍(在同一個類中);
            (2)函數名字相同;
            (3)參數不同;
            (4)virtual關鍵字可有可無。
            覆蓋是指派生類函數覆蓋基類函數,特征是:
            (1)不同的范圍(分別位于派生類與基類);
            (2)函數名字相同;
            (3)參數相同;
            (4)基類函數必須有virtual關鍵字。

            “隱藏”是指派生類的函數屏蔽了與其同名的基類函數,
            規(guī)則如下:
            (1)如果派生類的函數與基類的函數同名,但是參數不同。
            ???? 此時,不論有無virtual關鍵字,基類的函數將被隱藏(注意別與重載混淆)。
            (2)如果派生類的函數與基類的函數同名,并且參數也相同,但是基類函數沒有virtual關鍵字。
            ???? 此時,基類的函數被隱藏(注意別與覆蓋混淆)。

            如下示例程序中:
            (1)函數Derived::f(float)覆蓋了Base::f(float)。
            (2)函數Derived::g(int)隱藏了Base::g(float),而不是重載。
            (3)函數Derived::h(float)隱藏了Base::h(float),而不是覆蓋。

            #include<iostream.h>

            class Base{
            public:
            virtual void f(floatx){cout<<"Base::f(float)"<<x<<endl;}
            ??????? void g(floatx){cout<<"Base::g(float)"<<x<<endl;
            ??????? void h(floatx){cout<<"Base::h(float)"<<x<<endl;}
            };

            class Derived:publicBase{
            public:
            virtual void f(floatx){cout<<"Derived::f(float)"<<x<<endl;}
            ??????? void g(intx){cout<<"Derived::g(int)"<<x<<endl;}
            ??????? void h(floatx){cout<<"Derived::h(float)"<<x<<endl;}
            };
            void main(void){
            ? Derived d;
            ? Base *pb=&d;
            ? Derived *pd=&d;
            ??
            ? //Good:behavior depends solely on type of the object
            ? pb->f(3.14f);???? //Derived::f(float)3.14
            ? pd->f(3.14f);???? //Derived::f(float)3.14

            ? //Bad:behavior depends on type of the pointer
            ? pb->g(3.14f);???? //Base::g(float)3.14
            ? pd->g(3.14f);???? //Derived::g(int)3(surprise!)

            ? //Bad:behavior depends on type of the pointer
            ? pb->h(3.14f);???? //Base::h(float)3.14(surprise!)
            ? pd->h(3.14f);???? //Derived::h(float)3.14

            posted @ 2006-09-24 21:06 Jerry Cat 閱讀(215) | 評論 (0)編輯 收藏
                 摘要: 1.?變量作用域 在vc7.1中, 如果一個變量定義在for語句的條件從句中,那么這個變量可以在for之后使用。但Vc8禁止這樣,會報告一個C2065錯誤. for ?( int ?i? = ? ...  閱讀全文
            posted @ 2006-09-24 21:04 Jerry Cat 閱讀(588) | 評論 (1)編輯 收藏
            1. 編譯時出現:WINVER not defined. Defaulting to 0×0501 (Windows XP and Windows .NET Server)
            這個問題是因為沒有指定工程要使用的平臺SDK的版本。
            Minimum system required
            Macros to define
            Windows Server 2003 family
            _WIN32_WINNT>=0×0502
            Windows XP
            _WIN32_WINNT>=0×0501
            Windows 2000
            _WIN32_WINNT>=0×0500
            Windows NT 4.0
            _WIN32_WINNT>=0×0400
            Windows Me
            _WIN32_WINDOWS=0×0490
            Windows 98
            _WIN32_WINDOWS>=0×0410
            Internet Explorer 6.0
            _WIN32_IE>=0×0600
            Internet Explorer 5.01, 5.5
            _WIN32_IE>=0×0501
            Internet Explorer 5.0, 5.0a, 5.0b
            _WIN32_IE>=0×0500
            Internet Explorer 4.01
            _WIN32_IE>=0×0401
            Internet Explorer 4.0
            _WIN32_IE>=0×0400
            Internet Explorer 3.0, 3.01, 3.02
            _WIN32_IE>=0×0300
            解決辦法:
            屬性,C/C++,命令行,附加項中添加 /D_WIN32_WINNT=0×0501 (因為我是在xp下工作的所以是0×0501)
            ?
            2. Link時出現:LINK?: warning LNK4075: 忽略”/EDITANDCONTINUE”(由于”/INCREMENTAL:NO”規(guī)范)
            這個問題是因為在vc6中,工程使用的增量編譯。
            解決辦法:
            屬性,鏈接器,常規(guī),啟動增量鏈接 選擇 是(INCREMENTAL)
            ?
            3. 編譯時出現: warning C4129: “U” : 不可識別的字符轉義序列
            error C3847: 通用字符中的錯誤符號;必須使用十六進制數字
            原因:為開發(fā)全球通用的應用程序,.NET Framework 使用 Unicode UTF-16(Unicode 轉換格式,16 位編碼形式)來表示字符。在某些情況下,.NET Framework 在內部使用 UTF-8。引入通用字符名稱的格式是 \u####\U########
            解決辦法:
            //#include MAKEPATH(MAIN_IMAGE_PATH, FunUtil\\Unit_star.txt)
            #include “..\\ImageData\\ML128160\\FunUtil\\Unit_star.txt”
            ?
            4. 鏈接時出現:LIBCMTD.lib(crt0dat.obj) : error LNK2005: _exit 已經在 MSVCRTD.lib(MSVCR71D.dll) 中定義 等類似錯誤
            原因:
            Run-Time Library
            ?Run-Time Library是編譯器提供的標準庫,提供一些基本的庫函數和系統(tǒng)調用。
            我們一般使用的Run-Time Library是C Run-Time Libraries。當然也有Standard C++ libraries。
            C Run-Time Libraries實現ANSI C的標準庫。VC安裝目錄的CRT目錄有C Run-Time庫的大部分源代碼。 C Run-Time Libraries有靜態(tài)庫版本,也有動態(tài)鏈接庫版本;有單線程版本,也有多線程版本;還有調試和非調試版本。
            ?動態(tài)鏈接庫版本:
            /MD Multithreaded DLL 使用導入庫MSVCRT.LIB
            /MDd Debug Multithreaded DLL 使用導入庫MSVCRTD.LIB
            ?靜態(tài)庫版本:
            /ML Single-Threaded 使用靜態(tài)庫LIBC.LIB
            /MLd Debug Single-Threaded 使用靜態(tài)庫LIBCD.LIB
            /MT Multithreaded 使用靜態(tài)庫LIBCMT.LIB
            /MTd Debug Multithreaded 使用靜態(tài)庫LIBCMTD.LIB
            若要使用此運行時庫
            請忽略這些庫
            單線程 (libc.lib)
            libcmt.lib、msvcrt.lib、libcd.lib、libcmtd.lib、msvcrtd.lib
            多線程 (libcmt.lib)
            libc.lib、msvcrt.lib、libcd.lib、libcmtd.lib、msvcrtd.lib
            使用 DLL 的多線程 (msvcrt.lib)
            libc.lib、libcmt.lib、libcd.lib、libcmtd.lib、msvcrtd.lib
            調試單線程 (libcd.lib)
            libc.lib、libcmt.lib、msvcrt.lib、libcmtd.lib、msvcrtd.lib
            調試多線程 (libcmtd.lib)
            libc.lib、libcmt.lib、msvcrt.lib、libcd.lib、msvcrtd.lib
            使用 DLL 的調試多線程 (msvcrtd.lib)
            libc.lib、libcmt.lib、msvcrt.lib、libcd.lib、libcmtd.lib
            解決方法:
            屬性,鏈接器,輸入,忽略指定庫 libc.lib、libcmt.lib、msvcrt.lib、libcd.lib、libcmtd.lib (這是我需要忽略的,你可以根據你工程的實際情況選擇。)

            update(20060205):
            5. 鏈接是出現不能打開 mfc4xx.lib的錯誤時,這是因為VC7對MFC的dll進行了升級。

            解決辦法:
            屬性,鏈接器,輸入,附加依賴項 中 添加mfc71d.lib。
            posted @ 2006-09-24 21:01 Jerry Cat 閱讀(838) | 評論 (0)編輯 收藏
            windows核心編程--內核對象

            簡單地說:


            內核對象是系統(tǒng)的一種資源。系統(tǒng)對象一旦產生,任何應用程序都可以開啟并且使用該對象。系統(tǒng)給內核對象一個計數值作為管理只用,內核對象包括:
             event,mutex,semaphore,file,file-mapping,preocess,thread.

            這些內核對象每次產生都會返回一個handle,作為標示,每使用一次,對應的計數值加1,調用CloseHandle可以結束內核對象的使用。

            具體:


            1.? 內核對象:
            ??? 1).符號對象
            ??? 2).事件對象
            ??? 3).文件對象
            ?? ?4).文件影象對象
            ??? 5).I/O完成對象
            ??? 6).作業(yè)對象
            ??? 7).信箱對象
            ??? 8).互斥對象
            ??? 9).管道對象
            ??? 10).進程對象
            ??? 11).信標對象
            ??? 12).線程對象
            ??? 13).待計時器對象
            ???? 等

            2.內核對象只能由內核所擁有,而不是由進程擁有.(就是說進程沒有了,內核還可以被其他進程使用)

            3.內核對象的數據結構有計數器,進程調用時,計數器增1,調用結束,計數器減1,內核對象計數器為零時,銷毀此內核對象.(系統(tǒng)來管理內核對象)

            4.內核安全性,進程使用什么權限調用內核對象,由SECURITY_ATTRIBUTES結構的數據結構來指定.幾乎所有的調用內核對象的函數都含有SECURITY_ATTRIBUTES結構的指針參數.(可以由這個參數來判斷是不是內核對象哦)
            typedef struct _SECURITY_ATTRIBUTES {
            ? DWORD? nLength; ??//結構體長度
            ? LPVOID lpSecurityDescriptor; ?//安全性設置
            ? BOOL?? bInheritHandle; ?//可繼承性
            } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES;

            5.進程的內核對象的句柄表,進程調用內核對象時,就會創(chuàng)建內核對象的句柄表,就是內核對象在進程中的索引,索引值就是調用內核對象函數返回的句柄.關閉所有的內核對象,使用CloseHandle();

            6.跨越進程邊界共享內核對象
            MICROSOFT把句柄設計成進程句柄,不設計成系統(tǒng)句柄是為了實現句柄的健壯性和安全性。
            1)
            內核對象句柄的繼承性。(為了實現內核的多個進程的共享)
            ??? 作用:為了子進程實現對父進程創(chuàng)建的內核對象的訪問。?
            ??? 步驟:首先,父進程創(chuàng)建內核對象時,初始化SECURITY_ATTRIBUTES結構的對象,讓SECURITY_ATTRIBUTES結構體的成員變量bInheritHandle設置為TRUE。
            ??? ?? 然后,子進程創(chuàng)建后,生成自己的句柄表,句柄表遍歷父進程的句柄表,找到有繼承性的句柄,并復制一份到子進程的句柄表中,子進程的內核對象和父進程的內核對象使用相同的內存塊指針,內核對象計數器在子進程中創(chuàng)建內核對象后增一,父進程調用CloseHandle()來關閉內核對象,確不影響子進程使用該內核對象。
            2)改變句柄的標志
            BOOL SetHandleInformation(
            ? HANDLE hObject,? // handle to object
            ? DWORD dwMask,??? // flags to change
            ? DWORD dwFlags??? // new values for flags
            );

            打開內核的可繼承性標志
            SetHandleInformation(hobj,HANDLE_FLAG_INHERIT,HANDLE_FLAG_INHERIT);
            關閉內核的可繼承性標志
            SetHandleInformation(hobj,HANDLE_FLAG_INHERIT,0);
            若想讓內核對象不被關閉,設置HANDLE_FLAG_PROTECT_FROM_CLOSE。

            獲得句柄標志的函數
            BOOL GetHandleInformation(
            ? HANDLE hObject,??? // handle to object
            ? LPDWORD lpdwFlags? // handle properties
            );

            3)命名對象
            作用:
            讓進程中的內核對象可以共享,讓別的進程可以通過命名空間,跨進程來訪問這個進程的內核對象。
            創(chuàng)建對象和訪問對象使用函數
            創(chuàng)建對象Create*:如果命名的內核對象已經存在并具備安全訪問權限,則參數被忽略,進程的句柄表復制一份內核對象的指針和標志到進程的句柄表,如果不存在,則馬上創(chuàng)建內核對象。
            例子:
            HANDLE CreateMutex(
            ? LPSECURITY_ATTRIBUTES lpMutexAttributes,? // SD
            ? BOOL bInitialOwner,?????????????????????? // initial owner
            ? LPCTSTR lpName??????????????????????????? // 對象名字
            );

            打開對象Open*:如果命名的內核對象已經存在并具備安全訪問權限,進程的句柄表復制一份內核對象的指針和標志到進程的句柄表,如果不存在,則返回NULL,使用GetLassError(),得到返回值2。

            4)終端服務的名字空間
            每個客戶程序會話都有自己的服務名字空間,一個會話無法訪問另一個會話的對象,盡管他們具備相同的對象名字。
            服務程序的名字空間對象總放在全局名字空間中。

            5)復制對象句柄
            DuplicateHandle函數來對另一個進程對象的句柄進行復制到調用此函數的進程句柄表中,實現進程間共享內核對象。
            BOOL DuplicateHandle(
            ? HANDLE hSourceProcessHandle,? // handle to source process
            ? HANDLE hSourceHandle,???????? // handle to duplicate
            ? HANDLE hTargetProcessHandle,? // handle to target process
            ? LPHANDLE lpTargetHandle,????? // duplicate handle
            ? DWORD dwDesiredAccess,??????? // requested access
            ? BOOL bInheritHandle,????????? // handle inheritance option
            ? DWORD dwOptions?????????????? // optional actions
            );

            posted @ 2006-09-24 05:11 Jerry Cat 閱讀(736) | 評論 (0)編輯 收藏

            [轉]DLL的進入點函數

            一個D L L可以擁有單個進入點函數。系統(tǒng)在不同的時間調用這個進入點函數,這個問題將在下面加以介紹。這些調用可以用來提供一些信息,通常用于供D L L進行每個進程或線程的初始化和清除操作。如果你的D L L不需要這些通知信息,就不必在D L L源代碼中實現這個函數。例如,如果你創(chuàng)建一個只包含資源的D L L,就不必實現該函數。如果確實需要在D L L中接受通知信息,可以實現類似下面的進入點函數:

            						BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD fdwReason, PVOID fImpLoad)
            {
               switch(fdwReason) 
               {
                  case DLL_PROCESS_ATTACH:
                     //The DLL is being mapped into the process's address space.
                     break;
            
                  case DLL_THREAD_ATTACH:
                     //A thread is being created.
                     break;
            
                  case DLL_THREAD_DETACH:
                     //A thread is exiting cleanly.
                     break;
            
                  case DLL_PROCESS_DETACH:
                     //The DLL is being unmapped from the process's address space.
                     break;
               }
               return(TRUE);  // Used only for DLL_PROCESS_ATTACH
            }
            				

            注意函數名D l l M a i n是區(qū)分大小寫的。

            參數h i n s t D l l包含了D L L的實例句柄。與( w ) Wi n M a i n函數的h i n s t E x e參數一樣,這個值用于標識D L L的文件映像被映射到進程的地址空間中的虛擬內存地址。通常應將這個參數保存在一個全局變量中,這樣就可以在調用加載資源的函數(如D i a l o g B o x和L o a d S t r i n g)時使用它。最后一個參數是f I m p L o a d,如果D L L是隱含加載的,那么該參數將是個非0值,如果D L L是顯式加載的,那么它的值是0。

            參數f d w R e a s o n用于指明系統(tǒng)為什么調用該函數。該參數可以使用4個值中的一個。這4個值是: D L L _ P R O C E S S _ AT TA C H、D L L _ P R O C E S S _ D E TA C H、D L L _ T H R E A D _ AT TA C H或D L L _ T H R E A D _ D E TA C H。這些值將在下面介紹。

            注意必須記住,D L L使用D l l M a i n函數來對它們進行初始化。當你的D l l M a i n函數執(zhí)行時,同一個地址空間中的其他D L L可能尚未執(zhí)行它們的D l l M a i n函數。這意味著它們尚未初始化,因此你應該避免調用從其他D L L中輸入的函數。此外,你應該避免從D l l M a i n內部調用L o a d L i b r a r y ( E x )和F r e e L i b r a r y函數,因為這些函數會形式一個依賴性循環(huán)。

            DLL_PROCESS_ATTACH通知
            當D L L被初次映射到進程的地址空間中時,系統(tǒng)將調用該D L L的D l l M a i n函數,給它傳遞參數f d w R e a s o n的值D L L _ P R O C E S S _ AT TA C H。只有當D L L的文件映像初次被映射時,才會出現這種情況。如果線程在后來為已經映射到進程的地址空間中的D L L調用L o a d L i b r a r y ( E x )函數,那么操作系統(tǒng)只是遞增D L L的使用計數,它并不再次用D L L _ P R O C E S S _ AT TA C H的值來調用D L L的D l l M a i n函數。
            當處理D L L _ P R O C E S S _ AT TA C H時,D L L應該執(zhí)行D L L中的函數要求的任何與進程相關的初始化。例如, D L L可能包含需要使用它們自己的堆棧(在進程的地址空間中創(chuàng)建)的函數。

            DLL_PROCESS_DETACH通知
            D L L從進程的地址空間中被卸載時,系統(tǒng)將調用D L L的D l l M a i n函數,給它傳遞f d w R e a s o n的值D L L _ P R O C E S S _ D E TA C H。當D L L處理這個值時,它應該執(zhí)行任何與進程相關的清除操作。例如, D L L可以調用H e a p D e s t r o y函數來撤消它在D L L _ P R O C E S S _ D E TA C H通知期間創(chuàng)建的堆棧。

            DLL_THREAD_ATTACH通知
            當在一個進程中創(chuàng)建線程時,系統(tǒng)要查看當前映射到該進程的地址空間中的所有D L L文件映像,并調用每個文件映像的帶有D L L _ T H R E A D _ AT TA C H值的D l l M a i n函數。這可以告訴所有的D L L執(zhí)行每個線程的初始化操作。新創(chuàng)建的線程負責執(zhí)行D L L的所有D l l M a i n函數中的代碼。只有當所有的D L L都有機會處理該通知時,系統(tǒng)才允許新線程開始執(zhí)行它的線程函數。

            DLL_THREAD_DETACH通知
            讓線程終止運行的首選方法是使它的線程函數返回。這使得系統(tǒng)可以調用E x i t T h r e a d來撤消該線程。E x i t T h r e a d函數告訴系統(tǒng),該線程想要終止運行,但是系統(tǒng)并不立即將它撤消。相反, 它要取出這個即將被撤消的線程, 并讓它調用已經映射的D L L 的所有帶有D L L _ T H R E A D _ D E TACH 值的D l l M a i n函數。這個通知告訴所有的D L L執(zhí)行每個線程的清除操作。


            DllMain與C/C++運行期庫
            當編寫一個D L L時,你需要得到C / C + +運行期庫的某些初始幫助。例如,如果你創(chuàng)建的D L L包含一個全局變量,而這個全局變量是個C + +類的實例。在你順利地在D l l M a i n函數中使用這個全局變量之前,該變量必須調用它的構造函數。這是由C / C + +運行期庫的D L L啟動代碼來完成的。當你的D L L文件映像被映射到進程的地址空間中時,系統(tǒng)實際上是調用_ D l l M a i n C RTS t a r t u p函數,而不是調用D l l M a i n函數。

            延遲加載DLL (但是怎么延遲那?^_^)

            Microsoft Visual C++ 6.0提供了一個出色的新特性,它能夠使D L L的操作變得更加容易。這個特性稱為延遲加載D L L。延遲加載的D L L是個隱含鏈接的D L L,它實際上要等到你的代碼試圖引用D L L中包含的一個符號時才進行加載。延遲加載的D L L在下列情況下是非常有用的:

            ? 如果你的應用程序使用若干個D L L,那么它的初始化時間就比較長,因為加載程序要將所有需要的D L L映射到進程的地址空間中。解決這個問題的方法之一是在進程運行的時候分開加載各個D L L。延遲加載的D L L能夠更容易地完成這樣的加載。

            ? 如果調用代碼中的一個新函數,然后試圖在老版本的系統(tǒng)上運行你的應用程序,而該系統(tǒng)中沒有該函數,那么加載程序就會報告一個錯誤,并且不允許該應用程序運行。你需要一種方法讓你的應用程序運行,然后,如果(在運行時)發(fā)現該應用程序在老的系統(tǒng)上運行,那么你將不調用遺漏的函數。

            函數轉發(fā)器

            函數轉發(fā)器是D L L的輸出節(jié)中的一個項目,用于將對一個函數的調用轉至另一個D L L中的另一個函數。
            ?DLL轉移
            M i c r o s o f t給Windows 2000增加了一個D L L轉移特性。這個特性能夠強制操作系統(tǒng)的加載程序首先從你的應用程序目錄中加載文件模塊。只有當加載程序無法在應用程序目錄中找到該文件時,它才搜索其他目錄。為了強制加載程序總是首先查找應用程序的目錄,要做的工作就是在應用程序的目錄中放入一個文件。該文件的內容可以忽略,但是該文件必須稱為A p p N a m e . l o c a l。例如,如果有一個可執(zhí)行文件的名字是S u p e r A p p . e x e ,那么轉移文件必須稱為S u p e r A p p . e x e . l o c a l。在系統(tǒng)內部, L o a d L i b r a r y ( E x )已經被修改,以便查看是否存在該文件。如果應用程序的目錄中存在該文件,該目錄中的模塊就已經被加載。如果應用程序的目錄中不存在這個模塊,L o a d L i b r a r y ( E x )將正常運行。對于已經注冊的C O M對象來說,這個特性是非常有用的。它使應用程序能夠將它的C O M對象D L L放入自己的目錄,這樣,注冊了相同C O M對象的其他應用程序就無法干擾你的操作。

            posted @ 2006-09-23 21:02 Jerry Cat 閱讀(592) | 評論 (0)編輯 收藏

            如何在C語言中巧用正則表達式

            如果用戶熟悉Linux下的sed、awk、grep或vi,那么對正則表達式這一概念肯定不會陌生。由于它可以極大地簡化處理字符串時的復雜度,因此現在已經在許多Linux實用工具中得到了應用。千萬不要以為正則表達式只是Perl、Python、Bash等腳本語言的專利,作為C語言程序員,用戶同樣可以在自己的程序中運用正則表達式。?

            標準的C和C++都不支持正則表達式,但有一些函數庫可以輔助C/C++程序員完成這一功能,其中最著名的當數Philip?Hazel的Perl-Compatible?Regular?Expression庫,許多Linux發(fā)行版本都帶有這個函數庫。?

            編譯正則表達式?

            為了提高效率,在將一個字符串與正則表達式進行比較之前,首先要用regcomp()函數對它進行編譯,將其轉化為regex_t結構:?

            int ?regcomp(regex_t? * preg,? const ? char ? * regex,? int ?cflags);?

            參數regex是一個字符串,它代表將要被編譯的正則表達式;參數preg指向一個聲明為regex_t的數據結構,用來保存編譯結果;參數cflags決定了正則表達式該如何被處理的細節(jié)。?

            如果函數regcomp()執(zhí)行成功,并且編譯結果被正確填充到preg中后,函數將返回0,任何其它的返回結果都代表有某種錯誤產生。?

            匹配正則表達式?

            一旦用regcomp()函數成功地編譯了正則表達式,接下來就可以調用regexec()函數完成模式匹配:?

            int ?regexec( const ??regex_t?? * preg,?? const ?? char ? * string ,?size_t?nmatch,regmatch_t?pmatch[],? int ?eflags);?

            typedef?
            struct ?
            {?
            ??regoff_t?rm_so;?
            ??regoff_t?rm_eo;?
            }
            ?regmatch_t;?
            參數preg指向編譯后的正則表達式,參數string是將要進行匹配的字符串,而參數nmatch和pmatch則用于把匹配結果返回給調用程序,最后一個參數eflags決定了匹配的細節(jié)。?

            在調用函數regexec()進行模式匹配的過程中,可能在字符串string中會有多處與給定的正則表達式相匹配,參數pmatch就是用來保存這些匹配位置的,而參數nmatch則告訴函數regexec()最多可以把多少個匹配結果填充到pmatch數組中。當regexec()函數成功返回時,從string+pmatch[0].rm_so到string+pmatch[0].rm_eo是第一個匹配的字符串,而從string+pmatch[1].rm_so到string+pmatch[1].rm_eo,則是第二個匹配的字符串,依此類推。?

            釋放正則表達式?

            無論什么時候,當不再需要已經編譯過的正則表達式時,都應該調用函數regfree()將其釋放,以免產生內存泄漏。?
            void ?regfree(regex_t? * preg);?

            函數regfree()不會返回任何結果,它僅接收一個指向regex_t數據類型的指針,這是之前調用regcomp()函數所得到的編譯結果。?

            如果在程序中針對同一個regex_t結構調用了多次regcomp()函數,POSIX標準并沒有規(guī)定是否每次都必須調用regfree()函數進行釋放,但建議每次調用regcomp()函數對正則表達式進行編譯后都調用一次regfree()函數,以盡早釋放占用的存儲空間。?

            報告錯誤信息?

            如果調用函數regcomp()或regexec()得到的是一個非0的返回值,則表明在對正則表達式的處理過程中出現了某種錯誤,此時可以通過調用函數regerror()得到詳細的錯誤信息。?

            size_t?regerror( int ?errcode,? const ?regex_t? * preg,? char ? * errbuf,?size_t?errbuf_size);?

            參數errcode是來自函數regcomp()或regexec()的錯誤代碼,而參數preg則是由函數regcomp()得到的編譯結果,其目的是把格式化消息所必須的上下文提供給regerror()函數。在執(zhí)行函數regerror()時,將按照參數errbuf_size指明的最大字節(jié)數,在errbuf緩沖區(qū)中填入格式化后的錯誤信息,同時返回錯誤信息的長度。?

            應用正則表達式?

            最后給出一個具體的實例,介紹如何在C語言程序中處理正則表達式。?

            #include? < stdio.h > ;?
            #include?
            < sys / types.h >
            ;?
            #include?
            < regex.h >
            ;?

            /* ?取子串的函數? */
            ?
            static ? char * ?substr( const ? char *
            str,?unsigned?start,?unsigned?end)?
            {?
            ??unsigned?n?
            = ?end? -
            ?start;?
            ??
            static ? char ?stbuf[ 256
            ];?
            ??strncpy(stbuf,?str?
            +
            ?start,?n);?
            ??stbuf[n]?
            = ? 0
            ;?
            ??
            return
            ?stbuf;?
            }
            ?
            /* ?主程序? */
            ?
            int ?main( int ?argc,? char **
            ?argv)?
            {?
            ??
            char ? *
            ?pattern;?
            ??
            int ?x,?z,?lno? = ? 0 ,?cflags? = ? 0
            ;?
            ??
            char ?ebuf[ 128 ],?lbuf[ 256
            ];?
            ??regex_t?reg;?
            ??regmatch_t?pm[
            10
            ];?
            ??
            const ?size_t?nmatch? = ? 10
            ;?
            ??
            /* ?編譯正則表達式 */
            ?
            ??pattern?
            = ?argv[ 1
            ];?
            ??z?
            = ?regcomp( &
            reg,?pattern,?cflags);?
            ??
            if ?(z? != ? 0 )
            {?
            ????regerror(z,?
            & reg,?ebuf,? sizeof
            (ebuf));?
            ????fprintf(stderr,?
            " %s:?pattern?'%s'?\n "
            ,?ebuf,?pattern);?
            ????
            return ? 1
            ;?
            ??}
            ?
            ??
            /* ??逐行處理輸入的數據? */
            ?
            ??
            while (fgets(lbuf,? sizeof (lbuf),?stdin))?
            {?
            ????
            ++
            lno;?
            ????
            if ?((z? = ?strlen(lbuf))? > ;? 0 ? && ?lbuf[z - 1 ]? == ? ' \n '
            )?
            ??????lbuf[z?
            - ? 1 ]? = ? 0
            ;?
            ????
            /* ?對每一行應用正則表達式進行匹配? */
            ?
            ????z?
            = ?regexec( & reg,?lbuf,?nmatch,?pm,? 0
            );?
            ????
            if ?(z? == ?REG_NOMATCH)? continue
            ;?
            ????
            else ? if ?(z? != ? 0 )?
            {?
            ??????regerror(z,?
            & reg,?ebuf,? sizeof
            (ebuf));?
            ??????fprintf(stderr,?
            " %s:?regcom('%s')\n "
            ,?ebuf,?lbuf);?
            ??????
            return ? 2
            ;?
            ????}
            ?
            ????
            /* ?輸出處理結果? */
            ?
            ????
            for ?(x? = ? 0 ;?x? < ?nmatch? && ?pm[x].rm_so? != ? - 1 ;? ++ ?x)?
            {?
            ??????
            if ?( ! x)?printf( " %04d:?%s\n "
            ,?lno,?lbuf);?
            ??????printf(
            " ??$%d='%s'\n "
            ,?x,?substr(lbuf,?pm[x].rm_so,?pm[x].rm_eo));?
            ????}
            ?
            ??}
            ?
            ??
            /* ?釋放正則表達式?? */
            ?
            ??regfree(
            &
            reg);?
            ??
            return ? 0
            ;?
            }
            ?

            上述程序負責從命令行獲取正則表達式,然后將其運用于從標準輸入得到的每行數據,并打印出匹配結果。執(zhí)行下面的命令可以編譯并執(zhí)行該程序:?

            # ??gcc?regexp.c?-o?regexp?
            #??./regexp??'regex[a-z]*'?<?regexp.c?

            0003 : ? # include?<regex.h>;?
            ??$ 0 = ' regex ' ?
            0027 :
            ???regex_t?reg;?
            ??$
            0 = ' regex '
            ?
            0054 : ?????z? = ?regexec( & reg , ?lbuf , ?nmatch , ?pm , ? 0
            );?
            ??$
            0 = ' regexec ' ?

            小結?

            對那些需要進行復雜數據處理的程序來說,正則表達式無疑是一個非常有用的工具。本文重點在于闡述如何在C語言中利用正則表達式來簡化字符串處理,以便在數據處理方面能夠獲得與Perl語言類似的靈活性。
            posted @ 2006-09-23 02:29 Jerry Cat 閱讀(309) | 評論 (0)編輯 收藏

            Yahoo拋棄3721是明智之舉

            看到消息Yahoo中國最終將拋棄3721,覺得這真是明智之舉。(參考:馬云:"不再發(fā)展3721 不會選流氓作對手") 

            3721就是流氓的代名詞, 雖然3721的流氓程度也許比起現在后輩的那些新流氓們要文雅一些,但基本就是五十步和百步的區(qū)別。 而3721可謂是這些鋪天蓋地的流氓軟件的最大的罪魁禍首 --因為做流氓軟件,不但沒有被懲罰, 反而發(fā)了財 --這才導致更多聰明的腦袋不把力氣往正路上去, 想盡辦法去做流氓, 搞得全中國幾千萬臺電腦中毒,搞得中國網民不敢隨便下載。

            有個和孔子有關的故事:

               魯國之法,魯人為人臣妾於諸侯,有能贖之者,取其金於府。子貢贖魯人於諸侯,來而讓,不取其金。孔子曰:"賜失之矣。自今以往,魯人不贖人矣。取其金,則無損於行;不取其金,則不復贖人矣。"《呂氏春秋 察微》
            ?? 子路拯人于溺,其人謝之以牛,子路受之。孔子喜曰:自今魯國多拯人于溺矣。


            翻譯如下:


            第一則翻譯成白話文:春秋時代,魯國有這樣一條法規(guī):凡是魯國人到其他國家去旅行,看到有魯國人淪為奴隸,可以自己墊錢把他先贖回,待回魯國后到官府去報銷。官府用國庫的錢支付贖金,并給予一定的獎勵。子貢到國外去,恰好碰到有一個魯國人在那里做奴隸,就掏錢贖出了他。回國以后這個學生既沒有張揚,也沒去報銷所墊付的贖金。那個被贖回的人把情況講給眾人,人們都稱贊這個學生仗義,人格高尚。一時間,街頭巷尾都把這件事當作美談。孔子知道了這件事,不僅沒有表揚這個學生,還對他進行了嚴厲的批評,責怪他犯了一個有違社會大道的錯誤,是只為小義而不顧大道。

            孔子指出:由于這個學生沒有到官府去報銷贖金而被人們稱贊為品格高尚,那么其他的人在國外看到魯國人淪為奴隸,就要對是否墊錢把他贖出來產生猶豫。因為墊錢把他贖出來再去官府報銷領獎,人們就會說自己不仗義,不高尚;不去官府報銷,自己的損失誰來補。于是,多一事不如少一事,只好假裝沒看見,從客觀上講,這個學生的行為妨礙了更多的在外國做奴隸的魯國人被贖買回來。

            ??? 第二則是講,有人掉進水里,親人在岸上喊,如果能救上他的,就送恩人一頭牛以作報酬,子路聽到馬上跳下水救起那個人,高興地接受了報酬。其他人覺得子路貪小利。孔子卻表揚了他。說你為大家做了一個榜樣,今后再有人遇到險情,大家都會奮不顧身,整個國家就會有許多人因此而得救。權利與義務是對等的,既然行善,沒有必要害怕獲得相應的權利。

            ??? 明代的袁子凡對這兩則故事有過精辟的論述:自俗眼觀之,子貢不受金為優(yōu),子路之受牛為劣;孔子則取由而黜賜焉。乃知人之為善,不論現行而論流弊;不論一時而論久遠;不論一身而論天下。現行雖善,而其流足以害人;則似善而實非也;現行雖不善,而其流足以濟人,則非善而實是也;然此就一節(jié)論之耳。他如非義之義,非禮之禮,非信之信,非慈之慈,皆當抉擇。

            這個故事和流氓關系并不太大, 但說的是一件事情的社會效應。如果流氓總是得逞, 這個社會上流氓的傾向就越大; 反之,如果流氓被打擊得越多, 流氓傾向就會越少。

            所以是流氓就應該堅決去打擊、去鄙視,不管他是有錢的流氓還是沒錢的流氓,或者是已經宣稱自己不是流氓的流氓,只有這樣才能讓流氓的習氣最終在中國互聯網界消失。Yahoo中國現在這種壯士斷腕地拋棄3721, 絕對具有長遠而深刻的意義。

            ”不和流氓為伍,不和流氓做對手“,這句話說得好! 支持一下!

            posted @ 2006-09-13 23:53 Jerry Cat 閱讀(399) | 評論 (0)編輯 收藏
            [轉]Windows 語音編程初步

            一、SAPI簡介

            軟件中的語音技術包括兩方面的內容,一個是語音識別(speech recognition) 和語音合成(speech synthesis)。這兩個技術都需要語音引擎的支持。微軟推出的應用編程接口API,雖然現在不是業(yè)界標準,但是應用比較廣泛。

            SAPI全稱 The Microsoft Speech API.相關的SR和SS引擎位于Speech SDK開發(fā)包中。這個語音引擎支持多種語言的識別和朗讀,包括英文、中文、日文等。

            SAPI包括以下組件對象(接口):

            (1)Voice Commands API。對應用程序進行控制,一般用于語音識別系統(tǒng)中。識別某個命令后,會調用相關接口是應用程序完成對應的功能。如果程序想實現語音控制,必須使用此組對象。
            (2)Voice Dictation API。聽寫輸入,即語音識別接口。
            (3)Voice Text API。完成從文字到語音的轉換,即語音合成。
            (4)Voice Telephone API。語音識別和語音合成綜合運用到電話系統(tǒng)之上,利用此接口可以建立一個電話應答系統(tǒng),甚至可以通過電話控制計算機。
            (5)Audio Objects API。封裝了計算機發(fā)音系統(tǒng)。

            SAPI是架構在COM基礎上的,微軟還提供了ActiveX控件,所以不僅可用于一般的windows程序,還可以用于網頁、VBA甚至EXCEL的圖表中。如果對COM感到陌生,還可以使用微軟的C++ WRAPPERS,它用C++類封裝了語音SDK COM對象。

            二、安裝SAPI SDK。

            首先從這個站點下載開發(fā)包: http://www.microsoft.com/speech/download/sdk51

            Microsoft Speech SDK 5.1添加了Automation支持。所以可以在VB,ECMAScript等支持Automation的語言中使用。

            版本說明:
            Version: 5.1
            發(fā)布日期: 8/8/2001
            語音: English
            下載尺寸: 2.0 MB - 288.8 MB

            這個SDK開發(fā)包還包括了可以隨便發(fā)布的英文和中文的語音合成引擎(TTS),和英文、中文、日文的語音識別引擎(SR)。

            系統(tǒng)要求98以上版本。編譯開發(fā)包中的例子程序需要vc6以上環(huán)境。

            ******下載說明******:
            (1)如果要下載例子程序,說明文檔,SAPI以及用于開發(fā)的美國英語語音引擎,需要下載SpeechSDK51.exe,大約68M。
            (2)如果想要使用簡體中文和日文的語音引擎,需要下載SpeechSDK51LangPack.exe。大約82M。
            (3)如果想要和自己的軟件一起發(fā)布語音引擎,需要下載SpeechSDK51MSM.exe,大約132M。
            ???? (在這個地址,我未能成功下載)。
            (4)如果要獲取XP下的 Mike 和 Mary 語音,下載Sp5TTIntXP.exe。大約3.5M。
            (5)如果要獲取開發(fā)包的文檔說明,請下載sapi.chm。大約2.3M。這個在sdk51里面已經包含。

            下載完畢后,首先安裝SpeechSDK51.exe,然后安裝中文語言補丁包SpeechSDK51LangPack,然后展開
            msttss22l,自動將所需dll安裝到系統(tǒng)目錄。

            三、配置vc環(huán)境

            在vc6.0的環(huán)境下編譯語音工程,首先要配置編譯環(huán)境。假設sdk安裝在d:\Microsoft Speech SDK 5.1\路徑下,打開工程設置對話框,在c/c++欄中選擇Preprocessor分類,然后在"附加包含路徑"中輸入
            d:\Microsoft Speech SDK 5.1\include
            告訴vc編譯程序所需的SAPI頭文件的位置。
            然后切換到LINK欄,在Input分類下的附加庫路徑中輸入:
            d:\Microsoft Speech SDK 5.1\lib\i386
            使vc在鏈接的時候能夠找到sapi.lib。

            四、語音合成的應用。即使用SAPI實現TTS(Text to Speech)。

            1、首先要初始化語音接口,一般有兩種方式:
            ?? ISpVoice* pVoice;
            ?? ::CoInitialize(NULL);
            ?? HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice,
            ?????????????? (void **)&pVoice);
            ?? 然后就可以使用這個指針調用SAPI函數了,例如
            ?? pVoice->SetVolume(50);//設置音量
            ?? pVoice->Speak(str.AllocSysString(),SPF_ASYNC,NULL);

            ?? 另外也可以使用如下方式:
            ??? CComPtr<ISpVoice>?? m_cpVoice;
            ??? HRESULT? hr = m_cpVoice.CoCreateInstance( CLSID_SpVoice );
            ?? 在下面的例子中都用這個m_cpVoice變量。

            ?? CLSID_SpVoice的定義位于SPAI.H中。

            2、獲取/設置輸出頻率。

            ?? SAPI朗讀文字的時候,可以采用多種頻率方式輸出聲音,比如:
            ?? 8kHz 8Bit Mono、8kHz 8Bit Stereo、44kHz 16Bit Mono、44kHz 16Bit Stereo等。在音調上有所差別。具體可以參考sapi.h。

            ?? 可以使用如下代碼獲取當前的配置:
            ?? CComPtr<ISpStreamFormat> cpStream;
            ?? HRESULT hrOutputStream = m_cpVoice->GetOutputStream(&cpStream);
            ?? if (hrOutputStream == S_OK)
            ?? {
            ?????? CSpStreamFormat Fmt;
            ?????? hr = Fmt.AssignFormat(cpStream);
            ?????? if (SUCCEEDED(hr))
            ?????? {
            ?????????? SPSTREAMFORMAT eFmt = Fmt.ComputeFormatEnum();
            ?????? }
            ?? }
            ??? SPSTREAMFORMAT 是一個ENUM類型,定義位于SPAI.H中。每一個值對應了不同的頻率設置。例如 SPSF_8kHz8BitStereo? = 5

            ??? 通過如下代碼設置當前朗讀頻率:
            ??? CComPtr<ISpAudio>?? m_cpOutAudio; //聲音輸出接口
            ??? SpCreateDefaultObjectFromCategoryId( SPCAT_AUDIOOUT, &m_cpOutAudio ); //創(chuàng)建接口

            ??? SPSTREAMFORMAT eFmt = 21; //SPSF_22kHz 8Bit Stereo

            ??? CSpStreamFormat Fmt;
            ??? Fmt.AssignFormat(eFmt);
            ??? if ( m_cpOutAudio )
            ??? {
            ?hr = m_cpOutAudio->SetFormat( Fmt.FormatId(), Fmt.WaveFormatExPtr() );
            ??? }
            ??? else? hr = E_FAIL;

            ??? if( SUCCEEDED( hr ) )
            ?? {
            ?????? m_cpVoice->SetOutput( m_cpOutAudio, FALSE );
            ?? }

            3、獲取/設置播放所用語音。

            ?? 引擎中所用的語音數據文件一般保存在SpeechEngines下的spd或者vce文件中。安裝sdk后,在注冊表中保存了可用的語音,比如英文的男/女,簡體中文的男音等。位置是:
            ?? HKEY_LOCAL_MACHINE\Software\Microsoft\Speech\Voices\Tokens
            如果安裝在中文操作系統(tǒng)下,則缺省所用的朗讀語音是簡體中文。SAPI的缺點是不能支持中英文混讀,在朗讀中文的時候,遇到英文,只能逐個字母讀出。所以需要程序自己進行語音切換。

            (1) 可以采用如下的函數把當前SDK支持的語音填充在一個組合框中:
            ??? // SAPI5 helper function in sphelper.h
            ??? HWND hWndCombo = GetDlgItem( hWnd, IDC_COMBO_VOICES ); //組合框句柄
            ??? HRESULT hr = SpInitTokenComboBox( hWndCombo , SPCAT_VOICES );
            ??? 這個函數是通過IEnumSpObjectTokens接口枚舉當前可用的語音接口,把接口的說明文字添加到組合框中,并且把接口的指針作為LPARAM
            ??? 保存在組合框中。
            ??? 一定要記住最后程序退出的時候,釋放組合框中保存的接口:
            ??? SpDestroyTokenComboBox( hWndCombo );
            ??? 這個函數的原理就是逐個取得combo里面每一項的LPARAM數據,轉換成IUnknown接口指針,然后調用Release函數。
            (2) 當組合框選擇變化的時候,可以用下面的函數獲取用戶選擇的語音:
            ??? ISpObjectToken* pToken = SpGetCurSelComboBoxToken( hWndCombo );

            (3) 用下面的函數獲取當前正在使用的語音:
            ??? CComPtr<ISpObjectToken> pOldToken;
            ??? HRESULT hr = m_cpVoice->GetVoice( &pOldToken );
            (4) 當用戶選擇的語音和當前正在使用的不一致的時候,用下面的函數修改:
            ??? if (pOldToken != pToken)
            ??? {???????
            ???????? // 首先結束當前的朗讀,這個不是必須的。
            ???????? HRESULT hr = m_cpVoice->Speak( NULL, SPF_PURGEBEFORESPEAK, 0);
            ???????? if (SUCCEEDED (hr) )
            ??????? {
            ??????????? hr = m_cpVoice->SetVoice( pToken );
            ???????? }
            ??? }
            (5) 也可以直接使用函數SpGetTokenFromId獲取指定voice的Token指針,例如:
            ????? WCHAR pszTokenId[] = L"HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Speech\\Voices\\Tokens\\MSSimplifiedChineseVoice";
            ??? SpGetTokenFromId(pszTokenID , &pChineseToken);

            4、開始/暫停/恢復/結束當前的朗讀
            ??
            ?? 要朗讀的文字必須位于寬字符串中,假設位于szWTextString中,則:
            ?? 開始朗讀的代碼:
            ?? hr = m_cpVoice->Speak( szWTextString, SPF_ASYNC | SPF_IS_NOT_XML, 0 );
            ?? 如果要解讀一個XML文本,用:
            ?? hr = m_cpVoice->Speak( szWTextString, SPF_ASYNC | SPF_IS_XML, 0 );

            ?? 暫停的代碼:?? m_cpVoice->Pause();
            ?? 恢復的代碼:?? m_cpVoice->Resume();
            ?? 結束的代碼:(上面的例子中已經給出了)
            ?? hr = m_cpVoice->Speak( NULL, SPF_PURGEBEFORESPEAK, 0);

            5、跳過部分朗讀的文字

            ?? 在朗讀的過程中,可以跳過部分文字繼續(xù)后面的朗讀,代碼如下:
            ?? ULONG ulGarbage = 0;
            ?? WCHAR szGarbage[] = L"Sentence";
            ?? hr = m_cpVoice->Skip( szGarbage, SkipNum, &ulGarbage );
            ?? SkipNum是設置要跳過的句子數量,值可以是正/負。
            ?? 根據sdk的說明,目前SAPI僅僅支持SENTENCE這個類型。SAPI是通過標點符號來區(qū)分句子的。

            6、播放WAV文件。SAPI可以播放WAV文件,這是通過ISpStream接口實現的:

            ?? CComPtr<ISpStream>?????? cpWavStream;
            ?? WCHAR??????????????????? szwWavFileName[NORM_SIZE] = L"";;

            ?? USES_CONVERSION;
            ?? wcscpy( szwWavFileName, T2W( szAFileName ) );//從ANSI將WAV文件的名字轉換成寬字符串

            ?? //使用sphelper.h 提供的這個函數打開 wav 文件,并得到一個 IStream 指針
            ?? hr = SPBindToFile( szwWavFileName, SPFM_OPEN_READONLY, &cpWavStream );
            ?? if( SUCCEEDED( hr ) )
            ?? {
            ??????? m_cpVoice->SpeakStream( cpWavStream, SPF_ASYNC, NULL );//播放WAV文件
            ?? }
            7、將朗讀的結果保存到wav文件
            ?? TCHAR szFileName[256];//假設這里面保存著目標文件的路徑
            ?? USES_CONVERSION;
            ?? WCHAR m_szWFileName[MAX_FILE_PATH];
            ?? wcscpy( m_szWFileName, T2W(szFileName) );//轉換成寬字符串

            ?? //創(chuàng)建一個輸出流,綁定到wav文件
            ?? CSpStreamFormat OriginalFmt;
            ?? CComPtr<ISpStream>? cpWavStream;
            ?? CComPtr<ISpStreamFormat>??? cpOldStream;
            ?? HRESULT hr = m_cpVoice->GetOutputStream( &cpOldStream );
            ?? if (hr == S_OK) hr = OriginalFmt.AssignFormat(cpOldStream);
            ?? else? hr = E_FAIL;
            ?? // 使用sphelper.h中提供的函數創(chuàng)建 wav 文件
            ?? if (SUCCEEDED(hr))
            ?? {
            ????? hr = SPBindToFile( m_szWFileName, SPFM_CREATE_ALWAYS, &cpWavStream,
            ???????????????????????? &OriginalFmt.FormatId(), OriginalFmt.WaveFormatExPtr() );
            ??? }
            ?? if( SUCCEEDED( hr ) )
            ?? {
            ????? //設置聲音的輸出到 wav 文件,而不是 speakers
            ????? m_cpVoice->SetOutput(cpWavStream, TRUE);
            ??? }
            ??? //開始朗讀
            ??? m_cpVoice->Speak( szWTextString, SPF_ASYNC | SPF_IS_NOT_XML, 0 );

            ??? //等待朗讀結束
            ??? m_cpVoice->WaitUntilDone( INFINITE );
            ??? cpWavStream.Release();

            ??? //把輸出重新定位到原來的流
            ??? m_cpVoice->SetOutput( cpOldStream, FALSE );
            ???
            8、設置朗讀音量和速度
            ?? m_cpVoice->SetVolume((USHORT)hpos); //設置音量,范圍是 0 - 100
            ?? m_cpVoice->SetRate(hpos);??//設置速度,范圍是 -10 - 10

            ?? hpos的值一般位于

            9、設置SAPI通知消息。SAPI在朗讀的過程中,會給指定窗口發(fā)送消息,窗口收到消息后,可以主動獲取SAPI的事件,
            ?? 根據事件的不同,用戶可以得到當前SAPI的一些信息,比如正在朗讀的單詞的位置,當前的朗讀口型值(用于顯
            ?? 示動畫口型,中文語音的情況下并不提供這個事件)等等。

            ?? 要獲取SAPI的通知,首先要注冊一個消息:
            ?? m_cpVoice->SetNotifyWindowMessage( hWnd, WM_TTSAPPCUSTOMEVENT, 0, 0 );
            ?? 這個代碼一般是在主窗口初始化的時候調用,hWnd是主窗口(或者接收消息的窗口)句柄。WM_TTSAPPCUSTOMEVENT
            ?? 是用戶自定義消息。

            ?? 在窗口響應WM_TTSAPPCUSTOMEVENT消息的函數中,通過如下代碼獲取sapi的通知事件:

            ??? CSpEvent??????? event;? // 使用這個類,比用 SPEVENT結構更方便

            ??? while( event.GetFrom(m_cpVoice) == S_OK )
            ??? {
            ??????? switch( event.eEventId )
            ??????? {
            ?????????? 。。。
            ??????? }
            ??? }

            ?? eEventID有很多種,比如SPEI_START_INPUT_STREAM表示開始朗讀,SPEI_END_INPUT_STREAM表示朗讀結束等。
            ?? 可以根據需要進行判斷使用。

            四、結束語
            ??
            ?? SAPI的功能很多,比如語音識別、使用語法分析等,由于條件和精力有限,我未能一一嘗試,感興趣的朋友可以自己安裝一個研究一下。
            ?? 另外提供一個簡單例子程序的下載,位置是:
            ??
            ftp://vckbase:vckbase@210.192.111.117/user/iwaswzq/Universe.rar

            posted @ 2006-09-13 00:45 Jerry Cat 閱讀(3429) | 評論 (1)編輯 收藏

            ??翻譯者 : myan
            出處: http://www.sgi.com/technology/stl??

            1995年3月,dr.dobb's journal特約記者, 著名技術書籍作家al stevens采訪了stl創(chuàng)始人alexander stepanov. 這份訪談紀錄是迄今為止對于stl發(fā)展歷史的最完備介紹, 侯捷先生在他的stl有關文章里推薦大家閱讀這篇文章. 因此我將該文全文翻譯如下:

            q: 您對于generic programming進行了長時間的研究, 請就此談談.
            a: 我開始考慮有關gp的問題是在7o年代末期, 當時我注意到有些算法并不依賴于數據結構的特定實現,而只是依賴于該結構的幾個基本的語義屬性. 于是我開始研究大量不同的算法,結果發(fā)現大部分算法可以用這種方法從特定實現中抽象出來, 而且效率無損. 對我來說,效率是至關重要的, 要是一種算法抽象在實例化會導致性能的下降, 那可不夠棒.
            ??
            ?? 當時我認為這項研究的正確方向是創(chuàng)造一種編程語言. 我和我的兩個朋友一起開始干起來.一個是現在的紐約州立大學教授deepak kapur, 另一個是倫塞里爾技術學院教授david musser.當時我們三個在通用電器公司研究中心工作. 我們開始設計一種叫tecton的語言. 該語言有一種我們稱為"通用結構"的東西, 其實不過是一些形式類型和屬性的集合體, 人們可以用它來描述算法. 例如一些數學方面的結構充許人們在其上定義一個代數操作, 精化之,擴充之, 做各種各樣的事.

            ?? 雖然有很多有趣的創(chuàng)意, 最終該項研究沒有取得任何實用成果, 因為tecton語言是函數型語言. 我們信奉backus的理念,相信自己能把編程從von neumann風格中解放出來. 我們不想使用副效應, 這一點限制了我們的能力, 因為存在大量需要使用諸如"狀態(tài)", "副效應"等觀念的算法.??

            ?? 我在70年代末期在tecton上面所認識到了一個有趣的問題: 被廣泛接受的adt觀念有著根本性的缺陷. 人們通常認為adt的特點是只暴露對象行為特征, 而將實現隱藏起來. 一項操作的復雜度被認為是與實現相關的屬性, 所以抽象的時候應予忽略. 我則認識到, 在考慮一個(抽象)操作時, 復雜度(或者至少是一般觀念上的復雜度)必須被同時考慮在內. 這一點現在已經成了gp的核心理念之一.

            ?? 例如一個抽象的棧stack類型,??僅僅保證你push進去的東西可以隨后被pop出來是不夠的,同樣極端重要的是, 不管stack有多大, 你的push操作必須能在常數時間內完成. 如果我寫了一個stack, 每push一次就慢一點, 那誰都不會用這個爛玩藝.

            ?? 我們是要把實現和界面分開, 但不能完全忽略復雜度. 復雜度必須是, 而且也確實是橫陳于模塊的使用者與實現者之間的不成文契約. adt觀念的引入是為了允許軟件模塊相互可替換. 但除非另一個模塊的操作復雜度與這個模塊類似, 否則你肯定不愿意實現這種互換.如果我用另外一個模塊替換原來的模塊, 并提供完全相同的接口和行為, 但就是復雜度不同, 那么用戶肯定不高興. 就算我費盡口舌介紹那些抽象實現的優(yōu)點, 他肯定還是不樂意用. 復雜度必須被認為是接口的一部分.

            ?? 1983年左右, 我轉往紐約布魯克林技術大學任教. 開始研究的是圖的算法, 主要的合作伙伴是現在ibm的aaron kershenbaum. 他在圖和網絡算法方面是個專家, 我使他相信高序(high order)的思想和gp能夠應用在圖的算法中. 他支持我與他合作開始把這些想法用于實際的網絡算法. 某些圖的算法太復雜了, 只進行過理論分析, 從來沒有實現過. 他企圖建立一個包含有高序的通用組件的工具箱, 這樣某些算法就可以實現了. 我決定使用lisp語言的一個變種scheme語言來建立這樣一個工具箱. 我們倆建立了一個巨大的庫, 展示了各種編程技術.網絡算法是首要目標. 不久當時還在通用電器的david musser加了進來, 開發(fā)了更多的組件,一個非常大的庫. 這個庫供大學里的本科生使用, 但從未商業(yè)化. 在這項工作中, 我了解到副效應是很重要的, 不利用副效應, 你根本沒法進行圖操作. 你不能每次修改一個端點(vertex)時都在圖上兜圈子. 所以, 當時得到的經驗是在實現通用算法時可以把高序技術和副效應結合起來. 副效應不總是壞的, 只有在被錯誤使用時才是.

            ?? 1985年夏, 我回到通用電器講授有關高序程序設計的課程. 我展示了在構件復雜算法時這項技術的應用. 有一個聽課的人叫陳邇, 當時是信息系統(tǒng)實驗室的主任. 他問我是否能用ada語言實現這些技術, 形成一個工業(yè)強度的庫, 并表示可以提供支持. 我是個窮助教, 所以盡管我當時對于ada一無所知, 我還是回答"好的". 我跟dave musser一起建立這個ada庫. 這是很重要的一個時期, 從象scheme那樣的動態(tài)類型語言(dynamically typed language)轉向ada這樣的強類型語言, 使我認識到了強類型的重要性. 誰都知道強類型有助于糾錯. 我則發(fā)現在ada的通用編程中, 強類型是獲取設計思想的有力工具. 它不僅是查錯工具, 而且是思想工具.這項工作給了我對于組件空間進行正交分解的觀念. 我認識到, 軟件組件各自屬于不同的類別.oop的狂熱支持者認為一切都是對象. 但我在ada通用庫的工作中認識到, 這是不對的. 二分查找就不是個對象, 它是個算法. 此外, 我還認識到, 通過將組件空間分解到幾個不同的方向上, 我們可以減少組件的數量, 更重要的是, 我們可以提供一個設計產品的概念框架.

            ?? 隨后, 我在貝爾實驗室c++組中得到一份工作, 專事庫研究. 他們問我能不能用c++做類似的事.我那時還不懂c++, 但當然, 我說我行. 可結果我不行, 因為1987年時, c++中還沒有模板, 這玩意兒在通用編程中是個必需品. 結果只好用繼承來獲取通用性, 那顯然不理想.直到現在c++繼承機制也不大用在通用編程中, 我們來說說為什么. 很多人想用繼承實現數據結構和容器類, 結果幾乎全部一敗涂地. c++的繼承機制及與之相關的編程風格有著戲劇性的局限. 用這種方式進行通用編程, 連等于判斷這類的小問題都解決不了. 如果你以x類作為基類, 設計了一個虛函數operater==, 接受一個x類對象, 并由x派生類y, 那么y的operator==是在拿y類對象與x類對象做比較. 以動物為例, 定義animal類, 派生giraffe(長頸鹿)類. 定義一個成員函數mate(), 實現與另一個哺乳動物的交配操作, 返回一個animal對象. 現在看看你的派生類giraffe,它當然也有一個mate()方法, 結果一個長頸鹿同一個動物交配, 返回一個動物對象. 這成何體統(tǒng)?當然, 對于c++程序員來說, 交配函數沒那么重要, 可是operator==就很重要了.

            ?? 對付這種問題, 你得使用模板. 用模板機制, 一切如愿.

            ?? 盡管沒有模板, 我還是搞出來一個巨大的算法庫, 后來成了unix system laboratory standard component library的一部分. 在bell lab, 我從象andy koenig, bjarne stroustrup(andrew koenig, 前iso c++標準化委員會主席; bjarne stroustrup, c++之父 -- 譯者)這類專家身上學到很多東西. 我認識到c/c++的重要, 它們的一些成功之處是不能被忽略的. 特別是我發(fā)現指針是個好東東. 我不是說空懸的指針, 或是指向棧的指針. 我是說指針這個一般觀念. 地址的觀念被廣泛使用著. 沒有指針我們就沒法描述并行算法.

            ?? 我們現在來探討一下為什么說c是一種偉大的語言. 通常人們認為c是編程利器并且獲得如此成功,是因為unix是用c寫的. 我不同意. 計算機的體系結構是長時間發(fā)展演變的結果, 不是哪一個聰明的人創(chuàng)造的. 事實上是廣大程序員在解決實際問題的過程中提出的要求推動了那些天才提出這些體系. 計算機經過多次進化, 現在只需要處理字節(jié)地址索引的內存, 線性地址空間和指針. 這個進化結果是對于人們要求解決問題的自然反映. dennis ritchie天才的作品c, 正反映了演化了30年的計算機的最小模型. c當時并不是什么利器. 但是當計算機被用來處理各種問題時, 作為最小模型的c成了一種非常強大的語言, 在各個領域解決各種問題時都非常高效. 這就是c可移植性的奧秘, c是所有計算機的最佳抽象模型, 而且這種抽象確確實實是建立在實際的計算機, 而不是假想的計算機上的. 人們可以比較容易的理解c背后的機器模型, 比理解ada和scheme語言背后的機器模型要容易的多. c的成功是因為c做了正確的事, 不是因為at&t的極力鼓吹和unix.

            ?? c++的成功是因為bjarne stroustrup以c為出發(fā)點來改進c, 引入更多的編程技術, 但始終保持在c所定義的機器模型框架之內, 而不是閉門造車地自己搞出一個新的機器模型來. c的機器模型非常簡單. 你擁有內存, 對象保存在那里面, 你又有指向連續(xù)內存空間的指針, 很好理解. c++保留了這個模型, 不過大大擴展了內存中對象的范疇, 畢竟c的數據類型太有限了, 它允許你建立新的類型結構, 但不允許你定義類型方法. 這限制了類型系統(tǒng)的能力. c++把c的機器模型擴展為真正類型系統(tǒng).

            ?? 1988年我到惠普實驗室從事通用庫開發(fā)工作. 但實際上好幾年我都是在作磁盤驅動器. 很有趣但跟
            ?? gp毫不相關. 92年我終于回到了gp領域, 實驗室主任bill worley建立了一個算法研究項目, 由我
            ?? 負責. 那時候c++已經有模板了. 我發(fā)現bjarne的模板設計方案是非常天才的. 在bell lab時, 我參
            ?? 加過有關模班設計的幾個早期的討論, 跟bjarne吵得很兇, 我認為c++的模板設計應該盡可能向ada的
            ?? 通用方案看齊. 我想可能我吵得太兇了, 結果bjarne決定堅決拒絕我的建議. 我當時就認識到在c++
            ?? 中設置模板函數的必要性了, 那時候好多人都覺得最好只有模板類. 不過我覺得一個模板函數在使用
            ?? 之前必須先顯式實例化, 跟ada似的. bjarne死活不聽我的, 他把模板函數設計成可以用重載機制來
            ?? 隱式實例化. 后來這個特別的技術在我的工作中變得至關重要, 我發(fā)現它容許我做很多在ada中不可能
            ?? 的任務. 非常高興bjarne當初沒聽我的.

            q: 您是什么時候第一次構思stl的, 最初的目的是什么?
            a: 92年那個項目建立時由8個人, 漸漸地人越來越少, 最后剩下倆, 我和李夢, 而且李小姐是這個領域的新手. 在她的專業(yè)研究中編譯器是主要工作, 不過她接受了gp研究的想法, 并且堅信此項研究將帶給軟件開發(fā)一個大變化, 要知道那時候有這個信念的認可是寥寥無幾. 沒有她, 我可不敢想象我能搞定stl, 畢竟stl標著兩個人的名字:stepanov和lee. 我們寫了一個龐大的庫, 龐大的代碼量, 龐大的數據結構組件,函數對象, 適配器類, 等等. 可是雖然有很多代碼, 卻沒有文檔. 我們的工作被認為是一個驗證性項目,其目的是搞清楚到底能不能在使算法盡可能通用化的前提下仍然具有很高的效率. 我們化了很多時間來比較, 結果發(fā)現, 我們算法不僅最通用, 而且要率與手寫代碼一樣高效, 這種程序設計風格在性能上是不打折扣的! 這個庫在不斷成長, 但是很難說它是什么時候成為一個"項目"的. stl的誕生是好幾件事情的機緣巧合才促成的.

            q: 什么時候, 什么原因促使您決定建議使stl成為ansi/iso標準c++一部分的?
            a: 1993年夏, andy koenig跑到斯坦福來講c++課, 我把一些有關的材料給他看, 我想他當時確實是很興奮.他安排我9月到圣何塞給c++標準委員會做一個演講. 我演講的題目是"c++程序設計的科學", 講得很理論化, 要點是存在一些c++的基本元素所必須遵循的, 有關基本操作的原則. 我舉了一些例子, 比如構造函數, 賦值操作, 相等操作. 作為一種語言,??c++沒有什么限制. 你可以用operator==()來做乘法. 但是相等操作就應該是相等操作. 它要有自反性,??a == a; 它要有對稱性, a == b 則 b == a; 它還要有傳遞性. 作為一個數學公理, 相等操作對于其他操作是基本的要素. 構造函數和相等操作之間的聯系就有公理性的東西在里邊. 你用拷貝構造函數生成了一個新對象, 那么這個對象和原來那個就應該是相等的. c++是沒有做強行要求, 但是這是我們都必須遵守這個規(guī)則. 同樣的, 賦值操作也必須產生相等的對象. 我展示了一些基本操作的"公理", 還講了一點迭代子(iterator), 以及一些通用算法怎樣利用迭代子來工作. 我覺得那是一個兩小時的枯燥演講, 但卻非常受歡迎. 不過我那時并沒有想把這個東西塞在標準里, 它畢竟是太過先進的編程技術, 大概還不適于出現在現實世界里, 恐怕那些做實際工作的人對它沒什么興趣.

            ?? 我是在9月做這個演講的, 直到次年(1994)月, 我都沒往ansi標準上動過什么腦筋. 1月6日, 我收到andy koenig的一封信(他那時是標準文檔項目編輯), 信中說如果我希望stl成為標準庫的一部分, 可以在1月25日之前提交一份建議到委員會. 我的答復是:"andy, 你發(fā)瘋了嗎?", 他答復道:"不錯, 是的我發(fā)瘋了, 為什么咱們不瘋一次試試看?"

            ?? 當時我們有很多代碼, 但是沒有文檔, 更沒有正式的建議書. 李小姐和我每星期工作80小時, 終于在期限之前寫出一份正式的建議書. 當是時也, 只有andy一個人知道可能會發(fā)生些什么. 他是唯一的支持者, 在那段日子里他確實提供了很多幫助. 我們把建議寄出去了, 然后就是等待. 在寫建議的過程中我們做了很多事. 當你把一個東西寫下來, 特別是想到你寫的可能會成為標準, 你就會發(fā)現設計中的所有紕漏. 寄出標準后,我們不得不一段一段重寫了庫中間的代碼, 以及幾百個組件, 一直到3月份圣迭戈會議之前. 然后我們又重新修訂了建議書, 因為在重新寫代碼的過程中, 我們又發(fā)現建議書中間的很多瑕疵.

            q: 您能描述一下當時委員會里的爭論嗎? 建議一開始是被支持呢, 還是反對?
            a: 我當時無法預料會發(fā)生些什么. 我做了一個報告, 反響很好. 但當時有許多反對意見. 主要的意見是:這是一份龐大的建議, 而且來得太晚, 前一次會議上已經做出決議, 不在接受任何大的建議. 而這個東西是有史以來最大的建議, 包括了一大堆新玩藝. 投票的結果很有趣, 壓倒多數的意見認為應對建議進行再考慮, 并把投票推遲到下次會議, 就是后來眾所周知的滑鐵盧會議.

            ?? bjarne stroustrup成了stl的強有力支持者. 很多人都通過建議、更改和修訂的方式給予了幫助。bjarne干脆跑到這來跟我們一起工作了一個禮拜。andy更是無時無刻的幫助我們。c++是一種復雜的語言,不是總能搞得清楚確切的含義的。差不多每天我都要問andy和bjarne c++能不能干這干那。我得把特殊的榮譽歸于andy, 是他提出把stl作為c++標準庫的一部分;而bjarne也成了委員會中stl的主要鼓吹者。其他要感謝的人還有:mike vilot,標準庫小組的負責人; rogue wave公司的nathan myers(rogue wave是boland c++builder中stl方案的提供商 —— 譯者),andersen咨詢公司的larry podmolik。確實有好多人要致謝。

            ?? 在圣迭戈提出的stl實際與當時的c++,我們被要求用新的ansi/iso c++語言特性重寫stl,這些特性中有一些是尚未實現的。為了正確使用這些新的、未實現的c++特性,bjarne和andy花了無以計數的時間來幫助我們。

            ?? 人們希望容器獨立于內存模式,這有點過分,因為語言本身并沒有包括內存模式。所以我們得要想出一些機制來抽象內存模式。在stl的早期版本里,假定容器的容積可以用size_t類型來表示,迭代子之間的距離可以用ptrdiff_t來表示。現在我們被告知,你為什么不抽象的定義這些類型?這個要求比較高,連語言本身都沒有抽象定義這些類型,而且c/c++數組還不能被這些類型定義所限定。我們發(fā)明了一個機制稱作"allocator",封裝了內存模式的信息。這個機制深刻地影響了庫中間的每一個組件。你可能疑惑:內存模式和算法或者容器類接口有什么關系?如果你使用size_t這樣的東西,你就無法使用 t* 對象,因為存在不同的指針類型(t*, t huge *, 等等)。這樣你就不能使用引用,因為內存模式不同的話,會產成不同的引用類型。這樣就會導致標準庫產生龐大的分支。

            ?? 另外一件重要的事情是我們原先的關聯類型數據結構被擴展了。這比較容易一些,但是最為標準的東西總是很困難的,因為我們做的東西人們要使用很多年。從容器的觀點看,stl做了十分清楚的二分法設計。所有的容器類被分成兩種:順序的和關聯的,就好像常規(guī)的內存和按內容尋址的內存一般。這些容器的語義十分清楚。

            ?? 當我到滑鐵盧以后,bjarne用了不少時間來安慰我不要太在意成敗與否,因為雖然看上去似乎不會成功,但是我們畢竟做到了最好。我們試過了,所以應該坦然面對。成功的期望很低。我們估計大部分的意見將是反對。但是事實上,確實有一些反對意見,但不占上風。滑鐵盧投票的結果讓人大跌眼鏡,80%贊成,20%反對。所有人都預期會有一場惡戰(zhàn),一場大論戰(zhàn)。結果是確實有爭論,但投票是壓倒性的。

            q: stl對于1994年2月發(fā)行的ansi/iso c++工作文件中的類庫有何影響?
            a: stl被放進了滑鐵盧會議的工作文件里。stl文檔被分解成若干部分,放在了文件的不同部分中。mike
            ?? vilot負責此事。我并沒有過多地參與編輯工作,甚至也不是c++委員會的成員。不過每次有關stl的
            ?? 建議都由我來考慮。委員會考慮還是滿周到的。

            q: 委員會后來又做了一些有關模板機制的改動,哪些影響到了stl?
            a: 在stl被接受之前,有兩個變化影響到了我們修訂stl。其一是模板類增加了包含模板函數的能力。stl廣泛地使用了這個特性來允許你建立各種容納容器的容器。一個單獨的構造函數就能讓你建立一個能容納list或其他容器的vector。還有一個模板構造函數,從迭代子構造容器對象,你可以用一對迭代子當作參數傳給它,這對迭代子之間的元素都會被用來構造新的容器類對象。另一個stl用到的新特性是把模板自身當作模板參數傳給模板類。這項技術被用在剛剛提到的allocator中。

            q: 那么stl影響了模板機制嗎?
            a: 在弗基山谷的會議中,bjarne建議給模板增加一個“局部特殊化”(partial specialization)的特性。這個特性可以讓很多算法和類效率更高,但也會帶來代碼體積上的問題。我跟bjarne在這個建議上共同研究了一段時間,這個建議就是為了使stl更高效而提出的。我們來解釋一下什么是“局部特殊化”。你現在有一個模板函數 swap( t&, t& ),用來交換兩個參數。但是當t是某些特殊的類型參數時,你想做一些特殊的事情。例如對于swap( int&, int& ),你想用一種特別的操作來交換數據。這一點在沒有局部特殊化機制的情況下是不可能的。有了局部特殊化機制,你可以聲明一個模板函數如下:
            ??
            ?????? template <class t> void swap( vector<t>&, vector<t>& );

            ?? 這種形式給vector容器類的swap操作提供了一種特別的辦法。從性能的角度講,這是非常重要的。如果你用通用的形式去交換vector,會使用三個賦值操作,vector被復制三次,時間復雜度是線性的。然而,如果我們有一個局部特殊化的swap版本專門用來交換兩個vector,你可以得到一個時間復雜度為常數的,非常快的操作,只要移動vector頭部的兩個指針就ok。這能讓vector上的sort算法運行得更快。沒有局部特殊化,讓某一種特殊的vector,例如vector<int>運行得更快的唯一辦法是讓程序員自己定一個特殊的swap函數,這行得通,但是加重了程序員的負擔。在大部分情況下,局部特殊化機制能夠讓算法在某些通用類上表現得更高效。你有最通用的swap,不那么通用的swap,更不通用的swap,完全特殊的swap這么一系列重載的swap,然后你使用局部特殊化,編譯器會自動找到最接近的那個swap。另一個例子copy。現在我們的copy就是通過迭代子一個一個地拷貝。使用模板特殊化可以定義一個模板函數:

            template <class t> t** copy( t**, t**, t** );

            ?? 這可以用memcpy高效地拷貝一系列指針來實現,因為是指針拷貝,我們可以不必擔心構造對象和析構對象的開銷。這個模板函數可以定義一次,然后供整個庫使用,而且用戶不必操心。我們使用局部特殊化處理了一些算法。這是個重要的改進,據我所知在弗基山谷會議上得到了好評,將來會成為標準的一部分。(后來的確成了標準的一部分 —— 譯者)

            q: 除了標準類庫外,stl對那一類的應用程序來說最有用處?
            a: 我希望stl能夠引導大家學習一種新的編程風格:通用編程。我相信這種風格適用于任何種類的應用程序。這種風格就是:用最通用的方式來寫算法和數據結構。這些結構所要求的語義特性應該能夠被清楚地歸類和分類,而這些歸類分類的原則應該是任何對象都能滿足的。理解和發(fā)展這種技術還要很長時間,stl不過是這個過程的起點。

            ?? 我們最終會對通用的組件有一個標準的分類,這些組件具有精心定義的接口和復雜度。程序員們將不必在微觀層次上編程。你再也不用去寫一個二分查找算法。就是在現在,stl也已經提供了好幾個通用的二分查找算法,凡是能用二分查找算法的場合,都可以使用這些算法。算法所要求的前提條件很少:你只要在代碼里使用它。我希望所有的組件都能有這么一天。我們會有一個標準的分類,人們不用再重復這些工作。

            ?? 這就是douglas mcilroy的夢想,他在1969年關于“構件工廠”的那篇著名文章中所提出來的東西。stl就是這種“構件工廠”的一個范例。當然,還需要有主流的力量介入這種技術的發(fā)展之中,光靠研究機構不行,工業(yè)界應該想程序員提供組件和工具,幫助他們找到所需的組件,把組件粘合到一起,然后確定復雜度是否達到預期。

            q: stl沒有實現一個持久化(persistent)對象容器模型。map和multimap似乎是比較好的候選者,它們可以把對象按索引存入持久對象數據庫。您在此方向上做了什么工作嗎,或者對這類實現有何評論?
            a:很多人都注意到這個問題。stl沒實現持久化是有理由的。stl在當時已經是能被接受的最巨大的庫了。再大一點的話,我認為委員會肯定不會接受。當然持久化是確實是一些人提出的問題。在設計stl,特別是設計allocator時,bjarne認為這個封裝了內存模式的組件可以用來封裝持久性內存模式。bjarne的洞察秋毫非常的重要和有趣,好幾個對象數據庫公司正在盯著這項技術。1994年10月我參加了object database management group的一個會議,我做了一個關于演說。他們非常感興趣,想讓他們正在形成中的組件庫的接口與stl一致,但不包括allocator在內。不過該集團的某些成員仔細分析了allocator是否能夠被用來實現持久化。我希望與stl接口一致的組件對象持久化方案能在接下來的一年里出現。

            q:set,multiset,map和multimap是用紅黑樹實現的,您試過用其他的結構,比如b*樹來實現嗎?
            a:我不認為b*適用于內存中的數據結構,不過當然這件事還是應該去做的。應該對許多其他的數據結構,比如跳表(skip list)、伸展樹(splay tree)、半平衡樹(half-balanced tree)等,也實現stl容器的標準接口。應該做這樣的研究工作,因為stl提供了一個很好的框架,可以用來比較這些結構的性能。結口是固定的,基本的復雜度是固定的,現在我們就可一個對各種數據結構進行很有意義的比較了。在數據結構領域里有很多人用各種各樣的接口來實現不同的數據結構,我希望他們能用stl框架來把這些數據結構變成通用的。
            ?? (譯者注:上面所提到的各種數據結構我以為大多并非急需,而一個stl沒有提供而又是真正重要的數據結構是哈希結構。后來在stepanov和matt austern等人的sgi*stl中增補了hashset,hashmap和hashtable三種容器,使得這個stl實現才比較完滿。眾所周知,紅黑樹的時間復雜度為o(logn), 而理想hash結構為o(1)。當然,如果實現了持久化,b+樹也是必須的。)

            q:有沒有編譯器廠商跟您一起工作來把stl集成到他們的產品中去?
            a:是的,我接到了很多廠家的電話。borland公司的peter becker出的力特別大。他幫助我實現了對應borland編譯器的所有內存模式的allocator組件。symantec打算為他們的macintosh編譯器提供一個stl實現。edison設計集團也很有幫助。我們從大多數編譯器廠商都得到了幫助。
            ?? (譯者注:以目前的stl版本來看,最出色的無疑是sgi*stl和ibm stl for as/390,所有windows下的的stl實現都不令人滿意。根據測試數據,windows下最好的stl運行在piii 500mhz上的速度遠遠落后與在250mhz sgi工作站(irix操作系統(tǒng))上運行的sgi*stl。以我個人經驗,linux也是運行stl的極佳平臺。而在windows的stl實現中,又以borland c++builder的rogue wave stl為最差,其效率甚至低于jit執(zhí)行方式下的java2。visual c++中的stl是著名大師p. j. plauger的個人作品,性能較好,但其queue組件效率很差,慎用)

            q:stl包括了對ms-dos的16位內存模式編譯器的支持,不過當前的重點顯然是在32位上線性內存模式(flat model)的操作系統(tǒng)和編譯器上。您覺得這種面向內存模式的方案以后還會有效嗎?
            a:拋開intel的體系結構不談,內存模式是一個對象,封裝了有關指針的信息:這個指針的整型尺寸和距離類型是什么,相關的引用類型是什么,等等。如果我們想利用各種內存,比如持久性內存,共享內存等等,抽象化的工作就非常重要了。stl的一個很漂亮的特性是整個庫中唯一與機器類型相關的部分——代表真實指針,真實引用的組件——被封裝到大約16行代碼里,其他的一切,容器、算法等等,都與機器無關(真是牛啊!)。從移植的觀點看,所有及其相關的東西,象是地址記法,指針等,都被封裝到一個微小的,很好理解的機制里面。這樣一來,allocator對于stl而言就不是那么重要了,至少不像對于基本數據結構和算法的分解那么重要。


            q:ansi/iso c標準委員會認為像內存模式這類問題是平臺相關的,沒有對此做出什么具體規(guī)定。c++委員會會不會采取不同的態(tài)度?為什么?
            a:我認為stl在內存模式這一點上跟c++標準相比是超前的。但是在c和c++之間有著顯著的不同。c++有構造函數和new操作符來對付內存模式問題,而且它們是語言的一部分。現在看來似乎讓new操作符像stl容器使用allocater那樣來工作是很有意義的。不過現在對問題的重要性不像stl出現之前那么顯著了,因為在大多數場合,stl數據結構將讓new失業(yè)。大部分人不再需要分配一個數組,因為stl在做這類事情上更為高效。要知道我對效率的迷信是無以復加的,可我在我的代碼里從不使用new,匯編代碼表明其效率比使用new時更高。隨著stl的廣泛使用,new會逐漸淡出江湖。而且stl永遠都會記住回收內存,因為當一個容器,比如vector退出作用域時,它的析構函數被調用,會把容器里的所有東西都析構。你也不必再擔心內存泄漏了。stl可以戲劇性地降低對于垃圾收集機制的需求。使用stl容器,你可以為所欲為,不用關心內存的管理,自有stl構造函數和析構函數來對付。


            q:c++標準庫子委員會正在制訂標準名空間(namespace)和異常處理機制。stl類會有名空間嗎,會拋出異
            常嗎?
            a:是的。該委員會的幾個成員正在考慮這件事,他們的工作非常卓越。

            q:現在的stl跟最終作為標準的stl會有多大不同?委員會會不會干預某些變化,新的設計會不會被嚴格地控
            制起來?
            a:多數人的意見看起來是不希望對stl做任何重要的改變。

            q:在成為標準之前,程序員們怎樣的一些stl經驗?
            a:他們可以從butler.hpl.hp.com/stl當下stl頭文件,在borland和ibm或其他足夠強勁的的編譯器中使用它。學習這種編程技術的唯一途徑是編程,看看范例,試著用這種技術來編程。

            q:您正在和p. j. plauger合作一本stl的書。那本書的重點是什么?什么時候面世?
            a:計劃95年夏天面世,重點是對stl實現技術的詳解,跟他那本標準c庫實現和標準c++庫實現的書類似。他是
            這本書的第一作者。該書可以作為stl的參考手冊。我希望跟bjarne合作另寫一本書,在c++/stl背景下介紹語言與庫的交互作用。

            ?? 好多工作都等著要做。為了stl的成功,人們需要對這種編程技術進行更多的試驗性研究,更多的文章和書籍應該對此提供幫助。要準備開設此類課程,寫一些入門指南,開發(fā)一些工具幫助人們漫游stl庫。stl是一個
            框架,應該有好的工具來幫助使用這個框架。
            ?? (譯者注:他說這番話時,并沒有預計到在接下來的幾年里會發(fā)生什么。由于internet的大爆炸和java、vb、delphi等語言的巨大成功,工業(yè)界的重心一下子從經典的軟件工程領域轉移到internet上。再加上標準c++直到98年才制訂,完全符合要求的編譯器直到現在都還沒有出現,stl并沒有立刻成為人們心中的關注焦點。他提到的那本書也遲遲不能問世,直到前幾天(2001年元旦之后),這本眾人久已期盼的書終于問世,由p. j. plauger, alexander stepanov, meng lee, david musser四大高手聯手奉獻,prentice hall出版。不過該書主要關注的是stl的實現技術,不適用于普通程序員。

            ???? 另外就p. j. plauger做一個簡介:其人是標準c中stdio庫的早期實現者之一,91年的一本關于標準c庫的書使他名滿天下。他現在是c/c++ use's journal的主編,與microsoft保持著良好的,甚至是過分親密的關系,visual c++中的stl和其他的一些內容就是出自他的那只生花妙筆。不過由于跟ms的關系已經影響到了他的中立形象,現在有不少人對他有意見。

            ???? 至于stepanov想象中的那本與stroustrup的書,起碼目前是沒聽說。其實這兩位都是典型的編程圣手,跟ken thompson和dennis ritchie是一路的,懶得親自寫書,往往做個第二作者。如果作為第一作者,寫出來的書肯定是學院味十足,跟標準文件似的,不適合一般程序員閱讀。在計算機科學領域,編程圣手同時又是寫作高手的人是鳳毛麟角,最著名的可能是外星人d. e. knuth, c++領域里則首推前面提到的andrew koenig。可惜我們中國程序員無緣看到他的書。)

            q:通用編程跟oop之間有什么關系?
            a:一句話,通用編程是oop基本思想的自然延續(xù)。什么是oop的基本思想呢?把組件的實現和接口分開,并且讓組件具有多態(tài)性。不過,兩者還是有根本的不同。oop強調在程序構造中語言要素的語法。你必須得繼承,使用類,使用對象,對象傳遞消息。gp不關心你繼承或是不繼承,它的開端是分析產品的分類,有些什么種類,他們的行為如何。就是說,兩件東西相等意味著什么?怎樣正確地定義相等操作?不單單是相等操作那么簡單,你往深處分析就會發(fā)現“相等”這個一般觀念意味著兩個對象部分,或者至少基本部分是相等的,據此我們就可以有一個通用的相等操作。再說對象的種類。假設存在一個順序序列和一組對于順序序列的操作。那么這些操作的語義是什么?從復雜度權衡的角度看,我們應該向用戶提供什么樣的順序序列?該種序列上存在那些操作?那種排序是我們需要的?只有對這些組件的概念型分類搞清楚了,我們才能提到實現的問題:使用模板、繼承還是宏?使用什么語言和技術?gp的基本觀點是把抽象的軟件組件和它們的行為用標準的分類學分類,出發(fā)點就是要建造真實的、高效的和不取決于語言的算法和數據結構。當然最終的載體還是語言,沒有語言沒法編程。stl使用c++,你也可以用ada來實現,用其他的語言來實現也行,結果會有所不同,但基本的東西是一樣的。到處都要用到二分查找和排序,而這就是人們正在做的。對于容器的語義,不同的語言會帶來輕微的不同。但是基本的區(qū)別很清楚是gp所依存的語義,以及語義分解。例如,我們決定需要一個組件swap,然后指出這個組件在不同的語言中如果工作。顯然重點是語義以及語義分類。而oop所強調的(我認為是過分強調的)是清楚的定義類之間的層次關系。oop告訴了你如何建立層次關系,卻沒有告訴你這些關系的實質。
            ?? (這段不太好理解,有一些術語可能要過一段時間才會有合適的中文翻譯——譯者)

            q:您對stl和gp的未來怎么看?
            a:我剛才提到過,程序員們的夢想是擁有一個標準的組件倉庫,其中的組件都具有良好的、易于理解的和標準的接口。為了達成這一點,gp需要有一門專門的科學來作為基礎和支柱。stl在某種程度上開始了這項工作,它對于某些基本的組件進行了語義上的分類。我們要在這上面下更多的功夫,目標是要將軟件工程從一種手工藝技術轉化為工程學科。這需要一門對于基本概念的分類學,以及一些關于這些基本概念的定理,這些定理必須是容易理解和掌握的,每一個程序員即使不能很清楚的知道這些定理,也能正確地使用它。很多人根本不知道交換律,但只要上過學的人都知道2+5等于5+2。我希望所有的程序員都能學習一些基本的語義屬性和基本操作:賦值意味著什么?相等意味著什么?怎樣建立數據結構,等等。

            ?? 當前,c++是gp的最佳載體。我試過其他的語言,最后還是c++最理想地達成了抽象和高效的統(tǒng)一。但是我覺得可能設計出一種語言,基于c和很多c++的卓越思想,而又更適合于gp。它沒有c++的一些缺陷,特別是不會像c++一樣龐大。stl處理的東西是概念,什么是迭代子,不是類,不是類型,是概念。說得更正式一些,這是bourbaki所說的結構類型(structure type),是邏輯學家所說的理念(theory),或是類型理論學派的人所說的種類(sort),這種東西在c++里沒有語言層面上的對應物(原文是incarnation,直譯為肉身——譯者),但是可以有。你可以擁有一種語言,使用它你可以探討概念,精化概念,最終用一種非常“程序化”(programmatic,直譯為節(jié)目的,在這里是指符合程序員習慣的——譯者)的手段把它們轉化為類。當然確實有一些語言能處理種類(sorts),但是當你想排序(sort)時它們沒什么用處。我們能夠有一種語言,用它我們能定義叫做foward iterator(前向迭代子)的東西,在stl里這是個概念,沒有c++對應物。然后我們可以從forword iterator中發(fā)展出bidirectional iterator(雙向迭代子),再發(fā)展出random iterator。可能設計一種語言大為簡化gp,我完全相信該語言足夠高效,其機器模型與c/c++充分接近。我完全相信能夠設計出一種語言,一方面盡可能地靠近機器層面以達成絕對的高效,另一方面能夠處理非常抽象化的實體。我認為該語言的抽象性能夠超過c++,同時又與底層的機器之間契合得天衣無縫。我認為gp會影響到語言的研究方向,我們會有適于gp的實用語言。從這些話中你應該能猜出我下一步的計劃。

            mengyan
            譯于2001年1月
            posted @ 2006-09-11 07:31 Jerry Cat 閱讀(152) | 評論 (0)編輯 收藏
            僅列出標題
            共14頁: 1 2 3 4 5 6 7 8 9 Last 

            <2006年6月>
            28293031123
            45678910
            11121314151617
            18192021222324
            2526272829301
            2345678

            常用鏈接

            留言簿(7)

            隨筆檔案

            最新隨筆

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

            久久久一本精品99久久精品88| 波多野结衣中文字幕久久| 久久久高清免费视频| 久久亚洲中文字幕精品有坂深雪| 人人狠狠综合久久88成人| 久久综合狠狠综合久久激情 | 欧美久久久久久精选9999| 国产69精品久久久久9999APGF | 国产偷久久久精品专区| 99久久人妻无码精品系列| 久久精品国产亚洲5555| 久久国产精品无码一区二区三区| 久久精品国产亚洲7777| 久久国产欧美日韩精品| 久久婷婷五月综合97色直播| 99久久er这里只有精品18| 午夜视频久久久久一区 | 精品久久久久久国产| 亚洲七七久久精品中文国产| 天天久久狠狠色综合| 久久久久亚洲av无码专区导航 | 亚洲色欲久久久综合网东京热 | 久久久久久国产精品无码下载| 欧美一区二区精品久久| 久久精品国产亚洲AV无码娇色| 无码人妻少妇久久中文字幕 | 久久久久99精品成人片牛牛影视| 久久精品国产99久久无毒不卡 | 亚洲av伊人久久综合密臀性色| 精品久久人人做人人爽综合| 国产一区二区三区久久| 精品无码久久久久久午夜| 久久久久久国产精品无码下载| 亚洲国产精品无码久久九九| 久久精品无码一区二区三区免费| aaa级精品久久久国产片| 久久精品国产99久久久| 久久久久久无码Av成人影院| 麻豆成人久久精品二区三区免费| 色欲久久久天天天综合网| 久久天天躁狠狠躁夜夜avapp|