其實會想寫這一篇,是因為聽過幾次同事跑去問另外的同事有關於字節的問題,而讓我比較訝異的是,問的同事年資已經十幾年有了,所以,我才想說,是不是有許多人寫了很久,卻只知其然不知其所以然,所以,才有這一篇的誕生。

 

好吧,讓我們一塊來聊一聊關於這一塊吧。

 

首先,我們來討論一下: 多字節字符與寬字節字符

[       Char & wchar_t       ]

我們都知道,在C裡面,表示字符的有兩種 : char & wchar_t ,char 叫"多字節字符",一個char 佔一個字節,之所以叫多字節字符是因為它表示一個字的時候,也可能是一個字節。一個英文字符( 如 ' a ' ) 用一個char表示。

 

 

wchar_t則被稱為寬字符,一個wchar_t佔2字符,而之所以叫做寬字符,是因為所有的字都要用兩個字節 ( 即一個wchar_t ) 來表示,不管是英文還是中文。


	/*
		cout是用来输出char的,
		如果用cout输出wchar,
		只会输出地址,要使用wcout输出wchar。
	*/
	wcout.imbue(locale(""));      

	wchar_t wch1 = L's';             
	wcout << "wch1:" << wch1 << endl;
	wchar_t wch2 = L'中';            
	wcout << "wch2:" << wch2 << endl;

	wchar_t wstr[2] = L"中";          
	wcout << "wstr:" << wstr << endl;
	wchar_t wstr2[3] = L"中文";
	wcout << "wstr2:" << wstr2 << endl;

 

說明 : 

1. 用常量字符給wchar_t變量賦值的時候,前面要加L。 ( 至於為啥要加L,請繼續往下看,我會解釋 )

2. 用常量字符給wchar_t陣列賦值的時候,前面要加L。 ( 至於為啥要加L,請繼續往下看,我會解釋 )

3. 如果不加L,英文可以正常顯示,但對於非英文的文字 ( 如中文 )就會出錯。

 

 

 

 

 

 

 

 

 

 

String 與 wstring

字符數組可以表是一個字符串,但它是一個定長的字符串,我們在使用之前必須知道這個數組的長度,以方便字符串的操作,STL為我們定意好了字符串的類,string & wstring。

 

string    : 是普通的多字節版本,是基於char的,對char數組進行一種封裝。

wstring : 是Unicode版本,是基於wchar_t的,對wchar_t數組進行一種封裝。

 

 

 

 

 

 

字符集( Charcater Set ) & 字符編碼 ( Encoding )

 

Charcater Set : 是一個系統支持的所有抽像字符的集合,也就是一系列字符的集合。字符是各種文字和符號的總稱,包括各國家文字、標點符號、圖型符號、數字....等。

常見的字符集有 : ASCII 字符集、GB2312字符集 ( 簡體中文 )、BIG5字符集 ( 繁體中文 )、Unicode字符集。

 

字符編碼 ( Character Encoding ) : 是一套法則,使用該法則能夠對自然語言的字符的一個字符集 ( 如字母表或音節表 ) ,與電腦能識別的二進制數字進行配對。即它能夠在符號集合與數字系統之間建立對應關係,是信息處理的一項基本技術。

通常啊,人們用符號集合 ( 一般情況下就是文字 ) 來表達信息,而電腦的信息處理系統則是以二進制的數字來存儲和處理信息的。字符編碼就是將符號轉換為電腦能看得懂的二進制編碼。

 

一般啊,一個字符集等同於一個編碼方式,ANSI體系 ( ANSI是一種字符代碼,為使電腦支持更多語言,通常使用0x80~0xFF範圍的2個字節來表示1個字符 ) 的字符集 如 ACSII、ISO 8859-1 、GB2312、BIG 5...etc 。

 

而我們說一種編碼都是針對某一特定的字符集,一個字符集上也可以有多種編碼方式,例如UCS字符集 (也是Unicode使用的字符集 )上有UTF-8、UTF-16、UTF-32 等編碼方式。

 

 

