青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

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

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

#

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

  一、ASCII 輸出

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

  如果你用過(guò)標(biāo)準(zhǔn)控制臺(tái)流"cin"?和 "cout," 那現(xiàn)在的事情對(duì)你來(lái)說(shuō)很簡(jiǎn)單。 我們現(xiàn)在開(kāi)始講輸出部分,首先聲明一個(gè)類(lèi)對(duì)象。ofstream fout;

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

fout.open("output.txt");

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

ofstream fout("output.txt");

  這是我們使用的方法, 因?yàn)檫@樣創(chuàng)建和打開(kāi)一個(gè)文件看起來(lái)更簡(jiǎn)單. 順便說(shuō)一句, 如果你要打開(kāi)的文件不存在,它會(huì)為你創(chuàng)建一個(gè), 所以不用擔(dān)心文件創(chuàng)建的問(wèn)題. 現(xiàn)在就輸出到文件,看起來(lái)和"cout"的操作很像。 對(duì)不了解控制臺(tái)輸出"cout"的人, 這里有個(gè)例子。

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)閉文件,或者回寫(xiě)文件緩沖. 文件關(guān)閉之后就不能再操作了, 所以只有在你不再操作這個(gè)文件的時(shí)候才調(diào)用它,它會(huì)自動(dòng)保存文件。 回寫(xiě)緩沖區(qū)會(huì)在保持文件打開(kāi)的情況下保存文件, 所以只要有必要就使用它。回寫(xiě)看起來(lái)像另一次輸出, 然后調(diào)用方法關(guān)閉。像這樣:

fout << flush; fout.close();

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

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

  很簡(jiǎn)單吧! 現(xiàn)在繼續(xù)文件輸入, 需要一點(diǎn)技巧, 所以先確認(rèn)你已經(jīng)明白了流操作,對(duì) "<<" 和">>" 比較熟悉了, 因?yàn)槟憬酉聛?lái)還要用到他們。繼續(xù)…

  二、ASCII 輸入

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

  12 GameDev 15.45 L This is really awesome!

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

ifstream fin("input.txt");

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

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

  也可以把這四行讀取文件的代碼寫(xiě)為更簡(jiǎn)單的一行。

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

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

  This is really awesome!

  如果你想把整行讀入一個(gè)char數(shù)組, 我們沒(méi)辦法用">>"?操作符,因?yàn)槊總€(gè)單詞之間的空格(空白字符)會(huì)中止文件的讀取。為了驗(yàn)證:

char sentence[101]; fin >> sentence;

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

fin.getline(sentence, 100);

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

  你應(yīng)該已經(jīng)知道如何讀取和寫(xiě)入ASCII文件了。但我們還不能罷休,因?yàn)槎M(jìn)制文件還在等著我們。

  三、二進(jìn)制 輸入輸出

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

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

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

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

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

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

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

  這樣就寫(xiě)入了整個(gè)結(jié)構(gòu)! 接下來(lái)是輸入. 輸入也很簡(jiǎn)單,因?yàn)閞ead()?函數(shù)的參數(shù)和 write()是完全一樣的, 使用方法也相同。

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

  我不多解釋用法, 因?yàn)樗蛍rite()是完全相同的。二進(jìn)制文件比ASCII文件簡(jiǎn)單, 但有個(gè)缺點(diǎn)是無(wú)法用文本編輯器編輯。 接著, 我解釋一下ifstream 和ofstream 對(duì)象的其
posted @ 2007-04-29 18:24 true 閱讀(606) | 評(píng)論 (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;  
  }  

得到文件長(zhǎng)度
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 閱讀(841) | 評(píng)論 (0)編輯 收藏

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

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

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

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

