你如何应对标准库中签名的char – > int问题?

这是我工作中一个长期存在的问题,我意识到我仍然没有一个很好的解决方案……

C天真地为int定义了它的所有字符测试函数:

int isspace(int ch); 

但是char经常被签名,并且一个完整的角色通常不适合int,或任何用于字符串******的单个存储单元。

这些函数已成为当前C ++函数和方法的逻辑模板,并为当前的标准库奠定了基础。 事实上,他们仍然得到了支持。

因此,如果您使用isspace(* pchar),最终可能会出现符号扩展问题。 他们很难看到,因此根据我的经验他们很难防范。

类似地,因为isspace()和它的所有类型都是内联的,并且因为字符串的实际宽度通常是未知的,而不是字符串分析 – 这意味着任何现代字符库本质上都不应该只是在char或wchar_t周围,而只是指针/迭代器,因为只有通过分析字符流才能知道它有多少组成一个逻辑字符,我对如何最好地解决这些问题感到有些不知所措?

我一直在期待一个真正强大的库,它基于抽象出任何字符的大小因素,并且只使用字符串(提供诸如isspace之类的东西等),但要么我错过了,要么是另一个更简单的解决方案盯着我面对所有人(谁知道你在做什么)使用……


**这些问题不适用于可以完全包含完整字符的固定大小的字符编码 – UTF-32显然是唯一具有这些特征的选项(或者将自己局限于ASCII或其他特殊环境的专用环境) 。


所以,我的问题是:

“你如何以不受两个问题影响的方式测试空白,可打印等等:

1)标志扩展,和
2)可变宽度字符问题

毕竟,大多数字符编码都是可变宽度:UTF-7,UTF-8,UTF-16,以及Shift-JIS等旧标准。 如果编译器将char视为带符号的8位单元,即使扩展ASCII也可能具有简单的符号扩展问题。

请注意:

无论char_type的大小是多少,对于大多数字符编码方案来说都是错误的。

这个问题出现在标准C库以及C ++标准库中; 仍尝试传递char和wchar_t,而不是各种isspace,isprint等实现中的字符串迭代器。

实际上,正是这些类型的函数破坏了std :: string的通用性。 如果它只在存储单元中工作,并且没有试图假装将存储单元的含义理解为逻辑字符(例如isspace),那么抽象将更加诚实,并且会迫使程序员看起来其他有效的解决方案……

谢谢

参与的每个人。 在这个讨论和WChars,编码,标准和可移植性之间,我可以更好地处理这些问题。 虽然没有简单的答案,但每一点理解都有帮助。

你如何以不受两个问题影响的方式测试空白,可打印等等:
1)签署扩展
2)可变宽度字符问题
毕竟,所有常用的Unicode编码都是可变宽度的,无论程序员是否意识到:UTF-7,UTF-8,UTF-16,以及诸如Shift-JIS之类的旧标准……

显然,您必须使用支持Unicode的库,因为您已经(正确地)certificate了C ++ 03标准库不是。 C ++ 11库已得到改进,但对于大多数用法来说仍然不够好。 是的,有些操作系统有一个32位的wchar_t,这使得它们能够正确处理UTF32,但这是一个实现,并不是C ++保证的,对于许多unicode任务来说远远不够,例如迭代Graphemes(字母) 。

IBMICU
libiconv的
microUTF-8
UTF-8 CPP,版本1.0
utfproc
以及http://unicode.org/resources/libraries.html上的更多内容。

如果问题不是关于特定的字符测试,而是更多关于代码实践的问题:做你的框架做的任何事情。 如果您正在为linux / QT /网络编码,请将所有内容保存在UTF-8中。 如果您使用Windows进行编码,请将所有内容保存在UTF-16中。 如果您需要弄乱代码点,请将所有内容保存在UTF-32中。 否则(对于便携式通用代码),做任何你想做的事情,因为无论如何,你必须翻译一些操作系统或其他。

我认为你混淆了许多不相关的概念。

