在C/C++中将UTF-8字符串存储在内存中的最佳方式是什么?

10
查看Unicode标准,他们建议使用普通的char来存储UTF-8编码的字符串。在C++和基本的std::string中,这是否按预期工作,还是存在可能会造成问题的UTF-8编码案例呢?
例如,在计算长度时,它可能与字节数不相同 - 应该如何处理?阅读标准,我可能可以使用char数组进行存储,但我仍然需要自己编写像strlen等操作编码文本的函数,因为据我所了解的问题,标准例程要么只针对ASCII,要么使用Unicode宽字符文字(16位或更多),这不被Unicode标准推荐。到目前为止,我发现关于编码方面最好的来源是Joel's on Software上的一篇文章,但它并没有解释我们这些可怜的C++开发者应该使用什么。
6个回答

5
有一个名为"UTF8-CPP"的库,它可以让你将UTF-8字符串存储在标准的std::string对象中,并提供了额外的函数来枚举和操作utf-8字符。
我还没有测试过它,所以不知道它的价值如何,但我正在考虑自己使用它。

这可能是正确的方法。还有ICU库,它或多或少地做着同样的事情。 - sastanin

3

strlen函数计算第一个\0之前的非空字符数。在UTF-8中,这个计数是一个合理的数字(使用的字节数),但这个计数不是字符数(一个UTF-8字符通常是1-4个字符)。basic_string 不存储 \0,但它也保留了一个字节计数。

strcpy或basic_string的复制构造函数会复制所有字节,而不会仔细查看。

查找子字符串可以很好地工作,因为UTF_8的编码方式。字符的第一个字节允许的值与第二到第四个字节不同(前者永远不以10xxxxxx开头,后者总是这样)。

获取子字符串很棘手——如何指定位置?如果通过搜索ASCII文本标记(例如[和])找到开始和结束,则没有问题。你只需要获取中间的字节,这也是一个有效的UTF8字符串。但你无法硬编码位置,甚至相对偏移量也不行。即使+1个字符的相对偏移可能很困难;多少个字节?你最终会编写一个类似SkipOneChar的函数。


3

一个使用 ICU 库(C、C++、Java)的示例:

#include <iostream>
#include <unicode/unistr.h> // using ICU library

int main(int argc, char *argv[]) {
    // constructing a Unicode string
    UnicodeString ustr1("Привет"); // using platform's default codepage
    // calculating the length in characters, should be 6
    int ulen1=ustr1.length();
    // extracting encoded characters from a string
    int const bufsize=25;
    char encoded[bufsize];
    ustr1.extract(0,ulen1,encoded,bufsize,"UTF-8"); // forced UTF-8 encoding
    // printing the result
    std::cout << "Length of " << encoded << " is " << ulen1 << "\n";
    return 0;
}

建筑类

$ g++ -licuuc -o icu-example{,.cc}

运行中

$ ./icu-example
Length of Привет is 6

在我的Linux系统上,使用GCC 4.3.2和libicu 3.8.1可以正常工作。请注意,无论系统语言环境如何,它都会以UTF-8格式输出。如果你的语言环境不是UTF-8,你将无法正确地看到它。


2
如果您只是想读取和输出UTF8字符串,那么这取决于您想要做什么。只要设置了正确的语言环境,一切都可以正常运行。我们已经这样做了一段时间。我们有几个服务器进程并不涉及字符串处理。用户在Java中设置这些字符串,并作为UTF8到达,我们使用标准c str缓冲区处理它们。然后我们将数据发送回Java,Java再将其转换回来。
如果您想要UTF8字符的长度,则需要使用可以为您处理翻译的函数。
但您也可以自己编写,例如使用utf8-strlen

1
我们所采用的方案是:使用 std::string 存储 UTF8 编码。现在大多数操作都可以执行,但无法计算字符串长度。当您需要执行此类操作时,请使用 UTF8->std::wstring 转换函数(例如 boost::from_utf8)将其转换为 std::wstring。

0

来自UTF-8和Unicode FAQ:C对Unicode的支持

#include <stdio.h>
#include <locale.h>

int main()
{
  if (!setlocale(LC_CTYPE, "")) {
    fprintf(stderr, "Can't set the specified locale! "
            "Check LANG, LC_CTYPE, LC_ALL.\n");
    return 1;
  }
  printf("%ls\n", L"Schöne Grüße");
  return 0;
}

同样来自这里

好消息是,如果您使用wchar_t*字符串及其相关函数系列,例如wprintfwcslenwcslcat,则正在处理Unicode值。在C++世界中,您可以使用std::wstring提供友好的接口。我唯一的抱怨是,这些是32位(4字节)字符,因此对于所有语言而言,它们都是内存占用大户。选择这种方式的原因是它保证每个可能的字符都可以用一个值表示。

附注:这可能是特定于Linux的。有一个ICU库来处理复杂的事情。


当我在使用GCC 4.01的OS X上尝试时,它无法正常工作:它会将非ASCII字符打印为八进制代码中的转义字符。当我改用printf(“%s \ n”,“Schöne Grüße”)时,它可以正确地打印。因此,这不是获取字符串中utf-8字符数的解决方案。 - Thomas Tempelmann
我无法确定 OS X 的情况,但这个例子在 GNU/Linux 上的 GCC 4.3.2 确实是有效的,在 UTF-8 区域设置下。你在 OS X 中使用的是什么区域设置?我怀疑它不是 Unicode 区域设置。此外,可能在 OS X 中处理区域设置的方式有所不同,我不太清楚。 - sastanin
1
很抱歉,这里有很多错误。字符超出了保证的字符集范围;假设控制台可以打印wchar_t。在大多数PC上,wchar_t是2个字节。 - MSalters
  1. L"str" 的类型是 wchar_t 数组的一个示例。
  2. 我的控制台肯定可以打印 wchar_t 和大多数 Unicode;locale 负责转换。
  3. 对于 GNU 系统,wchar_t 总是 32 位宽,除非使用特殊的编译器标志。
  4. 我没有说它是跨平台的。
- sastanin

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