ACE_NT_Service
查看ACE源碼,其中和類(lèi) ACE_NT_Service實(shí)現(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)用于簡(jiǎn)化定義。具體的宏定義可以參考ACE代碼,這里不再列出,這里只分析相關(guān)的類(lèi)ACE_NT_Service的成員函數(shù)handle_control,init,open,wait和fini。函數(shù)handle_control被用于響應(yīng)SERVICE DISPATCHER請(qǐng)求,其必須和SVC函數(shù)交互以影響請(qǐng)求控制操作。缺省實(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響應(yīng)關(guān)閉操作 */
case SERVICE_CONTROL_SHUTDOWN:
case SERVICE_CONTROL_STOP:
this->stop_requested (control_code);
break;
/* 調(diào)用pause_requested響應(yīng)掛起操作 */
case SERVICE_CONTROL_PAUSE:
this->pause_requested (control_code);
break;
/* 調(diào)用continue_requested響應(yīng)掛起后啟動(dòng)操作 */
case SERVICE_CONTROL_CONTINUE:
this->continue_requested (control_code);
break;
/* 調(diào)用interrogate_requested報(bào)告當(dāng)前狀態(tài)*/
case SERVICE_CONTROL_INTERROGATE:
this->interrogate_requested (control_code);
break;

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

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

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

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

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

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

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

/* 打開(kāi)和host()上SCManager的通信 */
SC_HANDLE sc_mgr = ACE_TEXT_OpenSCManager (this->host (),……);
/* 以名稱(chēng)name() 創(chuàng)建服務(wù) */
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)閉服務(wù)句柄,重新寫(xiě)入新句柄 */
if (this->svc_sc_handle_ != 0)
CloseServiceHandle (this->svc_sc_handle_);
this->svc_sc_handle_ = sh;

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

/* 從SCM中刪除insert創(chuàng)建的服務(wù)句柄 */
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分別用于啟動(dòng)、停止、掛起和繼續(xù)服務(wù)。
start_svc函數(shù)的部分關(guān)鍵代碼解析

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

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

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

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

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

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

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

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

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

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

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

