我现在正在Linux上学习C语言,而且我遇到了一些奇怪的情况。
据我所知,标准C的char
数据类型是ASCII,1字节(8位)。它应该意味着它只能包含ASCII字符。
在我的程序中,我使用char input[]
,它由getchar
函数填充,就像这个伪代码一样:
char input[20];
int z, i;
for(i = 0; i < 20; i++)
{
z = getchar();
input[i] = z;
}
奇怪的是它不仅适用于ASCII字符,而且适用于我想象的任何字符,例如输入上的@&@{čřžŧ¶'`[łĐŧđж←^€~[←^ø{&}čž
。
我的问题是 - 怎么可能?它似乎是C中许多美丽的例外之一,但我真的很感激解释。这是操作系统,编译器,隐藏语言的附加超级功能吗?
感谢。
答案 0 :(得分:21)
这里没有魔法 - C语言让你可以访问原始字节,因为它们存储在comptuer内存中。 如果您的终端使用utf-8(很可能),非ASCII字符在内存中占用多个字节。再次显示时,我们的终端代码将这些序列转换为单个显示的字符。
只需更改代码即可打印字符串的strlen
,您就会明白我的意思。
要在C中正确处理utf-8非ASCII字符,你必须使用一些库来处理它们,比如glib,qt或许多其他字符。
答案 1 :(得分:17)
ASCII是一个7位字符集。在C中通常由8位字符表示。如果设置了8位字节中的最高位,则 不 为ASCII字符。
另请注意,您无法保证ASCII 为基础,许多人会忽略其他情况。如果你想检查一个“primitive”字节是否是一个字母字符,你可以换句话说,当你注意所有系统时,请说:
is_alpha = (c > 0x40 && c < 0x5b) || (c > 0x60 && c < 0x7b);
相反,您必须使用ctype.h
并说:
isalpha(c);
只有例外,AFAIK,对于数字,至少在大多数表上,它们具有连续的值。
因此这有效;
char ninec = '9';
char eightc = '8';
int nine = ninec - '0';
int eight = eightc - '0';
printf("%d\n", nine);
printf("%d\n", eight);
但这不能保证是'a':
alhpa_a = 0x61;
不基于ASCII的系统,即使用EBCDIC;在这样的平台上的C仍然运行良好,但在这里它们(大多数)使用8位而不是7位,即A
可以编码为十进制193
而不是65
,因为它是ASCII。
但是对于ASCII;具有十进制128 - 255(使用中的8位)的字节被扩展,而不是ASCII集的一部分。即ISO-8859使用此范围。
经常做什么;也是将两个或多个字节组合成一个字符。因此,如果您打印两个字节后,定义为utf8 0xc3 0x98
==Ø,那么您将获得此字符。
这又取决于你所处的环境。在许多系统/环境中,打印ASCII值在字符集,系统等之间给出相同的结果。但是打印字节&gt; 127或双字节字符根据本地配置给出不同的结果。
即:
先生。正在运行的 程序
JASN€
B先生得到
Jasπß
这可能与ISO-8859系列和扩展字符的单字节表示的Windows-1252特别相关。
10
开头。 那是; UTF-8中的第一个字节,如果不是ASCII,则表示该字符有多少字节。你也可以说ASCII字符表示不再有字节 - 因为最高位是0。
即如果文件被解释为UTF-8:
fgetc(c);
if c < 128, 0x80, then ASCII
if c == 194, 0xC2, then one more byte follow, interpret to symbol
if c == 226, 0xE2, then two more byte follows, interpret to symbol
...
举个例子。如果我们看一下你提到的一个角色。如果在UTF-8终端:
$ echo -n“č”| XXD
应该屈服:
0000000:c48d ..
换句话说,“č”由 两个 字节0xc4和0x8d表示。将-b添加到xxd命令,我们得到字节的二进制表示。我们按如下方式剖析它们:
___ byte 1 ___ ___ byte 2 ___
| | | |
0xc4 : 1100 0100 0x8d : 1000 1101
| |
| +-- all "follow" bytes starts with 10, rest: 00 1101
|
+ 11 -> 2 bits set = two byte symbol, the "bits set" sequence
end with 0. (here 3 bits are used 110) : rest 0 0100
Rest bits combined: xxx0 0100 xx00 1101 => 00100001101
\____/ \_____/
| |
| +--- From last byte
+------------ From first byte
这给我们:00100001101 2 = 269 10 = 0x10D =&gt;解码代码点U + 010D ==“č”。
此号码也可以在HTML中用作č
==č
这个以及许多其他代码系统的共同点是8位字节是基础。
通常这也是关于背景的问题。以GSM短信为例,使用ETSI GSM 03.38 / 03.40(3GPP TS 23.038,3GPP 23038)。在那里我们还找到一个7位字符表,7位GSM默认字母表,但不是将它们存储为8位,而是存储为7位 1 。这样,您可以将更多字符打包到给定的字节数中。即标准SMS 160字符变为1280位或160字节为ASCII,1120或140字节为SMS。
1不例外,(故事情节更多)。
即。一个简单的字节示例,以SMS UDP格式保存为septets(7bit)C8329BFD06到ASCII:
_________
7 bit UDP represented | +--- Alphas has same bits as ASCII
as 8 bit hex '0.......'
C8329BFDBEBEE56C32 1100100 d * Prev last 6 bits + pp 1
| | | | | | | | +- 00 110010 -> 1101100 l * Prev last 7 bits
| | | | | | | +--- 0 1101100 -> 1110010 r * Prev 7 + 0 bits
| | | | | | +----- 1110010 1 -> 1101111 o * Last 1 + prev 6
| | | | | +------- 101111 10 -> 1010111 W * Last 2 + prev 5
| | | | +--------- 10111 110 -> 1101111 o * Last 3 + prev 4
| | | +----------- 1111 1101 -> 1101100 l * Last 4 + prev 3
| | +------------- 100 11011 -> 1101100 l * Last 5 + prev 2
| +--------------- 00 110010 -> 1100101 e * Last 6 + prev 1
+----------------- 1 1001000 -> 1001000 H * Last 7 bits
'------'
|
+----- GSM Table as binary
9个字节“unpacked”变为10个字符。
答案 2 :(得分:4)
ASCII是7位,而不是8位。 a char []
保存字节,可以是任何编码 - iso8859-1,utf-8,无论你想要什么。 C不关心。
答案 3 :(得分:2)
非ASCII字符有一种数据类型wint_t
(#include <wchar.h>
)。您可以使用方法getwchar()
来阅读它们。
答案 4 :(得分:2)
这是UTF-8的魔力,你甚至不用担心它是如何工作的。唯一的问题是C数据类型被命名为char
(对于字符),而它实际上意味着 byte 。字符与编码它们的字节之间没有1:1的对应关系。
您的代码中发生的情况是,从程序的角度来看,您输入一个字节的序列,它将字节存储在内存中,如果您打印文本,则会打印字节。这段代码并不关心这些字节如何对字符进行编码,只有终端需要担心在输入上对它们进行编码并在输出上正确解释它们。
答案 5 :(得分:1)
当然有许多库可以完成这项工作,但是为了快速解码任何UTF8 unicode,这个小功能非常方便:
typedef unsigned char utf8_t;
#define isunicode(c) (((c)&0xc0)==0xc0)
int utf8_decode(const char *str,int *i) {
const utf8_t *s = (const utf8_t *)str; // Use unsigned chars
int u = *s,l = 1;
if(isunicode(u)) {
int a = (u&0x20)? ((u&0x10)? ((u&0x08)? ((u&0x04)? 6 : 5) : 4) : 3) : 2;
if(a<6 || !(u&0x02)) {
int b,p = 0;
u = ((u<<(a+1))&0xff)>>(a+1);
for(b=1; b<a; ++b)
u = (u<<6)|(s[l++]&0x3f);
}
}
if(i) *i += l;
return u;
}
考虑你的代码;你可以迭代字符串并读取unicode值:
int l;
for(i=0; i<20 && input[i]!='\0'; ) {
if(!isunicode(input[i])) i++;
else {
l = 0;
z = utf8_decode(&input[i],&l);
printf("Unicode value at %d is U+%04X and it\'s %d bytes.\n",i,z,l);
i += l;
}
}