首先, char只是一种数据类型。 它的首要含义是“系统的基本存储单元”,即“一个字节”。 其签名有意留给实现,以便每个实现可以选择最合适的(即硬件支持的)版本。 它的名字,暗示“字符”,很可能是C编程语言设计中最糟糕的决定。

下一个概念是文本字符串。 在基础上,文本是一系列单元,通常称为“字符”,但它可能比这更复杂。 为此,Unicode标准将术语“代码点”硬币化以指定最基本的文本单元。 就目前而言,对于我们程序员来说,“text”是一系列代码点。

问题是代码点多于可能的字节值。 可以用两种不同的方式克服这个问题:1)使用多字节编码将代码点序列表示为字节序列; 或2)使用不同的基本数据类型。 C和C ++实际上提供了两种解决方案:本机主机接口(命令行参数,文件内容,环境变量)作为字节序列提供; 但该语言还为“系统的字符集”提供了一个不透明的类型wchar_t ,以及它们之间的转换函数( mbstowcs / wcstombs )。

不幸的是,“系统的字符集”和“系统多字节编码”并没有具体的内容,因此,就像你之前的许多SO用户一样,你仍然不知道如何处理这些神秘的宽字符。 人们现在想要的是一种可以跨平台共享的明确编码。 我们为此目的唯一有用的编码是Unicode ,它为大量代码点赋予文本含义(目前最多为21个 )。 随着文本编码,出现了一系列字节串编码,UTF-8,UTF-16和UTF-32。

因此,检查给定文本字符串内容的第一步是将它从您拥有的任何输入转换为明确的(Unicode)编码字符串。 这个Unicode字符串本身可以用任何转换格式编码,但最简单的就是一系列原始代码点(通常是UTF-32,因为我们没有有用的21位数据类型)。

执行此转换已经超出了C ++标准(甚至是新标准)的范围,因此我们需要一个库来执行此操作。 由于我们对“系统的字符集”一无所知,我们还需要库来处理它。

一个受欢迎的库选择是iconv() ; 典型的序列从输入多字节char*mbstowcs()std::wstringwchar_t* wide字符串,然后通过iconv()的WCHAR_T到UTF32转换为std::u32stringuint32_t* raw Unicode码点序列。

在这一点上,我们的旅程结束。 我们现在可以通过代码点检查文本代码点(这可能足以判断某些东西是否是空格); 或者我们可以调用较重的文本处理库来对我们的Unicode码点流执行复杂的文本操作(例如规范化,规范化,表示转换等)。 这远远超出了通用程序员和文本处理专家的范围。

将EOF以外的负值传递给isspace和其他字符宏无论如何都是无效的。 如果你有一个char c ,并且你想测试它是否是空格,请执行isspace((unsigned char)c) 。 这涉及扩展(通过零扩展)。 isspace(*pchar)是错误的 – 不要写它,当你看到它时不要让它站起来。 如果你在看到它时训练自己恐慌,那么它就不那么难看了。

fgetc (例如)已经返回EOF或读取为unsigned char ,然后转换为int ,因此对于值没有符号扩展问题。

但这确实是琐事,因为标准字符宏不包括Unicode或多字节编码。 如果要正确处理Unicode,则需要Unicode库。 我没有看过C ++ 11或C1X在这方面提供的内容,除了C ++ 11有std::u32string这听起来很有前途。 在此之前,答案是使用特定于实现或第三方的东西。 (联合国)幸运的是有很多库可供选择。

可能(我推测)“完整的”Unicode分类数据库是如此之大,因此可能会发生变化,以至于C ++标准无论如何都要求“完全”支持是不切实际的。 它在某种程度上取决于应该支持哪些操作,但是你无法摆脱Unicode在20年内(自第一个标准版本以来)已经通过6个主要版本的问题,而C ++在13年中有2个主要版本。 就C ++而言,Unicode字符集是一个快速移动的目标,因此它始终是实现定义系统知道的代码点。

