• <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>

            網(wǎng)絡服務器軟件開發(fā)/中間件開發(fā),關(guān)注ACE/ICE/boost

            C++博客 首頁 新隨筆 聯(lián)系 聚合 管理
              152 Posts :: 3 Stories :: 172 Comments :: 0 Trackbacks

            #

             
            2007-04-18 17:34
            文件 I/O 在C++中比烤蛋糕簡單多了。 在這篇文章里,我會詳細解釋ASCII和二進制文件的輸入輸出的每個細節(jié),值得注意的是,所有這些都是用C++完成的。

              一、ASCII 輸出

              為了使用下面的方法, 你必須包含頭文件<fstream.h>(譯者注:在標準C++中,已經(jīng)使用<fstream>取代< fstream.h>,所有的C++標準頭文件都是無后綴的。)。這是 <iostream.h>的一個擴展集, 提供有緩沖的文件輸入輸出操作. 事實上, <iostream.h> 已經(jīng)被<fstream.h>包含了, 所以你不必包含所有這兩個文件, 如果你想顯式包含他們,那隨便你。我們從文件操作類的設計開始, 我會講解如何進行ASCII I/O操作。如果你猜是"fstream," 恭喜你答對了! 但這篇文章介紹的方法,我們分別使用"ifstream"?和 "ofstream" 來作輸入輸出。

              如果你用過標準控制臺流"cin"?和 "cout," 那現(xiàn)在的事情對你來說很簡單。 我們現(xiàn)在開始講輸出部分,首先聲明一個類對象。ofstream fout;

              這就可以了,不過你要打開一個文件的話, 必須像這樣調(diào)用ofstream::open()。

            fout.open("output.txt");

              你也可以把文件名作為構(gòu)造參數(shù)來打開一個文件.

            ofstream fout("output.txt");

              這是我們使用的方法, 因為這樣創(chuàng)建和打開一個文件看起來更簡單. 順便說一句, 如果你要打開的文件不存在,它會為你創(chuàng)建一個, 所以不用擔心文件創(chuàng)建的問題. 現(xiàn)在就輸出到文件,看起來和"cout"的操作很像。 對不了解控制臺輸出"cout"的人, 這里有個例子。

            int num = 150;
            char name[] = "John Doe";
            fout << "Here is a number: " << num << "\n";
            fout << "Now here is a string: " << name << "\n";

              現(xiàn)在保存文件,你必須關(guān)閉文件,或者回寫文件緩沖. 文件關(guān)閉之后就不能再操作了, 所以只有在你不再操作這個文件的時候才調(diào)用它,它會自動保存文件。 回寫緩沖區(qū)會在保持文件打開的情況下保存文件, 所以只要有必要就使用它。回寫看起來像另一次輸出, 然后調(diào)用方法關(guān)閉。像這樣:

            fout << flush; fout.close();

               現(xiàn)在你用文本編輯器打開文件,內(nèi)容看起來是這樣:

              Here is a number: 150 Now here is a string: John Doe

              很簡單吧! 現(xiàn)在繼續(xù)文件輸入, 需要一點技巧, 所以先確認你已經(jīng)明白了流操作,對 "<<" 和">>" 比較熟悉了, 因為你接下來還要用到他們。繼續(xù)…

              二、ASCII 輸入

              輸入和"cin" 流很像. 和剛剛討論的輸出流很像, 但你要考慮幾件事情。在我們開始復雜的內(nèi)容之前, 先看一個文本:

              12 GameDev 15.45 L This is really awesome!

              為了打開這個文件,你必須創(chuàng)建一個in-stream對象,?像這樣。

            ifstream fin("input.txt");

              現(xiàn)在讀入前四行. 你還記得怎么用"<<" 操作符往流里插入變量和符號吧?好,?在 "<<" (插入)?操作符之后,是">>" (提取) 操作符. 使用方法是一樣的. 看這個代碼片段.

            int number;
            float real;
            char letter, word[8];
            fin >> number; fin >> word; fin >> real; fin >> letter;

              也可以把這四行讀取文件的代碼寫為更簡單的一行。

            fin >> number >> word >> real >> letter;

              它是如何運作的呢? 文件的每個空白之后, ">>" 操作符會停止讀取內(nèi)容, 直到遇到另一個>>操作符. 因為我們讀取的每一行都被換行符分割開(是空白字符), ">>" 操作符只把這一行的內(nèi)容讀入變量。這就是這個代碼也能正常工作的原因。但是,可別忘了文件的最后一行。

              This is really awesome!

              如果你想把整行讀入一個char數(shù)組, 我們沒辦法用">>"?操作符,因為每個單詞之間的空格(空白字符)會中止文件的讀取。為了驗證:

            char sentence[101]; fin >> sentence;

              我們想包含整個句子, "This is really awesome!" 但是因為空白, 現(xiàn)在它只包含了"This". 很明顯, 肯定有讀取整行的方法, 它就是getline()。這就是我們要做的。

            fin.getline(sentence, 100);

              這是函數(shù)參數(shù). 第一個參數(shù)顯然是用來接受的char數(shù)組. 第二個參數(shù)是在遇到換行符之前,數(shù)組允許接受的最大元素數(shù)量. 現(xiàn)在我們得到了想要的結(jié)果:“This is really awesome!”。

              你應該已經(jīng)知道如何讀取和寫入ASCII文件了。但我們還不能罷休,因為二進制文件還在等著我們。

              三、二進制 輸入輸出

              二進制文件會復雜一點, 但還是很簡單的。首先你要注意我們不再使用插入和提取操作符(譯者注:<< 和 >> 操作符). 你可以這么做,但它不會用二進制方式讀寫。你必須使用read() 和write() 方法讀取和寫入二進制文件. 創(chuàng)建一個二進制文件, 看下一行。

            ofstream fout("file.dat", ios::binary);

              這會以二進制方式打開文件, 而不是默認的ASCII模式。首先從寫入文件開始。函數(shù)write() 有兩個參數(shù)。 第一個是指向?qū)ο蟮腸har類型的指針, 第二個是對象的大小(譯者注:字節(jié)數(shù))。 為了說明,看例子。

            int number = 30; fout.write((char *)(&number), sizeof(number));

              第一個參數(shù)寫做"(char *)(&number)". 這是把一個整型變量轉(zhuǎn)為char *指針。如果你不理解,可以立刻翻閱C++的書籍,如果有必要的話。第二個參數(shù)寫作"sizeof(number)". sizeof() 返回對象大小的字節(jié)數(shù). 就是這樣!

              二進制文件最好的地方是可以在一行把一個結(jié)構(gòu)寫入文件。 如果說,你的結(jié)構(gòu)有12個不同的成員。 用ASCII?文件,你不得不每次一條的寫入所有成員。 但二進制文件替你做好了。 看這個。

            struct OBJECT { int number; char letter; } obj;
            obj.number = 15;
            obj.letter = ‘M’;
            fout.write((char *)(&obj), sizeof(obj));

              這樣就寫入了整個結(jié)構(gòu)! 接下來是輸入. 輸入也很簡單,因為read()?函數(shù)的參數(shù)和 write()是完全一樣的, 使用方法也相同。

            ifstream fin("file.dat", ios::binary); fin.read((char *)(&obj), sizeof(obj));

              我不多解釋用法, 因為它和write()是完全相同的。二進制文件比ASCII文件簡單, 但有個缺點是無法用文本編輯器編輯。 接著, 我解釋一下ifstream 和ofstream 對象的其
            posted @ 2007-04-29 18:24 true 閱讀(591) | 評論 (0)編輯 收藏

            #include   "stdafx.h"  
              #include   "string.h"  
              #include   "iostream.h"  
              #include   <stdio.h>  
              #include   <fstream.h>  
               
              int   main(int   argc,   char*   argv[])  
              {  
                      fstream   f("e:\\test.txt",ios::in   |   ios::out   |   ios::trunc   |   ios::binary);  
              int   i;  
              cout<<"Enter   an   integer:"<<endl;  
              cin>>i;  
              f.write((char*)(&i),sizeof(i));  
              int   j=0;  
              f.seekg(0,ios::beg);  
              f.read((char*)(&j),sizeof(j));  
              cout<<j;  
              getchar();  
              return   0;  
              }  

            得到文件長度
            ifstream   in("readme.txt");  
              ...  
              streampos   pos   =   in.tellg();     //   save   current   position  
              in.seekg(0,   ios::end);  
              cout   <<   "file   length   ="   <<   in.tellg()   <<   endl;  
              in.seekg(pos);     //   restore   saved   position  
            posted @ 2007-04-29 18:14 true 閱讀(826) | 評論 (0)編輯 收藏

            ACE_NT_Service(WINDOWS)
            本人的觀點,SERVICE就是WINDOWS版的DAEMON。ACE_NT_Service通過包裝一整套WINDOWS提供的SERVICE API定義了一個控制NT SERVICE的接口。應用程序繼承該接口就可以實現(xiàn)和UNIX上DAEMON相似的功能。下面先簡單描述WINDOWSSERVICE程序框架,再詳細描述類ACE_NT_Service對WINDOWS SERVICE程序框架的包裝。

            WINDOWS SERVICE
            一個完整的NT SERVICE程序應該包含以下四部分:
            1.控制臺應用程序的main函數(shù)
            2.SERVICE入口函數(shù)ServiceMain
            3.SERVICE CONTROL HANDLER,SCM利用該函數(shù)和SERVICE通信并控制程序的起停。
            4.SERVICE安裝和卸載器

            ServiceMain和Service Control Handler
            首先我們來討論ServiceMain和Service Control Handler。WINDOWS規(guī)定每個SERVICE都擁有自己獨立的ServiceMain以及Service Control Handler函數(shù)。主程序調(diào)用StartServiceCtrlDispatcher時,WINDOWS為每個SERVICE創(chuàng)建一個線程,并且在新線程中運行ServiceMain函數(shù)。SCM利用Service Control Handler函數(shù)和SERVICE程序通信,用戶執(zhí)行start,stop,pause以及continue等操作時,SCM通過Service Control Handler函數(shù)來控制SERVICE的行為。Service Control Handler函數(shù)基本上會包含一個switch語句來處理每種情況。

            安裝/卸載SERVICE
            WINDOWS提供一些API來安裝/卸載SERVICE,這樣我們就可以不使用注冊函數(shù)就能在系統(tǒng)中注冊這些節(jié)點。這些API分別是CreateService和DeleteService。要安裝SERVICE,需要先利用函數(shù)OpenSCManager打開SCM數(shù)據(jù)庫,接著利用SERVICE的二進制文件路徑調(diào)用CreateService,在調(diào)用CreateService時需要為SERVICE指定名稱,原因是使用DeleteService刪除服務時需要利用該標識。

            ACE_NT_Service
            查看ACE源碼,其中和類 ACE_NT_Service實現(xiàn)密切相關(guān)的的文件有NT_Service.cpp、NT_Service.h、NT_Service.i。

            ACE_NT_Service中的ServiceMain和Service Control Handler
            ServiceMain和Service Control Handler定義具有固定模式,ACE_NT_Service提供宏#define ACE_NT_SERVICE_DEFINE(SVCNAME, SVCCLASS, SVCDESC)用于簡化定義。具體的宏定義可以參考ACE代碼,這里不再列出,這里只分析相關(guān)的類ACE_NT_Service的成員函數(shù)handle_control,init,open,wait和fini。函數(shù)handle_control被用于響應SERVICE DISPATCHER請求,其必須和SVC函數(shù)交互以影響請求控制操作。缺省實現(xiàn)包括SERVICE_CONTROL_STOP,SERVICE_CONTROL_PAUSE,SERVICE_CONTROL_CONTINUE,SERVICE_CONTROL_INTERROGATE,SERVICE_CONTROL_SHUTDOWN。

            函數(shù)handle_control的部分關(guān)鍵代碼解析
            /* 調(diào)用stop_requested響應關(guān)閉操作 */
            case SERVICE_CONTROL_SHUTDOWN:
            case SERVICE_CONTROL_STOP:
            this->stop_requested (control_code);
            break;
            /* 調(diào)用pause_requested響應掛起操作 */
            case SERVICE_CONTROL_PAUSE:
            this->pause_requested (control_code);
            break;
            /* 調(diào)用continue_requested響應掛起后啟動操作 */
            case SERVICE_CONTROL_CONTINUE:
            this->continue_requested (control_code);
            break;
            /* 調(diào)用interrogate_requested報告當前狀態(tài)*/
            case SERVICE_CONTROL_INTERROGATE:
            this->interrogate_requested (control_code);
            break;

            函數(shù)open 的部分關(guān)鍵代碼解析
            /* 報告狀態(tài) */
            this->report_status (SERVICE_START_PENDING, 0);
            /* 執(zhí)行用戶代碼 */
            int svc_return = this->svc ();

            函數(shù)fini 的部分關(guān)鍵代碼解析
            /* 報告狀態(tài) */
            return this->report_status (SERVICE_STOPPED, 0);

            函數(shù)stop_requested的部分關(guān)鍵代碼解析
            /* 報告狀態(tài) */
            this->report_status (SERVICE_STOP_PENDING);

            函數(shù)pause_requested的部分關(guān)鍵代碼解析
            /* 報告狀態(tài) */
            this->report_status (SERVICE_PAUSE_PENDING);
            /* 掛起*/
            this->suspend ();
            /* 報告狀態(tài) */
            this->report_status (SERVICE_PAUSED);

            函數(shù)continue_requested的部分關(guān)鍵代碼解析
            /* 報告狀態(tài) */
            this->report_status (SERVICE_CONTINUE_PENDING);
            /* 恢復*/
            this->resume ();
            /* 報告狀態(tài) */
            this->report_status (SERVICE_RUNNING);

            函數(shù)interrogate_requested的部分關(guān)鍵代碼解析
            /* 報告狀態(tài) */
            this->report_status (0);
            安裝/卸載SERVICE
            ACE_NT_Service定義兩個成員函數(shù)Insert,remove來安裝(卸載)SERVICE。它們分別在內(nèi)部調(diào)用WINDOWS API——CreateService以及DeleteService。

            Insert函數(shù)的部分關(guān)鍵代碼解析

            /* 打開和host()上SCManager的通信 */
            SC_HANDLE sc_mgr = ACE_TEXT_OpenSCManager (this->host (),……);
            /* 以名稱name() 創(chuàng)建服務 */
            SC_HANDLE sh = ACE_TEXT_CreateService (sc_mgr,this->name (),this->desc (),
            SERVICE_ALL_ACCESS,this->svc_status_.dwServiceType,start_type,
            error_control,exe_path,……);
            /* 關(guān)閉和SCManager的通信 */
            CloseServiceHandle (sc_mgr);
            /* 關(guān)閉服務句柄,重新寫入新句柄 */
            if (this->svc_sc_handle_ != 0)
            CloseServiceHandle (this->svc_sc_handle_);
            this->svc_sc_handle_ = sh;

            Remove函數(shù)部分關(guān)鍵代碼解析

            /* 從SCM中刪除insert創(chuàng)建的服務句柄 */
            if (DeleteService (this->svc_sc_handle()) == 0
            && GetLastError () != ERROR_SERVICE_MARKED_FOR_DELETE)
            控制SERVICE
            ACE_NT_Service定義成員函數(shù)start_svc, stop_svc, pause_svc, continue_svc分別用于啟動、停止、掛起和繼續(xù)服務。
            start_svc函數(shù)的部分關(guān)鍵代碼解析

            /* 啟動服務 */
            if (!ACE_TEXT_StartService (svc, argc, argv))
            this->wait_for_service_state (SERVICE_RUNNING, wait_time);

            stop_svc函數(shù)的部分關(guān)鍵代碼解析

            /* 關(guān)閉服務 */
            if (!ControlService (svc, SERVICE_CONTROL_STOP, &this->svc_status_))
            this->wait_for_service_state (SERVICE_STOPPED, wait_time);

            pause_svc函數(shù)的部分關(guān)鍵代碼解析

            /* 吊起服務 */
            if (!ControlService (svc, SERVICE_CONTROL_PAUSE,&this->svc_status_))
            this->wait_for_service_state (SERVICE_PAUSED,wait_time);

            continue_svc函數(shù)的部分關(guān)鍵代碼解析

            /* 將掛起業(yè)務重新啟動 */
            if (!ControlService (svc,SERVICE_CONTROL_CONTINUE,&this->svc_status_))
            this->wait_for_service_state (SERVICE_RUNNING,wait_time);

            一些輔助函數(shù)
            svc_sc_handle部份關(guān)鍵代碼解析

            /* 打開SCM */
            SC_HANDLE sc_mgr = ACE_TEXT_OpenSCManager (this->host (),……)
            if (sc_mgr != 0)
            {
            /* 獲取服務句柄 */
            this->svc_sc_handle_ = ACE_TEXT_OpenService (sc_mgr,……)
            /* 關(guān)閉SCM */
            CloseServiceHandle (sc_mgr);
            }
            /* 返回獲取到的服務句柄 */
            return this->svc_sc_handle_;

            wait_for_service_state部份關(guān)鍵代碼解析

            /* 獲取當前時間 */
            ACE_Time_Value time_out = ACE_OS::gettimeofday ();
            /* 加上等待時間 */
            if (wait_time != 0) time_out += *wait_time;
            // Poll until the service reaches the desired state.
            for (;
            {
            /* 查詢當前狀態(tài) */
            service_ok = 0 != QueryServiceStatus (this->svc_sc_handle_, &this->svc_status_);
            /* 如果已經(jīng)到達指定狀態(tài),退出循環(huán) */
            if (desired_state == this->svc_status_.dwCurrentState) break;
            /* 如果超出指定時間,退出循環(huán) */
            if (wait_time != 0 && ACE_OS::gettimeofday () > time_out )
            { ……
            break;
            }
            /* 睡眠等待 */
            ::Sleep (this->svc_status_.dwWaitHint);
            }

            report_status部份關(guān)鍵代碼解析
            /* 告訴系統(tǒng)服務新的狀態(tài) */
            SetServiceStatus (this->svc_handle_,&this->svc_status_) ? 0 : -1;

             

             



            Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=748930

            posted @ 2007-04-24 17:55 true 閱讀(1068) | 評論 (1)編輯 收藏

            摘要: 多線程同步技術(shù)是計算機軟件開發(fā)的重要技術(shù),本文對多線程的各種同步技術(shù)的原理和實現(xiàn)進行了初步探討。

            關(guān)鍵詞: VC++6.0; 線程同步;臨界區(qū);事件;互斥;信號量;

            正文

            使線程同步

              在程序中使用多線程時,一般很少有多個線程能在其生命期內(nèi)進行完全獨立的操作。更多的情況是一些線程進行某些處理操作,而其他的線程必須對其處理結(jié)果進行了解。正常情況下對這種處理結(jié)果的了解應當在其處理任務完成后進行。

              如果不采取適當?shù)拇胧渌€程往往會在線程處理任務結(jié)束前就去訪問處理結(jié)果,這就很有可能得到有關(guān)處理結(jié)果的錯誤了解。例如,多個線程同時訪問同一個全局變量,如果都是讀取操作,則不會出現(xiàn)問題。如果一個線程負責改變此變量的值,而其他線程負責同時讀取變量內(nèi)容,則不能保證讀取到的數(shù)據(jù)是經(jīng)過寫線程修改后的。

              為了確保讀線程讀取到的是經(jīng)過修改的變量,就必須在向變量寫入數(shù)據(jù)時禁止其他線程對其的任何訪問,直至賦值過程結(jié)束后再解除對其他線程的訪問限制。象這種保證線程能了解其他線程任務處理結(jié)束后的處理結(jié)果而采取的保護措施即為線程同步。

              線程同步是一個非常大的話題,包括方方面面的內(nèi)容。從大的方面講,線程的同步可分用戶模式的線程同步和內(nèi)核對象的線程同步兩大類。用戶模式中線程的同步方法主要有原子訪問和臨界區(qū)等方法。其特點是同步速度特別快,適合于對線程運行速度有嚴格要求的場合。

              內(nèi)核對象的線程同步則主要由事件、等待定時器、信號量以及信號燈等內(nèi)核對象構(gòu)成。由于這種同步機制使用了內(nèi)核對象,使用時必須將線程從用戶模式切換到內(nèi)核模式,而這種轉(zhuǎn)換一般要耗費近千個CPU周期,因此同步速度較慢,但在適用性上卻要遠優(yōu)于用戶模式的線程同步方式。

            臨界區(qū)

              臨界區(qū)(Critical Section)是一段獨占對某些共享資源訪問的代碼,在任意時刻只允許一個線程對共享資源進行訪問。如果有多個線程試圖同時訪問臨界區(qū),那么在有一個線程進入后其他所有試圖訪問此臨界區(qū)的線程將被掛起,并一直持續(xù)到進入臨界區(qū)的線程離開。臨界區(qū)在被釋放后,其他線程可以繼續(xù)搶占,并以此達到用原子方式操作共享資源的目的。

              臨界區(qū)在使用時以CRITICAL_SECTION結(jié)構(gòu)對象保護共享資源,并分別用EnterCriticalSection()和LeaveCriticalSection()函數(shù)去標識和釋放一個臨界區(qū)。所用到的CRITICAL_SECTION結(jié)構(gòu)對象必須經(jīng)過InitializeCriticalSection()的初始化后才能使用,而且必須確保所有線程中的任何試圖訪問此共享資源的代碼都處在此臨界區(qū)的保護之下。否則臨界區(qū)將不會起到應有的作用,共享資源依然有被破壞的可能。

            圖1 使用臨界區(qū)保持線程同步

            下面通過一段代碼展示了臨界區(qū)在保護多線程訪問的共享資源中的作用。通過兩個線程來分別對全局變量g_cArray[10]進行寫入操作,用臨界區(qū)結(jié)構(gòu)對象g_cs來保持線程的同步,并在開啟線程前對其進行初始化。為了使實驗效果更加明顯,體現(xiàn)出臨界區(qū)的作用,在線程函數(shù)對共享資源g_cArray[10]的寫入時,以Sleep()函數(shù)延遲1毫秒,使其他線程同其搶占CPU的可能性增大。如果不使用臨界區(qū)對其進行保護,則共享資源數(shù)據(jù)將被破壞(參見圖1(a)所示計算結(jié)果),而使用臨界區(qū)對線程保持同步后則可以得到正確的結(jié)果(參見圖1(b)所示計算結(jié)果)。代碼實現(xiàn)清單附下:

            // 臨界區(qū)結(jié)構(gòu)對象
            CRITICAL_SECTION g_cs;
            // 共享資源
            char g_cArray[10];
            UINT ThreadProc10(LPVOID pParam)
            {
             // 進入臨界區(qū)
             EnterCriticalSection(&g_cs);
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[i] = 'a';
              Sleep(1);
             }
             // 離開臨界區(qū)
             LeaveCriticalSection(&g_cs);
             return 0;
            }
            UINT ThreadProc11(LPVOID pParam)
            {
             // 進入臨界區(qū)
             EnterCriticalSection(&g_cs);
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[10 - i - 1] = 'b';
              Sleep(1);
             }
             // 離開臨界區(qū)
             LeaveCriticalSection(&g_cs);
             return 0;
            }
            ……
            void CSample08View::OnCriticalSection()
            {
             // 初始化臨界區(qū)
             InitializeCriticalSection(&g_cs);
             // 啟動線程
             AfxBeginThread(ThreadProc10, NULL);
             AfxBeginThread(ThreadProc11, NULL);
             // 等待計算完畢
             Sleep(300);
             // 報告計算結(jié)果
             CString sResult = CString(g_cArray);
             AfxMessageBox(sResult);
            }


              在使用臨界區(qū)時,一般不允許其運行時間過長,只要進入臨界區(qū)的線程還沒有離開,其他所有試圖進入此臨界區(qū)的線程都會被掛起而進入到等待狀態(tài),并會在一定程度上影響。程序的運行性能。尤其需要注意的是不要將等待用戶輸入或是其他一些外界干預的操作包含到臨界區(qū)。如果進入了臨界區(qū)卻一直沒有釋放,同樣也會引起其他線程的長時間等待。換句話說,在執(zhí)行了EnterCriticalSection()語句進入臨界區(qū)后無論發(fā)生什么,必須確保與之匹配的LeaveCriticalSection()都能夠被執(zhí)行到。可以通過添加結(jié)構(gòu)化異常處理代碼來確保LeaveCriticalSection()語句的執(zhí)行。雖然臨界區(qū)同步速度很快,但卻只能用來同步本進程內(nèi)的線程,而不可用來同步多個進程中的線程。

              MFC為臨界區(qū)提供有一個CCriticalSection類,使用該類進行線程同步處理是非常簡單的,只需在線程函數(shù)中用CCriticalSection類成員函數(shù)Lock()和UnLock()標定出被保護代碼片段即可。對于上述代碼,可通過CCriticalSection類將其改寫如下:

            // MFC臨界區(qū)類對象
            CCriticalSection g_clsCriticalSection;
            // 共享資源
            char g_cArray[10];
            UINT ThreadProc20(LPVOID pParam)
            {
             // 進入臨界區(qū)
             g_clsCriticalSection.Lock();
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[i] = 'a';
              Sleep(1);
             }
             // 離開臨界區(qū)
             g_clsCriticalSection.Unlock();
             return 0;
            }
            UINT ThreadProc21(LPVOID pParam)
            {
             // 進入臨界區(qū)
             g_clsCriticalSection.Lock();
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[10 - i - 1] = 'b';
              Sleep(1);
             }
             // 離開臨界區(qū)
             g_clsCriticalSection.Unlock();
             return 0;
            }
            ……
            void CSample08View::OnCriticalSectionMfc()
            {
             // 啟動線程
             AfxBeginThread(ThreadProc20, NULL);
             AfxBeginThread(ThreadProc21, NULL);
             // 等待計算完畢
             Sleep(300);
             // 報告計算結(jié)果
             CString sResult = CString(g_cArray);
             AfxMessageBox(sResult);
            }


            管理事件內(nèi)核對象

              在前面講述線程通信時曾使用過事件內(nèi)核對象來進行線程間的通信,除此之外,事件內(nèi)核對象也可以通過通知操作的方式來保持線程的同步。對于前面那段使用臨界區(qū)保持線程同步的代碼可用事件對象的線程同步方法改寫如下:

            // 事件句柄
            HANDLE hEvent = NULL;
            // 共享資源
            char g_cArray[10];
            ……
            UINT ThreadProc12(LPVOID pParam)
            {
             // 等待事件置位
             WaitForSingleObject(hEvent, INFINITE);
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[i] = 'a';
              Sleep(1);
             }
             // 處理完成后即將事件對象置位
             SetEvent(hEvent);
             return 0;
            }
            UINT ThreadProc13(LPVOID pParam)
            {
             // 等待事件置位
             WaitForSingleObject(hEvent, INFINITE);
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[10 - i - 1] = 'b';
              Sleep(1);
             }
             // 處理完成后即將事件對象置位
             SetEvent(hEvent);
             return 0;
            }
            ……
            void CSample08View::OnEvent()
            {
             // 創(chuàng)建事件
             hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
             // 事件置位
             SetEvent(hEvent);
             // 啟動線程
             AfxBeginThread(ThreadProc12, NULL);
             AfxBeginThread(ThreadProc13, NULL);
             // 等待計算完畢
             Sleep(300);
             // 報告計算結(jié)果
             CString sResult = CString(g_cArray);
             AfxMessageBox(sResult);
            }


              在創(chuàng)建線程前,首先創(chuàng)建一個可以自動復位的事件內(nèi)核對象hEvent,而線程函數(shù)則通過WaitForSingleObject()等待函數(shù)無限等待hEvent的置位,只有在事件置位時WaitForSingleObject()才會返回,被保護的代碼將得以執(zhí)行。對于以自動復位方式創(chuàng)建的事件對象,在其置位后一被WaitForSingleObject()等待到就會立即復位,也就是說在執(zhí)行ThreadProc12()中的受保護代碼時,事件對象已經(jīng)是復位狀態(tài)的,這時即使有ThreadProc13()對CPU的搶占,也會由于WaitForSingleObject()沒有hEvent的置位而不能繼續(xù)執(zhí)行,也就沒有可能破壞受保護的共享資源。在ThreadProc12()中的處理完成后可以通過SetEvent()對hEvent的置位而允許ThreadProc13()對共享資源g_cArray的處理。這里SetEvent()所起的作用可以看作是對某項特定任務完成的通知。

              使用臨界區(qū)只能同步同一進程中的線程,而使用事件內(nèi)核對象則可以對進程外的線程進行同步,其前提是得到對此事件對象的訪問權(quán)。可以通過OpenEvent()函數(shù)獲取得到,其函數(shù)原型為:

            HANDLE OpenEvent(
             DWORD dwDesiredAccess, // 訪問標志
             BOOL bInheritHandle, // 繼承標志
             LPCTSTR lpName // 指向事件對象名的指針
            );


              如果事件對象已創(chuàng)建(在創(chuàng)建事件時需要指定事件名),函數(shù)將返回指定事件的句柄。對于那些在創(chuàng)建事件時沒有指定事件名的事件內(nèi)核對象,可以通過使用內(nèi)核對象的繼承性或是調(diào)用DuplicateHandle()函數(shù)來調(diào)用CreateEvent()以獲得對指定事件對象的訪問權(quán)。在獲取到訪問權(quán)后所進行的同步操作與在同一個進程中所進行的線程同步操作是一樣的。

              如果需要在一個線程中等待多個事件,則用WaitForMultipleObjects()來等待。WaitForMultipleObjects()與WaitForSingleObject()類似,同時監(jiān)視位于句柄數(shù)組中的所有句柄。這些被監(jiān)視對象的句柄享有平等的優(yōu)先權(quán),任何一個句柄都不可能比其他句柄具有更高的優(yōu)先權(quán)。WaitForMultipleObjects()的函數(shù)原型為:

            DWORD WaitForMultipleObjects(
             DWORD nCount, // 等待句柄數(shù)
             CONST HANDLE *lpHandles, // 句柄數(shù)組首地址
             BOOL fWaitAll, // 等待標志
             DWORD dwMilliseconds // 等待時間間隔
            );


              參數(shù)nCount指定了要等待的內(nèi)核對象的數(shù)目,存放這些內(nèi)核對象的數(shù)組由lpHandles來指向。fWaitAll對指定的這nCount個內(nèi)核對象的兩種等待方式進行了指定,為TRUE時當所有對象都被通知時函數(shù)才會返回,為FALSE則只要其中任何一個得到通知就可以返回。dwMilliseconds在飫锏淖饔糜朐赪aitForSingleObject()中的作用是完全一致的。如果等待超時,函數(shù)將返回WAIT_TIMEOUT。如果返回WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1中的某個值,則說明所有指定對象的狀態(tài)均為已通知狀態(tài)(當fWaitAll為TRUE時)或是用以減去WAIT_OBJECT_0而得到發(fā)生通知的對象的索引(當fWaitAll為FALSE時)。如果返回值在WAIT_ABANDONED_0與WAIT_ABANDONED_0+nCount-1之間,則表示所有指定對象的狀態(tài)均為已通知,且其中至少有一個對象是被丟棄的互斥對象(當fWaitAll為TRUE時),或是用以減去WAIT_OBJECT_0表示一個等待正常結(jié)束的互斥對象的索引(當fWaitAll為FALSE時)。 下面給出的代碼主要展示了對WaitForMultipleObjects()函數(shù)的使用。通過對兩個事件內(nèi)核對象的等待來控制線程任務的執(zhí)行與中途退出:

            // 存放事件句柄的數(shù)組
            HANDLE hEvents[2];
            UINT ThreadProc14(LPVOID pParam)
            {
             // 等待開啟事件
             DWORD dwRet1 = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE);
             // 如果開啟事件到達則線程開始執(zhí)行任務
             if (dwRet1 == WAIT_OBJECT_0)
             {
              AfxMessageBox("線程開始工作!");
              while (true)
              {
               for (int i = 0; i < 10000; i++);
               // 在任務處理過程中等待結(jié)束事件
               DWORD dwRet2 = WaitForMultipleObjects(2, hEvents, FALSE, 0);
               // 如果結(jié)束事件置位則立即終止任務的執(zhí)行
               if (dwRet2 == WAIT_OBJECT_0 + 1)
                break;
              }
             }
             AfxMessageBox("線程退出!");
             return 0;
            }
            ……
            void CSample08View::OnStartEvent()
            {
             // 創(chuàng)建線程
             for (int i = 0; i < 2; i++)
              hEvents[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
              // 開啟線程
              AfxBeginThread(ThreadProc14, NULL);
              // 設置事件0(開啟事件)
              SetEvent(hEvents[0]);
            }
            void CSample08View::OnEndevent()
            {
             // 設置事件1(結(jié)束事件)
             SetEvent(hEvents[1]);
            }


              MFC為事件相關(guān)處理也提供了一個CEvent類,共包含有除構(gòu)造函數(shù)外的4個成員函數(shù)PulseEvent()、ResetEvent()、SetEvent()和UnLock()。在功能上分別相當與Win32 API的PulseEvent()、ResetEvent()、SetEvent()和CloseHandle()等函數(shù)。而構(gòu)造函數(shù)則履行了原CreateEvent()函數(shù)創(chuàng)建事件對象的職責,其函數(shù)原型為:

            CEvent(BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );


              按照此缺省設置將創(chuàng)建一個自動復位、初始狀態(tài)為復位狀態(tài)的沒有名字的事件對象。封裝后的CEvent類使用起來更加方便,圖2即展示了CEvent類對A、B兩線程的同步過程:

            圖2 CEvent類對線程的同步過程示意

            B線程在執(zhí)行到CEvent類成員函數(shù)Lock()時將會發(fā)生阻塞,而A線程此時則可以在沒有B線程干擾的情況下對共享資源進行處理,并在處理完成后通過成員函數(shù)SetEvent()向B發(fā)出事件,使其被釋放,得以對A先前已處理完畢的共享資源進行操作。可見,使用CEvent類對線程的同步方法與通過API函數(shù)進行線程同步的處理方法是基本一致的。前面的API處理代碼可用CEvent類將其改寫為:

            // MFC事件類對象
            CEvent g_clsEvent;
            UINT ThreadProc22(LPVOID pParam)
            {
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[i] = 'a';
              Sleep(1);
             }
             // 事件置位
             g_clsEvent.SetEvent();
             return 0;
            }
            UINT ThreadProc23(LPVOID pParam)
            {
             // 等待事件
             g_clsEvent.Lock();
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[10 - i - 1] = 'b';
              Sleep(1);
             }
             return 0;
            }
            ……
            void CSample08View::OnEventMfc()
            {
             // 啟動線程
             AfxBeginThread(ThreadProc22, NULL);
             AfxBeginThread(ThreadProc23, NULL);
             // 等待計算完畢
             Sleep(300);
             // 報告計算結(jié)果
             CString sResult = CString(g_cArray);
             AfxMessageBox(sResult);
            }

              信號量內(nèi)核對象

              信號量(Semaphore)內(nèi)核對象對線程的同步方式與前面幾種方法不同,它允許多個線程在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大線程數(shù)目。在用CreateSemaphore()創(chuàng)建信號量時即要同時指出允許的最大資源計數(shù)和當前可用資源計數(shù)。一般是將當前可用資源計數(shù)設置為最大資源計數(shù),每增加一個線程對共享資源的訪問,當前可用資源計數(shù)就會減1,只要當前可用資源計數(shù)是大于0的,就可以發(fā)出信號量信號。但是當前可用計數(shù)減小到0時則說明當前占用資源的線程數(shù)已經(jīng)達到了所允許的最大數(shù)目,不能在允許其他線程的進入,此時的信號量信號將無法發(fā)出。線程在處理完共享資源后,應在離開的同時通過ReleaseSemaphore()函數(shù)將當前可用資源計數(shù)加1。在任何時候當前可用資源計數(shù)決不可能大于最大資源計數(shù)。

            圖3 使用信號量對象控制資源

            下面結(jié)合圖例3來演示信號量對象對資源的控制。在圖3中,以箭頭和白色箭頭表示共享資源所允許的最大資源計數(shù)和當前可用資源計數(shù)。初始如圖(a)所示,最大資源計數(shù)和當前可用資源計數(shù)均為4,此后每增加一個對資源進行訪問的線程(用黑色箭頭表示)當前資源計數(shù)就會相應減1,圖(b)即表示的在3個線程對共享資源進行訪問時的狀態(tài)。當進入線程數(shù)達到4個時,將如圖(c)所示,此時已達到最大資源計數(shù),而當前可用資源計數(shù)也已減到0,其他線程無法對共享資源進行訪問。在當前占有資源的線程處理完畢而退出后,將會釋放出空間,圖(d)已有兩個線程退出對資源的占有,當前可用計數(shù)為2,可以再允許2個線程進入到對資源的處理。可以看出,信號量是通過計數(shù)來對線程訪問資源進行控制的,而實際上信號量確實也被稱作Dijkstra計數(shù)器。

              使用信號量內(nèi)核對象進行線程同步主要會用到CreateSemaphore()、OpenSemaphore()、ReleaseSemaphore()、WaitForSingleObject()和WaitForMultipleObjects()等函數(shù)。其中,CreateSemaphore()用來創(chuàng)建一個信號量內(nèi)核對象,其函數(shù)原型為:

            HANDLE CreateSemaphore(
             LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全屬性指針
             LONG lInitialCount, // 初始計數(shù)
             LONG lMaximumCount, // 最大計數(shù)
             LPCTSTR lpName // 對象名指針
            );


              參數(shù)lMaximumCount是一個有符號32位值,定義了允許的最大資源計數(shù),最大取值不能超過4294967295。lpName參數(shù)可以為創(chuàng)建的信號量定義一個名字,由于其創(chuàng)建的是一個內(nèi)核對象,因此在其他進程中可以通過該名字而得到此信號量。OpenSemaphore()函數(shù)即可用來根據(jù)信號量名打開在其他進程中創(chuàng)建的信號量,函數(shù)原型如下:

            HANDLE OpenSemaphore(
             DWORD dwDesiredAccess, // 訪問標志
             BOOL bInheritHandle, // 繼承標志
             LPCTSTR lpName // 信號量名
            );


              在線程離開對共享資源的處理時,必須通過ReleaseSemaphore()來增加當前可用資源計數(shù)。否則將會出現(xiàn)當前正在處理共享資源的實際線程數(shù)并沒有達到要限制的數(shù)值,而其他線程卻因為當前可用資源計數(shù)為0而仍無法進入的情況。ReleaseSemaphore()的函數(shù)原型為:

            BOOL ReleaseSemaphore(
             HANDLE hSemaphore, // 信號量句柄
             LONG lReleaseCount, // 計數(shù)遞增數(shù)量
             LPLONG lpPreviousCount // 先前計數(shù)
            );


              該函數(shù)將lReleaseCount中的值添加給信號量的當前資源計數(shù),一般將lReleaseCount設置為1,如果需要也可以設置其他的值。WaitForSingleObject()和WaitForMultipleObjects()主要用在試圖進入共享資源的線程函數(shù)入口處,主要用來判斷信號量的當前可用資源計數(shù)是否允許本線程的進入。只有在當前可用資源計數(shù)值大于0時,被監(jiān)視的信號量內(nèi)核對象才會得到通知。

              信號量的使用特點使其更適用于對Socket(套接字)程序中線程的同步。例如,網(wǎng)絡上的HTTP服務器要對同一時間內(nèi)訪問同一頁面的用戶數(shù)加以限制,這時可以為沒一個用戶對服務器的頁面請求設置一個線程,而頁面則是待保護的共享資源,通過使用信號量對線程的同步作用可以確保在任一時刻無論有多少用戶對某一頁面進行訪問,只有不大于設定的最大用戶數(shù)目的線程能夠進行訪問,而其他的訪問企圖則被掛起,只有在有用戶退出對此頁面的訪問后才有可能進入。下面給出的示例代碼即展示了類似的處理過程:

            // 信號量對象句柄
            HANDLE hSemaphore;
            UINT ThreadProc15(LPVOID pParam)
            {
             // 試圖進入信號量關(guān)口
             WaitForSingleObject(hSemaphore, INFINITE);
             // 線程任務處理
             AfxMessageBox("線程一正在執(zhí)行!");
             // 釋放信號量計數(shù)
             ReleaseSemaphore(hSemaphore, 1, NULL);
             return 0;
            }
            UINT ThreadProc16(LPVOID pParam)
            {
             // 試圖進入信號量關(guān)口
             WaitForSingleObject(hSemaphore, INFINITE);
             // 線程任務處理
             AfxMessageBox("線程二正在執(zhí)行!");
             // 釋放信號量計數(shù)
             ReleaseSemaphore(hSemaphore, 1, NULL);
             return 0;
            }
            UINT ThreadProc17(LPVOID pParam)
            {
             // 試圖進入信號量關(guān)口
             WaitForSingleObject(hSemaphore, INFINITE);
             // 線程任務處理
             AfxMessageBox("線程三正在執(zhí)行!");
             // 釋放信號量計數(shù)
             ReleaseSemaphore(hSemaphore, 1, NULL);
             return 0;
            }
            ……
            void CSample08View::OnSemaphore()
            {
             // 創(chuàng)建信號量對象
             hSemaphore = CreateSemaphore(NULL, 2, 2, NULL);
             // 開啟線程
             AfxBeginThread(ThreadProc15, NULL);
             AfxBeginThread(ThreadProc16, NULL);
             AfxBeginThread(ThreadProc17, NULL);
            }


            圖4 開始進入的兩個線程

            圖5 線程二退出后線程三才得以進入

            上述代碼在開啟線程前首先創(chuàng)建了一個初始計數(shù)和最大資源計數(shù)均為2的信號量對象hSemaphore。即在同一時刻只允許2個線程進入由hSemaphore保護的共享資源。隨后開啟的三個線程均試圖訪問此共享資源,在前兩個線程試圖訪問共享資源時,由于hSemaphore的當前可用資源計數(shù)分別為2和1,此時的hSemaphore是可以得到通知的,也就是說位于線程入口處的WaitForSingleObject()將立即返回,而在前兩個線程進入到保護區(qū)域后,hSemaphore的當前資源計數(shù)減少到0,hSemaphore將不再得到通知,WaitForSingleObject()將線程掛起。直到此前進入到保護區(qū)的線程退出后才能得以進入。圖4和圖5為上述代脈的運行結(jié)果。從實驗結(jié)果可以看出,信號量始終保持了同一時刻不超過2個線程的進入。

              在MFC中,通過CSemaphore類對信號量作了表述。該類只具有一個構(gòu)造函數(shù),可以構(gòu)造一個信號量對象,并對初始資源計數(shù)、最大資源計數(shù)、對象名和安全屬性等進行初始化,其原型如下:

            CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );


              在構(gòu)造了CSemaphore類對象后,任何一個訪問受保護共享資源的線程都必須通過CSemaphore從父類CSyncObject類繼承得到的Lock()和UnLock()成員函數(shù)來訪問或釋放CSemaphore對象。與前面介紹的幾種通過MFC類保持線程同步的方法類似,通過CSemaphore類也可以將前面的線程同步代碼進行改寫,這兩種使用信號量的線程同步方法無論是在實現(xiàn)原理上還是從實現(xiàn)結(jié)果上都是完全一致的。下面給出經(jīng)MFC改寫后的信號量線程同步代碼:

            // MFC信號量類對象
            CSemaphore g_clsSemaphore(2, 2);
            UINT ThreadProc24(LPVOID pParam)
            {
             // 試圖進入信號量關(guān)口
             g_clsSemaphore.Lock();
             // 線程任務處理
             AfxMessageBox("線程一正在執(zhí)行!");
             // 釋放信號量計數(shù)
             g_clsSemaphore.Unlock();
             return 0;
            }
            UINT ThreadProc25(LPVOID pParam)
            {
             // 試圖進入信號量關(guān)口
             g_clsSemaphore.Lock();
             // 線程任務處理
             AfxMessageBox("線程二正在執(zhí)行!");
             // 釋放信號量計數(shù)
             g_clsSemaphore.Unlock();
             return 0;
            }
            UINT ThreadProc26(LPVOID pParam)
            {
             // 試圖進入信號量關(guān)口
             g_clsSemaphore.Lock();
             // 線程任務處理
             AfxMessageBox("線程三正在執(zhí)行!");
             // 釋放信號量計數(shù)
             g_clsSemaphore.Unlock();
             return 0;
            }
            ……
            void CSample08View::OnSemaphoreMfc()
            {
             // 開啟線程
             AfxBeginThread(ThreadProc24, NULL);
             AfxBeginThread(ThreadProc25, NULL);
             AfxBeginThread(ThreadProc26, NULL);
            }

              互斥內(nèi)核對象

              互斥(Mutex)是一種用途非常廣泛的內(nèi)核對象。能夠保證多個線程對同一共享資源的互斥訪問。同臨界區(qū)有些類似,只有擁有互斥對象的線程才具有訪問資源的權(quán)限,由于互斥對象只有一個,因此就決定了任何情況下此共享資源都不會同時被多個線程所訪問。當前占據(jù)資源的線程在任務處理完后應將擁有的互斥對象交出,以便其他線程在獲得后得以訪問資源。與其他幾種內(nèi)核對象不同,互斥對象在操作系統(tǒng)中擁有特殊代碼,并由操作系統(tǒng)來管理,操作系統(tǒng)甚至還允許其進行一些其他內(nèi)核對象所不能進行的非常規(guī)操作。為便于理解,可參照圖6給出的互斥內(nèi)核對象的工作模型:

            圖6 使用互斥內(nèi)核對象對共享資源的保護
            圖(a)中的箭頭為要訪問資源(矩形框)的線程,但只有第二個線程擁有互斥對象(黑點)并得以進入到共享資源,而其他線程則會被排斥在外(如圖(b)所示)。當此線程處理完共享資源并準備離開此區(qū)域時將把其所擁有的互斥對象交出(如圖(c)所示),其他任何一個試圖訪問此資源的線程都有機會得到此互斥對象。

              以互斥內(nèi)核對象來保持線程同步可能用到的函數(shù)主要有CreateMutex()、OpenMutex()、ReleaseMutex()、WaitForSingleObject()和WaitForMultipleObjects()等。在使用互斥對象前,首先要通過CreateMutex()或OpenMutex()創(chuàng)建或打開一個互斥對象。CreateMutex()函數(shù)原型為:

            HANDLE CreateMutex(
             LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全屬性指針
             BOOL bInitialOwner, // 初始擁有者
             LPCTSTR lpName // 互斥對象名
            );

              參數(shù)bInitialOwner主要用來控制互斥對象的初始狀態(tài)。一般多將其設置為FALSE,以表明互斥對象在創(chuàng)建時并沒有為任何線程所占有。如果在創(chuàng)建互斥對象時指定了對象名,那么可以在本進程其他地方或是在其他進程通過OpenMutex()函數(shù)得到此互斥對象的句柄。OpenMutex()函數(shù)原型為:

            HANDLE OpenMutex(
             DWORD dwDesiredAccess, // 訪問標志
             BOOL bInheritHandle, // 繼承標志
             LPCTSTR lpName // 互斥對象名
            );

              當目前對資源具有訪問權(quán)的線程不再需要訪問此資源而要離開時,必須通過ReleaseMutex()函數(shù)來釋放其擁有的互斥對象,其函數(shù)原型為:

            BOOL ReleaseMutex(HANDLE hMutex);

              其唯一的參數(shù)hMutex為待釋放的互斥對象句柄。至于WaitForSingleObject()和WaitForMultipleObjects()等待函數(shù)在互斥對象保持線程同步中所起的作用與在其他內(nèi)核對象中的作用是基本一致的,也是等待互斥內(nèi)核對象的通知。但是這里需要特別指出的是:在互斥對象通知引起調(diào)用等待函數(shù)返回時,等待函數(shù)的返回值不再是通常的WAIT_OBJECT_0(對于WaitForSingleObject()函數(shù))或是在WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1之間的一個值(對于WaitForMultipleObjects()函數(shù)),而是將返回一個WAIT_ABANDONED_0(對于WaitForSingleObject()函數(shù))或是在WAIT_ABANDONED_0到WAIT_ABANDONED_0+nCount-1之間的一個值(對于WaitForMultipleObjects()函數(shù))。以此來表明線程正在等待的互斥對象由另外一個線程所擁有,而此線程卻在使用完共享資源前就已經(jīng)終止。除此之外,使用互斥對象的方法在等待線程的可調(diào)度性上同使用其他幾種內(nèi)核對象的方法也有所不同,其他內(nèi)核對象在沒有得到通知時,受調(diào)用等待函數(shù)的作用,線程將會掛起,同時失去可調(diào)度性,而使用互斥的方法卻可以在等待的同時仍具有可調(diào)度性,這也正是互斥對象所能完成的非常規(guī)操作之一。

              在編寫程序時,互斥對象多用在對那些為多個線程所訪問的內(nèi)存塊的保護上,可以確保任何線程在處理此內(nèi)存塊時都對其擁有可靠的獨占訪問權(quán)。下面給出的示例代碼即通過互斥內(nèi)核對象hMutex對共享內(nèi)存快g_cArray[]進行線程的獨占訪問保護。下面給出實現(xiàn)代碼清單:

            // 互斥對象
            HANDLE hMutex = NULL;
            char g_cArray[10];
            UINT ThreadProc18(LPVOID pParam)
            {
             // 等待互斥對象通知
             WaitForSingleObject(hMutex, INFINITE);
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[i] = 'a';
              Sleep(1);
             }
             // 釋放互斥對象
             ReleaseMutex(hMutex);
             return 0;
            }
            UINT ThreadProc19(LPVOID pParam)
            {
             // 等待互斥對象通知
             WaitForSingleObject(hMutex, INFINITE);
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[10 - i - 1] = 'b';
              Sleep(1);
             }
             // 釋放互斥對象
             ReleaseMutex(hMutex);
             return 0;
            }
            ……
            void CSample08View::OnMutex()
            {
             // 創(chuàng)建互斥對象
             hMutex = CreateMutex(NULL, FALSE, NULL);
             // 啟動線程
             AfxBeginThread(ThreadProc18, NULL);
             AfxBeginThread(ThreadProc19, NULL);
             // 等待計算完畢
             Sleep(300);
             // 報告計算結(jié)果
             CString sResult = CString(g_cArray);
             AfxMessageBox(sResult);
            }

              互斥對象在MFC中通過CMutex類進行表述。使用CMutex類的方法非常簡單,在構(gòu)造CMutex類對象的同時可以指明待查詢的互斥對象的名字,在構(gòu)造函數(shù)返回后即可訪問此互斥變量。CMutex類也是只含有構(gòu)造函數(shù)這唯一的成員函數(shù),當完成對互斥對象保護資源的訪問后,可通過調(diào)用從父類CSyncObject繼承的UnLock()函數(shù)完成對互斥對象的釋放。CMutex類構(gòu)造函數(shù)原型為:

            CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );

              該類的適用范圍和實現(xiàn)原理與API方式創(chuàng)建的互斥內(nèi)核對象是完全類似的,但要簡潔的多,下面給出就是對前面的示例代碼經(jīng)CMutex類改寫后的程序?qū)崿F(xiàn)清單:

            // MFC互斥類對象
            CMutex g_clsMutex(FALSE, NULL);
            UINT ThreadProc27(LPVOID pParam)
            {
             // 等待互斥對象通知
             g_clsMutex.Lock();
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[i] = 'a';
              Sleep(1);
             }
             // 釋放互斥對象
             g_clsMutex.Unlock();
             return 0;
            }
            UINT ThreadProc28(LPVOID pParam)
            {
             // 等待互斥對象通知
             g_clsMutex.Lock();
             // 對共享資源進行寫入操作
             for (int i = 0; i < 10; i++)
             {
              g_cArray[10 - i - 1] = 'b';
              Sleep(1);
             }
             // 釋放互斥對象
             g_clsMutex.Unlock();
             return 0;
            }
            ……
            void CSample08View::OnMutexMfc()
            {
             // 啟動線程
             AfxBeginThread(ThreadProc27, NULL);
             AfxBeginThread(ThreadProc28, NULL);
             // 等待計算完畢
             Sleep(300);
             // 報告計算結(jié)果
             CString sResult = CString(g_cArray);
             AfxMessageBox(sResult);
            }

              小結(jié)

              線程的使用使程序處理更夠更加靈活,而這種靈活同樣也會帶來各種不確定性的可能。尤其是在多個線程對同一公共變量進行訪問時。雖然未使用線程同步的程序代碼在邏輯上或許沒有什么問題,但為了確保程序的正確、可靠運行,必須在適當?shù)膱龊喜扇【€程同步措施。

            posted @ 2007-04-23 14:59 true 閱讀(260) | 評論 (0)編輯 收藏

            第二章
            1.接口定義語言支持繼承,僅定義接口,和使用的數(shù)據(jù)類型,沒有控制結(jié)構(gòu),不能編譯,需映射到其它語言如C++,
            2.語言映射,將接口映射到某具體語言
            3.操作激活和分派設施
              CORBA應用通過在CORBA對象上激活請求或接收請求而工作。
              靜態(tài)激活分配和動態(tài)激活分配。前者可以看成是客戶端的本地代理,為很多開發(fā)者使用,
              它更接近與自然編程模型,后者在網(wǎng)關(guān),網(wǎng)橋應用較多,他們不需要編譯時信息,只需
              接收和轉(zhuǎn)發(fā)請求。。
            4.對象適配器
               是servants和ORB的粘合劑,與設計模型領(lǐng)域的對象適配器是一回事。
               CORBA對象適配器滿足三個條件:
                 1.創(chuàng)建對象引用,允許客戶查找對象
                 2.確保每個目標對象由一個servant實例化
                 3.接收server端ORB分派的請求,進一步轉(zhuǎn)發(fā)到實例化目標對象的servants
            5.請求激活有如下特點:
             位置透明性:客戶不需要知道目標對象的運行地址空間,可能是通過網(wǎng)絡運行在另一臺機器上
             服務器透明性:客戶不需要知道那個服務器在提供服務
             語言獨立性:客戶不需要知道服務器端用的語言
             實現(xiàn)獨立性:客戶不需要知道服務器是然后實現(xiàn)對象的
             架構(gòu)獨立性:客戶不需要知道服務器的服務器架構(gòu),不用關(guān)注字節(jié)序等問題
             OS獨立性:客戶不需要知道服務器的Os類型
             協(xié)議獨立性:不需要知道使用的傳輸協(xié)議
             傳輸獨立性:客戶不需知道傳輸消息時的鏈路層等信息
            6.對象引用語義
             
            posted @ 2007-04-17 18:22 true 閱讀(466) | 評論 (0)編輯 收藏

            做東西設計不要太復雜,復雜往往費力不討好,要善于和leader的打交道,設計說起來容易,但做起來細節(jié)很多。要爭取學習的時間。
            posted @ 2007-04-17 11:31 true 閱讀(243) | 評論 (0)編輯 收藏

            /C++中的日期和時間

            C/C++中的日期和時間
            作者:日期和時間 出處:日期和時間 更新時間: 2005年09月15日
            摘要:
            本文從介紹基礎(chǔ)概念入手,探討了在C/C++中對日期和時間操作所用到的數(shù)據(jù)結(jié)構(gòu)和函數(shù),并對計時、時間的獲取、時間的計算和顯示格式等方面進行了闡述。本文還通過大量的實例向你展示了time.h頭文件中聲明的各種函數(shù)和數(shù)據(jù)結(jié)構(gòu)的詳細使用方法。

            關(guān)鍵字:UTC(世界標準時間),Calendar Time(日歷時間),epoch(時間點),clock tick(時鐘計時單元)

            1.概念
            在C/C++中,對字符串的操作有很多值得注意的問題,同樣,C/C++對時間的操作也有許多值得大家注意的地方。最近,在技術(shù)群中有很多網(wǎng)友也多次問到過C++語言中對時間的操作、獲取和顯示等等的問題。下面,在這篇文章中,筆者將主要介紹在C/C++中時間和日期的使用方法.

            通過學習許多C/C++庫,你可以有很多操作、使用時間的方法。但在這之前你需要了解一些“時間”和“日期”的概念,主要有以下幾個:

            Coordinated Universal Time(UTC):協(xié)調(diào)世界時,又稱為世界標準時間,也就是大家所熟知的格林威治標準時間(Greenwich Mean Time,GMT)。比如,中國內(nèi)地的時間與UTC的時差為+8,也就是UTC+8。美國是UTC-5。

            Calendar Time:日歷時間,是用“從一個標準時間點到此時的時間經(jīng)過的秒數(shù)”來表示的時間。這個標準時間點對不同的編譯器來說會有所不同,但對一個編譯系統(tǒng)來說,這個標準時間點是不變的,該編譯系統(tǒng)中的時間對應的日歷時間都通過該標準時間點來衡量,所以可以說日歷時間是“相對時間”,但是無論你在哪一個時區(qū),在同一時刻對同一個標準時間點來說,日歷時間都是一樣的。

            epoch:時間點。時間點在標準C/C++中是一個整數(shù),它用此時的時間和標準時間點相差的秒數(shù)(即日歷時間)來表示。

            clock tick:時鐘計時單元(而不把它叫做時鐘滴答次數(shù)),一個時鐘計時單元的時間長短是由CPU控制的。一個clock tick不是CPU的一個時鐘周期,而是C/C++的一個基本計時單位。

            我們可以使用ANSI標準庫中的time.h頭文件。這個頭文件中定義的時間和日期所使用的方法,無論是在結(jié)構(gòu)定義,還是命名,都具有明顯的C語言風格。下面,我將說明在C/C++中怎樣使用日期的時間功能。

            2. 計時

            C/C++中的計時函數(shù)是clock(),而與其相關(guān)的數(shù)據(jù)類型是clock_t。在MSDN中,查得對clock函數(shù)定義如下:

            clock_t clock( void );

            這個函數(shù)返回從“開啟這個程序進程”到“程序中調(diào)用clock()函數(shù)”時之間的CPU時鐘計時單元(clock tick)數(shù),在MSDN中稱之為掛鐘時間(wal-clock)。其中clock_t是用來保存時間的數(shù)據(jù)類型,在time.h文件中,我們可以找到對它的定義:

            #ifndef _CLOCK_T_DEFINED
            typedef long clock_t;
            #define _CLOCK_T_DEFINED
            #endif

            很明顯,clock_t是一個長整形數(shù)。在time.h文件中,還定義了一個常量CLOCKS_PER_SEC,它用來表示一秒鐘會有多少個時鐘計時單元,其定義如下:

            #define CLOCKS_PER_SEC ((clock_t)1000)

            可以看到每過千分之一秒(1毫秒),調(diào)用clock()函數(shù)返回的值就加1。下面舉個例子,你可以使用公式clock()/CLOCKS_PER_SEC來計算一個進程自身的運行時間:

            void elapsed_time()
            {
            printf("Elapsed time:%u secs.\n",clock()/CLOCKS_PER_SEC);
            }

            當然,你也可以用clock函數(shù)來計算你的機器運行一個循環(huán)或者處理其它事件到底花了多少時間:

            #include “stdio.h”
            #include “stdlib.h”
            #include “time.h”

            int main( void )
            {
            long i = 10000000L;
            clock_t start, finish;
            double duration;
            /* 測量一個事件持續(xù)的時間*/
            printf( "Time to do %ld empty loops is ", i );
            start = clock();
            while( i-- )
            finish = clock();
            duration = (double)(finish - start) / CLOCKS_PER_SEC;
            printf( "%f seconds\n", duration );
            system("pause");
            }

            在筆者的機器上,運行結(jié)果如下:

            Time to do 10000000 empty loops is 0.03000 seconds

            上面我們看到時鐘計時單元的長度為1毫秒,那么計時的精度也為1毫秒,那么我們可不可以通過改變CLOCKS_PER_SEC的定義,通過把它定義的大一些,從而使計時精度更高呢?通過嘗試,你會發(fā)現(xiàn)這樣是不行的。在標準C/C++中,最小的計時單位是一毫秒。

            3.與日期和時間相關(guān)的數(shù)據(jù)結(jié)構(gòu)

            在標準C/C++中,我們可通過tm結(jié)構(gòu)來獲得日期和時間,tm結(jié)構(gòu)在time.h中的定義如下:

            #ifndef _TM_DEFINED
            struct tm {
            int tm_sec; /* 秒 – 取值區(qū)間為[0,59] */
            int tm_min; /* 分 - 取值區(qū)間為[0,59] */
            int tm_hour; /* 時 - 取值區(qū)間為[0,23] */
            int tm_mday; /* 一個月中的日期 - 取值區(qū)間為[1,31] */
            int tm_mon; /* 月份(從一月開始,0代表一月) - 取值區(qū)間為[0,11] */
            int tm_year; /* 年份,其值等于實際年份減去1900 */
            int tm_wday; /* 星期 – 取值區(qū)間為[0,6],其中0代表星期天,1代表星期一,以此類推 */
            int tm_yday; /* 從每年的1月1日開始的天數(shù) – 取值區(qū)間為[0,365],其中0代表1月1日,1代表1月2日,以此類推 */
            int tm_isdst; /* 夏令時標識符,實行夏令時的時候,tm_isdst為正。不實行夏令時的進候,tm_isdst為0;不了解情況時,tm_isdst()為負。*/
            };
            #define _TM_DEFINED
            #endif

            ANSI C標準稱使用tm結(jié)構(gòu)的這種時間表示為分解時間(broken-down time)。

            而日歷時間(Calendar Time)是通過time_t數(shù)據(jù)類型來表示的,用time_t表示的時間(日歷時間)是從一個時間點(例如:1970年1月1日0時0分0秒)到此時的秒數(shù)。在time.h中,我們也可以看到time_t是一個長整型數(shù):

            #ifndef _TIME_T_DEFINED
            typedef long time_t; /* 時間值 */
            #define _TIME_T_DEFINED /* 避免重復定義 time_t */
            #endif

            大家可能會產(chǎn)生疑問:既然time_t實際上是長整型,到未來的某一天,從一個時間點(一般是1970年1月1日0時0分0秒)到那時的秒數(shù)(即日歷時間)超出了長整形所能表示的數(shù)的范圍怎么辦?對time_t數(shù)據(jù)類型的值來說,它所表示的時間不能晚于2038年1月18日19時14分07秒。為了能夠表示更久遠的時間,一些編譯器廠商引入了64位甚至更長的整形數(shù)來保存日歷時間。比如微軟在Visual C++中采用了__time64_t數(shù)據(jù)類型來保存日歷時間,并通過_time64()函數(shù)來獲得日歷時間(而不是通過使用32位字的time()函數(shù)),這樣就可以通過該數(shù)據(jù)類型保存3001年1月1日0時0分0秒(不包括該時間點)之前的時間。

            在time.h頭文件中,我們還可以看到一些函數(shù),它們都是以time_t為參數(shù)類型或返回值類型的函數(shù):

            double difftime(time_t time1, time_t time0);
            time_t mktime(struct tm * timeptr);
            time_t time(time_t * timer);
            char * asctime(const struct tm * timeptr);
            char * ctime(const time_t *timer);

            此外,time.h還提供了兩種不同的函數(shù)將日歷時間(一個用time_t表示的整數(shù))轉(zhuǎn)換為我們平時看到的把年月日時分秒分開顯示的時間格式tm:

            struct tm * gmtime(const time_t *timer);
            struct tm * localtime(const time_t * timer);

            通過查閱MSDN,我們可以知道Microsoft C/C++ 7.0中時間點的值(time_t對象的值)是從1899年12月31日0時0分0秒到該時間點所經(jīng)過的秒數(shù),而其它各種版本的Microsoft C/C++和所有不同版本的Visual C++都是計算的從1970年1月1日0時0分0秒到該時間點所經(jīng)過的秒數(shù)。

            4.與日期和時間相關(guān)的函數(shù)及應用
            在本節(jié),我將向大家展示怎樣利用time.h中聲明的函數(shù)對時間進行操作。這些操作包括取當前時間、計算時間間隔、以不同的形式顯示時間等內(nèi)容。

            4.1 獲得日歷時間

            我們可以通過time()函數(shù)來獲得日歷時間(Calendar Time),其原型為:

            time_t time(time_t * timer);

            如果你已經(jīng)聲明了參數(shù)timer,你可以從參數(shù)timer返回現(xiàn)在的日歷時間,同時也可以通過返回值返回現(xiàn)在的日歷時間,即從一個時間點(例如:1970年1月1日0時0分0秒)到現(xiàn)在此時的秒數(shù)。如果參數(shù)為空(NUL),函數(shù)將只通過返回值返回現(xiàn)在的日歷時間,比如下面這個例子用來顯示當前的日歷時間:

            #include "time.h"
            #include "stdio.h"
            int main(void)
            {
            struct tm *ptr;
            time_t lt;
            lt =time(NUL);
            printf("The Calendar Time now is %d\n",lt);
            return 0;
            }

            運行的結(jié)果與當時的時間有關(guān),我當時運行的結(jié)果是:

            The Calendar Time now is 1122707619

            其中1122707619就是我運行程序時的日歷時間。即從1970年1月1日0時0分0秒到此時的秒數(shù)。

            4.2 獲得日期和時間

            這里說的日期和時間就是我們平時所說的年、月、日、時、分、秒等信息。從第2節(jié)我們已經(jīng)知道這些信息都保存在一個名為tm的結(jié)構(gòu)體中,那么如何將一個日歷時間保存為一個tm結(jié)構(gòu)的對象呢?

            其中可以使用的函數(shù)是gmtime()和localtime(),這兩個函數(shù)的原型為:

            struct tm * gmtime(const time_t *timer);
            struct tm * localtime(const time_t * timer);

            其中g(shù)mtime()函數(shù)是將日歷時間轉(zhuǎn)化為世界標準時間(即格林尼治時間),并返回一個tm結(jié)構(gòu)體來保存這個時間,而localtime()函數(shù)是將日歷時間轉(zhuǎn)化為本地時間。比如現(xiàn)在用gmtime()函數(shù)獲得的世界標準時間是2005年7月30日7點18分20秒,那么我用localtime()函數(shù)在中國地區(qū)獲得的本地時間會比世界標準時間晚8個小時,即2005年7月30日15點18分20秒。下面是個例子:

            #include "time.h"
            #include "stdio.h"
            int main(void)
            {
            struct tm *local;
            time_t t;
            t=time(NUL);
            local=localtime(&t);
            printf("Local hour is: %d\n",local->tm_hour);
            local=gmtime(&t);
            printf("UTC hour is: %d\n",local->tm_hour);
            return 0;
            }

            運行結(jié)果是:

            Local hour is: 15
            UTC hour is: 7

            4.3 固定的時間格式

            我們可以通過asctime()函數(shù)和ctime()函數(shù)將時間以固定的格式顯示出來,兩者的返回值都是char*型的字符串。返回的時間格式為:

            星期幾 月份 日期 時:分:秒 年\n\0
            例如:Wed Jan 02 02:03:55 1980\n\0

            其中\(zhòng)n是一個換行符,\0是一個空字符,表示字符串結(jié)束。下面是兩個函數(shù)的原型:

            char * asctime(const struct tm * timeptr);
            char * ctime(const time_t *timer);

            其中asctime()函數(shù)是通過tm結(jié)構(gòu)來生成具有固定格式的保存時間信息的字符串,而ctime()是通過日歷時間來生成時間字符串。這樣的話,asctime()函數(shù)只是把tm結(jié)構(gòu)對象中的各個域填到時間字符串的相應位置就行了,而ctime()函數(shù)需要先參照本地的時間設置,把日歷時間轉(zhuǎn)化為本地時間,然后再生成格式化后的字符串。在下面,如果t是一個非空的time_t變量的話,那么:

            printf(ctime(&t));

            等價于:

            struct tm *ptr;
            ptr=localtime(&t);
            printf(asctime(ptr));

            那么,下面這個程序的兩條printf語句輸出的結(jié)果就是不同的了(除非你將本地時區(qū)設為世界標準時間所在的時區(qū)):

            #include "time.h"
            #include "stdio.h"
            int main(void)
            {
            struct tm *ptr;
            time_t lt;
            lt =time(NUL);
            ptr=gmtime(<);
            printf(asctime(ptr));
            printf(ctime(<));
            return 0;
            }

            運行結(jié)果:

            Sat Jul 30 08:43:03 2005
            Sat Jul 30 16:43:03 2005

            4.4 自定義時間格式

            我們可以使用strftime()函數(shù)將時間格式化為我們想要的格式。它的原型如下:

            size_t strftime(
            char *strDest,
            size_t maxsize,
            const char *format,
            const struct tm *timeptr
            );

            我們可以根據(jù)format指向字符串中格式命令把timeptr中保存的時間信息放在strDest指向的字符串中,最多向strDest中存放maxsize個字符。該函數(shù)返回向strDest指向的字符串中放置的字符數(shù)。

            函數(shù)strftime()的操作有些類似于sprintf():識別以百分號(%)開始的格式命令集合,格式化輸出結(jié)果放在一個字符串中。格式化命令說明串strDest中各種日期和時間信息的確切表示方法。格式串中的其他字符原樣放進串中。格式命令列在下面,它們是區(qū)分大小寫的。

            %a 星期幾的簡寫
            %A 星期幾的全稱
            %b 月分的簡寫
            %B 月份的全稱
            %c 標準的日期的時間串
            %C 年份的后兩位數(shù)字
            %d 十進制表示的每月的第幾天
            %D 月/天/年
            %e 在兩字符域中,十進制表示的每月的第幾天
            %F 年-月-日
            %g 年份的后兩位數(shù)字,使用基于周的年
            %G 年分,使用基于周的年
            %h 簡寫的月份名
            %H 24小時制的小時
            %I 12小時制的小時
            %j 十進制表示的每年的第幾天
            %m 十進制表示的月份
            %M 十時制表示的分鐘數(shù)
            %n 新行符
            %p 本地的AM或PM的等價顯示
            %r 12小時的時間
            %R 顯示小時和分鐘:hh:mm
            %S 十進制的秒數(shù)
            %t 水平制表符
            %T 顯示時分秒:hh:mm:ss
            %u 每周的第幾天,星期一為第一天 (值從0到6,星期一為0)
            %U 第年的第幾周,把星期日做為第一天(值從0到53)
            %V 每年的第幾周,使用基于周的年
            %w 十進制表示的星期幾(值從0到6,星期天為0)
            %W 每年的第幾周,把星期一做為第一天(值從0到53)
            %x 標準的日期串
            %X 標準的時間串
            %y 不帶世紀的十進制年份(值從0到99)
            %Y 帶世紀部分的十進制年份
            %z,%Z 時區(qū)名稱,如果不能得到時區(qū)名稱則返回空字符。
            %% 百分號

            如果想顯示現(xiàn)在是幾點了,并以12小時制顯示,就象下面這段程序:

            #include “time.h”
            #include “stdio.h”
            int main(void)
            {
            struct tm *ptr;
            time_t lt;
            char str[80];
            lt=time(NUL);
            ptr=localtime(<);
            strftime(str,100,"It is now %I %p",ptr);
            printf(str);
            return 0;
            }

            其運行結(jié)果為:
            It is now 4PM

            而下面的程序則顯示當前的完整日期:

            #include <stdio.h>
            #include <time.h>

            void main( void )
            {
            struct tm *newtime;
            char tmpbuf[128];
            time_t lt1;
            time( <1 );
            newtime=localtime(<1);
            strftime( tmpbuf, 128, "Today is %A, day %d of %B in the year %Y.\n", newtime);
            printf(tmpbuf);
            }

            運行結(jié)果:

            Today is Saturday, day 30 of July in the year 2005.

            4.5 計算持續(xù)時間的長度

            有時候在實際應用中要計算一個事件持續(xù)的時間長度,比如計算打字速度。在第1節(jié)計時部分中,我已經(jīng)用clock函數(shù)舉了一個例子。Clock()函數(shù)可以精確到毫秒級。同時,我們也可以使用difftime()函數(shù),但它只能精確到秒。該函數(shù)的定義如下:

            double difftime(time_t time1, time_t time0);

            雖然該函數(shù)返回的以秒計算的時間間隔是double類型的,但這并不說明該時間具有同double一樣的精確度,這是由它的參數(shù)覺得的(time_t是以秒為單位計算的)。比如下面一段程序:

            #include "time.h"
            #include "stdio.h"
            #include "stdlib.h"
            int main(void)
            {
            time_t start,end;
            start = time(NUL);
            system("pause");
            end = time(NUL);
            printf("The pause used %f seconds.\n",difftime(end,start));//<-
            system("pause");
            return 0;
            }

            運行結(jié)果為:
            請按任意鍵繼續(xù). . .
            The pause used 2.000000 seconds.
            請按任意鍵繼續(xù). . .

            可以想像,暫停的時間并不那么巧是整整2秒鐘。其實,你將上面程序的帶有“//<-”注釋的一行用下面的一行代碼替換:

            printf("The pause used %f seconds.\n",end-start);

            其運行結(jié)果是一樣的。

            4.6 分解時間轉(zhuǎn)化為日歷時間

            這里說的分解時間就是以年、月、日、時、分、秒等分量保存的時間結(jié)構(gòu),在C/C++中是tm結(jié)構(gòu)。我們可以使用mktime()函數(shù)將用tm結(jié)構(gòu)表示的時間轉(zhuǎn)化為日歷時間。其函數(shù)原型如下:

            time_t mktime(struct tm * timeptr);

            其返回值就是轉(zhuǎn)化后的日歷時間。這樣我們就可以先制定一個分解時間,然后對這個時間進行操作了,下面的例子可以計算出1997年7月1日是星期幾:

            #include "time.h"
            #include "stdio.h"
            #include "stdlib.h"
            int main(void)
            {
            struct tm t;
            time_t t_of_day;
            t.tm_year=1997-1900;
            t.tm_mon=6;
            t.tm_mday=1;
            t.tm_hour=0;
            t.tm_min=0;
            t.tm_sec=1;
            t.tm_isdst=0;
            t_of_day=mktime(&t);
            printf(ctime(&t_of_day));
            return 0;
            }

            運行結(jié)果:

            Tue Jul 01 00:00:01 1997

            現(xiàn)在注意了,有了mktime()函數(shù),是不是我們可以操作現(xiàn)在之前的任何時間呢?你可以通過這種辦法算出1945年8月15號是星期幾嗎?答案是否定的。因為這個時間在1970年1月1日之前,所以在大多數(shù)編譯器中,這樣的程序雖然可以編譯通過,但運行時會異常終止。

            5.總結(jié)

            本文介紹了標準C/C++中的有關(guān)日期和時間的概念,并通過各種實例講述了這些函數(shù)和數(shù)據(jù)結(jié)構(gòu)的使用方法。筆者認為,和時間相關(guān)的一些概念是相當重要的,理解這些概念是理解各種時間格式的轉(zhuǎn)換的基礎(chǔ),更是應用這些函數(shù)和數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ)。


             上面是轉(zhuǎn)的,本人自己再加點,以備后用:

            /* //時間格式化為2007.07.02 14:50
             time_t tm_now ;
             time(&tm_now) ;
             char szTime[64]="";
             strftime(szTime,64,"%Y.%m.%d %H:%M",localtime(&tm_now));
             cout << szTime << endl;
             */

            posted @ 2007-04-12 12:04 true 閱讀(739) | 評論 (1)編輯 收藏

            使用 <multimap> 庫創(chuàng)建重復鍵關(guān)聯(lián)容器

            作者:Danny Kalev
            編譯:TT 工作室

            原文出處:Use multimap to Create Associative Containers with Duplicate Keys

            摘要:標準庫的  multimap 容器與 map 關(guān)聯(lián)容器非常類似——但是,multimap 允許重復鍵。這個特性使得 multimap 比想象的要有用得多。本文將對之進行探討。



              在“使用 <map> 庫創(chuàng)建關(guān)聯(lián)容器”一文中,我們討論了標準庫中的 map 關(guān)聯(lián)容器。但那只是 map 容器的一部分。標準庫還定義了一個 multimap 容器,它與 map 類似,所不同的是它允許重復鍵。這個屬性使得 multimap 比預想的要更有用:比如在電話簿中相同的人可以有兩個以上電話號碼,文件系統(tǒng)中可以將多個符號鏈接映射到相同的物理文件,或DNS服務器可以將幾個URLs映射到相同的IP地址。在這些場合,你可以象下面這樣:
            // 注: 偽碼
                        multimap <string, string> phonebook;
                        phonebook.insert("Harry","8225687"); // 家里電話
                        phonebook.insert("Harry","555123123"); // 單位電話
                        phonebook.insert("Harry"," 2532532532"); // 移動電話

              在 multimap 中能存儲重復鍵的能力大大地影響它的接口和使用。那么如何創(chuàng)建非唯一鍵的關(guān)聯(lián)容器呢?答案是使用在 <map> 庫中定義的 multimap 容器。

            提出問題
              與 map 不同,multimap 可以包含重復鍵。這就帶來一個問題:重載下標操作符如何返回相同鍵的多個關(guān)聯(lián)值?以下面的偽碼為例:

            string phone=phonebook["Harry];

              標準庫設計者的解決這個問題方法是從 multimap 中去掉下標操作符。因此,需要用不同的方法來插入和獲取元素以及和進行錯誤處理。

            插入
              假設你需要開發(fā)一個 DNS 后臺程序(也就是 Windows 系統(tǒng)中的服務程序),該程序?qū)?IP 地址映射匹配的 URL 串。你知道在某些情況下,相同的 IP 地址要被關(guān)聯(lián)到多個 URLs。這些 URLs 全都指向相同的站點。在這種情況下,你應該使用 multimap,而不是 map。例如:

            #include <map>
                        #include <string>
                        multimap <string, string> DNS_daemon;

              用 insert() 成員函數(shù)而不是下標操作符來插入元素。insert()有一個 pair 類型的參數(shù)。在“使用 <map> 庫創(chuàng)建關(guān)聯(lián)容器”中我們示范了如何使用 make_pair() 輔助函數(shù)來完成此任務。你也可以象下面這樣使用它:

            DNS_daemon.insert(make_pair("213.108.96.7","cppzone.com"));

              在上面的 insert()調(diào)用中,串 “213.108.96.7”是鍵,“cppzone.com”是其關(guān)聯(lián)的值。以后插入的是相同的鍵,不同的關(guān)聯(lián)值:

            DNS_daemon.insert(make_pair("213.108.96.7","cppluspluszone.com"));

              因此,DNS_daemon 包含兩個用相同鍵值的元素。注意 multimap::insert() 和 map::insert() 返回的值是不同的。

            typedef pair <const Key, T> value_type;
                        iterator
                        insert(const value_type&); // #1 multimap
                        pair <iterator, bool>
                        insert(const value_type&); // #2 map

              multimap::insert()成員函數(shù)返回指向新插入元素的迭代指針,也就是 iterator(multimap::insert()總是能執(zhí)行成功)。但是 map::insert() 返回 pair<iterator, bool>,此處 bool 值表示插入操作是否成功。

            查找單個值
              與 map 類似,multimap 具備兩個版本重載的 find()成員函數(shù):

            iterator find(const key_type& k);
                        const_iterator find(const key_type& k) const;

            find(k) 返回指向第一個與鍵 k 匹配的 pair 的迭代指針,這就是說,當你想要檢查是否存在至少一個與該鍵關(guān)聯(lián)的值時,或者只需第一個匹配時,這個函數(shù)最有用。例如:

            typedef multimap <string, string> mmss;
                        void func(const mmss & dns)
                        {
                        mmss::const_iterator cit=dns.find("213.108.96.7");
                        if (cit != dns.end())
                        cout <<"213.108.96.7 found" <<endl;
                        else
                        cout <<"not found" <<endl;
                        }

            處理多個關(guān)聯(lián)值
              count(k) 成員函數(shù)返回與給定鍵關(guān)聯(lián)的值得數(shù)量。下面的例子報告了有多少個與鍵 “213.108.96.7” 關(guān)聯(lián)的值:

            cout<<dns.count("213.108.96.7") //output: 2
                        <<" elements associated"<<endl;

              為了存取 multimap 中的多個值,使用 equal_range()、lower_bound()和 upper_bound()成員函數(shù):
            equal_range(k):該函數(shù)查找所有與 k 關(guān)聯(lián)的值。返回迭代指針的 pair,它標記開始和結(jié)束范圍。下面的例子顯示所有與鍵“213.108.96.7”關(guān)聯(lián)的值:

            typedef multimap <string, string>::const_iterator CIT;
                        typedef pair<CIT, CIT> Range;
                        Range range=dns.equal_range("213.108.96.7");
                        for(CIT i=range.first; i!=range.second; ++i)
                        cout << i->second << endl; //output: cpluspluszone.com
                        // cppzone.com

              lower_bound() 和 upper_bound():lower_bound(k) 查找第一個與鍵 k 關(guān)聯(lián)的值,而 upper_bound(k) 是查找第一個鍵值比 k 大的元素。下面的例子示范用 upper_bound()來定位第一個其鍵值大于“213.108.96.7”的元素。通常,當鍵是一個字符串時,會有一個詞典編纂比較:

            dns.insert(make_pair("219.108.96.70", "pythonzone.com"));
                        CIT cit=dns.upper_bound("213.108.96.7");
                        if (cit!=dns.end()) //found anything?
                        cout<<cit->second<<endl; //display: pythonzone.com

            如果你想顯示其后所有的值,可以用下面這樣的循環(huán):

            // 插入有相同鍵的多個值
                        dns.insert(make_pair("219.108.96.70","pythonzone.com"));
                        dns.insert(make_pair("219.108.96.70","python-zone.com"));
                        // 獲得第一個值的迭代指針
                        CIT cit=dns.upper_bound("213.108.96.7");
                        // 輸出: pythonzone.com,python-zone.com
                        while(cit!=dns.end())
                        {
                           cout<<cit->second<<endl;
                           ++cit;
                        }

            結(jié)論
              雖然 map 和 multimap 具有相同的接口,其重要差別在于重復鍵,設計和使用要區(qū)別對待。此外,還要注意每個容器里 insert()成員函數(shù)的細微差別。
             

            作者簡介
              Danny Kalev 是一名通過認證的系統(tǒng)分析師,專攻 C++ 和形式語言理論的軟件工程師。1997 年到 2000 年期間,他是 C++ 標準委員會成員。最近他以優(yōu)異成績完成了他在普通語言學研究方面的碩士論文。 業(yè)余時間他喜歡聽古典音樂,閱讀維多利亞時期的文學作品,研究 Hittite、Basque 和 Irish Gaelic 這樣的自然語言。其它興趣包括考古和地理。Danny 時常到一些 C++ 論壇并定期為不同的 C++ 網(wǎng)站和雜志撰寫文章。他還在教育機構(gòu)講授程序設計語言和應用語言課程。

            posted @ 2007-04-12 11:00 true 閱讀(1177) | 評論 (0)編輯 收藏

            utf8的編碼算法
            作者:轉(zhuǎn)載    轉(zhuǎn)貼自:轉(zhuǎn)載    點擊數(shù):827    文章錄入: zhaizl




                     
            例如字符"漢"的unicode是6C49,把這個unicode字符表示為一個大整數(shù),然后轉(zhuǎn)變成多字節(jié)編碼110110001001001:
                     
            觀察這個整數(shù)的二進制碼序列(110,110001,001001)
                      從后往前取
                     
            如果這個二進制序列只有后7位(小于128,也就是ascii字符)則直接取后7位二進制數(shù)形成一個utf8字符。
                     
            上面的字符“漢”二進制序列大于7位,所以取后6位(1001001),加10形成一個utf8字節(jié)(10 001001 ,16進制89)。
                     
            剩下的二進制序列(110,110001)從后向前取6位,加10形成一個utf8字節(jié)(10 110001,16進制B1)。
                     
            剩下的二進制序列(110)從后向前取6位,由于不足6位,將這個數(shù)和1110000相或,得到字符11100110,16進制E6
                     
            最后,就得到了utf8編碼,16進制表示為E6B189


            解讀UTF8編碼
            2007-01-19 10:40

            在網(wǎng)絡中有很多地方都有采用UTF8編碼,由于要編寫與郵件服務端有關(guān)的程序,而郵件服務端有些地方用到了UTF8編碼,所以對它有了初步的認識!

            它其實和Unicode是同類,就是在編碼方式上不同!
            首先UTF8編碼后的大小是不一定,不像Unicode編碼后的大小是一樣的! 
            我們先來看Unicode的編碼:一個英文字母 “a” 和 一個漢字 “好”,編碼后都是占用的空間大小是一樣的,都是兩個字節(jié)!

            而UTF8編碼:一個英文字母“a” 和 一個漢字 “好”,編碼后占用的空間大小就不樣了,前者是一個字節(jié),后者是三個字節(jié)!

            現(xiàn)在就讓我們來看看UTF8編碼的原理吧:
              因為一個字母還有一些鍵盤上的符號加起來只用二進制七位就可以表示出來,而一個字節(jié)就是八位,所以UTF8就用一個字節(jié)來表式字母和一些鍵盤上的符號。然而當我們拿到被編碼后的一個字節(jié)后怎么知道它的組成?它有可能是英文字母的一個字節(jié),也有可能是漢字的三個字節(jié)中的一個字節(jié)!所以,UTF8是有標志位的!

              當要表示的內(nèi)容是 7位 的時候就用一個字節(jié):0*******  第一個0為標志位,剩下的空間正好可以表示ASCII 0-127 的內(nèi)容。

              當要表示的內(nèi)容在 8 到 11 位的時候就用兩個字節(jié):110***** 10******  第一個字節(jié)的110和第二個字節(jié)的10為標志位。

              當要表示的內(nèi)容在 12 到 16 位的時候就用三個字節(jié):1110***** 10****** 10******    和上面一樣,第一個字節(jié)的1110和第二、三個字節(jié)的10都是標志位,剩下的空間正好可以表示漢字。

              以此類推:
            四個字節(jié):11110**** 10****** 10****** 10****** 
              五個字節(jié):111110*** 10****** 10****** 10****** 10****** 
              六個字節(jié):1111110** 10****** 10****** 10****** 10****** 10****** 
              .............................................
             ..............................................

            明白了沒有?
            編碼的方法是從低位到高位

            現(xiàn)在就讓我們來看看實例吧!

            紅色為標志位
            其它著色為了顯示其,編碼后的位置 

            Unicode十六進制


            Unicode二進制


            UTF8二進制


            UTF8十六進制


            UTF8字節(jié)數(shù)


            B


            00001011


            00001010


            B


            1


            9D


            00010011101


            11000010 10011101 


            C2 9D


            2


            A89E


            10101000 10011110


            11101010 10100010 10011110


            EA A2 9E


            3

            posted @ 2007-04-05 17:23 true 閱讀(667) | 評論 (0)編輯 收藏

            字符,字節(jié)和編碼

            [原創(chuàng)文章,轉(zhuǎn)載請保留或注明出處:http://www.regexlab.com/zh/encoding.htm]

            級別:中級

            摘要:本文介紹了字符與編碼的發(fā)展過程,相關(guān)概念的正確理解。舉例說明了一些實際應用中,編碼的實現(xiàn)方法。然后,本文講述了通常對字符與編碼的幾種誤解,由于這些誤解而導致亂碼產(chǎn)生的原因,以及消除亂碼的辦法。本文的內(nèi)容涵蓋了“中文問題”,“亂碼問題”。

            掌握編碼問題的關(guān)鍵是正確地理解相關(guān)概念,編碼所涉及的技術(shù)其實是很簡單的。因此,閱讀本文時需要慢讀多想,多思考。

            引言

            “字符與編碼”是一個被經(jīng)常討論的話題。即使這樣,時常出現(xiàn)的亂碼仍然困擾著大家。雖然我們有很多的辦法可以用來消除亂碼,但我們并不一定理解這些辦法的內(nèi)在原理。而有的亂碼產(chǎn)生的原因,實際上由于底層代碼本身有問題所導致的。因此,不僅是初學者會對字符編碼感到模糊,有的底層開發(fā)人員同樣對字符編碼缺乏準確的理解。

            回頁首

            1. 編碼問題的由來,相關(guān)概念的理解

            1.1 字符與編碼的發(fā)展

            從計算機對多國語言的支持角度看,大致可以分為三個階段:

              系統(tǒng)內(nèi)碼 說明 系統(tǒng)
            階段一 ASCII 計算機剛開始只支持英語,其它語言不能夠在計算機上存儲和顯示。 英文 DOS
            階段二 ANSI編碼
            (本地化)
            為使計算機支持更多語言,通常使用 0x80~0xFF 范圍的 2 個字節(jié)來表示 1 個字符。比如:漢字 '中' 在中文操作系統(tǒng)中,使用 [0xD6,0xD0] 這兩個字節(jié)存儲。

            不同的國家和地區(qū)制定了不同的標準,由此產(chǎn)生了 GB2312, BIG5, JIS 等各自的編碼標準。這些使用 2 個字節(jié)來代表一個字符的各種漢字延伸編碼方式,稱為 ANSI 編碼。在簡體中文系統(tǒng)下,ANSI 編碼代表 GB2312 編碼,在日文操作系統(tǒng)下,ANSI 編碼代表 JIS 編碼。

            不同 ANSI 編碼之間互不兼容,當信息在國際間交流時,無法將屬于兩種語言的文字,存儲在同一段 ANSI 編碼的文本中。
            中文 DOS,中文 Windows 95/98,日文 Windows 95/98
            階段三 UNICODE
            (國際化)
            為了使國際間信息交流更加方便,國際組織制定了 UNICODE 字符集,為各種語言中的每一個字符設定了統(tǒng)一并且唯一的數(shù)字編號,以滿足跨語言、跨平臺進行文本轉(zhuǎn)換、處理的要求。 Windows NT/2000/XP,Linux,Java

            字符串在內(nèi)存中的存放方法:

            在 ASCII 階段,單字節(jié)字符串使用一個字節(jié)存放一個字符(SBCS)。比如,"Bob123" 在內(nèi)存中為:

            42 6F 62 31 32 33 00
            B o b 1 2 3 \0

            在使用 ANSI 編碼支持多種語言階段,每個字符使用一個字節(jié)或多個字節(jié)來表示(MBCS),因此,這種方式存放的字符也被稱作多字節(jié)字符。比如,"中文123" 在中文 Windows 95 內(nèi)存中為7個字節(jié),每個漢字占2個字節(jié),每個英文和數(shù)字字符占1個字節(jié):

            D6 D0 CE C4 31 32 33 00
            1 2 3 \0

            在 UNICODE 被采用之后,計算機存放字符串時,改為存放每個字符在 UNICODE 字符集中的序號。目前計算機一般使用 2 個字節(jié)(16 位)來存放一個序號(DBCS),因此,這種方式存放的字符也被稱作寬字節(jié)字符。比如,字符串 "中文123" 在 Windows 2000 下,內(nèi)存中實際存放的是 5 個序號:

            2D 4E 87 65 31 00 32 00 33 00 00 00      ← 在 x86 CPU 中,低字節(jié)在前
            1 2 3 \0  

            一共占 10 個字節(jié)。

            回頁首

            1.2 字符,字節(jié),字符串

            理解編碼的關(guān)鍵,是要把字符的概念和字節(jié)的概念理解準確。這兩個概念容易混淆,我們在此做一下區(qū)分:

              概念描述 舉例
            字符 人們使用的記號,抽象意義上的一個符號。 '1', '中', 'a', '$', '¥', ……
            字節(jié) 計算機中存儲數(shù)據(jù)的單元,一個8位的二進制數(shù),是一個很具體的存儲空間。 0x01, 0x45, 0xFA, ……
            ANSI
            字符串
            在內(nèi)存中,如果“字符”是以 ANSI 編碼形式存在的,一個字符可能使用一個字節(jié)或多個字節(jié)來表示,那么我們稱這種字符串為 ANSI 字符串或者多字節(jié)字符串 "中文123"
            (占7字節(jié))
            UNICODE
            字符串
            在內(nèi)存中,如果“字符”是以在 UNICODE 中的序號存在的,那么我們稱這種字符串為 UNICODE 字符串或者寬字節(jié)字符串 L"中文123"
            (占10字節(jié))

            由于不同 ANSI 編碼所規(guī)定的標準是不相同的,因此,對于一個給定的多字節(jié)字符串,我們必須知道它采用的是哪一種編碼規(guī)則,才能夠知道它包含了哪些“字符”。而對于 UNICODE 字符串來說,不管在什么環(huán)境下,它所代表的“字符”內(nèi)容總是不變的。

            回頁首

            1.3 字符集與編碼

            各個國家和地區(qū)所制定的不同 ANSI 編碼標準中,都只規(guī)定了各自語言所需的“字符”。比如:漢字標準(GB2312)中沒有規(guī)定韓國語字符怎樣存儲。這些 ANSI 編碼標準所規(guī)定的內(nèi)容包含兩層含義:

            1. 使用哪些字符。也就是說哪些漢字,字母和符號會被收入標準中。所包含“字符”的集合就叫做“字符集”。
            2. 規(guī)定每個“字符”分別用一個字節(jié)還是多個字節(jié)存儲,用哪些字節(jié)來存儲,這個規(guī)定就叫做“編碼”。

            各個國家和地區(qū)在制定編碼標準的時候,“字符的集合”和“編碼”一般都是同時制定的。因此,平常我們所說的“字符集”,比如:GB2312, GBK, JIS 等,除了有“字符的集合”這層含義外,同時也包含了“編碼”的含義。

            UNICODE 字符集”包含了各種語言中使用到的所有“字符”。用來給 UNICODE 字符集編碼的標準有很多種,比如:UTF-8, UTF-7, UTF-16, UnicodeLittle, UnicodeBig 等。

            回頁首

            1.4 常用的編碼簡介

            簡單介紹一下常用的編碼規(guī)則,為后邊的章節(jié)做一個準備。在這里,我們根據(jù)編碼規(guī)則的特點,把所有的編碼分成三類:

            分類 編碼標準 說明
            單字節(jié)字符編碼 ISO-8859-1 最簡單的編碼規(guī)則,每一個字節(jié)直接作為一個 UNICODE 字符。比如,[0xD6, 0xD0] 這兩個字節(jié),通過 iso-8859-1 轉(zhuǎn)化為字符串時,將直接得到 [0x00D6, 0x00D0] 兩個 UNICODE 字符,即 "ÖÐ"。

            反之,將 UNICODE 字符串通過 iso-8859-1 轉(zhuǎn)化為字節(jié)串時,只能正常轉(zhuǎn)化 0~255 范圍的字符。
            ANSI 編碼 GB2312,
            BIG5,
            Shift_JIS,
            ISO-8859-2 ……
            把 UNICODE 字符串通過 ANSI 編碼轉(zhuǎn)化為“字節(jié)串”時,根據(jù)各自編碼的規(guī)定,一個 UNICODE 字符可能轉(zhuǎn)化成一個字節(jié)或多個字節(jié)。

            反之,將字節(jié)串轉(zhuǎn)化成字符串時,也可能多個字節(jié)轉(zhuǎn)化成一個字符。比如,[0xD6, 0xD0] 這兩個字節(jié),通過 GB2312 轉(zhuǎn)化為字符串時,將得到 [0x4E2D] 一個字符,即 '中' 字。

            “ANSI 編碼”的特點:
            1. 這些“ANSI 編碼標準”都只能處理各自語言范圍之內(nèi)的 UNICODE 字符。
            2. “UNICODE 字符”與“轉(zhuǎn)換出來的字節(jié)”之間的關(guān)系是人為規(guī)定的。
            UNICODE 編碼 UTF-8,
            UTF-16, UnicodeBig ……
            與“ANSI 編碼”類似的,把字符串通過 UNICODE 編碼轉(zhuǎn)化成“字節(jié)串”時,一個 UNICODE 字符可能轉(zhuǎn)化成一個字節(jié)或多個字節(jié)。

            與“ANSI 編碼”不同的是:
            1. 這些“UNICODE 編碼”能夠處理所有的 UNICODE 字符。
            2. “UNICODE 字符”與“轉(zhuǎn)換出來的字節(jié)”之間是可以通過計算得到的。

            我們實際上沒有必要去深究每一種編碼具體把某一個字符編碼成了哪幾個字節(jié),我們只需要知道“編碼”的概念就是把“字符”轉(zhuǎn)化成“字節(jié)”就可以了。對于“UNICODE 編碼”,由于它們是可以通過計算得到的,因此,在特殊的場合,我們可以去了解某一種“UNICODE 編碼”是怎樣的規(guī)則。

            回頁首

            2. 字符與編碼在程序中的實現(xiàn)

            2.1 程序中的字符與字節(jié)

            在 C++ 和 Java 中,用來代表“字符”和“字節(jié)”的數(shù)據(jù)類型,以及進行編碼的方法:

            類型或操作 C++ Java
            字符 wchar_t char
            字節(jié) char byte
            ANSI 字符串 char[] byte[]
            UNICODE 字符串 wchar_t[] String
            字節(jié)串→字符串 mbstowcs(), MultiByteToWideChar() string = new String(bytes, "encoding")
            字符串→字節(jié)串 wcstombs(), WideCharToMultiByte() bytes = string.getBytes("encoding")

            以上需要注意幾點:

            1. Java 中的 char 代表一個“UNICODE 字符(寬字節(jié)字符)”,而 C++ 中的 char 代表一個字節(jié)。
            2. MultiByteToWideChar() 和 WideCharToMultiByte() 是 Windows API 函數(shù)。

            回頁首

            2.2 C++ 中相關(guān)實現(xiàn)方法

            聲明一段字符串常量:

            // ANSI 字符串,內(nèi)容長度 7 字節(jié)
            char
                 sz[20] = "中文123";

            // UNICODE 字符串,內(nèi)容長度 5 個 wchar_t(10 字節(jié))
            wchar_t wsz[20] = L"\x4E2D\x6587\x0031\x0032\x0033";

            UNICODE 字符串的 I/O 操作,字符與字節(jié)的轉(zhuǎn)換操作:

            // 運行時設定當前 ANSI 編碼,VC 格式
            setlocale(LC_ALL, ".936");

            // GCC 中格式
            setlocale(LC_ALL, "zh_CN.GBK");

            // Visual C++ 中使用小寫 %s,按照 setlocale 指定編碼輸出到文件
            // GCC 中使用大寫 %S

            fwprintf(fp, L"%s\n", wsz);

            // 把 UNICODE 字符串按照 setlocale 指定的編碼轉(zhuǎn)換成字節(jié)
            wcstombs(sz, wsz, 20);
            // 把字節(jié)串按照 setlocale 指定的編碼轉(zhuǎn)換成 UNICODE 字符串
            mbstowcs(wsz, sz, 20);

            在 Visual C++ 中,UNICODE 字符串常量有更簡單的表示方法。如果源程序的編碼與當前默認 ANSI 編碼不符,則需要使用 #pragma setlocale,告訴編譯器源程序使用的編碼:

            // 如果源程序的編碼與當前默認 ANSI 編碼不一致,
            // 則需要此行,編譯時用來指明當前源程序使用的編碼

            #pragma setlocale
            (".936")

            // UNICODE 字符串常量,內(nèi)容長度 10 字節(jié)
            wchar_t wsz[20] = L"中文123";

            以上需要注意 #pragma setlocale 與 setlocale(LC_ALL, "") 的作用是不同的,#pragma setlocale 在編譯時起作用,setlocale() 在運行時起作用。

            回頁首

            2.3 Java 中相關(guān)實現(xiàn)方法

            字符串類 String 中的內(nèi)容是 UNICODE 字符串:

            // Java 代碼,直接寫中文
            String
            string = "中文123";

            // 得到長度為 5,因為是 5 個字符
            System.out.println(string.length());

            字符串 I/O 操作,字符與字節(jié)轉(zhuǎn)換操作。在 Java 包 java.io.* 中,以“Stream”結(jié)尾的類一般是用來操作“字節(jié)串”的類,以“Reader”,“Writer”結(jié)尾的類一般是用來操作“字符串”的類。

            // 字符串與字節(jié)串間相互轉(zhuǎn)化

            // 按照 GB2312 得到字節(jié)(得到多字節(jié)字符串)

            byte
            [] bytes = string.getBytes("GB2312");

            // 從字節(jié)按照 GB2312 得到 UNICODE 字符串
            string = new String(bytes, "GB2312");

            // 要將 String 按照某種編碼寫入文本文件,有兩種方法:

            // 第一種辦法:用 Stream 類寫入已經(jīng)按照指定編碼轉(zhuǎn)化好的字節(jié)串

            OutputStream os = new FileOutputStream("1.txt");
            os.write(bytes);
            os.close();

            // 第二種辦法:構(gòu)造指定編碼的 Writer 來寫入字符串
            Writer ow = new OutputStreamWriter(new FileOutputStream("2.txt"), "GB2312");
            ow.write(string);
            ow.close();

            /* 最后得到的 1.txt 和 2.txt 都是 7 個字節(jié) */

            如果 java 的源程序編碼與當前默認 ANSI 編碼不符,則在編譯的時候,需要指明一下源程序的編碼。比如:

            E:\>javac -encoding BIG5 Hello.java

            以上需要注意區(qū)分源程序的編碼與 I/O 操作的編碼,前者是在編譯時起作用,后者是在運行時起作用。

            回頁首

            3. 幾種誤解,以及亂碼產(chǎn)生的原因和解決辦法

            3.1 容易產(chǎn)生的誤解
              對編碼的誤解
            誤解一 在將“字節(jié)串”轉(zhuǎn)化成“UNICODE 字符串”時,比如在讀取文本文件時,或者通過網(wǎng)絡傳輸文本時,容易將“字節(jié)串”簡單地作為單字節(jié)字符串,采用每“一個字節(jié)”就是“一個字符”的方法進行轉(zhuǎn)化。

            而實際上,在非英文的環(huán)境中,應該將“字節(jié)串”作為 ANSI 字符串,采用適當?shù)木幋a來得到 UNICODE 字符串,有可能“多個字節(jié)”才能得到“一個字符”。

            通常,一直在英文環(huán)境下做開發(fā)的程序員們,容易有這種誤解。
            誤解二 在 DOS,Windows 98 等非 UNICODE 環(huán)境下,字符串都是以 ANSI 編碼的字節(jié)形式存在的。這種以字節(jié)形式存在的字符串,必須知道是哪種編碼才能被正確地使用。這使我們形成了一個慣性思維:“字符串的編碼”。

            當 UNICODE 被支持后,Java 中的 String 是以字符的“序號”來存儲的,不是以“某種編碼的字節(jié)”來存儲的,因此已經(jīng)不存在“字符串的編碼”這個概念了。只有在“字符串”與“字節(jié)串”轉(zhuǎn)化時,或者,將一個“字節(jié)串”當成一個 ANSI 字符串時,才有編碼的概念。

            不少的人都有這個誤解。

            第一種誤解,往往是導致亂碼產(chǎn)生的原因。第二種誤解,往往導致本來容易糾正的亂碼問題變得更復雜。

            在這里,我們可以看到,其中所講的“誤解一”,即采用每“一個字節(jié)”就是“一個字符”的轉(zhuǎn)化方法,實際上也就等同于采用 iso-8859-1 進行轉(zhuǎn)化。因此,我們常常使用 bytes = string.getBytes("iso-8859-1") 來進行逆向操作,得到原始的“字節(jié)串”。然后再使用正確的 ANSI 編碼,比如 string = new String(bytes, "GB2312"),來得到正確的“UNICODE 字符串”。

            回頁首

            3.2 非 UNICODE 程序在不同語言環(huán)境間移植時的亂碼

            非 UNICODE 程序中的字符串,都是以某種 ANSI 編碼形式存在的。如果程序運行時的語言環(huán)境與開發(fā)時的語言環(huán)境不同,將會導致 ANSI 字符串的顯示失敗。

            比如,在日文環(huán)境下開發(fā)的非 UNICODE 的日文程序界面,拿到中文環(huán)境下運行時,界面上將顯示亂碼。如果這個日文程序界面改為采用 UNICODE 來記錄字符串,那么當在中文環(huán)境下運行時,界面上將可以顯示正常的日文。

            由于客觀原因,有時候我們必須在中文操作系統(tǒng)下運行非 UNICODE 的日文軟件,這時我們可以采用一些工具,比如,南極星,AppLocale 等,暫時的模擬不同的語言環(huán)境。

            回頁首

            3.3 網(wǎng)頁提交字符串

            當頁面中的表單提交字符串時,首先把字符串按照當前頁面的編碼,轉(zhuǎn)化成字節(jié)串。然后再將每個字節(jié)轉(zhuǎn)化成 "%XX" 的格式提交到 Web 服務器。比如,一個編碼為 GB2312 的頁面,提交 "中" 這個字符串時,提交給服務器的內(nèi)容為 "%D6%D0"。

            在服務器端,Web 服務器把收到的 "%D6%D0" 轉(zhuǎn)化成 [0xD6, 0xD0] 兩個字節(jié),然后再根據(jù) GB2312 編碼規(guī)則得到 "中" 字。

            在 Tomcat 服務器中,request.getParameter() 得到亂碼時,常常是因為前面提到的“誤解一”造成的。默認情況下,當提交 "%D6%D0" 給 Tomcat 服務器時,request.getParameter() 將返回 [0x00D6, 0x00D0] 兩個 UNICODE 字符,而不是返回一個 "中" 字符。因此,我們需要使用 bytes = string.getBytes("iso-8859-1") 得到原始的字節(jié)串,再用 string = new String(bytes, "GB2312") 重新得到正確的字符串 "中"。

            回頁首

            3.4 從數(shù)據(jù)庫讀取字符串

            通過數(shù)據(jù)庫客戶端(比如 ODBC 或 JDBC)從數(shù)據(jù)庫服務器中讀取字符串時,客戶端需要從服務器獲知所使用的 ANSI 編碼。當數(shù)據(jù)庫服務器發(fā)送字節(jié)流給客戶端時,客戶端負責將字節(jié)流按照正確的編碼轉(zhuǎn)化成 UNICODE 字符串。

            如果從數(shù)據(jù)庫讀取字符串時得到亂碼,而數(shù)據(jù)庫中存放的數(shù)據(jù)又是正確的,那么往往還是因為前面提到的“誤解一”造成的。解決的辦法還是通過 string = new String( string.getBytes("iso-8859-1"), "GB2312") 的方法,重新得到原始的字節(jié)串,再重新使用正確的編碼轉(zhuǎn)化成字符串。

            回頁首

            3.5 電子郵件中的字符串

            當一段 Text 或者 HTML 通過電子郵件傳送時,發(fā)送的內(nèi)容首先通過一種指定的字符編碼轉(zhuǎn)化成“字節(jié)串”,然后再把“字節(jié)串”通過一種指定的傳輸編碼(Content-Transfer-Encoding)進行轉(zhuǎn)化得到另一串“字節(jié)串”。比如,打開一封電子郵件源代碼,可以看到類似的內(nèi)容:

            Content-Type: text/plain;
                    charset="gb2312"
            Content-Transfer-Encoding: base64

            sbG+qcrQuqO17cf4yee74bGjz9W7+b3wudzA7dbQ0MQNCg0KvPKzxqO6uqO17cnnsaPW0NDEDQoNCg==

            最常用的 Content-Transfer-Encoding 有 Base64 和 Quoted-Printable 兩種。在對二進制文件或者中文文本進行轉(zhuǎn)化時,Base64 得到的“字節(jié)串”比 Quoted-Printable 更短。在對英文文本進行轉(zhuǎn)化時,Quoted-Printable 得到的“字節(jié)串”比 Base64 更短。

            郵件的標題,用了一種更簡短的格式來標注“字符編碼”和“傳輸編碼”。比如,標題內(nèi)容為 "中",則在郵件源代碼中表示為:

            // 正確的標題格式
            Subject: =?GB2312?B?1tA=?=

            其中,

            • 第一個“=?”與“?”中間的部分指定了字符編碼,在這個例子中指定的是 GB2312。
            • “?”與“?”中間的“B”代表 Base64。如果是“Q”則代表 Quoted-Printable。
            • 最后“?”與“?=”之間的部分,就是經(jīng)過 GB2312 轉(zhuǎn)化成字節(jié)串,再經(jīng)過 Base64 轉(zhuǎn)化后的標題內(nèi)容。

            如果“傳輸編碼”改為 Quoted-Printable,同樣,如果標題內(nèi)容為 "中":

            // 正確的標題格式
            Subject: =?GB2312?Q?=D6=D0?=

            如果閱讀郵件時出現(xiàn)亂碼,一般是因為“字符編碼”或“傳輸編碼”指定有誤,或者是沒有指定。比如,有的發(fā)郵件組件在發(fā)送郵件時,標題 "中":

            // 錯誤的標題格式
            Subject: =?ISO-8859-1?Q?=D6=D0?=

            這樣的表示,實際上是明確指明了標題為 [0x00D6, 0x00D0],即 "ÖÐ",而不是 "中"。

            回頁首

            4. 幾種錯誤理解的糾正

            誤解:“ISO-8859-1 是國際編碼?”

            非也。iso-8859-1 只是單字節(jié)字符集中最簡單的一種,也就是“字節(jié)編號”與“UNICODE 字符編號”一致的那種編碼規(guī)則。當我們要把一個“字節(jié)串”轉(zhuǎn)化成“字符串”,而又不知道它是哪一種 ANSI 編碼時,先暫時地把“每一個字節(jié)”作為“一個字符”進行轉(zhuǎn)化,不會造成信息丟失。然后再使用 bytes = string.getBytes("iso-8859-1") 的方法可恢復到原始的字節(jié)串。

            誤解:“Java 中,怎樣知道某個字符串的內(nèi)碼?”

            Java 中,字符串類 java.lang.String 處理的是 UNICODE 字符串,不是 ANSI 字符串。我們只需要把字符串作為“抽象的符號的串”來看待。因此不存在字符串的內(nèi)碼的問題。

            posted @ 2007-04-05 17:14 true 閱讀(513) | 評論 (0)編輯 收藏

            僅列出標題
            共15頁: First 7 8 9 10 11 12 13 14 15 
            2021国产成人精品久久| 91性高湖久久久久| 久久精品?ⅴ无码中文字幕| 99久久精品国内| 日产精品久久久久久久| 伊人色综合久久天天人守人婷| 91精品国产91久久久久久| 99re久久精品国产首页2020| 久久香蕉超碰97国产精品| 亚洲AV无码久久精品成人| 色综合久久中文字幕无码| 久久午夜夜伦鲁鲁片免费无码影视| 亚洲国产成人久久综合野外| 人妻丰满?V无码久久不卡| 色偷偷91久久综合噜噜噜噜| 伊人久久大香线蕉综合网站| 久久精品中文字幕大胸| 欧美黑人激情性久久| 亚洲精品美女久久久久99| 日本人妻丰满熟妇久久久久久| 午夜天堂精品久久久久| 成人综合伊人五月婷久久| 国产叼嘿久久精品久久| 久久电影网| 久久精品国产色蜜蜜麻豆| 久久综合综合久久综合| 国产精品久久久久久搜索| 久久成人国产精品一区二区| 人人狠狠综合久久亚洲高清| 中文精品久久久久人妻不卡| 久久亚洲国产成人精品性色| 91亚洲国产成人久久精品| 性做久久久久久久久浪潮| 国产精品99久久99久久久| 国产午夜精品久久久久九九| 亚洲精品NV久久久久久久久久 | 成人久久久观看免费毛片| 久久99久久无码毛片一区二区| 久久大香萑太香蕉av| 久久91综合国产91久久精品| 日本亚洲色大成网站WWW久久 |