我知道这个问题可能非常基本。如果这是显而易见的事,请原谅我。 请考虑以下程序:
#include <stdio.h>
int main(void) {
// this is a string in English
char * str_1 = "This is a string.";
// this is a string in Russian
char * str_2 = "Это строковая константа.";
// iterator
int i;
// print English string as a string
printf("%s\n", str_1);
// print English string byte by byte
for(i = 0; str_1[i] != '\0'; i++) {
printf(" %c ",(char) str_1[i]);
}
printf("\n");
// print numerical values of English string byte by byte
for(i = 0; str_1[i] != '\0'; i++) {
printf("%03d ",(int) str_1[i]);
}
printf("\n");
// print Russian string as a string
printf("%s\n", str_2);
// print Russian string byte by byte
for(i = 0; str_2[i] != '\0'; i++) {
printf(" %c ",(char) str_2[i]);
}
printf("\n");
// print numerical values of Russian string byte by byte
for(i = 0; str_2[i] != '\0'; i++) {
printf("%03d ",(int) str_2[i]);
}
printf("\n");
return(0);
}
输出:
This is a string.
T h i s i s a s t r i n g .
084 104 105 115 032 105 115 032 097 032 115 116 114 105 110 103 046
Это строковая константа.
▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ .
-48 -83 -47 -126 -48 -66 032 -47 -127 -47 -126 -47 -128 -48 -66 -48 -70 -48 -66 -48 -78 -48 -80 -47 -113 032 -48 -70 -48 -66 -48 -67 -47 -127 -47 -126 -48 -80 -48 -67 -47 -126 -48 -80 046
可以看出,英文(ASCII)字符串可以打印为字符串,也可以使用数组索引进行访问,逐字符打印(逐字节),但俄语字符串(我相信编码为UTF-8)可以作为字符串打印但不能逐个字符地访问。
据我所知,原因是在这种情况下俄语字符使用两个字节而不是一个字节进行编码。
我想知道的是,是否有任何简单的方法可以通过正确声明数据类型或通过标记字符串以某种方式使用标准C库函数逐字符打印Unicode字符串(在本例中为两个字节乘两个字节)或者通过设置区域设置或以其他方式。
我尝试用“u8”在俄语字符串前面,即char * str_2 = u8"..."
,但这不会改变行为。我想远离使用宽字符来假设使用什么语言,例如每个字符恰好两个字节。任何建议将不胜感激。
答案 0 :(得分:3)
我认为来自mblen()
的{{1}},mbtowc()
,wctomb()
,mbstowcs()
和wcstombs()
函数具有部分相关性。例如,您可以通过<stdlib.h>
找出字符串中每个字符组成的字节数。
另一个很少使用的标题和函数是mblen()
和<locale.h>
。
以下是对您的代码的修改:
setlocale()
#include <assert.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static inline void ntbs_hex_dump(const char *pc_ntbs)
{
unsigned char *ntbs = (unsigned char *)pc_ntbs;
for (int i = 0; ntbs[i] != '\0'; i++)
printf(" %.2X ", ntbs[i]);
putchar('\n');
}
static inline void ntbs_chr_dump(const char *pc_ntbs)
{
unsigned char *ntbs = (unsigned char *)pc_ntbs;
for (int i = 0; ntbs[i] != '\0'; i++)
printf(" %c ", ntbs[i]);
putchar('\n');
}
int main(void)
{
char *loc = setlocale(LC_ALL, "");
printf("Locale: %s\n", loc);
char *str_1 = "This is a string.";
char *str_2 = "Это строковая константа.";
printf("English:\n");
printf("%s\n", str_1);
ntbs_chr_dump(str_1);
ntbs_hex_dump(str_1);
printf("Russian:\n");
printf("%s\n", str_2);
ntbs_chr_dump(str_2);
ntbs_hex_dump(str_2);
char *mbp = str_2;
while (*mbp != '\0')
{
enum { MBS_LEN = 10 };
int mbl = mblen(mbp, strlen(mbp));
char mbs[MBS_LEN];
assert(mbl < MBS_LEN - 1 && mbl > 0);
// printf("mbl = %d\n", mbl);
memmove(mbs, mbp, mbl);
mbs[mbl] = '\0';
printf(" %s ", mbs);
mbp += mbl;
}
putchar('\n');
return(0);
}
很重要,至少在macOS Sierra 10.12.2(GCC 6.3.0)上,这是我开发和测试它的地方。没有它,setlocale()
总是返回mblen()
,代码中没有任何好处。
我得到的输出是:
1
通过更多的努力,代码可以更紧密地打印UTF-8数据的字节对。 D0和D1前导字节对于BMP(基本多语言平面)中西里尔字母代码块U + 0400 ... U + 04FF的UTF-8编码是正确的。
仅为了您的娱乐价值:BSD Locale: en_US.UTF-8
English:
This is a string.
T h i s i s a s t r i n g .
54 68 69 73 20 69 73 20 61 20 73 74 72 69 6E 67 2E
Russian:
Это строковая константа.
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .
D0 AD D1 82 D0 BE 20 D1 81 D1 82 D1 80 D0 BE D0 BA D0 BE D0 B2 D0 B0 D1 8F 20 D0 BA D0 BE D0 BD D1 81 D1 82 D0 B0 D0 BD D1 82 D0 B0 2E
Э т о с т р о к о в а я к о н с т а н т а .
拒绝处理输出,因为这些问号代表无效代码:sed
。
答案 1 :(得分:2)
这是使用sscanf
功能的简单解决方案。 C99要求printf
和scanf
(和朋友)理解l
和%s
字符代码的%c
大小限定符,导致它们在多字节之间进行转换(即UTF-8)表示和宽字符串/字符(即wchar_t
,这是一个足以包含代码点的整数类型)。这意味着您可以使用它一次将一个字符串分开一个(多字节)字符,而不必担心序列是否只是七位字符(英语)。 (如果这看起来很复杂,请查看代码。基本上,它只是在格式字符串中添加l
限定符。)
这确实使用wchar_t
,在某些平台上可能限制为16位(Windows,咳嗽,咳嗽)。我怀疑如果你在Windows上使用星界字符,你最终会有代理字符,这很可能会让你感到悲伤,但代码在Linux和Mac上运行良好,至少在不太古老的版本中
请注意在程序开头调用setlocale
。任何宽字符函数的工作都是必要的;它将执行区域设置设置为默认系统区域设置,通常是多字节字符为UTF-8的区域设置。 (但是,下面的代码并不是非常关心。它只需要函数的输入是当前语言环境指定的多字节表示。)
它可能不是最快解决这个问题的方法,但它的优势在于编写起来相当简单,至少在我看来是这样。
以下内容基于原始代码,但为简单起见,我将输出重构为单个函数。我还将数字输出更改为十六进制(因为它更容易使用代码图表进行验证)。
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
/* Print the string three ways */
void print3(const char* s);
void print3(const char* s) {
wchar_t wch;
int n;
// print as a string
printf("%s\n", s);
// print char by char
for (int i = 0; s[i] != '\0'; i += n) {
sscanf(s+i, "%lc%n", &wch, &n);
printf(" %lc ", wch);
}
putchar('\n');
// print numerical values char by char
for (int i = 0; s[i] != '\0'; i += n) {
sscanf(s+i, "%lc%n", &wch, &n);
printf(" %05lx ", (unsigned long)wch);
}
putchar('\n');
}
int main(void) {
setlocale(LC_ALL, "");
char *str_1 = "This is a string.";
char *str_2 = "Это строковая константа.";
char *str_3 = u8"\U0001d7d8\U0001d7d9\U0001f638 in the astral plane";
print3(str_1);
print3(str_2);
print3(str_3);
return 0;
}
以上尝试模仿OP中的代码。我实际上更喜欢使用指针而不是索引来编写循环,并检查sscanf
的返回码作为终止条件:
/* Print the string three ways */
void print3(const char* s) {
wchar_t wch;
int n;
// print as a string
printf("%s\n", s);
// print char by char
for (const char* p = s;
sscanf(p, "%lc%n", &wch, &n) > 0;
p += n) {
printf(" %lc ", wch);
}
putchar('\n');
for (const char* p = s;
sscanf(p, "%lc%n", &wch, &n) > 0;
p += n) {
printf(" %5.4lx ", (unsigned long)wch);
}
putchar('\n');
}
更好的做法是确保sscanf
没有返回错误,表明存在无效的多字节序列。
这是我系统上的输出:
This is a string.
T h i s i s a s t r i n g .
0054 0068 0069 0073 0020 0069 0073 0020 0061 0020 0073 0074 0072 0069 006e 0067 002e
Это строковая константа.
Э т о с т р о к о в а я к о н с т а н т а .
042d 0442 043e 0020 0441 0442 0440 043e 043a 043e 0432 0430 044f 0020 043a 043e 043d 0441 0442 0430 043d 0442 0430 002e
in the astral plane
i n t h e a s t r a l p l a n e
1d7d8 1d7d9 1f638 0020 0069 006e 0020 0074 0068 0065 0020 0061 0073 0074 0072 0061 006c 0020 0070 006c 0061 006e 0065
答案 2 :(得分:1)
您被正确建议编写自己的UTF-8解析器,实际上是quite easy to do。这是一个示例实现:
int utf8decode(unsigned char *utf8, unsigned *code) {
while(*utf8) { /* Scan the whole string */
if ((utf8[0] & 128) == 0) { /* Handle single-byte characters */
*code = utf8[0];
utf8++;
} else { /* Looks like it's a 2-byte character; is it? */
if ((utf8[0] >> 5) != 6 || (utf8[1] >> 6) != 2)
return 1;
/* Yes, it is; do bit magic */
*code = ((utf8[0] & 31) << 6) + (utf8[1] & 63);
utf8 += 2;
}
code++;
}
*code = 0;
return 0; /* We got it! */
}
让我们做一些测试:
int main(void) {
int i = 0;
unsigned char *str = "Это строковая константа.";
unsigned codes[1024]; /* Hope it's long enough */
if (utf8decode(str, codes) == 1) /* Decode */
return 1;
while(codes[i]) /* Print the result */
printf("%u ", codes[i++]);
puts(""); /* Final newline */
return 0;
}
1069 1090 1086 32 1089 1090 1088 1086 1082 1086 1074 1072 1103 32 1082 1086 1085 1089 1090 1072 1085 1090 1072 46