report_status部份關(guān)鍵代碼解析
/* 告訴系統(tǒng)服務(wù)新的狀態(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 閱讀(1088) | 評(píng)論 (1)編輯 收藏

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

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

正文

使線程同步

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

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

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

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

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

臨界區(qū)

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

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

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

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

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


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

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

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


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

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

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


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

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

HANDLE OpenEvent(
 DWORD dwDesiredAccess, // 訪問(wèn)標(biāo)志
 BOOL bInheritHandle, // 繼承標(biāo)志
 LPCTSTR lpName // 指向事件對(duì)象名的指針
);


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

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

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


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

// 存放事件句柄的數(shù)組
HANDLE hEvents[2];
UINT ThreadProc14(LPVOID pParam)
{
 // 等待開(kāi)啟事件
 DWORD dwRet1 = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE);
 // 如果開(kāi)啟事件到達(dá)則線程開(kāi)始執(zhí)行任務(wù)
 if (dwRet1 == WAIT_OBJECT_0)
 {
  AfxMessageBox("線程開(kāi)始工作!");
  while (true)
  {
   for (int i = 0; i < 10000; i++);
   // 在任務(wù)處理過(guò)程中等待結(jié)束事件
   DWORD dwRet2 = WaitForMultipleObjects(2, hEvents, FALSE, 0);
   // 如果結(jié)束事件置位則立即終止任務(wù)的執(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);
  // 開(kāi)啟線程
  AfxBeginThread(ThreadProc14, NULL);
  // 設(shè)置事件0(開(kāi)啟事件)
  SetEvent(hEvents[0]);
}
void CSample08View::OnEndevent()
{
 // 設(shè)置事件1(結(jié)束事件)
 SetEvent(hEvents[1]);
}


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

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


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

圖2 CEvent類(lèi)對(duì)線程的同步過(guò)程示意

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

// MFC事件類(lèi)對(duì)象
CEvent g_clsEvent;
UINT ThreadProc22(LPVOID pParam)
{
 // 對(duì)共享資源進(jìn)行寫(xiě)入操作
 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();
 // 對(duì)共享資源進(jìn)行寫(xiě)入操作
 for (int i = 0; i < 10; i++)
 {
  g_cArray[10 - i - 1] = 'b';
  Sleep(1);
 }
 return 0;
}
……
void CSample08View::OnEventMfc()
{
 // 啟動(dòng)線程
 AfxBeginThread(ThreadProc22, NULL);
 AfxBeginThread(ThreadProc23, NULL);
 // 等待計(jì)算完畢
 Sleep(300);
 // 報(bào)告計(jì)算結(jié)果
 CString sResult = CString(g_cArray);
 AfxMessageBox(sResult);
}

  信號(hào)量?jī)?nèi)核對(duì)象

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

圖3 使用信號(hào)量對(duì)象控制資源

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

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

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


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

HANDLE OpenSemaphore(
 DWORD dwDesiredAccess, // 訪問(wèn)標(biāo)志
 BOOL bInheritHandle, // 繼承標(biāo)志
 LPCTSTR lpName // 信號(hào)量名
);


  在線程離開(kāi)對(duì)共享資源的處理時(shí),必須通過(guò)ReleaseSemaphore()來(lái)增加當(dāng)前可用資源計(jì)數(shù)。否則將會(huì)出現(xiàn)當(dāng)前正在處理共享資源的實(shí)際線程數(shù)并沒(méi)有達(dá)到要限制的數(shù)值,而其他線程卻因?yàn)楫?dāng)前可用資源計(jì)數(shù)為0而仍無(wú)法進(jìn)入的情況。ReleaseSemaphore()的函數(shù)原型為:

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


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

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

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


圖4 開(kāi)始進(jìn)入的兩個(gè)線程

圖5 線程二退出后線程三才得以進(jìn)入

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

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

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


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

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

  互斥內(nèi)核對(duì)象

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

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

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

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

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

HANDLE OpenMutex(
 DWORD dwDesiredAccess, // 訪問(wèn)標(biāo)志
 BOOL bInheritHandle, // 繼承標(biāo)志
 LPCTSTR lpName // 互斥對(duì)象名
);

  當(dāng)目前對(duì)資源具有訪問(wèn)權(quán)的線程不再需要訪問(wèn)此資源而要離開(kāi)時(shí),必須通過(guò)ReleaseMutex()函數(shù)來(lái)釋放其擁有的互斥對(duì)象,其函數(shù)原型為:

BOOL ReleaseMutex(HANDLE hMutex);

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

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

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

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

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

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

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

  小結(jié)

  線程的使用使程序處理更夠更加靈活,而這種靈活同樣也會(huì)帶來(lái)各種不確定性的可能。尤其是在多個(gè)線程對(duì)同一公共變量進(jìn)行訪問(wèn)時(shí)。雖然未使用線程同步的程序代碼在邏輯上或許沒(méi)有什么問(wèn)題,但為了確保程序的正確、可靠運(yùn)行,必須在適當(dāng)?shù)膱?chǎng)合采取線程同步措施。

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

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

做東西設(shè)計(jì)不要太復(fù)雜,復(fù)雜往往費(fèi)力不討好,要善于和leader的打交道,設(shè)計(jì)說(shuō)起來(lái)容易,但做起來(lái)細(xì)節(jié)很多。要爭(zhēng)取學(xué)習(xí)的時(shí)間。
posted @ 2007-04-17 11:31 true 閱讀(252) | 評(píng)論 (0)編輯 收藏

/C++中的日期和時(shí)間

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

關(guān)鍵字:UTC(世界標(biāo)準(zhǔn)時(shí)間),Calendar Time(日歷時(shí)間),epoch(時(shí)間點(diǎn)),clock tick(時(shí)鐘計(jì)時(shí)單元)

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

通過(guò)學(xué)習(xí)許多C/C++庫(kù),你可以有很多操作、使用時(shí)間的方法。但在這之前你需要了解一些“時(shí)間”和“日期”的概念,主要有以下幾個(gè):

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

Calendar Time:日歷時(shí)間,是用“從一個(gè)標(biāo)準(zhǔn)時(shí)間點(diǎn)到此時(shí)的時(shí)間經(jīng)過(guò)的秒數(shù)”來(lái)表示的時(shí)間。這個(gè)標(biāo)準(zhǔn)時(shí)間點(diǎn)對(duì)不同的編譯器來(lái)說(shuō)會(huì)有所不同,但對(duì)一個(gè)編譯系統(tǒng)來(lái)說(shuō),這個(gè)標(biāo)準(zhǔn)時(shí)間點(diǎn)是不變的,該編譯系統(tǒng)中的時(shí)間對(duì)應(yīng)的日歷時(shí)間都通過(guò)該標(biāo)準(zhǔn)時(shí)間點(diǎn)來(lái)衡量,所以可以說(shuō)日歷時(shí)間是“相對(duì)時(shí)間”,但是無(wú)論你在哪一個(gè)時(shí)區(qū),在同一時(shí)刻對(duì)同一個(gè)標(biāo)準(zhǔn)時(shí)間點(diǎn)來(lái)說(shuō),日歷時(shí)間都是一樣的。

epoch:時(shí)間點(diǎn)。時(shí)間點(diǎn)在標(biāo)準(zhǔn)C/C++中是一個(gè)整數(shù),它用此時(shí)的時(shí)間和標(biāo)準(zhǔn)時(shí)間點(diǎn)相差的秒數(shù)(即日歷時(shí)間)來(lái)表示。

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

我們可以使用ANSI標(biāo)準(zhǔn)庫(kù)中的time.h頭文件。這個(gè)頭文件中定義的時(shí)間和日期所使用的方法,無(wú)論是在結(jié)構(gòu)定義,還是命名,都具有明顯的C語(yǔ)言風(fēng)格。下面,我將說(shuō)明在C/C++中怎樣使用日期的時(shí)間功能。

2. 計(jì)時(shí)

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

clock_t clock( void );

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

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

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

#define CLOCKS_PER_SEC ((clock_t)1000)

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

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

當(dāng)然,你也可以用clock函數(shù)來(lái)計(jì)算你的機(jī)器運(yùn)行一個(gè)循環(huán)或者處理其它事件到底花了多少時(shí)間:

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

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

在筆者的機(jī)器上,運(yùn)行結(jié)果如下:

Time to do 10000000 empty loops is 0.03000 seconds

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

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

在標(biāo)準(zhǔn)C/C++中,我們可通過(guò)tm結(jié)構(gòu)來(lái)獲得日期和時(shí)間,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; /* 時(shí) - 取值區(qū)間為[0,23] */
int tm_mday; /* 一個(gè)月中的日期 - 取值區(qū)間為[1,31] */
int tm_mon; /* 月份(從一月開(kāi)始,0代表一月) - 取值區(qū)間為[0,11] */
int tm_year; /* 年份,其值等于實(shí)際年份減去1900 */
int tm_wday; /* 星期 – 取值區(qū)間為[0,6],其中0代表星期天,1代表星期一,以此類(lèi)推 */
int tm_yday; /* 從每年的1月1日開(kāi)始的天數(shù) – 取值區(qū)間為[0,365],其中0代表1月1日,1代表1月2日,以此類(lèi)推 */
int tm_isdst; /* 夏令時(shí)標(biāo)識(shí)符,實(shí)行夏令時(shí)的時(shí)候,tm_isdst為正。不實(shí)行夏令時(shí)的進(jìn)候,tm_isdst為0;不了解情況時(shí),tm_isdst()為負(fù)。*/
};
#define _TM_DEFINED
#endif

