淺談C中的wprintf和寬字元顯示

來源:互聯網
上載者:User

轉自:http://blog.csdn.net/lovekatherine/archive/2007/11/06/1868724.aspx  [在此向原文作者說聲謝謝!若有讀者看到文章轉載時請寫該轉載地址,不要寫我的BLOG地址。尊重他人的勞動成果 ^_^ ]

今天在CSDN的Blog首頁看到一篇文章“也談電腦字元編碼 ”,由於前一陣業餘翻譯了“UTF-8 and Unicode FAQ for Unix/Linux”一文,自己對字元集、編碼和Unicode等內容一直保著者很強的興趣,自然不會放過這樣的文章。

作者的文章寫得很明白易懂,雖然有一些概念上的細節問題我覺得有商榷之處;作者還給出一個簡單的在windows下使用wprintf正確輸出字串“中文”的小例子,我linux下模仿作者給出的範例程式碼寫了如下的範例程式碼:

#include <cstdio>
#include <cstdlib>
#include <clocale>
#include <cwchar>

int main(int argc, char * argv[])
...{
    wchar_t wstr[] = L"中文";   
    setlocale(LC_ALL, "zh_CN.UTF-8");       
    wprintf(L"%s\n",wstr);
   
    return 0;
}

這裡需要說明的是我的機器的locale為"zh_CN-UTF-8"

然而程式的運行結果卻讓我很詫異

whodare@whodare:$ ./a.out
-N

我的第一反應就是作者的範例程式碼是不是有問題,畢竟這裡面調用的全都是C的標準庫函數,不應該存在移植性問題;然而,我找了台windows機器測試作者的代碼,結果讓我很鬱悶,一切正常......

為 什麼我在Linux下的程式就不對呢?我很不服氣,於是開始以各種關鍵字進行搜尋,想看看別人是否遇到過類似的問題。一個搜尋結果引起了我的主意,有人說 問題出在wprintf中的格式轉換符上,將%s替換成%ls就沒有這樣的問題。帶著幾分懷疑,我修改了上面的程式,編譯運行後,居然真的就沒問題了

#include <cstdio>
#include <cstdlib>
#include <clocale>
#include <cwchar>

int main(int argc, char * argv[])
......{
    wchar_t wstr[] = L"中文";   
    setlocale(LC_ALL, "zh_CN.UTF-8");      
    wprintf(L"%s ",wstr);
    wprintf(L"%ls ",wstr);
   
    return 0;
}

上述代碼的運行結果

whodare@whodare:$ ./a.out
-N
中文

問題解決了,可我還是感到迷茫:格式轉換符"ls"和“s"的區別是什嗎?為什麼原來的程式會出問題?“-N"這個字串是怎麼冒出來的?為什麼作者在windows下的程式就不存在該問題?

這麼多的疑惑堵在心口,我哪能心安呢。知其然還要知其所以然嘛!花了一個下午的時間仔細讀了下wprintf的manual,並在gdb的協助下做了各種實驗,終於算是把我的疑惑基本都解決了。

一、以下的所有實驗都是以“中文”為例,因此有必要先把它的Unicdoe碼值、UTF-8編碼都列出來,以便於更好的理解下文

‘中’   Unicode碼值:U+4E2D  UTF-8 編碼 e4 b8 ad
‘文’   Unicode碼值:U+6587  UTF-8 編碼  e6 96 87

二、我們需要理解用char[ ]和wchar_t [ ]來存放“中文”時有什麼不同

    char    str[]="中文";
    wchar_t wstr[] = L"中文";   

我們使用gdb這個強大的工具來查看str[]和wst[]中究竟都存放了哪些值(請注意顏色之間的對應關係)

(gdb) x /8xb &str
0xbf83decd:     0xe4    0xb8    0xad    0xe6    0x96    0x87    0x00    0xf0
(gdb) x /12xb &wstr
   0xbf83dec0:     0x2d    0x4e    0x00    0x00    0x87    0x65    0x00    0x00
   0xbf83dec8:     0x00    0x00    0x00    0x00

不難看出,char str[ ]中儲存的是“中文"的UTF-8編碼,這是因為我的機器的locale是zh_CN.UTF-8,程式源檔案的自然採用的是UTF-8編碼,因此編譯器 在處理 char str[ ]="中文"; 時,t它對str[]所做得初始化實際上可以理解成    char str[ ]={ 0xe4,0xb8,0xad,0xe6,0x96,0x87,0x00}

而wchar_t wstr[ ]中存放的是“中文"的Unicode碼值,這符合C標準對寬字元的定義。這裡需要解釋的是C標準中規定寬字元是16 bit的字元,而從GNU glibc 2.2開始,類型wchar_t只用於存放32-bit的ISO 10646碼值(你可以粗略的把ISO 10646理解成Unicode,儘管它們並不是一回事),而獨立於當前使用的locale;因此在上面的輸出中,我們看到每個Unicode碼值用 32bit表示,而不是16bit。