從電腦的字符編碼發展歷史角度來看,大概經歷了三個階段 :
 

第一階段 : ASCII字符集和ASCII編碼

電腦剛開始只支援英文的 ( 即拉丁字符 ) ,其他語言不能夠在電腦上存儲和顯示。ASCII用一個字節(Byte)的7位(bit)表示一個字符,第一位置0。後來為了表示更多的歐洲常用字符又對ASCII進行了擴展,又有了EASCII,EASCII用8位表示一個字符,使它能多表示128個字符,支持了部分西歐字符。

 

第二階段 : ANSI 編碼 ( 本地化 )

為了使電腦支持更多語言,通常使用0x80~0xFF範圍的2個字節來表示1個字符。

 

第三階段 : UNICODE ( 國際化 )

為了使國際間信息交流更加方便,國際組織制定了UNICODE字符集,為各種語言中的每一個字符設定了統一並且唯一的數字編號,以滿足跨語言、跨平台進行文本轉換、處理的要求。UNICODE常見的有三種編碼方式 : UTF-8 ( 1個字節表示 )、UTF-16 ( 2個字節表示 )、UTF-32 ( 4個字節表示 )。

 

 

 

 

 

 

 

接下來,我們來詳細的解釋一下什麼是ASCII ( 撐住啊啊啊 ~~ )

 

 

 

ASCII字符集&編碼

 

ASCII ( American Standard Code for Information Interchange , 美國信息交換標準代碼 ) 是基於拉丁字母的一套電腦編碼系統。它主要用於顯示現代英語,而其擴展版本EASCII則可以免強顯示其他西歐語言。它是現今最通用的單字節編碼系統 ( 但是它有被Unicode 追上的跡象 ),並等同於國際標準ISO/IEC646。

 

ASCII字符集 : 主要包括控制字符 ( Enter 、Backspace ..etc ),可顯示字符 ( 英文大小寫字符、阿拉伯數字和西文符號 ) 。 ( 字符 = 字+ 符號 )

 

 

 

 

 

 

BIG 5

接下來,我們淺談一下BIG5好了,畢竟在台灣地區,都是使用繁體中文 ( 正體中文 )。

BIG5又稱大五碼或五大碼,是使用蠻體中文社群中最常用的電腦漢字字元集標準,共收錄13,060個漢字。

中文碼分為內碼及交換碼兩類,Big5屬於中文內碼,知名的中文交換碼有CCCII、CNS11643。( 這兩個有興趣的朋友再請自行研究囉 )

Big5雖普及於台灣香港澳門等繁體中文通行區,但長期以來並非當地的國家/地區標準或官方標準,而只是業界標準倚天中文系統Windows繁體中文版等主要系統的字元集都是以Big5為基準,但廠商又各自增加不同的造字與造字區,衍生成多種不同版本。

2003年,Big5收錄到CNS11643中文標準交換碼的附錄當中,取得了較正式的地位。這個最新版本稱為Big5-2003

https://zh.wikipedia.org/wiki/%E5%A4%A7%E4%BA%94%E7%A2%BC

 

 

 

 

 

偉大的Unicode

Unicode中文萬國碼、國際碼、統一碼、單一碼)是電腦科學領域裡的一項業界標準。它對世界上大部分的文字系統進行了整理、編碼,使得電腦可以用更為簡單的方式來呈現和處理文字。

Unicode伴隨著通用字元集的標準而發展,同時也以書本的形式[1]對外發表。Unicode至今仍在不斷增修,每個新版本都加入更多新的字元。目前最新的版本為2019年3月5日公布的12.0.0[2],已經收錄超過13萬個字元(第十萬個字元在2005年獲採納)。Unicode涵蓋的資料除了視覺上的字形、編碼方法、標準的字元編碼外,還包含了字元特性,如大小寫字母。

Unicode發展由非營利機構統一碼聯盟負責,該機構致力於讓Unicode方案取代既有的字元編碼方案。因為既有的方案往往空間非常有限,亦不適用於多語環境。

