如何检查字符编码是否为ASCII?

7

我想编写以下函数:

int char_index(char c) 
{
  if (is_ascii<char>)
    return c - 'A';
  else 
    return c == 'A' ? 0 :
           c == 'B' ? 1 :
           // ...
}

有没有类似于 std 中的 is_ascii 函数?我想象中应该有一些类似 std::numeric_limits<T>::is_iec559 的东西,可以判断某个浮点类型 T 是否符合 IEE 754 标准的要求。
我认为我可以用类似于 if (65 == 'A' && ...) 的方法来实现 is_ascii,该方法列举了整个ASCII字符集,并将它们与 int 表示进行比较,但这很麻烦。此外,我不确定如何检查诸如 SOH(标题起始)等不可打印字符。
在用户代码中编写此函数是否可行,还是必须依赖于实现提供这样的功能?

如果你已经有了 return c == 'A' ? 0 : c == 'B' ? 1 : 等等的代码,那么你就不需要 is_ascii,因为它在任何情况下都可以工作。 - n. m.
@n.'pronouns'm. 当然,我不想写那个。我可以断言在这种情况下不处理非ASCII编码就可以了。(示例只是展示了一种处理方式)。 - cigien
在文档中写明不处理非ASCII编码,并结束它。 - n. m.
@n.'pronouns'm. 文档很好,但编译器并不关心它。我希望程序在这种情况下能够知道 - cigien
“我希望程序能够知道这种情况何时发生。” 为什么?你真的认为在黄石火山爆发之前,它会发生足够多的次数来值得你花时间处理吗? - n. m.
显示剩余3条评论
4个回答

2
我假设您想检查编译器在将源代码中的字符串文字和字符文字转换为机器码时是否使用ASCII编码。

std 中是否有像 is_ascii 这样的函数?

我不知道有没有这样的函数。

我可以自己实现类似于 if(65 == 'A' && ...) 的 is_ascii 函数,枚举整个 ASCII 字符集。

那就这么做吧。检查可以作为 c-char 的字符,所以是从基本源字符集中获取的所有字符:
a b c d e f g h i j k l m n o p q r s t u v w x y z
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
0 1 2 3 4 5 6 7 8 9
_ { } [ ] # ( ) < > % : ; . ? * + - / ^ & | ~ ! = , \ " '

和转义序列:

\a  \b  \f  \n  \r  \t  \v

无法检查“整个”ASCII字符集,因为编译器不会将程序转录到整个ASCII字符集中。它只将基本字符集字符和转义序列映射到其机器表示形式,而不是整个字符集(可能有编译器扩展)。

但这很烦人。

但这是唯一的方法。要验证您的实现使用了某些字符集,您必须检查它使用的所有字符。所以检查它们。它最终会成为consteval

如何检查像SOH(标题起始)等不可打印字符。

不用。SOH字符不能在字符字面量中,您不必检查它们,因为在C语言中无法表示它。没有\SOH转义序列,0x01字节不在基本字符集中。您的编译器永远不会将一系列字符转换为SOH字符。一个有效的程序只由基本源字符集中的字符组成。 SOH字符的解释取决于将要接收它的东西,如果我写'\001',它将是等于1的字节,与编码无关。


呃,让我们写吧!以下是程序:

#include <type_traits>
#include <algorithm>
constexpr bool compiler_uses_ascii() {
    return 
        '\a'==0x07  &&  '\b'==0x08  &&  '\t'==0x09  &&  '\n'==0x0a  &&  '\v'==0x0b  &&  '\f'==0x0c  &&
        '\r'==0x0d  &&  '!'==0x21   &&  '#'==0x23   &&  '%'==0x25   &&  '&'==0x26   &&  '\''==0x27  &&
        '('==0x28   &&  ')'==0x29   &&  '*'==0x2a   &&  '+'==0x2b   &&  ','==0x2c   &&  '-'==0x2d   &&
        '.'==0x2e   &&  '/'==0x2f   &&  '0'==0x30   &&  '1'==0x31   &&  '2'==0x32   &&  '3'==0x33   &&
        '4'==0x34   &&  '5'==0x35   &&  '6'==0x36   &&  '7'==0x37   &&  '8'==0x38   &&  '9'==0x39   &&
        ':'==0x3a   &&  ';'==0x3b   &&  '<'==0x3c   &&  '='==0x3d   &&  '>'==0x3e   &&  '?'==0x3f   &&
        'A'==0x41   &&  'B'==0x42   &&  'C'==0x43   &&  'D'==0x44   &&  'E'==0x45   &&  'F'==0x46   &&
        'G'==0x47   &&  'H'==0x48   &&  'I'==0x49   &&  'J'==0x4a   &&  'K'==0x4b   &&  'L'==0x4c   &&
        'M'==0x4d   &&  'N'==0x4e   &&  'O'==0x4f   &&  'P'==0x50   &&  'Q'==0x51   &&  'R'==0x52   &&
        'S'==0x53   &&  'T'==0x54   &&  'U'==0x55   &&  'V'==0x56   &&  'W'==0x57   &&  'X'==0x58   &&
        'Y'==0x59   &&  'Z'==0x5a   &&  '['==0x5b   &&  '\\'==0x5c  &&  ']'==0x5d   &&  '^'==0x5e   &&
        '_'==0x5f   &&  'a'==0x61   &&  'b'==0x62   &&  'c'==0x63   &&  'd'==0x64   &&  'e'==0x65   &&
        'f'==0x66   &&  'g'==0x67   &&  'h'==0x68   &&  'i'==0x69   &&  'j'==0x6a   &&  'k'==0x6b   &&
        'l'==0x6c   &&  'm'==0x6d   &&  'n'==0x6e   &&  'o'==0x6f   &&  'p'==0x70   &&  'q'==0x71   &&
        'r'==0x72   &&  's'==0x73   &&  't'==0x74   &&  'u'==0x75   &&  'v'==0x76   &&  'w'==0x77   &&
        'x'==0x78   &&  'y'==0x79   &&  'z'==0x7a   &&  '{'==0x7b   &&  '|'==0x7c   &&  '}'==0x7d   &&
        '~'==0x7e;
}
constexpr int char_index(char c)
{
    if constexpr (compiler_uses_ascii()) {
        return c - 'A';
    } else {
        // Is that right? Maybe it is.
        const char a[] = "ABCDEFGHIJKLMNOPRSTUVXYZ";
        return std::find(a, a + sizeof(a), c) - a;
#if 0
        return
            c == 'A' ? 0 :  c == 'B' ? 1 :  c == 'C' ? 2 :  c == 'D' ? 3 :
            c == 'E' ? 4 :  c == 'F' ? 5 :  c == 'G' ? 6 :  c == 'H' ? 7 :
            c == 'I' ? 8 :  c == 'J' ? 9 :  c == 'K' ? 10 : c == 'L' ? 11 :
            c == 'M' ? 12 : c == 'N' ? 13 : c == 'O' ? 14 : c == 'P' ? 15 :
            c == 'Q' ? 16 : c == 'R' ? 17 : c == 'S' ? 18 : c == 'T' ? 19 :
            c == 'U' ? 20 : c == 'V' ? 21 : c == 'W' ? 22 : c == 'X' ? 23 :
            c == 'Y' ? 24 : c == 'Z' ? 25 : -1;
#endif
    }
}
#include <iostream>
int main() {
    std::cout << compiler_uses_ascii() << " " << char_index('B') << "\n";
}

执行时输出:

$ g++ 1.cpp -std=c++20 && ./a.out
1 1
$ g++ 1.cpp -fexec-charset=IBM-1047 -std=c++20 && ./a.out
0@1%

所以如果我理解你的意思,用户代码中无法检查整个字符集,是吗? - cigien
无法检查整个字符集,因为并非所有字符都被使用。您只能检查已使用的字符。 "编码"通常是将一个字节映射到另一个字节。如果要检查是否使用了特定编码,则只能检查映射中的字符,无法检查其他字节,因为它们不在映射中。 - KamilCuk

1

通常可以从 std::locale("").name() 获取编码信息,尽管它几乎永远不会是 ASCII,而是一些超集,例如 UTF-8 或 CP1252(除非基本编码是像 EBCDIC 这样的非 ASCII 编码)。现在没有人使用纯 ASCII 了。

如果允许使用 boost,则 boost::locale::info::encoding() 可以提供更可靠的编码信息。但是您仍然需要检查编码是否涵盖 ASCII 集。

要查看编码是否是 ASCII 的超集,可以检查 此处的列表


1
现在没有人使用纯ASCII了。嗯...随着嵌入式和物联网设备的不断增加,它们往往使用“newlib”中的“-nano”版本。它们都使用纯ASCII。 “wcs *”函数只是复制/截断字节并且什么也不做的存根。是的,人们使用纯ASCII环境。 - KamilCuk

1

为了检查所使用的编码是否与ASCII兼容,您可以使用C++11(或C11)的{{link1:unicode escape sequences}}并检查范围内所有Unicode代码点0x000x7f是否解析为相同的整数值。

