当前位置:主页C 语言入门教程

22. 多字节字符

文章来源:知付 更新时间:2022-05-28 16:39 热度:164

本章介绍 C 语言如何处理非英语字符。

目录
  • Unicode 简介

  • 字符的表示方法

  • 多字节字符的表示

  • 宽字符

  • 多字节字符处理函数

  • mblen()

  • wctomb()

  • mbtowc()

  • wcstombs()

  • mbstowcs()

Unicode 简介

C 语言诞生时,只考虑了英语字符,使用7位的 ASCII 码表示所有字符。ASCII 码的范围是0到127,也就是100多个字符,所以 char 类型只占用一个字节。

但是,如果处理非英语字符,一个字节就不够了,单单是中文,就至少有几万个字符,字符集就势必使用多个字节表示。

最初,不同国家有自己的字符编码方式,这样不便于多种字符的混用。因此,后来就逐渐统一到 Unicode 编码,将所有字符放入一个字符集。

Unicode 为每个字符提供一个号码,称为码点(code point),其中0到127的部分,跟 ASCII 码是重合的。通常使用“U+十六进制码点”表示一个字符,比如 U+0041 表示字母 A

Unicode 编码目前一共包含了100多万个字符,码点范围是 U+0000 到 U+10FFFF。完整表达整个 Unicode 字符集,至少需要三个字节。但是,并不是所有文档都需要那么多字符,比如对于 ASCII 码就够用的英语文档,如果每个字符使用三个字节表示,就会比单字节表示的文件体积大出三倍。

为了适应不同的使用需求,Unicode 标准委员会提供了三种不同的表示方法,表示 Unicode 码点。

  • UTF-8:使用1个到4个字节,表示一个码点。不同的字符占用的字节数不一样。

  • UTF-16:对于U+0000 到 U+FFFF 的字符(称为基本平面),使用2个字节表示一个码点。其他字符使用4个字节。

  • UTF-32:统一使用4个字节,表示一个码点。

其中,UTF-8 的使用最为广泛,因为对于 ASCII 字符(U+0000 到 U+007F),它只使用一个字节表示,这就跟 ASCII 的编码方式完全一样。

C 语言提供了两个宏,表示当前系统支持的编码字节长度。这两个宏都定义在头文件 limits.h

  • MB_LEN_MAX :任意支持地区的最大字节长度,定义在 limits.h

  • MB_CUR_MAX :当前语言的最大字节长度,总是小于或等于 MB_LEN_MAX ,定义在 stdlib.h

字符的表示方法

字符表示法的本质,是将每个字符映射为一个整数,然后从编码表获得该整数对应的字符。

C 语言提供了不同的写法,用来表示字符的整数号码。

  • 123 :以八进制值表示一个字符,斜杠后面需要三个数字。

  • x4D :以十六进制表示一个字符, x 后面是十六进制整数。

  • u2620 :以 Unicode 码点表示一个字符(不适用于 ASCII 字符),码点以十六进制表示, u 后面需要4个字符。

  • U0001243F :以 Unicode 码点表示一个字符(不适用于 ASCII 字符),码点以十六进制表示, U 后面需要8个字符。

printf("ABCn");
printf("101102103n");
printf("x41x42x43n");

上面三行都会输出“ABC”。

printf("u2022 Bullet 1n");
printf("U00002022 Bullet 1n");

上面两行都会输出“• Bullet 1”。

多字节字符的表示

C 语言预设只有基本字符,才能使用字面量表示,其它字符都应该使用码点表示,并且当前系统还必须支持该码点的编码方法。

所谓基本字符,指的是所有可打印的 ASCII 字符,但是有三个字符除外: @$`

因此,遇到非英语字符,应该将其写成 Unicode 码点形式。

char* s = "u6625u5929";
printf("%sn", s); // 春天

上面代码会输出中文“春天”。

如果当前系统是 UTF-8 编码,可以直接用字面量表示多字节字符。

char* s = "春天";
printf("%sn", s);

注意, u + 码点U + 码点 的写法,不能用来表示 ASCII 码字符(码点小于 0xA0 的字符),只有三个字符除外: 0x24$ ), 0x40@ )和 0x60` )。

char* s = "u0024u0040u0060";
printf("%sn", s);  // @

本章介绍 C 语言如何处理非英语字符。

