将wchar_t转换为char

48

我想知道这样做是否安全?

wchar_t wide = /* something */;
assert(wide >= 0 && wide < 256 &&);
char myChar = static_cast<char>(wide);

如果我相当确信宽字符将落在ASCII范围内。


1
请注意,ASCII范围严格为0..127,而不是测试所示的0..255。 - Jonathan Leffler
9个回答

37

为什么不直接使用库例程wcstombs呢。


3
那是针对字符串的。我只想转换一个单一字符。 - Cheok Yan Cheng
@Igor Zevaka,我刚刚测试了一下,发现它是错误的。你纠正了这个错误吗?谢谢。 - Frank

17
您正在寻找wctomb()函数:它在ANSI标准中,所以您可以信任它。即使wchar_t使用的代码超过255,它也能正常工作。但是您几乎肯定不想使用它。
wchar_t确实是一个整数类型,因此如果您真的这样做,编译器不会报错:
char x = (char)wc;

但是因为这是一个整型,所以绝对没有理由这样做。如果你不小心阅读了Herbert Schildt的C: The Complete Reference或基于此的任何 C 书籍,则你已经完全错误地受到了误导。字符应该是int或更好的类型。这意味着你应该这样写:

int x = getchar();

而不是这个:

char x = getchar(); /* <- WRONG! */

在整型方面,char 是毫无价值的。你不应该编写以 char 类型作为参数的函数,也不应该创建临时变量来存储 char 类型,同样的建议也适用于 wchar_t

char* 可能是字符串的一个方便的 typedef,但将其视为“字符数组”或“指向字符数组的指针”是一个初学者的错误想法——尽管 cdecl 工具说了什么。像这样将其视为实际的字符数组:

for(int i = 0; s[i]; ++i) {
  wchar_t wc = s[i];
  char c = doit(wc);
  out[i] = c;
}

这个方法是完全错误的。它不会做你想要的事情;它会以微妙且严重的方式中断,在不同平台上表现不同,而且你肯定会让用户感到非常困惑。如果你看到这条信息,那么你正在尝试重新实现已经包含在 ANSI C 中的 wctombs(),但这仍然是错误的。

你真正需要的是 iconv(),它可以将一个字符编码从一种编码方式(即使它被打包成 wchar_t 数组)转换为另一种编码方式的字符编码。

现在去读一下这篇文章,来了解 iconv 的问题所在。


2
我认为这个回答的开头陈述并不符合所提出的问题。在我看来,他是在询问如何将一个16位值截断为8位值;他从未提到过保留语义。 - Mike C
2
此外,他正在处理的“char”值可能是“char”,因为它来自于例如cin.getline()char[]进行操作。 - Mike C
1
正确但徒劳无功,"char是毫无价值的"这个说法是一个非常有争议的陈述。 - danius

16

assert是在调试模式下确保某些内容为真,而在发布版本中不会有任何影响的。最好使用if语句并为超出范围的字符制定一个替代计划,除非唯一出现超出范围的字符的方式是通过程序错误。

此外,根据您的字符编码,您可能会发现Unicode字符0x80到0xff及其char版本之间存在差异。


10

一种简单的方法是:

        wstring your_wchar_in_ws(<your wchar>);
        string your_wchar_in_str(your_wchar_in_ws.begin(), your_wchar_in_ws.end());
        char* your_wchar_in_char =  your_wchar_in_str.c_str();

我已经使用这种方法多年了 :)


1
但是当存在非ASCII字符时,它会失败还是生成垃圾? - IOviSpot

7

我之前写了一个简短的函数,用于将wchar_t数组打包成char数组。不在ANSI代码页(0-127)中的字符将被替换为“?”字符,并且它可以正确处理代理对。

size_t to_narrow(const wchar_t * src, char * dest, size_t dest_len){
  size_t i;
  wchar_t code;

  i = 0;

  while (src[i] != '\0' && i < (dest_len - 1)){
    code = src[i];
    if (code < 128)
      dest[i] = char(code);
    else{
      dest[i] = '?';
      if (code >= 0xD800 && code <= 0xD8FF)
        // lead surrogate, skip the next code unit, which is the trail
        i++;
    }
    i++;
  }

  dest[i] = '\0';

  return i - 1;

}