Unicode備受認可,並廣泛地應用於電腦軟體的國際化與在地化過程。有很多新科技,如可延伸標示語言(Extensible Markup Language,簡稱:XML)、Java程式語言以及現代的作業系統,都採用Unicode編碼。

 

起源與發展

Unicode是為了解決傳統的字元編碼方案的侷限而產生的,例如ISO 8859-1所定義的字元雖然在不同的國家中廣泛地使用,可是在不同國家間卻經常出現不相容的情況。很多傳統的編碼方式都有一個共同的問題,即容許電腦處理雙語環境(通常使用拉丁字母以及其本地語言),但卻無法同時支援多語言環境(指可同時處理多種語言混合的情況)。

Unicode編碼包含了不同寫法的字,如「ɑa」、「強/强」、「戶/户/戸」。然而在漢字方面引起了一字多形的認定爭議(詳見中日韓統一表意文字主題)。

在文字處理方面,統一碼為每一個字元而非字形定義唯一的程式碼(即一個整數)。換句話說,統一碼以一種抽象的方式(即數字)來處理字元,並將視覺上的演繹工作(例如字體大小、外觀形狀、字體形態、文體等)留給其他軟體來處理,例如網頁瀏覽器或是文字處理器。

目前,幾乎所有電腦系統都支援基本拉丁字母,並各自支援不同的其他編碼方式。Unicode為了和它們相互相容,其首256字元保留給ISO 8859-1所定義的字元,使既有的西歐語系文字的轉換不需特別考量;並且把大量相同的字元重複編到不同的字元碼中去,使得舊有紛雜的編碼方式得以和Unicode編碼間互相直接轉換,而不會遺失任何資訊。舉例來說,全形格式區段包含了主要的拉丁字母的全形格式,在中文、日文、以及韓文字形當中,這些字元以全形的方式來呈現,而不以常見的半形形式顯示,這對豎排文字和等寬排列文字有重要作用。

在表示一個Unicode的字元時,通常會用「U+」然後緊接著一組十六進位的數字來表示這一個字元。在基本多文種平面(英文:Basic Multilingual Plane,簡寫BMP。又稱為「零號平面」、plane 0)裏的所有字元,要用四個數字(即兩個char,16bit ,例如U+4AE0,共支援六萬多個字元);在零號平面以外的字元則需要使用五個或六個數字。舊版的Unicode標準使用相近的標記方法,但卻有些微小差異:在Unicode 3.0裏使用「U-」然後緊接著八個數字,而「U+」則必須隨後緊接著四個數字。

標準

位於美國加州的Unicode組織允許任何願意支付會費的公司和個人加入,其成員包含了主要的電腦軟硬體廠商,例如奧多比系統蘋果公司惠普IBM微軟全錄等。

20世紀80年代末,組成Unicode組織的商業機構,和國際合作的國際標準化組織因為電腦普及和資訊國際化的前提下,分別各自成立了Unicode組織[3]和ISO-10646工作小組。他們不久便發現對方的存在,大家為著相同的目的而工作。1991年,Unicode Consortium與ISO/IEC JTC1/SC2同意保持Unicode碼表與ISO 10646標準保持相容並密切協調各自標準近一步的擴展。雖然實際上兩者的字集編碼相同,但實質上兩者確實為兩個不同的標準。Unicode 1.1對應於ISO 10646-1:1993,Unicode 3.0對應於ISO 10646-1:2000,Unicode 3.2對應於ISO 10646-2:2001,Unicode 4.0對應於ISO 10646:2003,Unicode 5.0對應於ISO 10646:2003及附錄1–3。

Unicode自版本2.0開始保持了向後相容,即新的版本僅僅增加字元,原有字元不會被刪除或更名。