三、關於%s和%ls的區別

我搜到了一篇文章(很傷感,我再此發現在CS領域,最靠的住的資料總是英文的),裡面對各種格式轉換符有詳細的解釋,願意看原文的同學直接忽略本段文字.......

http://www-ccs.ucsd.edu/c/lib_prin.html

首先,%ls和%s的區別很簡單,%ls意味著將對應的參數會被當作基於寬字元的字串(wide chraracter string )看待,而%s則意味著對應的參數會被當作一般字元串(multi-byte string)看待。

其次,不要因為上面一句話而錯誤的認為%s只用於printf,而%ls只用於wprintf 。實際上,(printf, wprintf) 和(%s,%ls)這兩個元組之間是相互獨立的,也就是說它們之間的四種組合都是可以的。

再次,printf用於byte stream,即輸出資料流中的每個字元顫1 byte;而wprintf則用於wide stream,輸出資料流中的每個字元不止 1 byte。

說了一堆廢話,還是結合執行個體來看看%ls和%s的區別吧

例子1 printf + %s + wstr

printf("%s ",wstr);

whodare@whodare:$ ./a.out
-N

哈,這個鬱悶的"-N"又一次出現!為什麼會出現呢?讓我來分析一下printf在執行時所完成的操作吧。

這裡用了%s, printf 就會將對應的參數wstr視為一般字元串(儘管我們清楚他是個wcs而不是mbs);另一方面,我們已經看到了wstr[ ]的記憶體布局,其前3 byte為 0x2d ,0x4e,0x00。我們都知道C中的字串以'\0'為結束標誌,因此printf只會處理wstr[ ]中的前三個byte,而查一查ASCII表,0x2d對應字元'-',0x4e對應字元'N',所以我們會看到”-N"這個詭異的輸出。

例子2 printf + %ls + wstr

printf("%ls ",wstr);

whodare@whodare:$ ./a.out
中文

使用了%ls,printf會將對應的參數視為寬字元串(wcs),而printf又對應byte stream,因此這裡要對寬字元(wcs)進行轉換,變成普通的字串(mbs)。這裡的轉換是printf通過對每個寬字元隱式的調用wcrtomb ()這個標準庫函數完成的。按麼,wcrtomb()這個函數進行是按照什麼規則進行轉換的?這就是setlocale()的作用所在了,wcrtomb 會依據程式員設定的locale,將wcha_t中存放的碼值,轉換為相應的的多位元組編碼。

回到例子中,我的機器的locale為zh_CN.UTF-8,對應的編碼為UTF-8,因此wstr[ ]中存放的Unicode碼值會轉換為UTF-8編碼的形式輸出到標準輸出資料流中,這樣採用UTF-8編碼的console就能正確識別受到的位元組流並顯示出"中文"

例子3  wprintf + %s +wstr (最初的代碼!)

wprintf(L"%s ",wstr);

whodare@whodare:$ ./a.out
-N

使用了%s,wprintf會將對應的參數視為一般字元串mbs,儘管我們還是很清楚它其實是個wcs。wprintf 使用的是wide stream,因此需要將所給的mbs參數轉換為wcs再由wprintf完成輸出;這個轉換是由wprintf隱式的對mbs不斷調用mbrtowc來 完成,轉換規則依然是和locale相關的。

我們知道wstr的記憶體布局為:
    0x2d    0x4e    0x00    0x00    0x87    0x65    0x00    0x00
    0x00    0x00    0x00    0x00

該"mbs"的轉換結果為 L‘0x2d' + L '0x4e' + L '0x00' ,最終輸出結果又是討厭的"-N"

例子4 wprintf + %ls+ wstr

wprintf(L"%ls ",wstr);

whodare@whodare:$ ./a.out
中文

使用了%ls,wprintf會將對應參數視為寬字元串wcs,這次終於沒有搞錯。因此wprintf會順利的將給定的寬字元串寫入標準輸出資料流,最終正確顯示"中文"

看完這4個例子,你對wprintf、printf和%ls 、%s的使用還有疑惑嗎?

四、小結

    1。要清楚%ls和%s的意義在於指明所期待的參數是何種字串,而printf和wprintf的區別在於所使用的是不同類型的stream

    2。貌似在linux下輸出“中文"的正確方法是 wprintf( "%ls\n",L"中文") ,而引文中作者在Windows成功操作的wprintf("%s\n", L"中文")在linux無法正確工作,至於為何wprintf這個標準庫函數在兩個系統下有不同表現,我是無心再向下深挖了,難道這又是VC一處不符合 標準的地方?.......

    3 。貌似還有一個%S,單獨用於表示對應參數是寬字元串

       誰能告訴我該問題的答案,不盛感激.......

本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/code_robot/archive/2010/06/22/5686176.aspx

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.