ANSI C標(biāo)準(zhǔn)稱(chēng)使用tm結(jié)構(gòu)的這種時(shí)間表示為分解時(shí)間(broken-down time)。

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

#ifndef _TIME_T_DEFINED
typedef long time_t; /* 時(shí)間值 */
#define _TIME_T_DEFINED /* 避免重復(fù)定義 time_t */
#endif

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

在time.h頭文件中,我們還可以看到一些函數(shù),它們都是以time_t為參數(shù)類(lèi)型或返回值類(lèi)型的函數(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ù)將日歷時(shí)間(一個(gè)用time_t表示的整數(shù))轉(zhuǎn)換為我們平時(shí)看到的把年月日時(shí)分秒分開(kāi)顯示的時(shí)間格式tm:

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

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

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

4.1 獲得日歷時(shí)間

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

time_t time(time_t * timer);

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

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

運(yùn)行的結(jié)果與當(dāng)時(shí)的時(shí)間有關(guān),我當(dāng)時(shí)運(yùn)行的結(jié)果是:

The Calendar Time now is 1122707619

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

4.2 獲得日期和時(shí)間

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

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

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

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

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

運(yùn)行結(jié)果是:

Local hour is: 15
UTC hour is: 7

4.3 固定的時(shí)間格式

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

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

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

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

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