統一碼聯盟在1991年首次發布了The Unicode Standard。Unicode的開發結合了國際標準化組織所制定的ISO/IEC 10646,即通用字元集。Unicode與ISO/IEC 10646在編碼的運作原理相同,但The Unicode Standard包含了更詳盡的實現資訊、涵蓋了更細節的主題,諸如位元編碼(bitwise encoding)、校對以及呈現等。The Unicode Standard也列舉了諸多的字元特性,包含了那些必須支援兩種閱讀方向的文字(由左至右或由右至左的文字閱讀方向,例如阿拉伯文是由右至左)。Unicode與ISO/IEC 10646這兩個標準在術語上的使用有些微的不同。

在2005年,Unicode的第十萬個字元被引入成為標準之一,該字元被用於馬拉雅拉姆語

 

Unicode的編碼和實現

大概來說,Unicode編碼系統可分為編碼方式和實現方式兩個層次。

10大設計原則

在《The Unicode Standard Version 6.2 – Core Specification》[31] ,給出了Unicode的十大設計原則:

  • Universality:提供單一、綜合的字元集,編碼一切現代與大部分歷史文獻的字元。
  • Efficiency:易於處理與分析。
  • Characters, not glyphs:字元,而不是字形。
  • Semantics:字元要有良好定義的語意
  • Plain text:僅限於文字字元
  • Logical order:預設記憶體表示是其邏輯序
  • Unification:把不同語言的同一書寫系統(scripts)中相同字元統一起來。
  • Dynamic composition:附加符號可以動態組合。
  • Stability:已分配的字元與語意不再改變。
  • Convertibility:Unicode與其他著名字元集可以精確轉換。

資料來源 : https://zh.wikipedia.org/wiki/Unicode

 

( 至於 UTF-8 、UTF-16、UTF-32 就請各位自己去了解一下,畢竟這一篇並沒有要往這邊深入,過多的介紹只會讓主題跑掉而已 )

 

 

 

 

接下來,我們用實際範例來看,這樣可能會比較清楚,首先我們先來看怎麼樣設定Unicode Character & Muliti-Byte Character

 

 

Step1 . Project Name -> Properties -> General 

 

當你選擇Use Unicode Character Set 時,會有預設的編譯宏 : _UNICODE和UNICODE

 

 

 

當你選擇Use Multi-Byte Set 時,會有預設的編譯宏 : _MBCS

 

 

 

 

 

這樣,問題來了,Unicode Character Set 和 Multi-Byte Character Set有什麼區別 ?

來,我們來看一個例子,首先,我們先將模式調到Multi-Byte Character set去,然後呢,我們寫一個Messagebox


	void CForBlog_StringDlg::OnBnClickedOk()
   {
	::MessageBox(NULL, "Hello Baby", "Demo", MB_OK);
   }
 

 

這個Demo非常簡單,應該就不需要解釋了吧 ! 我們將Character set 設置在Multi-Byte的時候,可以正常運行和編譯,但是如果你設到Unicode Character Set的時候,就會有問題了 : 


1>c:\users\ericpeng\documents\visual studio 2013\projects\forblog_string\forblog_string\forblog_stringdlg.cpp(158): error C2664: 'int MessageBoxW(HWND,LPCWSTR,LPCWSTR,UINT)' : cannot convert argument 2 from 'const char [11]' to 'LPCWSTR'
1>          Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
 

 

這是因為MessageBox有兩個版本,一個MessageBoxW針對Unicode版本的,一個是MessageBoxA針對Multi-Byte的,它們通過不同的 Macro使用不同的版本。 當我們使用Unicode Character Set,就是預設了 _UNICODE、UNICODE ,所以Compile就會使用MessageBoxW,這時我們傳入多字節常量字符串肯定就會有問題拉,所以你必須要在前面加一個L或_T。

 

 

 

接下來,我們來理解一下為什麼要加上 _T或TEXT或L好了,其實你在MFC或Win32的程式中,你會看到很多地方會使用這三個,那麼這三個有啥區別 ? 

 

首先,我們看tchar.h的定義,_T和_Text的功能其實是一樣的,他們是同一個Macro

 

然後我們再看看_T的 : 