通常,有三种正确的方法来处理Unicode文本:

  1. 在所有I / O(包括返回或接受字符串的系统调用)中,在外部使用的字符编码和内部固定宽度编码之间转换所有内容。 您可以将此视为输入上的“反序列化”和输出上的“序列化”。 如果您有一些具有将其转换为字节流或从字节流转换的函数的对象类型,那么您不会将字节流与对象混淆,或者检查字节流的各个部分以查找您认为可识别的序列化数据的片段。 对于这个内部unicode字符串类,它不需要有任何不同。 请注意,该类不能std::string ,也可能不是std::wstring ,具体取决于实现。 只是假装标准库不提供字符串,如果它有帮助,或者使用大的东西的std::basic_string作为容器,而使用Unicode感知的库来做任何复杂的事情。 您可能还需要了解Unicode规范化,处理组合标记等,因为即使在固定宽度的Unicode编码中,每个字形可能有多个代码点。

  2. 混淆了一些字节序列和Unicode序列的特殊混合,仔细跟踪哪个是哪个。 它就像(1),但通常更难,因此虽然它可能是正确的,但在实践中它可能很容易出错。

  3. (仅限特殊用途):使用UTF-8进行所有操作。 有时这很好,例如,如果你所做的只是基于ASCII标点符号的解析输入,并连接输出的字符串。 基本上它适用于那些你不需要通过顶部位设置理解任何东西的程序,只需不加改变地传递它。 如果你需要实际渲染文本,或者以其他方式做一些人类认为“显而易见”但实际上很复杂的东西,它就不能很好地工作。 像整理一样。

前面有一条评论:像isspace这样的旧C函数由于某种原因而采用了int :它们也支持EOF作为输入,因此它们需要能够支持比char更适合的值。 “天真”的决定允许char签名 – 但是使其无签名会对PDP-11产生严重的性能影响。

现在回答你的问题:

1)签署扩展

C ++函数没有这个问题。 在C ++中,测试诸如字符是否为空格之类的“正确”方法是从您想要的任何语言环境中获取std::ctype facet并使用它。 当然, 的C ++本地化经过精心设计,使其尽可能难以使用,但如果您正在进行任何重要的文本处理,您很快就会想出自己的便利包装:function对象采用区域设置和掩码指定要测试的特性并不难。 使它成为掩码上的模板,并将其locale参数赋予全局语言环境的默认值也不是火箭科学。 抛出一些typedef,你可以将像IsSpace()这样的东西传递给std::find 。 唯一的缺点是管理你正在处理的std::ctype对象的生命周期。 但是,以下内容应该有效:

 template class Is // Must find a better name. { std::locale myLocale; //< Needed to ensure no premature destruction of facet std::ctype const* myCType; public: Is( std::locale const& l = std::locale() ) : myLocale( l ) , myCType( std::use_facet >( l ) ) { } bool operator()( char ch ) const { return myCType->is( mask, ch ); } }; typedef Is IsSpace; // ... 

(考虑到STL的影响,标准没有像上面那样定义标准,这有点令人惊讶。)

2)可变宽度字符问题。

没有真正的答案。 这一切都取决于你需要什么。 对于某些应用程序,只需查找一些特定的单字节字符就足够了,并且将所有内容保存在UTF-8中,并忽略多字节问题,这是一种可行(且简单)的解决方案。 除此之外,转换为UTF-32(或取决于您正在处理的文本类型,UTF-16)通常很有用,并将每个元素用作单个代码点。 另一方面,对于全文处理,即使您使用的是UTF-32,也必须处理多个代码点字符:序列\u006D\u0302是一个单个字符(一个小的m\u006D\u0302有一个\u006D\u0302 ) )。

我没有对Qt库的国际化能力进行过如此多的测试,但据我所知,QString完全具有unicode感知能力,并且使用的是QChar,它们是unicode-chars。 我不知道那些内部实现,但我希望这意味着QChar是可变大小的字符。

将自己绑定到像Qt这样的大框架只是为了使用字符串会很奇怪。

您似乎将7位ascii上定义的函数与通用空间识别function混淆。 标准C中的字符函数使用int来处理不同的编码,但允许EOF成为带外指示符。 符号扩展没有问题,因为定义这些函数的数字没有第8位。 提供这种可能性的字节是你的错误。