printf(ctime(&t));

等價(jià)于:

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

那么,下面這個(gè)程序的兩條printf語(yǔ)句輸出的結(jié)果就是不同的了(除非你將本地時(shí)區(qū)設(shè)為世界標(biāo)準(zhǔn)時(shí)間所在的時(shí)區(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;
}

運(yùn)行結(jié)果:

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

4.4 自定義時(shí)間格式

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

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

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

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

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

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

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

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

而下面的程序則顯示當(dāng)前的完整日期:

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

運(yùn)行結(jié)果:

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

4.5 計(jì)算持續(xù)時(shí)間的長(zhǎng)度

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

double difftime(time_t time1, time_t time0);

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

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

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

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

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

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

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

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

time_t mktime(struct tm * timeptr);

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

運(yùn)行結(jié)果:

Tue Jul 01 00:00:01 1997

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

5.總結(jié)

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


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

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

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

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

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

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



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

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

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

string phone=phonebook["Harry];

  標(biāo)準(zhǔn)庫(kù)設(shè)計(jì)者的解決這個(gè)問(wèn)題方法是從 multimap 中去掉下標(biāo)操作符。因此,需要用不同的方法來(lái)插入和獲取元素以及和進(jìn)行錯(cuò)誤處理。

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

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

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

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 包含兩個(gè)用相同鍵值的元素。注意 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 值表示插入操作是否成功。

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

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

find(k) 返回指向第一個(gè)與鍵 k 匹配的 pair 的迭代指針,這就是說(shuō),當(dāng)你想要檢查是否存在至少一個(gè)與該鍵關(guān)聯(lián)的值時(shí),或者只需第一個(gè)匹配時(shí),這個(gè)函數(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;
            }

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

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

  為了存取 multimap 中的多個(gè)值,使用 equal_range()、lower_bound()和 upper_bound()成員函數(shù):
equal_range(k):該函數(shù)查找所有與 k 關(guān)聯(lián)的值。返回迭代指針的 pair,它標(biāo)記開(kāi)始和結(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) 查找第一個(gè)與鍵 k 關(guān)聯(lián)的值,而 upper_bound(k) 是查找第一個(gè)鍵值比 k 大的元素。下面的例子示范用 upper_bound()來(lái)定位第一個(gè)其鍵值大于“213.108.96.7”的元素。通常,當(dāng)鍵是一個(gè)字符串時(shí),會(huì)有一個(gè)詞典編纂比較:

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):

// 插入有相同鍵的多個(gè)值
            dns.insert(make_pair("219.108.96.70","pythonzone.com"));
            dns.insert(make_pair("219.108.96.70","python-zone.com"));
            // 獲得第一個(gè)值的迭代指針
            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 具有相同的接口,其重要差別在于重復(fù)鍵,設(shè)計(jì)和使用要區(qū)別對(duì)待。此外,還要注意每個(gè)容器里 insert()成員函數(shù)的細(xì)微差別。
 

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

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

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




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


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

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

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

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

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

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

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

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

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

明白了沒(méi)有?
編碼的方法是從低位到高位

現(xiàn)在就讓我們來(lái)看看實(shí)例吧!

紅色為標(biāo)志位
其它著色為了顯示其,編碼后的位置 

Unicode十六進(jìn)制


Unicode二進(jìn)制


UTF8二進(jìn)制


UTF8十六進(jìn)制


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 閱讀(684) | 評(píng)論 (0)編輯 收藏

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

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

級(jí)別:中級(jí)

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

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

引言

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

回頁(yè)首

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

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

從計(jì)算機(jī)對(duì)多國(guó)語(yǔ)言的支持角度看,大致可以分為三個(gè)階段:

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

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

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

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

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

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

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

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

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

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

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

回頁(yè)首

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

理解編碼的關(guān)鍵,是要把字符的概念和字節(jié)的概念理解準(zhǔn)確。這兩個(gè)概念容易混淆,我們?cè)诖俗鲆幌聟^(qū)分:

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

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

