Mor*_*hai 9 c c++ character-encoding special-characters
这是我工作中一个长期存在的问题,我意识到我仍然没有一个很好的解决方案......
C天真地为int定义了它的所有字符测试函数:
int isspace(int ch);
Run Code Online (Sandbox Code Playgroud)
但是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之间,编码,标准和可移植性我对这些问题有了更好的处理.虽然没有简单的答案,但每一点理解都有帮助.
Moo*_*uck 10
如何测试空白,isprintable等,不会遇到两个问题:
1)符号扩展
2)可变宽度字符问题
毕竟,所有常用的Unicode编码都是可变宽度的,无论程序员是否实现:UTF-7,UTF-8,UTF-16,以及Shift-JIS等旧标准......
显然,您必须使用支持Unicode的库,因为您已经(正确地)证明了C++ 03标准库不是.C++ 11库已得到改进,但对于大多数用法来说仍然不够好.是的,有些OS'有一个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::wstring或wchar_t*宽字符串,并且然后经由iconv()的wchar_t的到UTF32转换到一个std::u32string或uint32_t*原始的Unicode码点序列.
在这一点上,我们的旅程结束.我们现在可以通过代码点检查文本代码点(这可能足以判断某些东西是否是空格); 或者我们可以调用一个较重的文本处理库来对我们的Unicode编码点流执行复杂的文本操作(如归一化,规范化,表象变换等).这远远超出了通用程序员和文本处理专家的范围.
将EOF以外的负值传递给isspace其他字符宏无论如何都是无效的.如果您有char c,并且想要测试它是否是空格,请执行isspace((unsigned char)c).这涉及扩展(通过零扩展).isspace(*pchar)是错误的 - 不要写它,当你看到它时不要让它站起来.如果你在看到它时训练自己恐慌,那么它就不那么难看了.
fgetc(例如)已经返回EOF或读取为a的字符unsigned char然后转换为int,因此对于这些值没有符号扩展问题.
但这确实是琐事,因为标准字符宏不包括Unicode或多字节编码.如果要正确处理Unicode,则需要Unicode库.我没有看过C++ 11或C1X在这方面提供的内容,除了C++ 11 std::u32string听起来很有前途.在此之前,答案是使用特定于实现或第三方的东西.(联合国)幸运的是有很多库可供选择.
可能(我推测)"完整"的Unicode分类数据库是如此之大,因此可能会发生变化,因此无论如何C++标准都无法获得"全面"支持.它在一定程度上取决于应该支持哪些操作,但是你无法摆脱Unicode在20年内(自第一个标准版本以来)已经通过6个主要版本的问题,而C++在13年中有2个主要版本.就C++而言,Unicode字符集是一个快速移动的目标,因此它始终是实现定义的系统知道的代码点.
通常,有三种正确的方法来处理Unicode文本:
在所有I/O(包括返回或接受字符串的系统调用)中,在外部使用的字符编码和内部固定宽度编码之间转换所有内容.您可以将此视为输入上的"反序列化"和输出上的"序列化".如果您有一些具有将其转换为字节流或从字节流转换的函数的对象类型,那么您不会将字节流与对象混淆,或者检查字节流的各个部分以查找您认为可识别的序列化数据的片段.对于这个内部unicode字符串类,它不需要有任何不同.请注意,类不能是std::string,可能不会是std::wstring要么,具体取决于实现.只是假装标准库不提供字符串,如果它有帮助,或使用std::basic_string大容量的东西作为容器,但使用Unicode感知库来做任何复杂的事情.您可能还需要了解Unicode规范化,处理组合标记等,因为即使在固定宽度的Unicode编码中,每个字形可能有多个代码点.
混淆了一些字节序列和Unicode序列的特殊混合,仔细跟踪哪个是哪个.它就像(1),但通常更难,因此虽然它可能是正确的,但在实践中它可能很容易出错.
(仅限特殊用途):使用UTF-8进行所有操作.有时这很好,例如,如果你所做的只是基于ASCII标点符号解析输入,并连接输出的字符串.基本上它适用于那些你不需要用顶部位设置理解任何东西的程序,只是不加改变地传递它.如果您需要实际渲染文本,或者以其他方式执行人类认为"明显"但实际上很复杂的事情,它就不能很好地工作.像整理一样.