##### 目录 [[隐藏](null )]

 - [Unicode 简介](https://wangdoc.com/clang/multibyte.html#unicode-%E7%AE%80%E4%BB%8B )

 - [字符的表示方法](https://wangdoc.com/clang/multibyte.html#%E5%AD%97%E7%AC%A6%E7%9A%84%E8%A1%A8%E7%A4%BA%E6%96%B9%E6%B3%95 )

 - [多字节字符的表示](https://wangdoc.com/clang/multibyte.html#%E5%A4%9A%E5%AD%97%E8%8A%82%E5%AD%97%E7%AC%A6%E7%9A%84%E8%A1%A8%E7%A4%BA )

 - [宽字符](https://wangdoc.com/clang/multibyte.html#%E5%AE%BD%E5%AD%97%E7%AC%A6 )

 - [多字节字符处理函数](https://wangdoc.com/clang/multibyte.html#%E5%A4%9A%E5%AD%97%E8%8A%82%E5%AD%97%E7%AC%A6%E5%A4%84%E7%90%86%E5%87%BD%E6%95%B0 )
 - [mblen()](https://wangdoc.com/clang/multibyte.html#mblen )

 - [wctomb()](https://wangdoc.com/clang/multibyte.html#wctomb )

 - [mbtowc()](https://wangdoc.com/clang/multibyte.html#mbtowc )

 - [wcstombs()](https://wangdoc.com/clang/multibyte.html#wcstombs )

 - [mbstowcs()](https://wangdoc.com/clang/multibyte.html#mbstowcs )

## Unicode 简介

C 语言诞生时,只考虑了英语字符,使用7位的 ASCII 码表示所有字符。ASCII 码的范围是0到127,也就是100多个字符,所以 ` char ` 类型只占用一个字节。

但是,如果处理非英语字符,一个字节就不够了,单单是中文,就至少有几万个字符,字符集就势必使用多个字节表示。

最初,不同国家有自己的字符编码方式,这样不便于多种字符的混用。因此,后来就逐渐统一到 Unicode 编码,将所有字符放入一个字符集。

Unicode 为每个字符提供一个号码,称为码点(code point),其中0到127的部分,跟 ASCII 码是重合的。通常使用“U+十六进制码点”表示一个字符,比如 ` U+0041 ` 表示字母 ` A ` 。

Unicode 编码目前一共包含了100多万个字符,码点范围是 U+0000 到 U+10FFFF。完整表达整个 Unicode 字符集,至少需要三个字节。但是,并不是所有文档都需要那么多字符,比如对于 ASCII 码就够用的英语文档,如果每个字符使用三个字节表示,就会比单字节表示的文件体积大出三倍。

为了适应不同的使用需求,Unicode 标准委员会提供了三种不同的表示方法,表示 Unicode 码点。

 - UTF-8:使用1个到4个字节,表示一个码点。不同的字符占用的字节数不一样。

 - UTF-16:对于U+0000 到 U+FFFF 的字符(称为基本平面),使用2个字节表示一个码点。其他字符使用4个字节。

 - UTF-32:统一使用4个字节,表示一个码点。

其中,UTF-8 的使用最为广泛,因为对于 ASCII 字符(U+0000 到 U+007F),它只使用一个字节表示,这就跟 ASCII 的编码方式完全一样。

C 语言提供了两个宏,表示当前系统支持的编码字节长度。这两个宏都定义在头文件 ` limits.h ` 。

 -  ` MB_LEN_MAX ` :任意支持地区的最大字节长度,定义在 ` limits.h ` 。

 -  ` MB_CUR_MAX ` :当前语言的最大字节长度,总是小于或等于 ` MB_LEN_MAX ` ,定义在 ` stdlib.h ` 。

## 字符的表示方法

字符表示法的本质,是将每个字符映射为一个整数,然后从编码表获得该整数对应的字符。

C 语言提供了不同的写法,用来表示字符的整数号码。

 -  ` 123 ` :以八进制值表示一个字符,斜杠后面需要三个数字。

 -  ` x4D ` :以十六进制表示一个字符, ` x ` 后面是十六进制整数。

 -  ` u2620 ` :以 Unicode 码点表示一个字符(不适用于 ASCII 字符),码点以十六进制表示, ` u ` 后面需要4个字符。

 -  ` U0001243F ` :以 Unicode 码点表示一个字符(不适用于 ASCII 字符),码点以十六进制表示, ` U ` 后面需要8个字符。

printf("ABCn");
printf("101102103n");
printf("x41x42x43n");

上面三行都会输出“ABC”。

printf("u2022 Bullet 1n");
printf("U00002022 Bullet 1n");

上面两行都会输出“• Bullet 1”。

## 多字节字符的表示

C 语言预设只有基本字符,才能使用字面量表示,其它字符都应该使用码点表示,并且当前系统还必须支持该码点的编码方法。

所谓基本字符,指的是所有可打印的 ASCII 字符,但是有三个字符除外: ` @ ` 、 ` $ ` 、 ` ` ` 。

因此,遇到非英语字符,应该将其写成 Unicode 码点形式。

char* s = "u6625u5929";
printf("%sn", s); // 春天

上面代码会输出中文“春天”。

如果当前系统是 UTF-8 编码,可以直接用字面量表示多字节字符。

char* s = "春天";
printf("%sn", s);

注意, ` u + 码点 ` 和 ` U + 码点 ` 的写法,不能用来表示 ASCII 码字符(码点小于 ` 0xA0 ` 的字符),只有三个字符除外: ` 0x24 ` ( ` $ ` ), ` 0x40 ` ( ` @ ` )和 ` 0x60 ` ( ` ` ` )。
上面代码会输出三个 Unicode 字符“@$`”,但是其它 ASCII 字符都不能用这种表示法表示。

为了保证程序执行时,字符能够正确解读,最好将程序环境切换到本地化环境。

setlocale(LC_ALL, "");

上面代码中,使用 ` setlocale() ` 切换执行环境到系统的本地化语言。 ` setlocale() ` 的原型定义在头文件 ` locale.h ` ,详见标准库部分的《locale.h》章节。

像下面这样,指定编码语言也可以。

setlocale(LC_ALL, "zh_CN.UTF-8");

上面代码将程序执行环境,切换到中文环境的 UTF-8 编码。

C 语言允许使用 ` u8 ` 前缀,对多字节字符串指定编码方式为 UTF-8。

char* s = u8"春天";
printf("%sn", s);

一旦字符串里面包含多字节字符,就意味着字符串的字节数与字符数不再一一对应了。比如,字符串的长度为10字节,就不再是包含10个字符,而可能只包含7个字符、5个字符等等。

setlocale(LC_ALL, "");

char* s = "春天";
printf("%dn", strlen(s)); // 6

上面示例中,字符串 ` s ` 只包含两个字符,但是 ` strlen() ` 返回的结果却是6,表示这两个字符一共占据了6个字节。

C 语言的字符串函数只针对单字节字符有效,对于多字节字符都会失效,比如 ` strtok() ` 、 ` strchr() ` 、 ` strspn() ` 、 ` toupper() ` 、 ` tolower() ` 、 ` isalpha() ` 等不会得到正确结果。

## 宽字符

上一小节的多字节字符串,每个字符的字节宽度是可变的。这种编码方式虽然使用起来方便,但是很不利于字符串处理,因此必须逐一检查每个字符占用的字节数。所以除了这种方式,C 语言还提供了确定宽度的多字节字符存储方式,称为宽字符(wide character)。

所谓“宽字符”,就是每个字符占用的字节数是固定的,要么是2个字节,要么是4个字节。这样的话,就很容易快速处理。

宽字符有一个单独的数据类型 wchar_t,每个宽字符都是这个类型。它属于整数类型的别名,可能是有符号的,也可能是无符号的,由当前实现决定。该类型的长度为16位(2个字节)或32位(4个字节),足以容纳当前系统的所有字符。它定义在头文件 ` wchar.h ` 里面。

宽字符的字面量必须加上前缀“L”,否则 C 语言会把字面量当作窄字符类型处理。

setlocale(LC_ALL, "");

wchar_t c = L'牛';
printf("%lcn", c);

wchar_t* s = L"春天";
printf("%lsn", s);

上面示例中,前缀“L”在单引号前面,表示宽字符,对应 ` printf() ` 的占位符为 ` %lc ` ;在双引号前面,表示宽字符串,对应 ` printf() ` 的占位符为 ` %ls ` 。

宽字符串的结尾也有一个空字符,不过是宽空字符,占用多个字节。

处理宽字符,需要使用宽字符专用的函数,绝大部分都定义在头文件 ` wchar.h ` 。

## 多字节字符处理函数

### mblen()

 ` mblen() ` 函数返回一个多字节字符占用的字符数。它的原型定义在头文件 ` stdlib.h ` 。

int mblen(const char* mbstr, size_t n);

它接受两个参数,第一个参数是多字节字符串指针,一般会检查该字符串的第一个字符;第二个参数是需要检查的字节数,这个数字不能大于当前系统单个字符占用的最大字节,一般使用 ` MB_CUR_MAX ` 。

它的返回值是该字符占用的字节数。如果当前字符是空的宽字符,则返回 ` 0 ` ;如果当前字符不是有效的多字节字符,则返回 ` -1 ` 。

setlocale(LC_ALL, "");

char* mbs1 = "春天";
printf("%dn", mblen(mbs1, MB_CUR_MAX)); // 3

char* mbs2 = "abc";
printf("%dn", mblen(mbs2, MB_CUR_MAX)); // 1

上面示例中,字符串“春天”的第一个字符“春”,占用3个字节;字符串“abc”的第一个字符“a”,占用1个字节。

### wctomb()

 ` wctomb() ` 函数(wide character to multibyte)用于将宽字符转为多字节字符。它的原型定义在头文件 ` stdlib.h ` 。

int wctomb(char* s, wchar_t wc);

 ` wctomb() ` 接受两个参数,第一个参数是作为目标的多字节字符数组,第二个参数是需要转换的一个宽字符。它的返回值是多字节字符存储占用的字节数量,如果无法转换,则返回 ` -1 ` 。

setlocale(LC_ALL, "");

wchar_t wc = L'牛';
char mbStr[10] = "";

int nBytes = 0;
nBytes = wctomb(mbStr, wc);

printf("%sn", mbStr); // 牛
printf("%dn", nBytes); // 3

上面示例中, ` wctomb() ` 将宽字符“牛”转为多字节字符, ` wctomb() ` 的返回值表示转换后的多字节字符占用3个字节。

### mbtowc()

 ` mbtowc() ` 用于将多字节字符转为宽字符。它的原型定义在头文件 ` stdlib.h ` 。

int mbtowc(
wchar_t wchar,
const char
mbchar,
size_t count
);

它接受3个参数,第一个参数是作为目标的宽字符指针,第二个参数是待转换的多字节字符指针,第三个参数是多字节字符的字节数。

它的返回值是多字节字符的字节数,如果转换失败,则返回 ` -1 ` 。

setlocale(LC_ALL, "");

char mbchar = "牛";
wchar_t wc;
wchar_t
pwc = &wc;

int nBytes = 0;
nBytes = mbtowc(pwc, mbchar, 3);

printf("%dn", nBytes); // 3
printf("%lcn", *pwc); // 牛

上面示例中, ` mbtowc() ` 将多字节字符“牛”转为宽字符 ` wc ` ,返回值是 ` mbchar ` 占用的字节数(占用3个字节)。

### wcstombs()

 ` wcstombs() ` 用来将宽字符串转换为多字节字符串。它的原型定义在头文件 ` stdlib.h ` 。

size_t wcstombs(
char mbstr,
const wchar_t
wcstr,
size_t count
);

它接受三个参数,第一个参数 ` mbstr ` 是目标的多字节字符串指针,第二个参数 ` wcstr ` 是待转换的宽字符串指针,第三个参数 ` count ` 是用来存储多字节字符串的最大字节数。

如果转换成功,它的返回值是成功转换后的多字节字符串的字节数,不包括尾部的字符串终止符;如果转换失败,则返回 ` -1 ` 。

下面是一个例子。

setlocale(LC_ALL, "");

char mbs[20];
wchar_t* wcs = L"春天";

int nBytes = 0;
nBytes = wcstombs(mbs, wcs, 20);

printf("%sn", mbs); // 春天
printf("%dn", nBytes); // 6

上面示例中, ` wcstombs() ` 将宽字符串 ` wcs ` 转为多字节字符串 ` mbs ` ,返回值 ` 6 ` 表示写入 ` mbs ` 的字符串占用6个字节,不包括尾部的字符串终止符。

如果 ` wcstombs() ` 的第一个参数是 NULL,则返回转换成功所需要的目标字符串的字节数。

### mbstowcs()

 ` mbstowcs() ` 用来将多字节字符串转换为宽字符串。它的原型定义在头文件 ` stdlib.h ` 。

size_t mbstowcs(
wchar_t wcstr,
const char
mbstr,
size_t count
);

它接受三个参数,第一个参数 ` wcstr ` 是目标宽字符串,第二个参数 ` mbstr ` 是待转换的多字节字符串,第三个参数是待转换的多字节字符串的最大字符数。

转换成功时,它的返回值是成功转换的多字节字符的数量;转换失败时,返回 ` -1 ` 。如果返回值与第三个参数相同,那么转换后的宽字符串不是以 NULL 结尾的。

下面是一个例子。

setlocale(LC_ALL, "");

char* mbs = "天气不错";
wchar_t wcs[20];

int nBytes = 0;
nBytes = mbstowcs(wcs, mbs, 20);

printf("%lsn", wcs); // 天气不错
printf("%dn", nBytes); // 4


上面示例中,多字节字符串 ` mbs ` 被 ` mbstowcs() ` 转为宽字符串,成功转换了4个字符,所以该函数的返回值为4。

如果 ` mbstowcs() ` 的第一个参数为 ` NULL ` ,则返回目标宽字符串会包含的字符数量。
分享到:

#免责声明#

版权声明:《 22. 多字节字符 》为作者 知付 原创文章,转载请注明原文地址!
本站所有文章,如无特殊说明或标注,均为本站原创或整合发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
本文地址:https://www.yoppunion.com/C%20%E8%AF%AD%E8%A8%80%E5%85%A5%E9%97%A8%E6%95%99%E7%A8%8B/108.html
同类推荐
评论列表
签到

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

支付宝扫一扫打赏

微信扫一扫打赏

微信扫一扫打赏