回頁(yè)首

1.3 字符集與編碼

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

  1. 使用哪些字符。也就是說(shuō)哪些漢字,字母和符號(hào)會(huì)被收入標(biāo)準(zhǔn)中。所包含“字符”的集合就叫做“字符集”。
  2. 規(guī)定每個(gè)“字符”分別用一個(gè)字節(jié)還是多個(gè)字節(jié)存儲(chǔ),用哪些字節(jié)來(lái)存儲(chǔ),這個(gè)規(guī)定就叫做“編碼”。

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

UNICODE 字符集”包含了各種語(yǔ)言中使用到的所有“字符”。用來(lái)給 UNICODE 字符集編碼的標(biāo)準(zhǔn)有很多種,比如:UTF-8, UTF-7, UTF-16, UnicodeLittle, UnicodeBig 等。

回頁(yè)首

1.4 常用的編碼簡(jiǎn)介

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

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

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

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

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

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

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

回頁(yè)首

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

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

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

類(lèi)型或操作 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")

以上需要注意幾點(diǎn):

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

回頁(yè)首

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

聲明一段字符串常量:

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

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

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

// 運(yùn)行時(shí)設(shè)定當(dāng)前 ANSI 編碼,VC 格式
setlocale(LC_ALL, ".936");

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

// Visual C++ 中使用小寫(xiě) %s,按照 setlocale 指定編碼輸出到文件
// GCC 中使用大寫(xiě) %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 字符串常量有更簡(jiǎn)單的表示方法。如果源程序的編碼與當(dāng)前默認(rèn) ANSI 編碼不符,則需要使用 #pragma setlocale,告訴編譯器源程序使用的編碼:

// 如果源程序的編碼與當(dāng)前默認(rèn) ANSI 編碼不一致,
// 則需要此行,編譯時(shí)用來(lái)指明當(dāng)前源程序使用的編碼

#pragma setlocale
(".936")

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

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

回頁(yè)首

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

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

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

// 得到長(zhǎng)度為 5,因?yàn)槭?5 個(gè)字符
System.out.println(string.length());

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

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

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

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

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

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

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

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

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

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

如果 java 的源程序編碼與當(dāng)前默認(rèn) ANSI 編碼不符,則在編譯的時(shí)候,需要指明一下源程序的編碼。比如:

E:\>javac -encoding BIG5 Hello.java

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

回頁(yè)首

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

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

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

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

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

不少的人都有這個(gè)誤解。

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

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

回頁(yè)首

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

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

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

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

回頁(yè)首

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

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

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

在 Tomcat 服務(wù)器中,request.getParameter() 得到亂碼時(shí),常常是因?yàn)榍懊嫣岬降?#8220;誤解一”造成的。默認(rèn)情況下,當(dāng)提交 "%D6%D0" 給 Tomcat 服務(wù)器時(shí),request.getParameter() 將返回 [0x00D6, 0x00D0] 兩個(gè) UNICODE 字符,而不是返回一個(gè) "中" 字符。因此,我們需要使用 bytes = string.getBytes("iso-8859-1") 得到原始的字節(jié)串,再用 string = new String(bytes, "GB2312") 重新得到正確的字符串 "中"。

回頁(yè)首

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

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

