在C11(ISO/IEC 9899:2011)標准中引入了對UTF8、UTF16以及UTF32字符編碼的支持。
其中,UTF8字符直接通過char來定義,字面量前綴使用u8。比如:
char c = u8'你';
const char *s = u8"你好";
而UTF16字符直接通過char16_t來定義,字面量前綴使用u。比如:
#include <uchar.h>
char16_t c = u'你';
const char16_t *s = "你好";
而UTF32字符直接通過char32_t來定義,字面量前綴使用U。比如:
#include <uchar.h>
char32_t c = U'你';
const char32_t *s = U"你好";
在使用char16_t以及char32_t的時候必須包含頭文件<uchar.h>。除此之外,C11標准中還添加了諸如wsprintf、wfprintf、vwprintf、wprintf等寬字符函數。不過這些函數的字符串都是const wchar_t*類型的,即寬字符指針類型。而對於Unicode字符的顯示是各家平台自己實現的。在OS X以及iOS中,至今(Apple LLVM 6.0)還沒完美地支持這一C11特性,但是UTF8、UTF16以及UTF32字面量都已經支持了,盡管系統本身不支持對UTF32編碼格式的解析。另外,也沒有包含<uchar.h>頭文件。不過,我們可以使用Foundation庫自帶的unichar類型來代替char16_t。另外,printf函數不支持對UTF16編碼字符的打印,若要打印UTF16字符或字符串,只能用Foundation裡的NSLog函數。
下面舉些例子:
#include <stdio.h>
#include <wchar.h>
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
const char *s = u8"你好,世界!";
printf("此UTF-8字符串為: %s\n", s);
unichar ch = u'你';
const unichar *us = u"好,世界!";
NSLog(@"該UTF16是:%C%S", ch, us);
wprintf(L"iOS does not support for printing wide-character unicodes!\n");
}
在NSString字符串格式中,%C對應類型為unichar(實際為unsigned short)的UTF16編碼字符;%S對應類型為const unichar*,即UTF16編碼的字符串。
由於OS X以及iOS所用的LLVM Clang編譯器沒有引入C11標准的<uchar.h>,因此有些UTF8與UTF16字符串的標准轉換函數在這些環境下均無法支持,我們只能通過Foundation庫的NSString來解決。不過,如果在Linux下,我們使用GCC4.8或更高版本的話,那麼就能使用標准的C11提供的轉換函數了。不過在標准C語言中,printf、puts這類打印函數只支持對UTF-8編碼格式的字符串的正確打印,因此我們要輸出的話需要把UTF-16編碼的字符串轉為UTF-8。下面介紹在標准C11情況下如果操作UTF-8、UTF16字符串,它們之間的相互轉換以及打印輸出。
#include <stdio.h>
#include <uchar.h>
size_t UTF16StrLen(const char16_t *utf16String)
{
if(utf16String == NULL)
return 0;
size_t index;
for(index = 0; utf16String[index] != u'\0'; index++);
return index;
}
size_t UTF16ToUTF8(char *mbBuffer, const char16_t *utf16String)
{
if(mbBuffer == NULL || utf16String == NULL)
return 0;
mbstate_t state = { };
size_t mbIndex = 0;
for(int utf16Index = 0; utf16String[utf16Index] != u'\0'; utf16Index++)
{
const size_t length = c16rtomb(&mbBuffer[mbIndex], utf16String[utf16Index], &state);
mbIndex += length;
}
mbBuffer[mbIndex] = '\0';
return mbIndex;
}
int main(int argc, char *argv[])
{
char16_t ch = u'好';
char chBuffer[64];
mbstate_t state = { };
size_t length = c16rtomb(chBuffer, ch, &state);
chBuffer[length] = '\0';
printf("The UTF-8 character length is: %zu, and the character is: %s\n", length, chBuffer);
const char *utf8Str = u8"你好, 世界。";
printf("The UTF-8 string is: %s\n", utf8Str);
const char16_t *utf16Str = u"你好, 世界。";
printf("The utf16 string length is: %zu\n", UTF16StrLen(utf16Str));
length = UTF16ToUTF8(chBuffer, utf16Str);
printf("The UTF-8 string length is: %zu, and the content is: %s\n", length, chBuffer);
printf("If the converted UTF-8 string is equal to the original one? %s\n", strcmp(chBuffer, utf8Str) == 0? "YES" : "NO");
}
在上述代碼中,<uchar.h>必須被包含。因為char16_t、char32_t、mbstate_t等類型都是定義在這個頭文件中的。C11標准庫僅提供了基本的UTF8字符串轉UTF16字符串、UTF16字符轉UTF8字符串等函數。但是木有提供獲取UTF16字符串長度以及如何將UTF16字符串轉UTF8字符串的函數。因此,我在開頭就實現這兩者功能。當然,上述代碼對UTF16的處理基本是將它作為老式的UCS-2編碼格式,因為它是雙字節固定長度的,而現代的UTF16可能是雙字節也可能是四字節變長的。像Apple搞出的Emoji就是四字節長度的UTF16編碼。所以,對於四字節的UTF16編碼而言,其轉換是否能有效工作尚不可知。
由於UTF8編碼格式的字符串對ASCII是兼容的,因此我們可以直接使用C90提供的strlen等標准庫對它們進行操作。