constexpr bool is_ascii_compatible()
{
    return '\u0000' == 0x00 && '\u0001' == 0x01 && '\u0002' == 0x02 && '\u0003' == 0x03 &&
           '\u0004' == 0x04 && '\u0005' == 0x05 && '\u0006' == 0x06 && '\u0007' == 0x07 &&
           '\u0008' == 0x08 && '\u0009' == 0x09 && '\u000a' == 0x0a && '\u000b' == 0x0b &&
           '\u000c' == 0x0c && '\u000d' == 0x0d && '\u000e' == 0x0e && '\u000f' == 0x0f &&
           '\u0010' == 0x10 && '\u0011' == 0x11 && '\u0012' == 0x12 && '\u0013' == 0x13 &&
           '\u0014' == 0x14 && '\u0015' == 0x15 && '\u0016' == 0x16 && '\u0017' == 0x17 &&
           '\u0018' == 0x18 && '\u0019' == 0x19 && '\u001a' == 0x1a && '\u001b' == 0x1b &&
           '\u001c' == 0x1c && '\u001d' == 0x1d && '\u001e' == 0x1e && '\u001f' == 0x1f &&
           '\u0020' == 0x20 && '\u0021' == 0x21 && '\u0022' == 0x22 && '\u0023' == 0x23 &&
           '\u0024' == 0x24 && '\u0025' == 0x25 && '\u0026' == 0x26 && '\u0027' == 0x27 &&
           '\u0028' == 0x28 && '\u0029' == 0x29 && '\u002a' == 0x2a && '\u002b' == 0x2b &&
           '\u002c' == 0x2c && '\u002d' == 0x2d && '\u002e' == 0x2e && '\u002f' == 0x2f &&
           '\u0030' == 0x30 && '\u0031' == 0x31 && '\u0032' == 0x32 && '\u0033' == 0x33 &&
           '\u0034' == 0x34 && '\u0035' == 0x35 && '\u0036' == 0x36 && '\u0037' == 0x37 &&
           '\u0038' == 0x38 && '\u0039' == 0x39 && '\u003a' == 0x3a && '\u003b' == 0x3b &&
           '\u003c' == 0x3c && '\u003d' == 0x3d && '\u003e' == 0x3e && '\u003f' == 0x3f &&
           '\u0040' == 0x40 && '\u0041' == 0x41 && '\u0042' == 0x42 && '\u0043' == 0x43 &&
           '\u0044' == 0x44 && '\u0045' == 0x45 && '\u0046' == 0x46 && '\u0047' == 0x47 &&
           '\u0048' == 0x48 && '\u0049' == 0x49 && '\u004a' == 0x4a && '\u004b' == 0x4b &&
           '\u004c' == 0x4c && '\u004d' == 0x4d && '\u004e' == 0x4e && '\u004f' == 0x4f &&
           '\u0050' == 0x50 && '\u0051' == 0x51 && '\u0052' == 0x52 && '\u0053' == 0x53 &&
           '\u0054' == 0x54 && '\u0055' == 0x55 && '\u0056' == 0x56 && '\u0057' == 0x57 &&
           '\u0058' == 0x58 && '\u0059' == 0x59 && '\u005a' == 0x5a && '\u005b' == 0x5b &&
           '\u005c' == 0x5c && '\u005d' == 0x5d && '\u005e' == 0x5e && '\u005f' == 0x5f &&
           '\u0060' == 0x60 && '\u0061' == 0x61 && '\u0062' == 0x62 && '\u0063' == 0x63 &&
           '\u0064' == 0x64 && '\u0065' == 0x65 && '\u0066' == 0x66 && '\u0067' == 0x67 &&
           '\u0068' == 0x68 && '\u0069' == 0x69 && '\u006a' == 0x6a && '\u006b' == 0x6b &&
           '\u006c' == 0x6c && '\u006d' == 0x6d && '\u006e' == 0x6e && '\u006f' == 0x6f &&
           '\u0070' == 0x70 && '\u0071' == 0x71 && '\u0072' == 0x72 && '\u0073' == 0x73 &&
           '\u0074' == 0x74 && '\u0075' == 0x75 && '\u0076' == 0x76 && '\u0077' == 0x77 &&
           '\u0078' == 0x78 && '\u0079' == 0x79 && '\u007a' == 0x7a && '\u007b' == 0x7b &&
           '\u007c' == 0x7c && '\u007d' == 0x7d && '\u007e' == 0x7e && '\u007f' == 0x7f;
}

这里有演示

编辑:

我稍微补充一下解决方案,因为它本身有点模糊。正如KamilCuk的答案所说,在C和C++中,字符字面量中出现的字符数量是有限的,因此像起始标题(ASCII中的0x01)无法用字符字面量表示(除非使用unicode转义序列\u\U),因为没有适用于它的转义序列,就像某些特殊字符(例如新行\n)一样。使用\x01\001也不起作用,因为它们只表示一个字节的数据而不是相应的ASCII字符。

通过使用Unicode转义序列,我们可以在所有支持的编码中表示任何ASCII字符,因为Unicode代码点在范围0x00到0x7f内与ASCII相同。这意味着表达式'\u0041' == 'A'必须在任何执行字符集下评估为true。通过这种方式,我们可以测试所有ASCII字符是否解析为相同的整数值,上面的代码就是这样做的。

-3

没有必要特别检查ASCII。你真正感兴趣的是在字母表中获取一个索引,你可以让编程语言为你处理:

int char_index(char c)
{
    if (c >= 'A' && c <= 'Z')
        return c - 'A';
    return -1;
}

3
EBCDIC 没有 A-Z 连续。 - Boann
1
@Boann 或许是这样,但谁会用 EBCDIC 写源代码呢?如果你使用特定字符集编写源代码,并配置编译器以处理该字符集的源代码,则可以编写使用该字符集中的字符字面量的代码,并假定该字符集的某些特征,例如字符顺序。 - Remy Lebeau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接