如果從數(shù)據(jù)庫(kù)讀取字符串時(shí)得到亂碼,而數(shù)據(jù)庫(kù)中存放的數(shù)據(jù)又是正確的,那么往往還是因?yàn)榍懊嫣岬降?#8220;誤解一”造成的。解決的辦法還是通過(guò) string = new String( string.getBytes("iso-8859-1"), "GB2312") 的方法,重新得到原始的字節(jié)串,再重新使用正確的編碼轉(zhuǎn)化成字符串。

回頁(yè)首

3.5 電子郵件中的字符串

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

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

sbG+qcrQuqO17cf4yee74bGjz9W7+b3wudzA7dbQ0MQNCg0KvPKzxqO6uqO17cnnsaPW0NDEDQoNCg==

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

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

// 正確的標(biāo)題格式
Subject: =?GB2312?B?1tA=?=

其中,

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

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

// 正確的標(biāo)題格式
Subject: =?GB2312?Q?=D6=D0?=

如果閱讀郵件時(shí)出現(xiàn)亂碼,一般是因?yàn)?#8220;字符編碼”或“傳輸編碼”指定有誤,或者是沒(méi)有指定。比如,有的發(fā)郵件組件在發(fā)送郵件時(shí),標(biāo)題 "中":

// 錯(cuò)誤的標(biāo)題格式
Subject: =?ISO-8859-1?Q?=D6=D0?=

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

回頁(yè)首

4. 幾種錯(cuò)誤理解的糾正

誤解:“ISO-8859-1 是國(guó)際編碼?”

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

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

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

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