w 应该是什么?难道不应该是 src 吗? - Olorin
代码并不完全是我最初的版本,而且在重构时我错过了那个实例。 - cvanbrederode

3
这是另一种做法,记得在结果上使用free()。
char* wchar_to_char(const wchar_t* pwchar)
{
    // get the number of characters in the string.
    int currentCharIndex = 0;
    char currentChar = pwchar[currentCharIndex];

    while (currentChar != '\0')
    {
        currentCharIndex++;
        currentChar = pwchar[currentCharIndex];
    }

    const int charCount = currentCharIndex + 1;

    // allocate a new block of memory size char (1 byte) instead of wide char (2 bytes)
    char* filePathC = (char*)malloc(sizeof(char) * charCount);

    for (int i = 0; i < charCount; i++)
    {
        // convert to char (1 byte)
        char character = pwchar[i];

        *filePathC = character;

        filePathC += sizeof(char);

    }
    filePathC += '\0';

    filePathC -= (sizeof(char) * charCount);

    return filePathC;
}

3
从技术上讲,'char'的范围可以与'signed char'或'unsigned char'中的任何一个相同。对于无符号字符,您的范围是正确的;理论上,对于有符号字符,您的条件是错误的。实际上,很少有编译器会反对 - 结果将是相同的。
小问题:在assert中的最后一个&&是语法错误。
断言是否适当取决于代码到达客户端时是否可以承受崩溃以及如果断言条件被违反但未编译到代码中,则您可以或应该执行什么操作。对于调试工作,它似乎很好,但您可能还需要在运行时进行活动测试来进行检查。

'char'和'signed char'是同义词。 - cvanbrederode
@cvanbrederode:那不是标准所说的。 §6.2.5 类型 ¶15 表示: charsigned charunsigned char 三种类型合称为字符类型。实现必须将 char 定义为与 signed charunsigned char 具有相同的范围、表示和行为。 脚注 45 表示: <limits.h> 中定义的 CHAR_MIN 将具有值 0 或 SCHAR_MIN 中的一个,这可以用来区分两个选项。无论选择哪个选项,char 都是另一种类型,与其他两种类型不兼容。 - Jonathan Leffler
1
我引用了C11标准 - ISO/IEC 9899:2011。我看到这实际上是一个C++问题。在旧的C++11标准 - ISO/IEC 14882:2011中,§3.9.1 基本类型 ¶1说:是否可以将char对象保存为负值是由实现定义的。字符可以明确声明为无符号或有符号。Plain char、signed char和unsigned char是三种不同的类型。char、signed char和unsigned char占用相同的存储空间,并具有相同的对齐要求(3.11);也就是说,它们具有相同的对象表示。 [...继续...] - Jonathan Leffler
在任何特定的实现中,普通的 char 对象可以取与 signed charunsigned char 相同的值;具体使用哪一个是由实现定义的。我认为在 C++14 标准中没有任何实质性变化。最终结果与 C 标准引用相同——普通的 charunsigned charsigned char 相同,并且由实现决定使用哪个。 - Jonathan Leffler

1

也可以将 wchar_t 转换为 wstring --> string --> char

wchar_t wide;
wstring wstrValue;
wstrValue[0] = wide

string strValue;
strValue.assign(wstrValue.begin(), wstrValue.end());  // convert wstring to string

char char_value = strValue[0];

这段代码有时可能会失败,因为您实际上是用另一个零字符替换了 wstrValue 中的一个零字符(请参考第 wstrValue[0] = wide 行)。如果实现缓存它,则字符串的长度将不正确,并且在稍后访问它时可能会导致访问冲突。 - AntonK

0
一般来说,不是这样的。int(wchar_t(255)) == int(char(255))当然是成立的,但这只意味着它们具有相同的int值。它们可能不代表相同的字符。
在大多数Windows PC上,你会看到这样的差异。例如,在Windows代码页1250上,char(0xFF)是与wchar_t(0x02D9)(点上方)相同的字符,而不是wchar_t(0x00FF)(带分音符的小y)。
请注意,即使对于ASCII范围也不成立,因为C++甚至不要求ASCII。特别是在IBM系统上,你可能会看到'A' != 65

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