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

            網絡服務器軟件開發/中間件開發,關注ACE/ICE/boost

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

            #

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

              一、ASCII 輸出

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

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

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

            fout.open("output.txt");

              你也可以把文件名作為構造參數來打開一個文件.

            ofstream fout("output.txt");

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

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

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

            fout << flush; fout.close();

               現在你用文本編輯器打開文件,內容看起來是這樣:

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

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

              二、ASCII 輸入

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

              12 GameDev 15.45 L This is really awesome!

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

            ifstream fin("input.txt");

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

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

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

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

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

              This is really awesome!

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

            char sentence[101]; fin >> sentence;

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

            fin.getline(sentence, 100);

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

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

              三、二進制 輸入輸出

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            ACE_NT_Service
            查看ACE源碼,其中和類 ACE_NT_Service實現密切相關的的文件有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代碼,這里不再列出,這里只分析相關的類ACE_NT_Service的成員函數handle_control,init,open,wait和fini。函數handle_control被用于響應SERVICE DISPATCHER請求,其必須和SVC函數交互以影響請求控制操作。缺省實現包括SERVICE_CONTROL_STOP,SERVICE_CONTROL_PAUSE,SERVICE_CONTROL_CONTINUE,SERVICE_CONTROL_INTERROGATE,SERVICE_CONTROL_SHUTDOWN。

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

            函數open 的部分關鍵代碼解析
            /* 報告狀態 */
            this->report_status (SERVICE_START_PENDING, 0);
            /* 執行用戶代碼 */
            int svc_return = this->svc ();

            函數fini 的部分關鍵代碼解析
            /* 報告狀態 */
            return this->report_status (SERVICE_STOPPED, 0);

            函數stop_requested的部分關鍵代碼解析
            /* 報告狀態 */
            this->report_status (SERVICE_STOP_PENDING);

            函數pause_requested的部分關鍵代碼解析
            /* 報告狀態 */
            this->report_status (SERVICE_PAUSE_PENDING);
            /* 掛起*/
            this->suspend ();
            /* 報告狀態 */
            this->report_status (SERVICE_PAUSED);

            函數continue_requested的部分關鍵代碼解析
            /* 報告狀態 */
            this->report_status (SERVICE_CONTINUE_PENDING);
            /* 恢復*/
            this->resume ();
            /* 報告狀態 */
            this->report_status (SERVICE_RUNNING);

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

            Insert函數的部分關鍵代碼解析

            /* 打開和host()上SCManager的通信 */
            SC_HANDLE sc_mgr = ACE_TEXT_OpenSCManager (this->host (),……);
            /* 以名稱name() 創建服務 */
            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,……);
            /* 關閉和SCManager的通信 */
            CloseServiceHandle (sc_mgr);
            /* 關閉服務句柄,重新寫入新句柄 */
            if (this->svc_sc_handle_ != 0)
            CloseServiceHandle (this->svc_sc_handle_);
            this->svc_sc_handle_ = sh;

            Remove函數部分關鍵代碼解析

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

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

            stop_svc函數的部分關鍵代碼解析

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

            pause_svc函數的部分關鍵代碼解析

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

            continue_svc函數的部分關鍵代碼解析

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

            一些輔助函數
            svc_sc_handle部份關鍵代碼解析

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

            wait_for_service_state部份關鍵代碼解析

            /* 獲取當前時間 */
            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 (;
            {
            /* 查詢當前狀態 */
            service_ok = 0 != QueryServiceStatus (this->svc_sc_handle_, &this->svc_status_);
            /* 如果已經到達指定狀態,退出循環 */
            if (desired_state == this->svc_status_.dwCurrentState) break;
            /* 如果超出指定時間,退出循環 */
            if (wait_time != 0 && ACE_OS::gettimeofday () > time_out )
            { ……
            break;
            }
            /* 睡眠等待 */
            ::Sleep (this->svc_status_.dwWaitHint);
            }

            report_status部份關鍵代碼解析
            /* 告訴系統服務新的狀態 */
            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 閱讀(1074) | 評論 (1)編輯 收藏

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

            關鍵詞: VC++6.0; 線程同步;臨界區;事件;互斥;信號量;

            正文

            使線程同步

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

              如果不采取適當的措施,其他線程往往會在線程處理任務結束前就去訪問處理結果,這就很有可能得到有關處理結果的錯誤了解。例如,多個線程同時訪問同一個全局變量,如果都是讀取操作,則不會出現問題。如果一個線程負責改變此變量的值,而其他線程負責同時讀取變量內容,則不能保證讀取到的數據是經過寫線程修改后的。

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

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

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

            臨界區

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

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

            圖1 使用臨界區保持線程同步

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

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


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

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

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


            管理事件內核對象

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

            // 事件句柄
            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()
            {
             // 創建事件
             hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
             // 事件置位
             SetEvent(hEvent);
             // 啟動線程
             AfxBeginThread(ThreadProc12, NULL);
             AfxBeginThread(ThreadProc13, NULL);
             // 等待計算完畢
             Sleep(300);
             // 報告計算結果
             CString sResult = CString(g_cArray);
             AfxMessageBox(sResult);
            }


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

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

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


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

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

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


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

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


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

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


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

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

            B線程在執行到CEvent類成員函數Lock()時將會發生阻塞,而A線程此時則可以在沒有B線程干擾的情況下對共享資源進行處理,并在處理完成后通過成員函數SetEvent()向B發出事件,使其被釋放,得以對A先前已處理完畢的共享資源進行操作。可見,使用CEvent類對線程的同步方法與通過API函數進行線程同步的處理方法是基本一致的。前面的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);
             // 報告計算結果
             CString sResult = CString(g_cArray);
             AfxMessageBox(sResult);
            }

              信號量內核對象

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

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

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

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

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


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

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


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

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


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

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

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


            圖4 開始進入的兩個線程

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

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

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

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


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

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

              互斥內核對象

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

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

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

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

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

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

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

            BOOL ReleaseMutex(HANDLE hMutex);

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

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

            // 互斥對象
            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()
            {
             // 創建互斥對象
             hMutex = CreateMutex(NULL, FALSE, NULL);
             // 啟動線程
             AfxBeginThread(ThreadProc18, NULL);
             AfxBeginThread(ThreadProc19, NULL);
             // 等待計算完畢
             Sleep(300);
             // 報告計算結果
             CString sResult = CString(g_cArray);
             AfxMessageBox(sResult);
            }

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

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

              該類的適用范圍和實現原理與API方式創建的互斥內核對象是完全類似的,但要簡潔的多,下面給出就是對前面的示例代碼經CMutex類改寫后的程序實現清單:

            // 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);
             // 報告計算結果
             CString sResult = CString(g_cArray);
             AfxMessageBox(sResult);
            }

              小結

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

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

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

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

            /C++中的日期和時間

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

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

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

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

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

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

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

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

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

            2. 計時

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

            clock_t clock( void );

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

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

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

            #define CLOCKS_PER_SEC ((clock_t)1000)

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

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

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

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

            int main( void )
            {
            long i = 10000000L;
            clock_t start, finish;
            double duration;
            /* 測量一個事件持續的時間*/
            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");
            }

            在筆者的機器上,運行結果如下:

            Time to do 10000000 empty loops is 0.03000 seconds

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

            3.與日期和時間相關的數據結構

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

            #ifndef _TM_DEFINED
            struct tm {
            int tm_sec; /* 秒 – 取值區間為[0,59] */
            int tm_min; /* 分 - 取值區間為[0,59] */
            int tm_hour; /* 時 - 取值區間為[0,23] */
            int tm_mday; /* 一個月中的日期 - 取值區間為[1,31] */
            int tm_mon; /* 月份(從一月開始,0代表一月) - 取值區間為[0,11] */
            int tm_year; /* 年份,其值等于實際年份減去1900 */
            int tm_wday; /* 星期 – 取值區間為[0,6],其中0代表星期天,1代表星期一,以此類推 */
            int tm_yday; /* 從每年的1月1日開始的天數 – 取值區間為[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結構的這種時間表示為分解時間(broken-down time)。

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

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

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

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

            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還提供了兩種不同的函數將日歷時間(一個用time_t表示的整數)轉換為我們平時看到的把年月日時分秒分開顯示的時間格式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秒到該時間點所經過的秒數,而其它各種版本的Microsoft C/C++和所有不同版本的Visual C++都是計算的從1970年1月1日0時0分0秒到該時間點所經過的秒數。

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

            4.1 獲得日歷時間

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

            time_t time(time_t * timer);

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

            #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;
            }

            運行的結果與當時的時間有關,我當時運行的結果是:

            The Calendar Time now is 1122707619

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

            4.2 獲得日期和時間

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

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

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

            其中gmtime()函數是將日歷時間轉化為世界標準時間(即格林尼治時間),并返回一個tm結構體來保存這個時間,而localtime()函數是將日歷時間轉化為本地時間。比如現在用gmtime()函數獲得的世界標準時間是2005年7月30日7點18分20秒,那么我用localtime()函數在中國地區獲得的本地時間會比世界標準時間晚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;
            }

            運行結果是:

            Local hour is: 15
            UTC hour is: 7

            4.3 固定的時間格式

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

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

            其中\n是一個換行符,\0是一個空字符,表示字符串結束。下面是兩個函數的原型:

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

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

            printf(ctime(&t));

            等價于:

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

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

            #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;
            }

            運行結果:

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

            4.4 自定義時間格式

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

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

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

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

            %a 星期幾的簡寫
            %A 星期幾的全稱
            %b 月分的簡寫
            %B 月份的全稱
            %c 標準的日期的時間串
            %C 年份的后兩位數字
            %d 十進制表示的每月的第幾天
            %D 月/天/年
            %e 在兩字符域中,十進制表示的每月的第幾天
            %F 年-月-日
            %g 年份的后兩位數字,使用基于周的年
            %G 年分,使用基于周的年
            %h 簡寫的月份名
            %H 24小時制的小時
            %I 12小時制的小時
            %j 十進制表示的每年的第幾天
            %m 十進制表示的月份
            %M 十時制表示的分鐘數
            %n 新行符
            %p 本地的AM或PM的等價顯示
            %r 12小時的時間
            %R 顯示小時和分鐘:hh:mm
            %S 十進制的秒數
            %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 時區名稱,如果不能得到時區名稱則返回空字符。
            %% 百分號

            如果想顯示現在是幾點了,并以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;
            }

            其運行結果為:
            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);
            }

            運行結果:

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

            4.5 計算持續時間的長度

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

            double difftime(time_t time1, time_t time0);

            雖然該函數返回的以秒計算的時間間隔是double類型的,但這并不說明該時間具有同double一樣的精確度,這是由它的參數覺得的(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;
            }

            運行結果為:
            請按任意鍵繼續. . .
            The pause used 2.000000 seconds.
            請按任意鍵繼續. . .

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

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

            其運行結果是一樣的。

            4.6 分解時間轉化為日歷時間

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

            time_t mktime(struct tm * timeptr);

            其返回值就是轉化后的日歷時間。這樣我們就可以先制定一個分解時間,然后對這個時間進行操作了,下面的例子可以計算出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;
            }

            運行結果:

            Tue Jul 01 00:00:01 1997

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

            5.總結

            本文介紹了標準C/C++中的有關日期和時間的概念,并通過各種實例講述了這些函數和數據結構的使用方法。筆者認為,和時間相關的一些概念是相當重要的,理解這些概念是理解各種時間格式的轉換的基礎,更是應用這些函數和數據結構的基礎。


             上面是轉的,本人自己再加點,以備后用:

            /* //時間格式化為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 閱讀(742) | 評論 (1)編輯 收藏

            使用 <multimap> 庫創建重復鍵關聯容器

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

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

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



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

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

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

            string phone=phonebook["Harry];

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

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

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

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

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

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

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

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

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

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

            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;
                        }

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

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

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

            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 關聯的值,而 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

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

            // 插入有相同鍵的多個值
                        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;
                        }

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

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

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

            utf8的編碼算法
            作者:轉載    轉貼自:轉載    點擊數:827    文章錄入: zhaizl




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


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

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

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

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

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

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

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

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

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

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

            現在就讓我們來看看實例吧!

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

            Unicode十六進制


            Unicode二進制


            UTF8二進制


            UTF8十六進制


            UTF8字節數


            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 閱讀(669) | 評論 (0)編輯 收藏

            字符,字節和編碼

            [原創文章,轉載請保留或注明出處:http://www.regexlab.com/zh/encoding.htm]

            級別:中級

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

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

            引言

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

            回頁首

            1. 編碼問題的由來,相關概念的理解

            1.1 字符與編碼的發展

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

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

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

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

            字符串在內存中的存放方法:

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

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

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

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

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

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

            一共占 10 個字節。

            回頁首

            1.2 字符,字節,字符串

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

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

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

            回頁首

            1.3 字符集與編碼

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

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

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

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

            回頁首

            1.4 常用的編碼簡介

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

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

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

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

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

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

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

            回頁首

            2. 字符與編碼在程序中的實現

            2.1 程序中的字符與字節

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

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

            以上需要注意幾點:

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

            回頁首

            2.2 C++ 中相關實現方法

            聲明一段字符串常量:

            // ANSI 字符串,內容長度 7 字節
            char
                 sz[20] = "中文123";

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

            UNICODE 字符串的 I/O 操作,字符與字節的轉換操作:

            // 運行時設定當前 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 指定的編碼轉換成字節
            wcstombs(sz, wsz, 20);
            // 把字節串按照 setlocale 指定的編碼轉換成 UNICODE 字符串
            mbstowcs(wsz, sz, 20);

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

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

            #pragma setlocale
            (".936")

            // UNICODE 字符串常量,內容長度 10 字節
            wchar_t wsz[20] = L"中文123";

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

            回頁首

            2.3 Java 中相關實現方法

            字符串類 String 中的內容是 UNICODE 字符串:

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

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

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

            // 字符串與字節串間相互轉化

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

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

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

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

            // 第一種辦法:用 Stream 類寫入已經按照指定編碼轉化好的字節串

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

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

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

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

            E:\>javac -encoding BIG5 Hello.java

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

            回頁首

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

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

            而實際上,在非英文的環境中,應該將“字節串”作為 ANSI 字符串,采用適當的編碼來得到 UNICODE 字符串,有可能“多個字節”才能得到“一個字符”。

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

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

            不少的人都有這個誤解。

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

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

            回頁首

            3.2 非 UNICODE 程序在不同語言環境間移植時的亂碼

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

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

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

            回頁首

            3.3 網頁提交字符串

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

            在服務器端,Web 服務器把收到的 "%D6%D0" 轉化成 [0xD6, 0xD0] 兩個字節,然后再根據 GB2312 編碼規則得到 "中" 字。

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

            回頁首

            3.4 從數據庫讀取字符串

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

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

            回頁首

            3.5 電子郵件中的字符串

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

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

            sbG+qcrQuqO17cf4yee74bGjz9W7+b3wudzA7dbQ0MQNCg0KvPKzxqO6uqO17cnnsaPW0NDEDQoNCg==

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

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

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

            其中,

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

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

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

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

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

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

            回頁首

            4. 幾種錯誤理解的糾正

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

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

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

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

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

            僅列出標題
            共15頁: First 7 8 9 10 11 12 13 14 15 
            99精品久久久久中文字幕| 无码超乳爆乳中文字幕久久| 97久久精品国产精品青草| 久久人人爽爽爽人久久久| 日本久久久久久中文字幕| 精品久久久久国产免费| 日韩欧美亚洲综合久久 | 久久经典免费视频| 久久午夜无码鲁丝片| 久久久久久久久久久免费精品| 伊人情人综合成人久久网小说 | 欧美麻豆久久久久久中文| 亚洲国产欧洲综合997久久| 青青草原1769久久免费播放| 久久国产AVJUST麻豆| 91久久九九无码成人网站| 777午夜精品久久av蜜臀| 久久人人爽人人爽人人片AV麻豆 | 久久这里只有精品首页| 精品久久久久久国产| 久久人人爽人人爽人人片AV麻烦| 欧美激情精品久久久久| 欧美牲交A欧牲交aⅴ久久 | 久久精品国产亚洲av高清漫画| 久久久久国色AV免费观看| 久久精品国产91久久综合麻豆自制 | 精品久久久噜噜噜久久久| 久久青青色综合| 中文字幕精品久久| 久久精品中文字幕有码| 2021国产成人精品久久| 久久亚洲欧美日本精品| 久久99毛片免费观看不卡| 久久国产高潮流白浆免费观看| 精品久久久一二三区| 人妻无码精品久久亚瑟影视| 亚洲欧美一区二区三区久久| 久久精品国产男包| 少妇精品久久久一区二区三区| 18岁日韩内射颜射午夜久久成人| 久久亚洲AV无码精品色午夜|