#ifdef  _UNICODE
// ...  
#define __T(x)      L ## x
// ...  
#else   /* ndef _UNICODE */
// ...  
#define __T(x)      x
// ... 
#endif  /* _UNICODE */
 

這樣懂了嗎 ? 當我們的Project在使用Use Unicode Character Set時,_T和_TEXT就會在常量字符串前面加L,否則( 即Use Multi-Byte Character Set 時 ) 就會以一般的字符串處理。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

接下來這一個部份,也是很多很多人在VC++裡面會搞混的東西。

DWORD 、LPSTR、LPWSTR、LPCSTR、LPCWSTR、LPTSTR、LPCTSTR

 

相互轉換方法 : 

LPWSTR->LPTSTR: W2T(); 
LPTSTR->LPWSTR: T2W(); 
LPCWSTR->LPCSTR: W2CT(); 
LPCSTR->LPCWSTR: T2CW();

ANSI->UNICODE: A2W(); 
UNICODE->ANSI: W2A();

 

 

Code

C++
// convert_from_char.cpp
// compile with: /clr /link comsuppw.lib

#include <iostream>
#include <stdlib.h>
#include <string>

#include "atlbase.h"
#include "atlstr.h"
#include "comutil.h"

using namespace std;
using namespace System;

int main()
{
    // Create and display a C style string, and then use it
    // to create different kinds of strings.
    char *orig = "Hello, World!";
    cout << orig << " (char *)" << endl;

    // newsize describes the length of the
    // wchar_t string called wcstring in terms of the number
    // of wide characters, not the number of bytes.
    size_t newsize = strlen(orig) + 1;

    // The following creates a buffer large enough to contain
    // the exact number of characters in the original string
    // in the new format. If you want to add more characters
    // to the end of the string, increase the value of newsize
    // to increase the size of the buffer.
    wchar_t * wcstring = new wchar_t[newsize];

    // Convert char* string to a wchar_t* string.
    size_t convertedChars = 0;
    mbstowcs_s(&convertedChars, wcstring, newsize, orig, _TRUNCATE);
    // Display the result and indicate the type of string that it is.
    wcout << wcstring << _T(" (wchar_t *)") << endl;

    // Convert the C style string to a _bstr_t string.
    _bstr_t bstrt(orig);
    // Append the type of string to the new string
    // and then display the result.
    bstrt += " (_bstr_t)";
    cout << bstrt << endl;

    // Convert the C style string to a CComBSTR string.
    CComBSTR ccombstr(orig);
    if (ccombstr.Append(_T(" (CComBSTR)")) == S_OK)
    {
        CW2A printstr(ccombstr);
        cout << printstr << endl;
    }

    // Convert the C style string to a CStringA and display it.
    CStringA cstringa(orig);
    cstringa += " (CStringA)";
    cout << cstringa << endl;

    // Convert the C style string to a CStringW and display it.
    CStringW cstring(orig);
    cstring += " (CStringW)";
    // To display a CStringW correctly, use wcout and cast cstring
    // to (LPCTSTR).
    wcout << (LPCTSTR)cstring << endl;

    // Convert the C style string to a basic_string and display it.
    string basicstring(orig);
    basicstring += " (basic_string)";
    cout << basicstring << endl;

    // Convert the C style string to a System::String and display it.
    String ^systemstring = gcnew String(orig);
    systemstring += " (System::String)";
    Console::WriteLine("{0}", systemstring);
    delete systemstring;
}
Output
Hello, World! (char *)
Hello, World! (wchar_t *)
Hello, World! (_bstr_t)
Hello, World! (CComBSTR)
Hello, World! (CStringA)
Hello, World! (CStringW)
Hello, World! (basic_string)
Hello, World! (System::String)

 

 

 

 

 

 

 

 

arrow
arrow
    創作者介紹
    創作者 Eric 的頭像
    Eric

    一個小小工程師的心情抒發天地

    Eric 發表在 痞客邦 留言(0) 人氣()