计划9尝试使用UTF库解决此问题,并假设所有输入数据都是UTF-8。 这允许一些与ASCII的向后兼容性的度量,因此不兼容的程序不会全部死亡,但允许正确编写新程序。

C中的常见概念,即使仍然是char*代表一个字母数组。 它应该被视为输入数据块。 要从此流中获取字母,请使用chartorune() 。 每个Rune都是一个字母(/符号/代码点)的表示,因此最终可以定义一个函数isspacerune() ,它最终会告诉你哪些字母是空格。

像使用char数组一样处理Rune数组,进行字符串操作,然后在写出之前调用runetochar()将字母重新编码为UTF-8。

符号扩展问题很容易处理。 你可以使用:

  • isspace((unsigned char) ch)
  • isspace(ch & 0xFF)
  • 使char成为无符号类型的编译器选项

至于可变长度字符问题(我假设是UTF-8),它取决于您的需求。

如果你只是处理ASCII空白字符\t\n\v\f\r ,那么isspace将正常工作; 非ASCII UTF-8代码单元将被简单地视为非空格。

但是如果你需要识别额外的Unicode空格字符\x85\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000 ,这是一个更多的工作。 你可以写一个函数

 bool isspace_utf8(const char* pChar) { uint32_t codePoint = decode_char(*pChar); return is_unicode_space(codePoint); } 

其中decode_char将UTF-8序列转换为相应的Unicode代码点, is_unicode_space对于具有类别Z字符或对于作为空格的Cc字符返回true。 iswspace可能会或可能不会帮助后者,具体取决于您的C ++库支持Unicode的程度。 最好为作业使用专用的Unicode库。

实际上大多数字符串都使用多字节编码,如UTF-7,UTF-8,UTF-16,SHIFT-JIS等。

没有程序员会使用UTF-7或Shift-JIS作为内部表示,除非他们喜欢痛苦。 坚持使用ŬTF-8,-16或-32,并且只能根据需要进行转换。

你的序言论点有些不妥,可以说是不公平的,它只是在图书馆设计中不支持Unicode编码 – 当然不是多种Unicode编码。

在开发Unicode之前,开发C和C ++语言以及大多数库。 同样,作为系统级语言,它们需要与执行环境的最小可寻址字大小相对应的数据类型。 不幸的是, char类型可能已经过载以表示执行环境的字符集和最小可寻址字。 历史已经certificate这可能是有缺陷的,但改变语言定义,实际上库会破坏大量的遗留代码,因此这些东西留给了新的语言,如C#,它具有8位byte和不同的char类型。

此外,Unicode表示的变量编码使其不适合于内置数据类型。 您显然知道这一点,因为您建议应对字符串而不是机器字类型执行Unicode字符操作。 这需要库支持,正如您所指出的那样,标准库不提供。 这有很多原因,但主要是它不在标准库的范围内,就像没有标准库支持网络或图形一样。 该库本质上不解决从深度嵌入到超级计算机的所有目标平台通常不普遍支持的任何内容。 所有这些事情必须由系统或第三方库提供。

对多字符编码的支持是关于系统/环境的互操作性,并且库也不打算支持它。 不兼容的编码系统之间的数据交换是应用程序问题而不是系统问题。

“你如何以不受两个问题影响的方式测试空白,可打印等等:

1)标志扩展,和

2)可变宽度字符问题

isspace()仅考虑较低的8位。 它的定义明确指出,如果传递的参数不能表示为unsigned char或等于宏EOF的值 ,则结果是未定义的。 如果按预期使用它,则不会出现问题。 问题是,它似乎不适合您应用它的目的。

毕竟,所有常用的Unicode编码都是可变宽度的,无论程序员是否意识到:UTF-7,UTF-8,UTF-16,以及诸如Shift-JIS之类的旧标准

没有为Unicode定义isspace()。 您需要一个旨在使用您正在使用的任何特定编码的库。 这个问题C的最佳Unicode库是什么? 可能是相关的。