在我刚开始学C/C++的时候,字符类型使用的都是char。接触Win32编程之后,养成了使用wchar_t的习惯,于是再写控制台程序的时候自然就使用wchar_t了。然而在控制台程序中使用宽字符会导致各种奇怪的问题,这些问题主要是在输出上。下面分享一下我在这方面的心得。
首先来看一下这段代码:
#include
int main() {
    wprintf(L"%s", L"博客园");
    return 0;
}
wprintf用于输出宽字符类型的字符串,看上去似乎没有错误。但这段代码的输出却是三个问号。这是使用wprintf时最典型的问题。解决方法是加入对_wsetlocale的调用:
#include
#include
int main() {
    _wsetlocale(LC_ALL, L"chs");
    wprintf(L"%s", L"博客园");
    return 0;
}
_wsetlocale是setlocale的宽字符版本,这两个函数的区别只在于返回值以及第二个参数使用的是否宽字符字符串,执行效果都是一样的。
要解释这段代码,首先要从控制台本身说起。凡是涉及到字符处理的地方都要用到字符集,而控制台是一个字符环境,因此控制台也需要使用字符集,它所使用的字符集叫做代码页,每一个代码页大致上对应一种自然语言,它定义了这种语言的字符如何与二进制代码相关联。例如,表示英语的代码页是437,表示简体中文的代码页是936。一个控制台窗口只能有一个活动代码页,所以不同语言的字符不能同时出现在一个控制台窗口中,除非这个字符是两者共有的,且有相同的二进制代码。可以通过chcp命令来改变当前控制台窗口所使用的代码页。
代码页实际上是一种多字节字符集,所以控制台本质上不支持Unicode。因此,如果直接向控制台输出宽字符,将不会得到正确的显示。必须先将宽字符转换成多字节字符,再进行输出。而wprintf函数在内部也的确是进行了这种转换,可以尝试一下在wprintf函数内单步执行,会看到执行过程最终到达wcstombs_s。
问题出现在转换的过程上。转换函数必须知道将宽字符的二进制代码转换成哪种代码页字符的二进制代码,如果选择的代码页与控制台的活动代码页不相符,那么同样也不会正确显示。上面的第一段代码正是由于没有选择合适的代码页,导致输出错误。而在第二段代码中,通过将区域设置为中国,告诉转换函数将宽字符转换成936代码页的多字节字符,这与控制台的活动代码页一致,所以就可以正确输出了。
这里简单介绍一下_wsetlocale函数。该函数设置C运行库使用的区域文化。区域文化影响到数字、货币以及时间等数值的显示格式,当然还有代码页。第一个参数指示使用区域文化的哪个方面,取值可以是LC_COLLATE,LC_CTYPE,LC_MONETARY,LC_NUMERIC,LC_TIME以及LC_ALL。例如,如果使用LC_NUMERIC,则C运行库输出数字的时候将使用指定区域文化的数字显示风格;如果使用LC_CTYPE,则只影响转换函数所选择的代码页。
第二个参数通过字符串指定区域文化。该字符串有一个固定的格式,详细情况可以参见MSDN文档。但一般情况下我们只需使用国家或地区的缩写即可,例如“chs”。如果使用空字符串“”,则表示根据当期操作系统的区域设置选择相应的代码页。所以如果操作系统选择的区域是“中文(中国)”,则也可以通过_wsetlocale(LC_ALL, “”)来设置正确的代码页。
C运行库默认使用一个名为“C”的区域文化,这是语言无关的,具有国际通用性,与其关联的代码页仅包含了ASCII中定义的字符。在程序启动的时候C运行库会以setlocale(LC_ALL, “C”)的方式调用setlocale,所以默认情况下wprintf不能正确输出含有中文的宽字符字符串。
C语言下对宽字符的输出处理就这样了。接下来看看C++对宽字符的输出处理。_wsetlocale只对C运行库有效,对cout和wcout是没有影响的。对于cout和wcout,应该使用其成员方法imbue:
std::wcout.imbue(std::locale("chs", std::locale::all));
locale对象构造方法的两个参数与_wsetlocale函数参数的意义是一样的,只是位置调转了。
与wprintf一样,wcout在输出宽字符字符串的时候,也是先将其转换成多字节字符字符串。不同的是,遇到代码页上不支持的字符的时候,wprint输出一个问号,而wcout无任何输出,同时将badbit和failbit置位,后续的输出全部都无效。个人认为wcout的处理方式欠妥,因为并不是所有场合都适合这样处理,还是wprintf的处理方式比较通用。
基于我自己的经验,个人认为对于控制台程序最好还是使用多字节字符集,而不要使用Unicode字符集。即不要定义_UNICODE和UNICODE标记。一旦使用了Unicode字符集,在输出上可能会出现很多莫名其妙且麻烦的问题。
最后对在网上看到的将char*字符串转换成wchar_t*字符串的方法发表一下看法。该方法的代码如下:
#include
#include
using namespace std;
int main() {
    wostringstream outStrStream;
    outStrStream << "博客园";
    wstring wstr = outStrStream.str();
    wcout << wstr << endl;
}
具体思路是:将char*类型的字符串输出到wostringstream对象中,再通过该对象的str方法获取转换后的字符串。这种方法作出了假设:wostringstream对象会自动将char*字符串转换成wchar_t*类型字符串。注意在这段代码中,没有调用wcout.imbu方法设置区域文化,但仍然能够正确输出中文。
编译、执行这段代码都没有问题,看上去似乎是正确的。但是如果试图获取转换后的字符串的长度就出问题了:
#include
#include
using namespace std;
int main() {
    wostringstream outStrStream;
    outStrStream << "博客园";
    wstring wstr = outStrStream.str();
    wcout << wstr.length() << endl;
}
这段程序将输出6,而不是3。除了长度之外,使用at方法获取到的字符也不是“博客园”中的一个。实际上,对该字符串进行操作的结果几乎都是不正确的。
为什么会出现这种情况呢?可以通过观察一下outStrStream对象内部的数据来寻找答案。下图是执行outStrStream << "博客园"之后的内存数据:
红色框内的便是outStrStream对象内的数据。再来看看宽字符与多字节字符的“博客园”字符串在内存中的实际数据:


#include
#include
using namespace std;
int main() {
    char* pStr = "博客园";
    wchar_t* pWStr = L"博客园";
}


上面的图是wchat_t*类型的,下面的图是char*类型的。通过这几幅图,可以看到outStrStream对象内的字符串仍然是多字节字符类型的字符串,只不过每个字节扩展成了两个字节。这根本不是宽字符类型的字符串,所以即使不调用wcout.imbue也能正确输出中文。
就写到这里吧。以上内容都是个人见解,如果存在错误疏漏请见谅。

[C/C++]宽字符与控制台输出相关推荐

  1. C/C++宽字符与控制台程序

    在我刚开始学C/C++的时候,字符类型使用的都是char.接触Win32编程之后,养成了使用wchar_t的习惯,于是再写控制台程序的时候自然就使用wchar_t了.然而在控制台程序中使用宽字符会导致 ...

  2. [C/C++]宽字符与控制台程序

    转自:http://www.cnblogs.com/zplutor/archive/2010/11/27/1889227.html 在我刚开始学C/C++的时候,字符类型使用的都是char.接触Win ...

  3. 宽字符编码和解码通用类[CodeWidthChartUtility]

    在做jsonp传递的时候遇到一个问题,当有特殊字符或中文的时候就会导致数据错误或者是乱码,刚开始有js的编码和解码和正则,都比较麻烦,现在找到了一种合适的解决方案,宽字符编码,js端会自动解析,能处理 ...

  4. 宽字符wchar_t和窄字符char区别和相互转换

    转自:http://blog.csdn.net/nodeathphoenix/article/details/7416725 1.    首先,说下窄字符char了,大家都很清楚,就是8bit表示的b ...

  5. The New C++ -- 基本数据类型和字面值常量 (5. 宽字符类型和宽字符字面值常量)

    注意:本章节内容设计C++11,其中部分内容可能还没有被所有编译器支持. 随着计算机技术的发展,软件国际化是不可避免的趋势.ASCII所支持的最多256种字符已经远远不能满足国际化的需求.对我们来说, ...

  6. 关于宽字符(C++将中文文本文件的内容输出到控制台

    宽字符实例:打开文件以行为单位读取文件的内容,并且将包含"人"字的行输出. //例11-14 用文件宽输入流查找文件中的"人"字. / /11 14. cpp ...

  7. wchar 格式控制符_控制台输出宽字符wchar_t的中文显示问题

    在缺省的C locale下,cout可以直接输出中文,但对于wcout却不行(至少VS 2005下不行).对于wcout,需要将其locale设为本地语言才能输出中文: wcout输出时显示不了中文, ...

  8. ASP.NET Core 解决控制台输出日志内容前面[40m等乱码字符

    在默认我写了一个 WPF 程序去做管理 ASP.NET Core 进程的日志的时候,重定向输出的内容里面每一行前面都添加了很多乱码字符串.其实这是 ASP.NET Core 控制台的颜色字符,解决方法 ...

  9. 输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变,句子中单词以单个空格符隔开,为简单起见,不带标点符号。 例如输入“I am a student”,则通过控制台输出“student a

    输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变,句子中单词以单个空格符隔开,为简单起见,不带标点符号. 例如输入"I am a student",则通过控制台输出& ...

最新文章

  1. 使用 Termux 查看连接到手机热点的ip地址
  2. 从5随机到7随机及其扩展
  3. 搜狗高速浏览器打开网页没有声音怎么办
  4. 利用cookies让sweetalert只出现一次
  5. Intel 64/x86_64/IA-32/x86处理器 - SIMD指令集 - MMX技术(2) - 数据转换指令
  6. python组合和继承_Python基础系列讲解——继承派生和组合的概念剖析
  7. 打破独立游戏开发者的困局
  8. 为什么我在实时编码时失败了?
  9. Android ui 测试课堂笔记
  10. r9270公版bios_R9280,R9270,HD7000,VBE7007.系显卡全套修改超频刷BIOS工具
  11. 华硕aura完全卸载_手感细腻,外观出色,配件良心、华硕TUF GAMING K7 光轴机械键盘...
  12. RHEL7 CentOS7 检查查看精简指令
  13. Log4j配置按照文件大小和日期分割日志文件
  14. 基于文本语义的智能问答机器人——工业应用
  15. 精益创业实战 - 内容简介
  16. 怎么样对阿里云ECS主机进行绑定域名
  17. 读取文件时内容乱码解决方法
  18. 【网络】抓包tcpdump
  19. 正则表达式--教程二(语法)
  20. Android——AndroidX

热门文章

  1. Halcon学习笔记:3D_coordinates(3D标定)
  2. 智慧消防物联网落地案例(云南、福建、陕西) java 物联网智慧消防
  3. 单点登录SSO----JSON Web Token(JWT)机制
  4. 真·浅谈System.setOut()
  5. 混拨vps与单城市拨号vps有什么区别?
  6. matlab定步长ode,[转载]matlab ode45 函数传自定义参数用法及定步长ode
  7. css-对号/叉号(纯css)
  8. db2 dec函数oracle,DB2常用函数与Oracle有什么区别?
  9. 解决tomcat启动-Skipping unneeded JARs during scanning can improve startup time and JSP compilation time
  10. 蓝屏代码大全(留着自己看)