大学生毕业设计课题做网站,wordpress企业建站视频教程,西安凤城二路网站建设,少儿戏曲知识 网站建设文章目录 第25章 国际化特性25.1 locale.h: 本地化25.1.1 类项25.1.2 setlocale函数25.1.3 localeconv函数 25.2 多字节字符和宽字符25.2.1 多字节字符25.2.2 宽字符25.2.3 Unicode和通用字符集25.2.4 Unicode编码25.2.5 多字节/宽字符转换函数25.2.6 多字节/宽字符串转… 文章目录 第25章 国际化特性25.1 locale.h: 本地化25.1.1 类项25.1.2 setlocale函数25.1.3 localeconv函数 25.2 多字节字符和宽字符25.2.1 多字节字符25.2.2 宽字符25.2.3 Unicode和通用字符集25.2.4 Unicode编码25.2.5 多字节/宽字符转换函数25.2.6 多字节/宽字符串转换函数 25.3 双联符和三联符25.3.1 三联符25.3.2 双联符25.3.3 iso646.h: 拼写替换 25.4 通用字符名(C99)25.5 wchar.h: 拓展的多字节和宽字符实用工具25.5.1 流的倾向性25.5.2 格式化宽字符输入/输出函数25.5.3 宽字符输入/输出函数25.5.4 通用的宽字符串实用工具25.5.4.1 宽字符串数值转换函数25.5.4.2 宽字符串复制函数25.5.4.3 宽字符串拼接函数25.5.4.4 宽字符串比较函数25.5.4.5 宽字符串搜索函数25.5.4.6 其他函数 25.5.5 宽字符时间转换函数25.5.6 扩展的多字节/宽字符转换实用工具25.5.6.1 单字节/宽字符转换函数25.5.6.2 转换状态函数25.5.6.3 可重启的多字节/宽字符转换函数25.5.6.4 可重启动的多字节/宽字符串转换函数 25.6 wctype.h宽字符分类和映射实用工具(C99)25.6.1 宽字符分类函数25.6.2 可扩展的宽字符分类函数25.6.3 宽字符大小写映射函数25.6.4 可扩展的宽字符大小写映射函数 25.7 uchar.h: 改进的Unicode支持(C1X)25.7.1 带u、U和u8前缀的字面串25.7.2 可重启动的多字节/宽字符转换函数 问与答写在最后 第25章 国际化特性
——即使你的计算机说英语它也可能产自日本。 过去C语言并不十分适合在非英语国家地区使用。C语言最初假定字符都是单字节的并且所有计算机都能识别字符#、[、\、]、^、{、|、}和~因为这些字符都需要在C程序中用到。遗憾的是这些假定并不是在世界的任何地方都适用。因此创建C89的专家又添加了新的特性和函数库以使C语言更加国际化。 1994年针对ISO C标准的修正草案Amendment1被批准通过这一增强的C89版本有时也称为C94或C95。这一草案通过双联符语言特性以及iso646.h、wchar.h和wctype.h提供了对国际化编程的额外函数库支持。C99以通用字符名的形式为国际化提供了更多的支持。C1X继续以uchar.h改进了这些支持。本章介绍C语言的所有国际化特性这些特性可能来自C89、Amendment1、C99或C1X。虽然来自Amendment1的修改事实上先于C99但我们也将其标记为C99的修改。 locale.h头25.1节提供了允许程序员针对特定的“地区”通常是国家或者说某种特定语言的地理区域调整程序行为的函数。多字节字符和宽字符25.2节使程序可以工作在更大的字符集上例如亚洲国家的字符集。通过双联符、三联符和iso646.h25.3节可以在一些不支持某些C语言编程中常用字符的机器上编写程序。通用字符名25.4节允许程序员把通用字符集中的字符嵌入程序的源代码中。wchar.h25.5节提供了用于宽字符输入/输出以及宽字符串操作的函数。wctype.h头25.6节提供了宽字符分类函数和大小写映射函数。最后uchar.h头25.7节提供了Unicode字符处理函数。 25.1 locale.h: 本地化 locale.h提供的函数用于控制C标准库中对于不同的地区会产生不一样的行为的部分。地区locale通常是国家或者说某种特定语言的地理区域。 在标准库中依赖地区的部分包括以下几项:
数字量的格式。例如在一些地区小数点是圆点297.48在另一些地区则是逗号297,48。货币量的格式。例如不同国家或地区的货币符号不同。字符集。字符集通常依赖于特定地区的语言。亚洲国家或地区所需的字符集通常比西方国家或地区大得多。日期和时间的表示形式。例如一些地区习惯在写日期时先写月8/24/2012而另一些地区习惯先写日24/8/2012。 25.1.1 类项 通过修改地区程序可以改变它的行为来适应世界的不同区域。但地区改动可能会影响库的许多部分其中一部分可能是我们不希望改变的。幸好我们不需要同时对库的所有部分进行改动。实际上可以使用下列宏中的一种来指定一个类项: LC_COLLATE。影响两个字符串比较函数strcoll和strxfrm的行为。这两个函数都在string.h23.6节中声明。LC_CTYPE。影响ctype.h23.5节中的函数isdigit和isxdigit除外的行为。同时还影响本章讨论的多字节函数和宽字符函数的行为。LC_MONETARY。影响由localeconv函数返回的货币格式信息。LC_NUMERIC。影响格式化输入/输出函数如printf和scanf使用的小数点字符以及stdlib.h中的数值转换函数如strtod26.2节还会影响localeconv函数返回的非货币格式信息。LC_TIME。影响strftime函数在time.h中声明26.3节的行为该函数将时间转换成字符串。在C99中还会影响wcsftime函数25.5节的行为。
C语言的具体实现中可以提供其他类项并定义上面未列出的以LC_开头的宏。例如大多数UNIX系统提供了一个LC_MESSAGES类项它会影响系统的肯定和否定响应格式。 25.1.2 setlocale函数
char *setlocale(int category, const char *locale);setlocale函数修改当前的地区可以是针对一个类项的也可以是针对所有类项的。如果第一个参数是LC_COLLATE、LC_CTYPE、LC_MONETARY、LC_NUMERIC或LC_TIME之一那么setlocale调用只会影响一个类项。如果第一个参数是LC_ALL调用就会影响所有类项。C标准对第二个参数仅定义了两种可能值C和。如果有其他地区则由具体的实现自行处理。 在任意程序执行开始时都会隐式执行调用: setlocale(LC_ALL, C);
//当地区设置为C时库函数按“正常”方式执行小数点是一个点。如果在程序运行起来后想改变地区就需要显式调用setlocale函数。用作为第二个参数调用setlocale函数可以切换到本地nativelocale模式。这种模式下程序会适应本地的环境。C标准并没有定义切换到本地模式的具体影响。setlocale函数的有些实现会检查当前的运行环境与getenv函数26.2节的方式一样查找特定名字可能与表示类项的宏同名的环境变量有些实现则根本什么都不做。C标准并没有要求setlocale有什么特定的作用。当然如果库中的setlocale什么都不做那么这个库在一些地区可能不会卖得很好。 补充小知识对于除C和以外的其他地区不同的编译器之间有很大的差异。GNU的C库称为glibc提供了POSIX地区该地区与一样。glibc用于Linux允许在需要的时候增加额外的地区。地区的格式为语言[_地域][.码集][指定符]。 其中方括号中的项是可选的。语言的可能值列在ISO 639标准中“地域”来自另一个标准ISO 3166“码集”指明字符集或字符集的编码方案。下面给出了几个例子 swedish
en_GB
en_IE
fr_CHen_IE地区有几种变体包括en_IEeuro使用欧元、en_IE.iso88591使用ISO/IEC8859-1字符集、en_IE.iso885915euro使用ISO/IEC8859-15字符集和欧元以及en_IE.utf8使用通用字符集的UTF-8编码方案。 Linux和其他一些版本的UNIX支持locale命令该命令可以用于获取地区信息。locale命令的用法之一是获取所有可用地区的列表这可以通过在命令行输入下面的语句来实现 locale -a地区信息正变得越来越重要因此统一字符联盟Unicode Consortium设立了一个泛区域数据仓库Common Locale Data Repository,CLDR项目来建立标准的地区集合。 当setlocale函数调用成功时它会返回一个指向字符串的指针这个字符串与新地区的类项相关联。例如这个字符串可能就是地区名字自身。如果调用失败setlocale函数返回空指针。
setlocale函数也可以当作查询函数使用。如果第二个参数是空指针setlocale函数会返回一个指向字符串的指针这个字符串与当前地区类项相关联。这一特性在第一个参数为LC_ALL时特别有用因为可以获取所有类项的当前设置。setlocale函数返回的字符串可以通过复制到变量中保存起来以便日后调用setlocale函数时使用。 25.1.3 localeconv函数
struct lconv *localeconv(void);虽然可以通过调用setlocale函数来获取当前地区的信息但setlocale函数可能不是以最有效的形式返回信息的。为了找到关于当前地区的很具体的信息小数点字符是什么货币符号是什么只需要用到声明在locale.h中的另一个函数localeconv。
localeconv函数返回指向struct lconv类型结构的指针。该结构的成员包含了当前地区的详细信息。该结构具有静态存储期以后可以通过调用localeconv函数或者setlocale函数来修改。在使用上述函数之一擦除结构信息之前请确保已经从lconv结构中提取了所需要的信息。 lconv结构中的一些成员具有char*类型另一些成员则具有char类型。表25-1列出了char*类型的成员其中前三个成员描述了非货币数值的格式其他成员则处理货币数值。此表还给出了C地区默认情况中每个成员的值值为意味着“不可用”。 表25-1 lconv结构的char*类型的成员
名称在“C”地区中的值描述decimal_point(非货币类)“.”十进制小数点字符thousands_sep(非货币类)“”在十进制小数点前用来分隔数字组的字符grouping(非货币类)“”数字组的大小mon_decimal_point(货币类)“”十进制小数点字符mon_thousands_sep(货币类)“”在十进制小数点前用来分隔数字组的字符mon_grouping(货币类)“”数字组的大小positive_sign(货币类)“”表示非负值的字符串negative_sign(货币类)“”表示负值的字符串currency_symbol(货币类)“”本地货币符号int_curr_symbol(货币类)“”国际货币符号①
①分隔符常常是空格或者点后边跟着3个字母的缩写。例如瑞士、英国和美国的国际货币符号分别是CHF、GBP和USD。 这里需要特别说明一下成员grouping和成员mon_grouping。这两个字符串中的每个字符都说明了一组数字的大小。分组工作是从十进制小数点开始自右向左进行的。值CHAR_MAX说明不需要继续分组了0说明前面的元素应该用于其余的数字。例如字符串\3\3的后边跟着\0说明第一组应该有3个数字以后所有其他数字也应该以3为单位分组。 lconv结构的char类型成员分为两组。第一组的成员见表25-2影响货币数值的本地格式化第二组的成员见表25-3影响货币数值的国际格式化。表25-3中只有一个成员不是C99新增的。如表25-2和表25-3所示C地区中每个char类型成员的值为CHAR_MAX表示“不可用”。
表25-2 lconv结构的char类型成员本地格式化
名称在“C”地区中的值描述frac_digitsCHAR_MAX十进制小数点后的数字个数p_cs_precedesCHAR_MAX如果currency_symbol在非负值之前则为1如果currency_symbol在数值之后则为0n_cs_precedesCHAR_MAX如果currency_symbol在负值之前则为1如果currency_symbol在数值之后则为0p_sep_by_spaceCHAR_MAX把currency_symbol和数值符号字符串与非负值分隔开见表25-4n_sep_by_spaceCHAR_MAX把currency_symbol和数值符号字符串与负值分隔开见表25-4p_sign_posnCHAR_MAX用于非负值时positive_sign的位置见表25-5n_sign_posnCHAR_MAX用于负值时negative_sign 的位置见表25-5 表25-3 lconv结构的char类型成员国际格式化
名称在“C”地区中的值描述int_frac_digitsCHAR_MAX十进制小数点后的数字个数int_p_cs_precedes①CHAR_MAX如果int_curr_symbol在非负值之前则为1如果int_curr_symbol在数值之后则为0int_n_cs_precedes①CHAR_MAX如果int_curr_symbol在负值之前则为1如果 int_curr_symbol在数值之后则为0int_p_sep_by_space①CHAR_MAX把int_curr_symbol和数值符号字符串与非负值分隔开见表25-4int_n_sep_by_space①CHAR_MAX把int_curr_symbol和数值符号字符串与负值分隔开见表25-4int_p_sign_posn①CHAR_MAX用于非负值时positive_sign的位置见表25-5int_n_sign_posn①CHAR_MAX用于负值时negative_sign的位置见表25-5
① 仅C99有。 表25-4解释了成员p_sep_by_space、n_sep_by_space、int_p_sep_by_space和int_n_sep_by_space值的含义。成员p_sep_by_space和n_sep_by_space的含义在C99中有所改变。在C89中它们只有两种可能的值1currency_symbol和货币量之间有空格和0currency_symbol和货币量之间没有空格。 表25-4 ...sep_by_space成员的值
值含义0货币符号与量之间没有空格1如果货币符号与量的符号相邻用空格把它们与量分隔开否则用空格把货币符号与量分隔开2如果货币符号与量的符号相邻用空格把它们分隔开否则用空格把量的符号与量分隔开 表25-5解释了成员p_sign_posn、n_sign_posn、int_p_sign_posn和int_n_sign_posn的含义。 表25-5 ...sign_posn成员的值
值含义0量和货币符号的外面有圆括号1量的符号在量和货币符号的前面2量的符号在量和货币符号的后面3量的符号刚好在货币符号的前面4量的符号刚好在货币符号的后面 为了说明lconv结构的成员如何随着地区的不同而不同下面来看两个示例。表25-6显示了lconv货币成员用于美国和芬兰两国时的常见值芬兰使用欧元作为货币。 表25-6 lconv货币成员用于美国和芬兰两国时的常见值
成员美国芬兰mon_decimal_point“.”“,”mon_thousands_sep“,” mon_grouping“\3”“\3”positive_sign“”“”negative_sign“-”“-”currency_symbol“$”“EUR”frac_digits22p_cs_precedes10n_cs_precedes10p_sep_by_space02n_sep_by_space02p_sign_posn11n_sign_posn11int_curr_symbolUSD EUR int_frac_digits22int_p_cs_precedes10int_n_cs_precedes10int_p_sep_by_space12int_n_sep_by_space12int_p_sign_posn11int_n_sign_posn11 表25-7是把7593.86格式化成上述两个地区的货币数值的情况具体形式与数值符号以及是本地化还是国际化有关。 表25-7 美国美元和芬兰欧元货币数值对比
美国芬兰本地格式正数$7,593.867 59386 EUR本地格式负数-$7,593.86- 7 59386 EUR国际化格式正数USD7,593.86国际化格式负数-USD7,593.86 请记住C语言的库函数不能自动格式化货币量需要由程序员使用lconv结构中的信息来完成格式化。 25.2 多字节字符和宽字符 程序在适应不同地区的过程中最大的难题之一就是字符集的问题。北美地区主要使用ASCII字符集及其扩展包括Latin-17.3节其他地区的情况较为复杂。在许多国家计算机采用类似于ASCII的字符集但是缺少了某些字符。25.3节将进一步讨论这个问题。其他国家或地区尤其是在亚洲则面临着另一个问题书写的语言需要巨大的字符集字符个数通常是以千计的。 因为定义已经把char类型值的大小限制为1字节所以通过改变char类型的含义来处理更大的字符集显然是不可能的。取而代之的是C语言允许编译器提供一种扩展字符集。这种字符集可以用于编写C程序例如在注释和字符串中也可以用于程序运行的环境中或者两者都有。C语言提供了两种对扩展字符集进行编码的方法多字节字符multibyte character和宽字符wide character。C语言还提供了把一种编码转换成另外一种编码的函数。 25.2.1 多字节字符 在多字节字符编码中用一个或多个字节表示一个扩展字符。根据字符的不同字节的数量可能发生变化。C语言要求任何扩展字符集必须包含特定的基本字符即字母、数字、运算符、标点符号和空白字符。这些字符都必须是单字节的。其他字节可以解释为多字节字符的开始。 一些多字节字符集依靠状态相关编码state-dependent encoding。在这类编码中每个多字节字符序列都以初始迁移状态initial shift state开始。以后遇到的特定字节称为迁移序列会改变迁移状态从而影响后续字节的含义。例如日本的JIS编码混合使用单字节码与双字节码嵌在字符串中的“转义序列”说明何时对单字节模式和双字节模式进行切换。与之相反Shift-JIS编码不是状态相关的。每个字符要求一个或者两个字节但是双字节字符的第一个字节总可以区别于单字节字符。 在任何编码中无论迁移状态如何C标准都要求始终用零字节来表示空字符。而且零字节不能是多字节字符的第二个或者更后面的字节。 C语言库提供了两个与多字节字符相关的宏MB_LEN_MAX和MB_CUR_MAX这两个宏说明了多字节字符中字节的最大数量。宏MB_LEN_MAX定义在limits.h中给出了任意支持地区的最大值而宏MB_CUR_MAX定义在stdlib.h中则给出了当前地区的最大值。改变地区可能会影响多字节字符的解释。显然宏MB_CUR_MAX不可能大过宏MB_LEN_MAX。
任何字符串都可能包含多字节字符尽管字符串的长度指的是字符串中字节的数目由strlen函数确定而不是字符的数目。特别地...printf和...scanf函数调用中的格式串可以包含多字节字符。因此C99标准把术语多字节字符串定义为字符串的同义词。 25.2.2 宽字符 另外一种对扩展字符集进行编码的方法是使用宽字符wide character。宽字符是一种整数其值代表字符。不同于长度可变的多字节字符特定实现中所支持的所有宽字符有着相同的字节数。宽字符串是指由宽字符组成的字符串其末尾有一个空宽字符数值为零的宽字符。 宽字符具有wchar_t类型在stddef.h和其他一些头中声明wchar_t必须是可以表示任何支持地区的最大扩展字符集的整数类型。例如如果两个字节足够表示任何扩展字符集那么可以把wchar_t定义成unsigned short int。 C语言支持宽字符常量和宽字面串。宽字符常量类似于普通的字符常量但需要有字母L作为前缀 La而宽字面串也需要用字母L作为前缀
Labc此字符串表示一个含有宽字符La、Lb和Lc并且后跟一个空的宽字符的数组。 25.2.3 Unicode和通用字符集 多字节字符和宽字符的差异在讨论Unicode时比较明显。Unicode是Unicode联盟Unicode Consortium开发的巨大字符集。Unicode联盟是由一些计算机制造商成立的目的在于创建用于计算机的国际化字符集。Unicode的前256个字符与Latin-1一样所以Unicode的前128个字符与ASCII字符集相匹配。但是Unicode所包括的范围远远超过Latin-1提供的字符几乎可以满足所有现代语言和旧式语言的需求。Unicode还包括许多专用符号如在数学和音乐中使用的符号。Unicode标准最早出版于1991年。 Unicode与国际标准ISO/IEC 10646紧密相关该标准定义了一种称为通用字符集Universal CharacterSet,UCS的字符编码方案。UCS是国际标准化组织ISO开发的差不多与Unicode同一时间启动。尽管UCS最初和Unicode不同但二者后来统一了。ISO现在与Unicode联盟紧密合作以确保ISO/IEC 10646和Unicode保持一致。因为Unicode和通用字符集非常相似所以本书经常将这两个术语互换使用。 Unicode最初只有65536个字符16位所能表示的字符数目后来发现这是不够的现在Unicode的字符已超过100000个。欲了解最新版本请访问Unicode官方网站。Unicode的前65536个字符包括最常用的字符称作基本多语种平面Basic Multilingual Plane,BMP。 25.2.4 Unicode编码 Unicode为每一个字符分配一个唯一的数称为码点。可以有多种方式使用字节来表示这些码点。我将介绍两种简单的方法一种使用宽字符另一种使用多字节字符。 UCS-2是一种宽字符编码方案它把每一个Unicode码点存储为两个字节。USC-2可以表示基本多语种平面上的所有字符码点在十六进制的0000和FFFF之间但是不能够表示不属于BMP的Unicode字符。 另一种流行的方式是8位的UCS转换格式UTF-8该方案使用多字节字符。UTF-8是由Ken Thompson和他在贝尔实验室的同事RobPike于1992年设计的就是设计B语言的那个Ken ThompsonB语言是C语言的前身。UTF-8的一个有用的性质就是ASCII字符在UTF-8中保持不变每个字符都是一个字节且使用同样的二进制编码。所以设计用于读取UTF-8数据的软件同样可以处理ASCII数据而不需要任何改变。基于这些原因UTF-8广泛用于因特网上基于文本的应用程序如网页和电子邮件。 在UTF-8中每个码点需要1~4字节。UTF-8中常用字符所需的字节数较少如表25-8所示。
表25-8 UTF-8编码
码点范围十六进制UTF-8字节序列二进制00000000007F0xxxxxxx0000800007FF110xxxxx 10xxxxxx00080000FFFF1110xxxx 10xxxxxx 10xxxxxx01000010FFFF11110xxx 10xxxxxx 10xxxxxx 10xxx
UTF-8读取码点值中的位将其分为几组由表25-8中的x来表示并把每一组分配给不同的字节。最简单的情况是码点在0~7F范围ASCII字符内此时只要在原数的7位之前加一个0即可。
码点在80~7FF范围包括所有的Latin-1字符内时需要将码点值的位分为两组一组5位另一组6位。5位组的前缀为1106位组的前缀为10。例如字符ä的码点为E4十六进制或11100100二进制。在UTF-8中可以将其表示为双字节序列1100001110100100。注意高亮的部分连起来就是00011100100。 如果字符的码点落在800~FFFF范围包含基本多语种平面中的剩余字符内那么需要3字节。其他的Unicode字符大多数很少用到都分配4字节。UTF-8有以下几个有用的性质: 128个ASCII字符中的每一个字符都可以用一个字节表示。仅由ASCII字符组成的字符串在UTF-8中保持不变。对于UTF-8字符串中的任意字节如果其最左边的位是0那么它一定是ASCII字符因为其他所有字节都以1开始。多字节字符的第一个字节指明了该字符的长度。如果字节开头1的个数为2那么这个字符的长度为2字节。如果字节开头1的个数为3或4那么这个字符的长度分别为3字节或4字节。在多字节序列中每隔一个字节就以10作为最左边的位。
最后三个性质特别重要因为它们可以保证一个多字节字符中的字节序列不会是另一个有效的多字节字符。这样一来简单地进行字节比较就可以从多字节字符串中搜索一个特定的字符或字符序列。 现在来看看UTF-8相比于UCS-2的优缺点。UCS-2的优点在于字符都是以最自然的格式存储的。UTF-8的优点在于它能处理所有的Unicode字符而不仅仅是BMP中的字符、所需的空间比UCS-2少且兼容ASCII。UCS-2用于WindowsNT操作系统但不如UTF-8流行使用4字节的新版本UCS-4正在逐渐取代UCS-2的地位。一些系统把UCS-2扩展为一种多字节编码方案方法是允许用可变数量的字节对来表示字符UCS-2使用一个字节对来表示字符。这样的编码方案称为UTF-16它的优点是能够兼容UCS-2。 25.2.5 多字节/宽字符转换函数
int mblen(const char *s, size_t n); //来自stdlib.h
int mbtowc(wchar_t * restrict pwc, const char * restrict s, size_t n); //来自stdlib.h
int wctomb(char *s, wchar_t wc); //来自stdlib.h尽管C89引入了多字节字符和宽字符的概念它只提供了5个函数来处理这些字符。现在介绍一下这些函数它们都属于stdlib.h头。C99的wchar.h和wctype.h头新增了许多多字节和宽字符函数25.5节和25.6节将加以讨论。 C89的多字节/宽字符函数分为两组。第一组把多字节格式的单个字符转换为宽字符格式或者进行反向转换。这些函数的行为依赖于当前地区的LC_CTYPE类项。如果多字节编码是依赖状态的函数的行为还依赖于当前的转换状态。转换状态不仅包含当前在多字节字符中的位置还包含当前的迁移状态。以空指针作为char*类型参数的值来调用这些函数会导致函数的内部转换状态设为初始转换状态。该状态表明当前没有正在处理的多字节字符且初始迁移状态有效。对函数的后续调用会更新其内部转换状态。 mblen函数检测第一个参数是否指向形成有效多字节字符的字节序列。如果是则函数返回字符中的字节数如果不是则函数返回-1。作为一种特殊情况如果函数的第一个参数指向空字符则mblen函数返回0。函数的第二个参数限制了mblen函数将检测的字节的数量通常情况下会传递MB_CUR_MAX。 下面的函数来自P.J.Plauger的《C标准库》一书它使用mblen函数来确定字符串是否由有效的多字节字符构成。如果s指向有效字符串则函数返回0。
int mbcheck(const char *s)
{ int n; for (mblen(NULL, 0); ; s n) if ((n mblen(s, MB_CUR_MAX)) 0) return n;
} mbcheck函数有两点需要特别说明一下。首先是mblen(NULL,0)的神秘调用。此调用把mblen的内部转换状态设置为初始转换状态针对多字节编码依赖状态的情况。其次是有关终止的问题。要记住s指向的是以空字符结尾的普通字符串。当mblen函数遇到这个空字符时将返回0这样会导致mbcheck函数返回。如果mblen因为遇到无效的多字节字符而返回-1那么mbcheck会提前返回。 mbtowc函数把第二个参数指向的多字节字符转换为宽字符。第一个参数指向函数用于存储结果的wchar_t类型变量第三个参数限制了mbtowc函数将检测的字节的数量。mbtowc函数返回和mblen函数一样的值如果多字节字符有效则返回多字节字符中字节的数量如果多字节字符无效则返回-1如果第二个参数指向空字符则返回0。 wctomb函数把宽字符第二个参数转换为多字节字符并把该多字节字符存储到第一个参数指向的数组中。wctomb函数可以向数组中存储多达MB_LEN_MAX个字符但是在最后不附加空字符。如果宽字符能与有效的多字节字符相对应wctomb函数会返回多字节字符中字节的数量否则返回-1。注意如果要求转换空的宽字符wctomb函数返回1。 下面这个函数也来自Plauger的《C标准库》一书使用wctomb函数来确定是否可以把宽字符字符串转换为有效的多字节字符
int wccheck(wchar_t *wcs)
{ char buf[MB_LEN_MAX]; int n; for (wctomb(NULL, 0); ; wcs) if ((n wctomb (buf, *wcs)) 0) return –1; /* invalid character */ else if (buf[n-1] \0) return 0; /* all characters are valid */
} 顺便说一下mblen、mbtowc和wctomb都可以用来测试多字节编码是否依赖状态。当传递空指针作为char*类型的参数时如果多字节字符的编码是依赖状态的那么上述每种函数都会返回非零值否则返回0。 25.2.6 多字节/宽字符串转换函数
size_t mbstowcs(wchar_t * restrict pwcs, const char * restrict s, size_t n); //来自stdlib.h
size_t wcstombs(char * restrict s, const wchar_t * restrict pwcs, size_t n); //来自stdlib.h剩下的C89多字节/宽字符函数把包含多字节字符的字符串转换为宽字符字符串或者进行反向转换。如何进行转换依赖于当前地区的LC_CTYPE类项。 mbstowcs函数把多字节字符序列转换为宽字符。函数的第二个参数指向包含待转换的多字节字符的数组第一个参数指向宽字符数组第三个参数限制了可以存储在数组中的宽字符数量。当达到上限或者遇到存储在宽字符数组中的空字符时mbstowcs函数就停止。函数会返回修改的数组元素的数量不包括末尾的空的宽字符。如果遇到无效的多字节字符mbstowcs函数返回-1强制转换为size_t类型。 wcstombs函数和mbstowcs函数正好相反它把宽字符序列转换为多字节字符。函数的第二个参数指向宽字符串第一个参数指向用于存储多字节字符的数组第三个参数限制了可以存储在数组中的字节的数量。当达到上限或者遇到自己存入的空字符时wcstombs函数就停止。函数会返回存储的字节数量不包括用于终止的空字符。如果遇到无法对应任何多字节字符的宽字符wcstombs函数返回-1强制转换为size_t类型。 mbstowcs函数假设要转换的字符串以初始迁移状态开始。由wcstombs函数产生的字符串始终是以初始迁移状态开始的。 25.3 双联符和三联符 某些国家或地区的程序员常常因为键盘缺少C语言需要的字符而无法进入C程序。在欧洲尤其如此那里的老式键盘提供的是欧洲语言所用的古老字符而不是C语言需要的字符如#、[、\、]、^、{、|、}和~。C89引入了三联符表示问题字符的三字符编码来解决这一问题。但是三联符没能流行起来所以标准的Amendment1增加了两处改进双联符和iso646.h头前者比三联符易读后者定义了表示特定C运算符的宏。 25.3.1 三联符 三联序列trigraph sequence或者简称为三联符是一种三字符编码它可以用于替代ASCII字符。表25-9给出了三联符的完整列表。所有三联符都以??开始这样做虽然并不足够醒目但至少便于发现。 表25-9 三联序列
三联序列等价的ASCII码??#??([??/|??)]??’^??{??!|??}??-~
三联符可以自由地替换成等价的ASCII码。例如程序
#include stdio.h
int main(void)
{ printf(hello, world\n); return 0;
}可以写成
??include stdio.h
int main(void)
?? printf(hello, world??/n); return 0;
?? 尽管三联符很少用到但遵循C89或C99标准的C编译器都必须能接受三联符。这个特性有时可能会导致问题。 请注意!!在字面串中请小心放置??因为编译器可能会把它视为三联符的开始标志。如果发生这种情况那么通过在第二个?字符的前面放置字符\来把第二个字符?变成转义序列。?\?这样组合的结果就不会被看作三联符的开始了。 25.3.2 双联符 因为三联符较难读懂所以C89标准的Amendment1增加了双联符digraph表示法。顾名思义双联符只需要两个字符而不是三个。双联符可以用于替代表25-10中的6个记号。 表25-10 双联符
双联符记号:[:]%{%}%:#%:%:##
双联符不同于三联符是记号的替代品而不是字符的替代品。因此字面串或字符常量中的双联符不会被识别出来。例如字符串::长度为4它包括字符、:、:和而不包括字符[和]。相反字符串??(??)长度为2因为编译器将三联序列??(替换为[并把三联序列??)替换为]。 双联符比起三联符来说功能更有限。 第一如我们所见双联符在字面串和字符常量中不起作用所以在这些情况下仍然需要三联符。第二双联符不能为字符\、^、|和~提供替代的表示方法。接下来讨论的iso646.h可以解决这一问题。 25.3.3 iso646.h: 拼写替换 iso646.h头相当简单。它只定义了表25-11所示的11个宏除此之外什么都没有。每一个宏表示一个包含字符、|、~、!或^的C运算符。这样一来即使键盘上缺少这些字符也仍然能够使用表中列出的运算符。 表25-11 iso646.h中的宏定义
宏值andand_eqbitandbitor|compl~not!not_eq!or||or_eq|xor^xor_eq^
这个头的名字源于ISO/IEC 646这是用于类ASCII字符集的旧版标准。该标准允许“国别变体”各个国家或地区可以用本地字符替换特定的ASCII字符从而导致双联符和iso646.h试图解决的那个问题。 25.4 通用字符名(C99)
25.2节讨论了通用字符集UCS它与Unicode紧密相关。C99提供了一种专门的特性——通用字符名它允许我们在程序源代码中使用UCS字符。 通用字符名类似于转义序列。但是普通的转义序列只能出现于字符常量和字面串中而通用字符名还可以用于标识符。这个特性允许程序员在为变量、函数等命名时使用他们的本地语言。 可以用2种方式书写通用字符名\udddd和\Udddddddd每个d都是一个十六进制的数字。在格式\Udddddddd中8个d组成一个8位的十六进制数用于标识目标字符的UCS码点。格式\udddd可以用于码点的十六进制值为FFFF或更小的字符包括基本多语种平面上的所有字符。 例如希腊字母β的UCS码点是000003B2所以该字符的通用字符名为\U000003B2或者是\U000003b2因为大小写在十六进制中无所谓。因为UCS码点的十六进制前4位是0所以也可以使用\u表示法将字符写为\u03B2或\u03b2。与Unicode相匹配的UCS码点的值可以在Unicode官方网站的CodeCharts页面上找到。 并不是所有的通用字符名都可以用于标识符C99标准列出了哪些通用字符名可以用于标识符。此外标识符不能以表示数字的通用字符名开头。 25.5 wchar.h: 拓展的多字节和宽字符实用工具 wchar.h头提供了宽字符输入/输出和宽字符串处理的函数。wchar.h头中的绝大部分函数都是其他头主要是stdio.h和string.h中函数的宽字符版本。 wchar.h头声明了以下一些类型和宏:
mbstate_t把多字节字符序列转换为宽字符序列或进行反向转换时可以用这个类型的值来存储转换状态。wint_t一种整数类型它的值表示扩展字符。WEOF一个表示wint_t类型值的宏该wint_t类型值与任何扩展字符不同。WEOF的用法与EOF很相似通常用于指明错误或文件末尾条件。
注意!!wchar.h为宽字符提供了函数但没有为多字节字符提供函数。这是因为C的普通库函数能够处理多字节字符所以不需要专门的函数。例如fprintf函数允许格式串包含多字节字符。
大多数宽字符函数的行为与标准库其他地方的某个函数一致。通常所做的修改仅仅是把参数和返回值的类型从char改成了wchar_t或者从char *改成了wchar_t *。另外表示字符计数的参数和返回值用宽字符而不是字节的个数来衡量。在本节下面的内容中将指出与每个宽字符函数对应的库函数如果存在的话。这里不会详细讨论宽字符函数除非它与相应的“非宽”版本有显著差异。 25.5.1 流的倾向性 在讨论wchar.h提供的输入/输出函数前先理解流的倾向性stream orientation是很重要的这个概念在C89中并不存在。 每个流要么是面向字节的传统方式要么是面向宽字符的把数据当成宽字符写入流中。第一次打开流时它没有倾向性。特别地标准流22.1节stdin、stdout和stderr在程序刚开始执行时是没有倾向性的。使用字节输入/输出函数在流上执行操作会使流成为面向字节的使用宽字符输入/输出函数执行操作会使流成为面向宽字符的。流的倾向性可以调用fwide函数进行选择本节后面会讲到。流只要保持打开状态就能保持其倾向性。调用freopen函数22.2节重新打开流会删除其倾向性。 往面向宽字符的流中写入宽字符时首先将宽字符转换为多字节字符然后再存入与流相关的文件。相反当从面向宽字符的流中读取输入时需要把流中的多字节字符转换为宽字符。文件中的多字节编码与程序中的字符和字符串编码相类似不同之处在于文件中的编码可能包含空字节。 每一个面向宽字符的流都有一个相关联的mbstate_t对象该对象用于记录流的转换状态。当写入流中的宽字符不能与任何多字节字符相对应或者从流中读取的字符序列不能构成有效的多字节字符时会出现编码错误。在上述任何一种情况下EILSEQ宏定义在errno.h头中的值会存储到errno变量24.2节中以指明错误的性质。 一旦流是面向字节的对其应用宽字符输入/输出函数就不合法了。类似地对面向宽字符的流应用字节输入/输出函数也是不合法的。其他流函数可以用于两种倾向性的流不过对于面向宽字符的流有以下几点需要特别考虑: 面向宽字符的二进制流受限于文本文件和二进制文件的文件定位限制。对面向宽字符的流执行文件定位操作之后宽字符输出函数也许会覆盖多字节字符的一部分。这样会导致文件的其他部分处于不确定的状态。对面向宽字符的流调用fgetpos函数22.7节会获取流的mbstate_t对象使其成为与流相关联的fpos_t对象的一部分。
以后如果使用该fpos_t对象来调用fsetpos函数22.7节mabstate_t对象会恢复以前的值。 25.5.2 格式化宽字符输入/输出函数
int fwprintf(FILE * restrict stream, const wchar_t * restrict format, ...);
int fwscanf(FILE * restrict stream, const wchar_t * restrict format, ...);
int swprintf(wchar_t * restrict s, size_t n, const wchar_t * restrict format, ...);
int swscanf(const wchar_t * restrict s, const wchar_t * restrict format, ...);
int vfwprintf(FILE * restrict stream, const wchar_t * restrict format, va_list arg);
int vfwscanf(FILE * restrict stream, const wchar_t * restrict format, va_list arg);
int vswprintf(wchar_t * restrict s, size_t n, const wchar_t * restrict format, va_list arg);
int vswscanf(const wchar_t * restrict s, const wchar_t * restrict format, va_list arg);
int vwprintf(const wchar_t * restrict format, va_list arg);
int vwscanf(const wchar_t * restrict format, va_list arg);
int wprintf(const wchar_t * restrict format, ...);
int wscanf(const wchar_t * restrict format, ...);这一组函数是stdio.h中的格式化输入/输出函数在22.3节讨论过的宽字符版本。wchar.h中的函数的参数类型为wchar_t *而不是char *但函数的行为与stdio.h中的函数基本相同。表25-12给出了stdio.h中的函数与宽字符函数的对应关系。如果没有特别说明表中左边一列的函数与它右边的函数功能相同。
表25-12 格式化的宽字符输入/输出函数及其在stdio.h中的对应函数
wchar.h函数stdio.h中的对应函数fwprintffprintffwscanffscanfswprintfSnprintf、sprintfswscanfsscanfvfwprintfvfprintfvfwscanfvfscanfvswprintfvsnprintf、vsprintfvswscanfvsscanfvwprintfvprintfvwscanfvscanfwprintfprintfwscanfscanf 这一组中的所有函数有以下几个共同特性: 都有包含宽字符的格式串。…printf函数返回输出的字符数量但现在是对宽字符计数。%n转换说明表示到目前为止输出…printf函数或读入…scanf函数的宽字符的数量。 fwprintf和fprintf还有以下不同 %c转换说明用于参数为int类型的情况。如果存在长度指定符l转换为%lc则假定参数的类型为wint_t。在上述两种情形下相应的参数都输出为宽字符。%s转换说明用于指向字符数组的指针该字符数组可以包括多字节字符。fprintf对多字节字符没有特殊规定。如果存在长度指定符l%ls相应的参数应该是包含宽字符的数组。在上述两种情形下数组里的字符都输出为宽字符。用于fprintf时%ls转换说明也表示宽字符数组但是在输出之前会将数组中的字符转换为多字节字符。 fwscanf函数不同于fscanf函数它读取宽字符。%c、%s和%[转换需要特别提一下。这些转换符都可以读取宽字符并在存入字符数组前将其转换为多字节字符。fwscanf使用mbstate_t对象来记录这一过程中的转换状态每次转换开始时把该对象设置为0。如果存在长度指定符l转换分别为%lc、%ls和%l[那么输入字符不需要转换而是直接存入wchar_t型的数组元素中。因此如果希望把宽字符字符串中的字符存为宽字符需要使用%ls。如果用%s而不是%ls宽字符能够从输入流中读出但是在存储之前会被转换为多字节字符。 swprintf将宽字符写入wchar_t类型的数组。它类似于sprintf和snprintf但不完全等同于这两个函数。类似于snprintf函数它用参数n来限制需要输出的宽字符的数目但swprintf返回实际输出的宽字符的数目不包括空字符。在这一点上它类似于sprintf函数而非snprintf函数swprintf函数返回没有长度限制的情况下应输出的字符数不包括空字符。如果待输出的宽字符数目为n或者更多swpritf函数返回负值这与sprintf函数和snprintf函数均不一样。 vswprintf函数与swprintf函数等价只是用arg取代了swprintf函数的可变参数列表。与swprintf函数类似但不等同于sprintf函数和snprintf函数一样vswprintf函数是vsprintf函数和vsnprintf函数的结合。如果尝试输出n个或者更多个宽字符vswprintf函数返回一个负整数这与swprintf函数类似。 25.5.3 宽字符输入/输出函数
wint_t fgetwc(FILE *stream);
wchar_t *fgetws(wchar_t * restrict s, int n, FILE * restrict stream);
wint_t fputwc(wchar_t c, FILE *stream);
int fputws(const wchar_t * restrict s, FILE *restrict stream);
int fwide(FILE *stream, int mode);
wint_t getwc(FILE *stream);
wint_t getwchar(void);
wint_t putwc(wchar_t c, FILE *stream);
wint_t putwchar(wchar_t c);
wint_t ungetwc(wint_t c, FILE *stream);这一组函数是stdio.h中的字符输入/输出函数在22.4节讨论过的宽字符版本。表25-13给出了stdio.h中的函数与宽字符函数的对应关系。如表所示fwide是唯一的全新函数。
表25-13 宽字符输入输出函数及其在stdio.h中的对应函数
wchar.h函数stdio.h中的对应函数fgetwcfgetcfgetwsfgetsfputwcfputcfputwsfputsfwide—getwcgetcgetwchargetcharputwcputcputwcharputcharungetwcungetc
除非特别说明可以认为表25-13中所列出的wchar.h中的函数和stdio.h中的对应函数行为一致。但是多数对应函数之间有一点细微的差别。为了指示错误或者文件结尾条件stdio.h中的一些字符输入/输出函数返回EOF但wchar.h中的对应函数返回WEOF。 还有一个问题会影响宽字符输入函数。调用读取单字符的函数fgetwc、getwc和getwchar时可能会因为输入流中的字节不能组成有效的宽字符或者可用的字节不够而导致调用失败。这样会造成编码错误进而导致函数将EILSEQ存入errno并返回WEOF。fgetws函数读取宽字符串也可能因为编码错误而失败这种情况下它会返回空指针。 宽字符输出函数也可能遇到编码错误。用于输出单字符的函数fputwc、putwc和putwchar在出现编码错误时将EILSEQ存入errno并返回WEOF。但用于输出宽字符字符串的fputws函数有所不同它在出现编码错误时返回EOF而不是WEOF。 fwide函数在C89函数中没有相对应的函数。fwide函数用于确定流的当前倾向性如果需要还可以设置流的倾向性。mode参数决定函数的行为。 mode0如果没有倾向性尝试使流面向宽字符。mode0如果没有倾向性尝试使流面向字节。mode0不改变倾向性。
如果流已经有了倾向性fwide不会改变其倾向性。 fwide返回的值依赖于函数调用后流的倾向性。如果流为面向宽字符的返回的值为正如果流为面向字节的返回的值为负如果流没有倾向性返回0。 25.5.4 通用的宽字符串实用工具 wchar.h头提供了许多函数来对宽字符串进行操作。它们是stdlib.h和string.h中函数的宽字符版本。 25.5.4.1 宽字符串数值转换函数
double wcstod(const wchar_t * restrict nptr, wchar_t ** restrict endptr);
float wcstof(const wchar_t * restrict nptr, wchar_t ** restrict endptr);
long double wcstold(const wchar_t * restrict nptr, wchar_t ** restrict endptr);
long int wcstol(const wchar_t * restrict nptr, wchar_t ** restrict endptr, int base);
long long int wcstoll(const wchar_t * restrict nptr, wchar_t ** restrict endptr, int base);
unsigned long int wcstoul( const wchar_t * restrict nptr, wchar_t ** restrict endptr, int base);
unsigned long long int wcstoull( const wchar_t * restrict nptr, wchar_t ** restrict endptr, int base); 这一组函数是stdlib.h中的数值转换函数将在26.2节讨论的宽字符版本。wchar.h中的函数的参数类型为wchar_t *和wchar_t **而不是char *和char **但它们的行为与stdlib.h中的函数基本一样。表25-14给出了stdlib.h中的函数及其对应的宽字符版本。
表25-14 宽字符串数值转换函数及其在stdlib.h中的对应函数
wchar.h函数stdlib.h中的对应函数wcstodstrtodwcstofstrtofwcstoldstrtoldwcstolstrtolwcstollstrtollwcstoulstrtoulwcstoullstrtoull
25.5.4.2 宽字符串复制函数
wchar_t *wcscpy(wchar_t * restrict s1, const wchar_t * restrict s2);
wchar_t *wcsncpy(wchar_t * restrict s1, const wchar_t * restrict s2, size_t n);
wchar_t *wmemcpy(wchar_t * restrict s1, const wchar_t * restrict s2, size_t n);
wchar_t *wmemmove(wchar_t *s1, const wchar_t *s2, size_t n);这一组函数是string.h中的字符串复制函数在23.6节讨论过的宽字符版本。wchar.h头中的函数的参数类型为wchar_t *而不是char *但它们的行为与string.h中的函数基本一致。表25-15给出了string.h中的函数及其对应的宽字符版本。
表25-15 宽字符串复制函数及其在string.h中的对应函数
wchar.h函数string.h中的对应函数wcscpystrcpywcsncpystrncpywmemcpymemcpywmemmovememmove
25.5.4.3 宽字符串拼接函数
wchar_t *wcscat(wchar_t * restrict s1, const wchar_t * restrict s2);
wchar_t *wcsncat(wchar_t * restrict s1, const wchar_t * restrict s2, size_t n);这一组函数是string.h中的字符串拼接函数在23.6节讨论过的宽字符版本。wchar.h中的函数的参数类型是wchar_t *而不是char *但它们的行为与string.h中的函数基本一样。表25-16给出了string.h中的函数及其对应的宽字符版本。
表25-16 宽字符串拼接函数及其在string.h中的对应函数
wchar.h函数string.h中的对应函数wcscatstrcatwcsncatstrncat
25.5.4.4 宽字符串比较函数
int wcscmp(const wchar_t *s1, const wchar_t *s2);
int wcscoll(const wchar_t *s1, const wchar_t *s2);
int wcsncmp(const wchar_t *s1, const wchar_t *s2, size_t n);
size_t wcsxfrm(wchar_t * restrict s1, const wchar_t * restrict s2, size_t n);
int wmemcmp(const wchar_t * s1, const wchar_t * s2, size_t n); 这一组函数是string.h中的字符串比较函数在23.6节讨论过的宽字符版本。wchar.h中的函数的参数类型是wchar_t *而不是char *但它们的行为与string.h中的函数基本一样。表25-17给出了string.h中的函数及其对应的宽字符版本。
表25-17 宽字符串比较函数及其在string.h中的对应函数
wchar.h函数string.h中的对应函数wcscmpstrcmpwcscollstrcollwcsncmpstrncmpwcsxfrmstrxfrmwmemcmpmemcmp
25.5.4.5 宽字符串搜索函数
wchar_t *wcschr(const wchar_t *s, wchar_t c);
size_t wcscspn(const wchar_t *s1, const wchar_t *s2);
wchar_t *wcspbrk(const wchar_t *s1, const wchar_t *s2);
wchar_t *wcsrchr(const wchar_t *s, wchar_t c);
size_t wcsspn(const wchar_t *s1, const wchar_t *s2);
wchar_t *wcsstr(const wchar_t *s1, const wchar_t *s2);
wchar_t *wcstok(wchar_t * restrict s1, const wchar_t * restrict s2, wchar_t ** restrict ptr);
wchar_t *wmemchr(const wchar_t *s, wchar_t c, size_t n);这一组函数是string.h中的字符串搜索函数在23.6节讨论过的宽字符版本。wchar.h中的函数的参数类型是wchar_t *和wchar_t **而不是char *和char **但它们的行为与string.h中的函数基本一样。表25-18给出了string.h中的函数及其对应的宽字符版本。
表25-18 宽字符串搜索函数及其在string.h中的对应函数
wchar.h函数string.h中的对应函数wcschrstrchrwcscspnstrcspnwcspbrkstrpbrkwcsrchrstrrchrwcsspnstrspnwcsstrstrstrwcstokstrtokwmemchrmemchr wcstok函数与strtok函数作用相同但由于有第三个参数所以用法略有不同。strtok函数只有两个参数。要了解wcstok的工作原理首先需要回顾一下strtok的行为。 23.6节讲到strtok在字符串中搜索一个“记号”就是一系列不包含特定分隔符的字符。调用strtok(s1,s2)会在s1中搜索一系列不包含在s2中的非空字符。strtok函数会在记号末尾的字符后面存储一个空字符作为标记然后返回一个指针指向记号的首字符。
以后可以调用strtok函数在同一字符串中搜索更多的记号。调用strtok(NULL,s2)就可以继续上一次的strtok函数调用。和上一次调用一样strtok函数会用一个空字符来标记记号的末尾然后返回一个指针指向记号的首字符。这个过程可以持续进行直到strtok函数返回空指针这表明找不到符合要求的记号。 strtok的一个问题是在搜索的时候使用静态变量来记录这样就无法同时对两个或更多个字符串进行搜索。而wcstok由于多了一个参数不存在这一问题。 wcstok的前两个参数与strtok是相同的当然它们指向宽字符串。第三个参数ptr将指向wchr_t *类型的变量。函数将在这个变量中存储信息使得之后调用wcstok时能够继续扫描同一个字符串当第一个参数为空指针时。当通过后续的wcstok调用继续进行搜索时用指向同一个变量的指针作为第三个参数这个变量的值在wcstok函数调用之间不能改变。 为了了解wcstok的工作原理让我们再来看看23.6节中的例子。假设str、p和q声明 如下 wchar_t str[] L April 28,1998;
wchar_t *p, *q;最初的wcstok调用用str作为第一个参数
p wcstok(str, L \t, q); 现在p指向April的第一个字符April之后有一个空的宽字符。用空指针作为第一个参数、q作为第三个参数调用wcstok可以从上次停下来的地方继续搜索
p wcstok(NULL, L \t,, q); 在这个调用之后p指向28的第一个字符现在28的后面有一个用于终止的空的宽字符。再次调用wcstok可以定位年
p wcstok(NULL, L \t, q);
//p现在指向1998的第一个字符。25.5.4.6 其他函数
size_t wcslen(const wchar_t *s);
wchar_t *wmemset(wchar_t *s, wchar_t c, size_t n);这一组函数是string.h中的其他字符串函数在23.6节讨论过的宽字符版本。wchar.h中的函数的参数类型是wchar_t *而不是char *但它们的行为与string.h中的函数基本一样。表25-19给出了string.h中的函数及其对应的宽字符版本。
表25-19 宽字符串其他函数与string.h中的对应函数
wchar.h函数string.h中的对应函数wcslenstrlenwmemsetmemset 25.5.5 宽字符时间转换函数
size_t wcsftime(wchar_t * restrict s, size_t maxsize, const wchar_t * restrict format, const struct tm * restrict timeptr);wcsftime函数是time.h头中的strftime函数将在26.3节讨论的宽字符版本。 25.5.6 扩展的多字节/宽字符转换实用工具 本节讨论wchar.h中用于在多字节字符和宽字符之间进行转换的函数。其中有5个函数mbrlen、mbrtowc、wcrtomb、mbsrtowcs和wcsrtombs与stdlib.h中的多字节/宽字符转换函数以及多字节/宽字符串转换函数相对应。wchar.h中的函数具有一个额外的参数——一个指向mbstate_t类型变量的指针。这个变量记录多字节字符序列向宽字符序列转换或反向转换的当前转换状态。因此wchar.h中的函数是“可再次启动的”以前一次函数调用中修改过的指向mbstate_t类型变量的指针作为参数可以用该调用的转换状态“再次启动”函数。这样的好处之一是可以让两个函数共享同样的转换状态。例如处理单个多字节字符构成的字符串时mbrtowc和mbsrtowcs函数调用可以共享同一个mbstate_t类型变量。 存储在mbstate_t类型变量中的转换状态包括当前迁移状态和多字节字符内的当前位置。将mbstate_t类型变量的字节设为0会使其处于初始转换状态这意味着还没有开始处理多字节字符且初始迁移状态有效
mbstate_t state;
...
memset (state, \0, sizeof(state)); 把state传递给任何一个可再次启动的函数将导致从初始转换状态开始进行转换。一旦在这些函数中修改了mbstate_t类型变量该变量就不能用于转换不同的多字节字符序列了也不能用于反向的转换否则会导致未定义的行为。改变某个地区的LC_CTYPE之后使用该变量也会导致未定义的行为。
25.5.6.1 单字节/宽字符转换函数
wint_t btowc(int c);
int wctob(wint_t c);这一组函数把单字节字符转换为宽字符或执行反向转换。
如果c等于EOF或者在初始迁移状态时c强制转换为unsignedchar不是有效的单字节符号那么btowc函数返回WEOF。否则btowc返回c的宽字符表示。 wctob函数执行btowc的反向操作。如果c在初始迁移状态时没有对应的多字节字符则返回EOF否则返回c的单字节表示。 25.5.6.2 转换状态函数
int mbsinit(const mbstate_t *ps);这一组只有一个函数mbsinit。如果ps是空指针或者它指向一个描述初始转换状态的mbstate_t型变量函数返回非零值。
25.5.6.3 可重启的多字节/宽字符转换函数
size_t mbrlen(const char * restrict s, size_t n, mbstate_t * restrict ps);
size_t mbrtowc(wchar_t * restrict pwc, const char * restrict s, size_t n, mbstate_t * restrict ps);
size_t wcrtomb(char * restrict s, wchar_t wc, mbstate_t * restrict ps);这一组函数是stdlib.h中的mblen、mbtowc和wctomb函数在25.2节讨论过的可重启动版本。新函数mblen、mbtowc和wctomb与stdlib.h中的对应函数有如下区别: mbrlen、mbrtowc和wcrtomb函数新增了一个参数ps。当这些函数中的任一函数被调用时相应的参数指向一个mbstate_t类型的变量函数会在这个变量中存储转换状态。如果与ps对应的实参是空指针函数将使用内部变量来存储转换状态在程序执行的一开始这个变量设置为初始转换状态。当s参数是空指针时旧版的mblen、mbtowc和wctomb函数在多字节字符编码依赖状态时返回非零值否则返回0。新版的函数不具有该行为。mbrlen、mbrtowc和wcrtomb函数的返回值为size_t类型而不是int类型旧版函数的返回值为int类型。 调用mbrlen等同于调用 mbrtowc(NULL, s, n, ps)但当ps是空指针时使用内部变量的地址来代替。如果s是空指针调用mbrtowc等同于调用
mbrtowc(NULL, , 1, ps)否则mbrtowc至多检查由s指向的n个字节来判断是否已处理完一个有效的多字节字符。注意在函数调用之前可能已经在处理多字节字符了这由ps指向的mbstate_t类型变量来记录。如果是这样这些字节将被转换为宽字符。只要pwc不为空就把该宽字符存于pwc指向的位置。如果该字符是空的宽字符把函数调用中使用的mbstate_t类型变量置为初始转换状态。 mbrtowc有多种可能的返回值。如果转换产生了空的宽字符其返回值为0。如果转换产生了非空的宽字符则返回一个范围在1~n的数该返回值是用于完成多字节字符的字节数。如果s指向的n个字节不足以完成多字节字符尽管这些字节本身是有效的则返回-2。最后如果出现编码错误函数遇到了不能形成有效的多字节字符的字节则返回-1在这种情况下mbrtowc仍会将EILSEQ存于errno中。 如果s是空指针调用wcrtomb等同于
wcrtomb(buf, L\0, ps)这里buf是内部缓冲区。否则wcrtomb将wc从宽字符转换为多字节字符并将其存于s指向的数组中。如果wc是空的宽字符wcrtomb中存储空字节如果必要前面还可以放一个迁移序列用于存储初始迁移状态。这种情况下调用中所用的mbstate_t类型变量置为初始转换状态。wcrtomb返回所存储的字节数包括迁移序列。如果wc不是有效的宽字符函数返回-1并将EILSEQ存于errno中。
25.5.6.4 可重启动的多字节/宽字符串转换函数
size_t mbsrtowcs(wchar_t * restrict dst, const char ** restrict src, size_t len, mbstate_t * restrict ps);
size_t wcsrtombs(char * restrict dst, const wchar_t ** restrict src, size_t len, mbstate_t * restrict ps);mbsrtowcs和wcsrtombs函数是stdlib.h中的mbstowcs和wcstombs函数在25.2节讨论过的可重启动版本。mbsrtowcs和wcsrtombs函数与stdlib.h中的对应函数基本一样只有如下区别。 mbsrtowcs和wcsrtombs都有一个额外的参数ps。当它们中的一个函数被调用时对应的参数指向一个mbstate_t类型的变量函数将使用该变量存储转换状态。如果ps对应的参数是空指针函数将使用内部变量来存储转换状态。在程序一开始执行时这个变量设置为初始转换状态。这两个函数在转换过程中都会更新状态。如果转换因为遇到空字符而停止mbstate_t型变量将置为初始转换状态。src参数表示包含待转换字符的数组源数组它是一个指向指针的指针。在旧版的mbstowcs函数和wcstombs函数中对应参数只是一个普通指针。这个变化使得mbsrtowcs和wcsrtombs可以记录转换停止的位置。如果转换因为达到空字符而停止则把src指向的指针设置为空否则使该指针刚好越过上一次转换成功的源字符。dst参数有可能是空指针在这种情况下不存储已转换的字符也不修改src指向的指针。当这两个函数在源数组里遇到无效字符时它们会将EILSEQ存于errno中同时返回-1而mbstowcs和wcstombs函数仅返回-1。 25.6 wctype.h宽字符分类和映射实用工具(C99) wctype.h头是ctype.h头23.5节的宽字符版本。ctype.h提供了两类函数字符分类函数如isdigit测试一个字符是否是数字和字符映射函数如toupper把小写字母转换为大写字母。wctype.h为宽字符提供了类似的函数但与ctype.h有一点重要区别wctype.h中的一些函数是“可扩展的”这意味着它们可以执行自定义的字符分类和映射。 wctype.h声明了三个类型和一个宏。wint_t类型和WEOF宏在25.5节中讨论过。另外两种类型是wctype_t其值表示特定于地区的字符分类和wctrans_t其值表示特定于地区的字符映射。
wctype.h中的大部分函数要求参数为wint_t类型。这个参数的值必须是一个宽字符wchar_t类型的值或WEOF传递其他参数会引起未定义的行为。
wctype.h中函数的行为受当前地区的LC_CTYPE类项的影响。 25.6.1 宽字符分类函数
int iswalnum(wint_t wc);
int iswalpha(wint_t wc);
int iswblank(wint_t wc);
int iswcntrl(wint_t wc);
int iswdigit(wint_t wc);
int iswgraph(wint_t wc);
int iswlower(wint_t wc);
int iswprint(wint_t wc);
int iswpunct(wint_t wc);
int iswspace(wint_t wc);
int iswupper(wint_t wc);
int iswxdigit(wint_t wc);对于每一个宽字符分类函数如果它的参数有特定的性质则返回非零值。表25-20列出了每个函数测试的性质。
表25-20 宽字符分类函数
函数测试iswalnum(wc)wc是否是字母或数字iswalpha(wc)wc是否是字母iswblank(wc)wc是否是标准空白①iswcntrl(wc)wc是否是控制字符iswdigit(wc)wc是否是十进制数字iswgraph(wc)wc是否是打印字符空格除外iswlower(wc)wc是否是小写字母iswprint(wc)wc是否是打印字符包含空格iswpunct(wc)wc是否是标点符号iswspace(wc)wc是否是空白字符iswupper(wc)wc是否是大写字母iswxdigit(wc)wc是否是十六进制数字
①标准空白字符是空格L 和水平制表符L\t。 表25-20的描述中忽略了宽字符的一些细节。例如C99标准中iswgraph的定义指出该函数“对任意给定的宽字符测试iswprint为真且iswspace为假”因此存在这样的可能性多个宽字符都可以被认作“空格”。 在大多数情况下宽字符分类函数与ctype.h中对应的函数一致如果ctype.h中的函数对某个字符返回非零值表明“真”那么wctype.h中相应的函数对该字符的宽字符版本返回真。唯一的例外是宽的空白字符不是空格中属于打印字符的那些字符用iswgraph和iswpunct分类的结果与用isgraph和ispunct分类的结果不同。例如使isgraph返回真的字符可能会使iswgraph返回假。 25.6.2 可扩展的宽字符分类函数
int iswctype(wint_t wc, wctype_t desc);
wctype_t wctype(const char *property);前面讨论的每一个宽字符分类函数都可以测试一个固定的条件。wctype和iswctype函数被设计为同时使用可以用于测试其他条件。 wctype函数的参数是一个描述一类宽字符类的字符串它返回一个表示这个类的wctype_t类型值。例如调用 wctype(upper)返回一个wctype_t类型的值表示大写字母类。C99标准要求允许用以下字符串作为wctype的参数
alnum alpha blank cntrl digit graph
lower print punct space upper xdigit其他字符串可以由实现提供。哪些字符串可以用作wctype的合法参数依赖于当前地区的LC_CTYPE类项。上面列出的12个字符串在所有地区都合法。如果当前地区不支持传递给wctype的字符串函数返回0。 调用iswctype函数需要用到两个参数wc宽字符和descwctype返回的值。如果wc属于与desc相对应的字符类那么iswctype函数返回非零值。例如调用 iswctype(wc, wctype(alnum))等价于iswalnum(wc)。如果传递给wctype的字符串不是上面列出的标准字符串则wctype和iswctype尤其有用。 25.6.3 宽字符大小写映射函数
wint_t towlower(wint_t wc);
wint_t towupper(wint_t wc);towlower和towupper函数分别是tolower和toupper对应的宽字符版本。例如towlower在参数是大写字母时返回参数的小写形式否则保持参数不变并将其返回。一般说来处理宽字符时会有一些突发情况。例如某个字母在当前地区可能有多种小写字母在这种情况下towlower可以返回其中任意一个。 25.6.4 可扩展的宽字符大小写映射函数
wint_t towctrans(wint_t wc, wctrans_t desc);
wctrans_t wctrans(const char *property);wctrans和towctrans函数一起使用以支持一般性的宽字符大小写映射。 wctrans函数的参数是一个字符串用于描述字符的大小写映射。它返回一个wctrans_t类型的值来表示该映射关系。例如调用 wctrans(tolower)返回一个表示从大写字母向小写字母映射的wctrans_t类型值。C99标准要求字符串tolower和toupper可以作为wctrans的参数。具体实现中还可以提供其他的字符串。哪些字符串可以用作wctrans的合法参数依赖于当前地区的LC_CTYPE类项。tolower和toupper在所有地区都合法。如果当前地区不支持传递给wctrans的字符串函数返回0。 调用towctrans函数需要用到两个参数wc宽字符和descwctrans返回的值。towctrans根据desc所指定的大小写映射关系将wc映射为另一个宽字符。例如调用 towctrans(wc, wctrans(tolower))等价于
towlower(wc)与实现定义的大小写映射一起使用时towctrans特别有用。 25.7 uchar.h: 改进的Unicode支持(C1X) 在C99中可以用wchar_t类型的变量保存宽字符。尽管绝大多数计算机系统开始支持Unicode字符集使用wchar_t类型保存的字符也都是Unicode字符但是C语言没有规定这种类型的长度再加上不同的操作系统使用不同的Unicode编码方案这就影响了文本的交换以及程序的可移植性。 举例来说Windows使用UTF-16编码因为单一16位只能表示基本多语种平面内的字符它使用的实际上是变长UTF-16编码对于基本多语种平面内的字符使用一个16位来表示对于其他字符则使用两个16位来表示代理对。与Windows不同Linux直接使用32位的UTF-32来编码字符。因为长度不统一所以当程序在不同的平台之间移植时就需要做麻烦的转换工作。 从C11开始标准库提供了头uchar.h并定义了两种具有明确长度的宽字符类型它们分别是char16_t和char32_t。char16_t是一个无符号整数类型和uint_least16_t相同用来保存长度为16位的字符通常用于保存UTF-16编码的字符char32_t也是一个无符号整数类型和uint_least32_t相同用来保存长度为32位的字符通常用于保存UTF-32编码的字符。 25.7.1 带u、U和u8前缀的字面串 和C99相比C1X的另一个显著变化是支持u、U和u8前缀的字面串以及u和U前缀的字符常量。带u前缀的字面串用于在程序编译期间创建一个元素类型为char16_t的静态数组带u前缀的字符常量是宽字符常量它的类型是char16_t例如 char16_t c ua;
char16_t * p uAye aye sir!\n; 带U前缀的字面串用于在程序编译期间创建一个元素类型为char32_t的静态数组带U前缀的字符常量是宽字符常量它的类型是char32_t例如
char32_t d Ua;
char32_t * q UYes captain!\n; u8前缀只适用于字面串用来明确指定字面串采用UTF-8编码方案例如 char s [] u8As you wish!\n;
//注意!在u、U、u8和它们后面的之间不能有任何空白否则将导致语法错误。25.7.2 可重启动的多字节/宽字符转换函数
size_t mbrtoc16(char16_t * restrict pc16, const char * restrict s, size_t n, mbstate_t * restrict ps);
size_t c16rtomb(char * restrict s, char16_t c16, mbstate_t restrict ps);
size_t mbrtoc32(char32_t * restrict pc32, const char * restrict s, size_t n, mbstate_t * restrict ps);
size_t c32rtomb(char * restrict s, char32_t c32, mbstate_t * restrict ps); 这些函数拥有一个参数ps它是指向mbstate_t的指针可用于完整地描述受这些函数影响的多字节字符序列的当前转换状态。如果与ps对应的实参是空指针函数将使用一个mbstate_t类型的内部变量来存储转换状态。在程序启动时这个变量被初始化到一个起始的转换状态。 函数mbrtoc16用来将多字节字符转换为用char16_t类型来表示的宽字符。如果s是空指针调用mbrtoc16等同于调用 mbrtoc16(NULL, , 1, ps)否则mbrtoc16至多检查由s指向的n个字节以确定完成下一个多字节字符所需要的字节数包括任何迁移序列。如果能够确定s中的下一个多字节字符是完整且有效的则将其转换为相应的16位宽字符并保存在pc16指向的位置如果pc16不是空指针的话。 如果宽字符是变长编码的比如UTF-16代理对可能需要执行该函数一次以上。换句话说上一次调用只是得到了宽字符编码的前一部分。后续的调用不会消费额外的输入还是在指定的n个字节内处理并转换和保存下一个宽字符。 如果转换后的结果是一个空宽字符则ps指向的转换状态恢复到最初的时候。表25-21列出了该函数的返回值及其含义
表25-21 mbrtoc16函数的转换结果
返回值含义0转换后的结果是空宽字符1~n实际用了几个字节完成的宽字符转换(size_t)-3本次调用是延续上一次的调用并已成功转换和保存宽字符(size_t)-2接下来的n个字节不足以表示一个多字节字符但它依然可能是有效的只是需要后面的字节才能完整表示(size_t)-1编码错误接下来的n个字节不能表示一个完整有效的多字节字符。此时errno的值是EILSEQ且转换状态是未指定的 函数c16rtomb将char16_t类型的宽字符转换为多字节字符。如果参数s为空指针则该函数等同于 c16rtomb(buf, L\0, ps)否则该函数计算将参数c16中的宽字符转换成多字节字符需要几个字节并将转换后的结果保存到参数s所指向的内存位置但是至多保存MB_CUR_MAX个字节。如果参数c16中是空宽字符则转换和保存的是以任意迁移序列为前导的空字节这个迁移序列用于恢复初始迁移状态。这种情况下调用中所用的mbstate_t类型变量置为初始转换状态。
此函数的返回值是转换并保存的字节数包括任何迁移序列。如果参数c16的值不代表有效的宽字符将发生编码错误保存的值是EILSEQ并且返回值是(size_t)-1。 函数mbrtoc32用于将多字节字符转换为char32_t类型的宽字符。如果s是空指针调用mbrtoc32等同于调用 mbrtoc32(NULL, , 1, ps)否则mbrtoc32至多检查由s指向的n个字节以确定完成下一个多字节字符所需要的字节数包括任何迁移序列。如果能够确定s中的下一个多字节字符是完整且有效的则将其转换为相应的32位宽字符并保存在pc32指向的位置如果pc32不是空指针的话。 如果宽字符是变长编码的这对于UTF-32来说是不可能的但是库函数不会预设任何具体的编码方案可能需要执行该函数一次以上。换句话说上一次调用只是得到了宽字符编码的前一部分。后续的调用不会消费额外的输入还是在指定的n个字节内处理并转换和保存下一个宽字符。 如果转换后的结果是一个空宽字符则ps指向的转换状态恢复到最初的时候。表25-22列出了该函数的返回值及其含义
表25-22 mbrtoc32函数的转换结果
返回值含义0转换后的结果是空宽字符1~n实际用了几个字节完成的宽字符转换(size_t)-3本次调用是延续上一次的调用并已成功转换和保存宽字符(size_t)-2接下来的n个字节不足以表示一个多字节字符但它依然可能是有效的只是需要后面的字节才能完整表示(size_t)-1编码错误接下来的n个字节不能表示一个完整有效的多字节字符。此时errno的值是EILSEQ且转换状态是未指定的 函数c32rtomb用于将char32_t类型的宽字符转换为多字节字符。如果参数s为空指针则该函数等同于 c32rtomb (buf, L\0, ps)否则该函数计算将参数c32中的宽字符转换成多字节字符需要几个字节并将转换后的结果保存到参数s所指向的内存位置但是至多保存MB_CUR_MAX个字节。如果参数c32中是空宽字符则转换和保存的是以任意迁移序列为前导的空字节这个迁移序列用于恢复初始迁移状态。这种情况下调用中所用的mbstate_t类型变量置为初始转换状态。
此函数的返回值是转换并保存的字节数包括任何迁移序列。如果参数c32的值不代表有效的宽字符将发生编码错误保存的值是EILSEQ并且返回值是(size_t)-1。 问与答 问1setlocale函数可以返回多长的地区信息字符串 答不存在最大长度。这就引发了一个问题如果不知道字符串的长度如何为字符串设置空间呢当然答案就是动态存储分配。下面这个程序段基于Harbison和Steele写的《C语言参考手册》一书中的类似示例说明了如何确定需要的空间数量动态地分配内存然后再把地区信息复制到此内存空间中
char *temp, *old_locale;
temp setlocale(LC_ALL, NULL);
if (temp NULL) { /* locale information not available */
}
old_locale malloc (strlen (temp) 1);
if (old_locale NULL) { /* memory allocation failed */
}
strcpy(old_locale, temp); 现在可以先切换到另一个地区然后再恢复到旧的地区
setlocale(LC_ALL, ); /* switches to native locale */
...
setlocale(LC_ALL, old_locale); /* restorees old locale */ 问2为什么C语言同时提供多字节字符和宽字符呢两者选其一难道不够吗 答这两种编码分别用于不同的目的。多字节字符用于输入/输出目的很方便因为输入/输出设备经常是面向字节的。但是宽字符更适用于程序内部因为每个宽字符占有相同的空间。因此程序可以读入多字节字符输入把它转换为便于程序内部操作的宽字符格式然后再把宽字符转换回用于输出的多字节格式。 问3Unicode和通用字符集UCS看起来很相似两者的区别是什么 答这两者所包含的字符一样而且表示字符所用的码点也一样。不过Unicode不仅仅是一个字符集。例如Unicode支持“双向显示”。有些语言包括阿拉伯语和希伯来语允许从右向左书写而不是从左向右书写。Unicode可以用于指定字符的显示顺序它允许文本中同时包含从左向右显示的字符和从右向左显示的字符。 写在最后 本文是博主阅读《C语言程序设计现代方法第2版·修订版》时所作笔记日后会持续更新后续章节笔记。欢迎各位大佬阅读学习如有疑问请及时联系指正希望对各位有所帮助Thank you very much!