僅列出標(biāo)題
共15頁(yè): First 7 8 9 10 11 12 13 14 15 
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            香蕉久久一区二区不卡无毒影院 | 亚洲大片一区二区三区| 老司机免费视频一区二区| 亚洲一区二区三区免费观看 | 亚洲国产精选| 久久久精品动漫| 亚洲主播在线| 亚洲一区二区三区视频播放| 国内精品久久久久久影视8| 在线观看亚洲精品| 国产精品一区在线观看你懂的| 欧美视频中文字幕| 欧美精品一区在线| 欧美国产极速在线| 欧美激情精品久久久久久变态| 亚洲自拍偷拍色片视频| 亚洲欧洲精品一区二区三区不卡| 欧美高清在线| 欧美一区网站| 久久久久久久精| 亚洲免费在线电影| 99国产精品视频免费观看一公开| 亚洲精品日产精品乱码不卡| 99亚洲一区二区| 一区二区三区视频在线 | 久久成人综合视频| 久久五月婷婷丁香社区| 欧美成人久久| 亚洲人成7777| 亚洲一区二区三区四区五区午夜 | 伊人久久综合97精品| 亚洲国产精品悠悠久久琪琪| 日韩亚洲精品在线| 午夜在线成人av| 美女露胸一区二区三区| 亚洲精品久久久久久下一站| 夜夜嗨av一区二区三区四季av| 中文精品视频一区二区在线观看| 午夜精品久久久久久久久| 欧美一区二区三区另类| 久久婷婷成人综合色| 欧美成人一二三| 欧美视频不卡| 亚洲第一区中文99精品| 99热在线精品观看| 久久精品99久久香蕉国产色戒| 欧美成人精品1314www| 9人人澡人人爽人人精品| 亚洲欧美日韩在线观看a三区| 久久一区二区精品| 久久久亚洲人| 国产欧美精品| 亚洲高清不卡在线| 亚洲欧美在线网| 欧美成人国产| 香蕉成人伊视频在线观看| 麻豆av一区二区三区| 欧美视频一区在线| 一区二区免费在线播放| 久久精品国产免费观看| 欧美波霸影院| 亚洲一二三区在线观看| 蜜桃av噜噜一区| 欧美日韩大片| 在线欧美视频| 久久久久久9| 亚洲欧美成人| 国产精品三级久久久久久电影| 亚洲免费精品| 亚洲国产精品悠悠久久琪琪| 久久久精品动漫| 国产亚洲一区二区三区在线观看 | 在线一区亚洲| 欧美日韩国产精品专区| 亚洲第一页在线| 久久久高清一区二区三区| 亚洲视频在线观看一区| 亚洲第一在线视频| 久久亚洲午夜电影| 亚洲第一福利视频| 欧美18av| 免费看精品久久片| 亚洲国产精品久久久久秋霞蜜臀| 久久久久青草大香线综合精品| 国产精品99久久久久久宅男 | 亚洲一区精品在线| 欧美视频在线观看一区二区| 中日韩男男gay无套| 亚洲区在线播放| 欧美日韩免费一区二区三区视频 | 亚洲欧洲日产国产综合网| 欧美www在线| 乱中年女人伦av一区二区| 精品88久久久久88久久久| 久久青青草综合| 久久人人九九| 亚洲人成人77777线观看| 亚洲国产小视频在线观看| 欧美成人免费一级人片100| 精品va天堂亚洲国产| 欧美阿v一级看视频| 欧美激情1区2区3区| 一区二区激情小说| 亚洲午夜久久久| 国产亚洲精品bt天堂精选| 美女主播精品视频一二三四| 久久成人一区| 女生裸体视频一区二区三区| 久久男女视频| 国产精品a久久久久久| 欧美激情在线观看| 永久免费精品影视网站| 校园春色综合网| 亚洲女性裸体视频| 久久网站免费| 久久精品免视看| 欧美日韩和欧美的一区二区| 亚洲国产天堂久久综合| 亚洲精品久久久久久久久| 美女视频网站黄色亚洲| 免费在线欧美黄色| 亚洲国语精品自产拍在线观看| 亚洲国产99精品国自产| 999亚洲国产精| 在线精品国产成人综合| 一本色道精品久久一区二区三区 | 老巨人导航500精品| 欧美日韩免费网站| 免费在线亚洲| 午夜精品免费在线| 久久久人成影片一区二区三区观看| 日韩视频二区| 久久精品在线视频| 欧美一区二区精品在线| 欧美激情综合网| 久久久久国色av免费观看性色| 欧美另类综合| 免费一级欧美片在线播放| 国产精品美女久久久久久久 | 午夜久久福利| 亚洲视频在线二区| 欧美国产精品久久| 99国产精品视频免费观看| 另类亚洲自拍| 日韩亚洲精品电影| 久久精品夜色噜噜亚洲a∨ | 亚洲女同性videos| 国产伦精品一区二区三区四区免费 | 久久蜜桃香蕉精品一区二区三区| 欧美成人亚洲成人日韩成人| 日韩一区二区精品葵司在线| 国产精品久久久亚洲一区 | 中文久久乱码一区二区| 在线午夜精品| 亚洲国产精品久久| 亚洲一线二线三线久久久| 欧美国产在线视频| 一区二区三区国产盗摄| 一区二区三区在线高清| 国产精品一区二区三区久久| 欧美日韩中文字幕综合视频| 欧美国产日韩二区| 老司机一区二区三区| 欧美一区二区高清| 先锋影音网一区二区| 亚洲午夜羞羞片| 99国产精品久久久久久久| 亚洲国产精品欧美一二99| 美女国产一区| 麻豆国产精品一区二区三区 | 久久久久青草大香线综合精品| **网站欧美大片在线观看| 欧美日韩系列| 久久亚洲风情| 亚洲视频一区二区| 免费h精品视频在线播放| 99re6热只有精品免费观看| 国产欧美一区二区色老头| 欧美激情精品久久久久久大尺度| 亚洲一区二区视频| 亚洲国产高清一区| 久久久久久欧美| 亚洲一区视频在线观看视频| 1024精品一区二区三区| 国产精品自在线| 欧美日韩亚洲另类| 噜噜噜在线观看免费视频日韩| 亚洲一区二区三区免费视频 | 久久久久久久久一区二区| 中日韩美女免费视频网站在线观看| 激情伊人五月天久久综合| 国产精品免费网站| 欧美精品啪啪| 久久一区二区三区国产精品 | 久久婷婷国产麻豆91天堂| 亚洲图片在线观看| 亚洲精品网址在线观看| 欧美成人日韩| 久久亚洲春色中文字幕久久久| 亚洲欧美日韩在线播放|