转自:http://www.cnblogs.com/zplutor/archive/2010/11/27/1889227.html

在我刚开始学C/C++的时候,字符类型使用的都是char。接触Win32编程之后,养成了使用wchar_t的习惯,于是再写控制台程序的时候自然就使用wchar_t了。然而在控制台程序中使用宽字符会导致各种奇怪的问题,这些问题主要是在输出上。下面分享一下我在这方面的心得。

首先来看一下这段代码:

#include <stdio.h>

int main() {

wprintf(L"%s", L"博客园");

return 0;

}

wprintf用于输出宽字符类型的字符串,看上去似乎没有错误。但这段代码的输出却是三个问号。这是使用wprintf时最典型的问题。解决方法是加入对_wsetlocale的调用:

#include <stdio.h>

#include <locale.h>

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字符集。这是一个明显错误的建议,因此将这段内容删去了。)

最后对在网上看到的将char*字符串转换成wchar_t*字符串的方法发表一下看法。该方法的代码如下:

#include <iostream>

#include <sstream>

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 <iostream>

#include <sstream>

using namespace std;

int main() {

wostringstream outStrStream;

outStrStream << "博客园";

wstring wstr = outStrStream.str();

wcout << wstr.length() << endl;

}

这段程序将输出6,而不是3。除了长度之外,使用at方法获取到的字符也不是“博客园”中的一个。实际上,对该字符串进行操作的结果几乎都是不正确的。

为什么会出现这种情况呢?可以通过观察一下outStrStream对象内部的数据来寻找答案。下图是执行outStrStream << "博客园"之后的内存数据:

红色框内的便是outStrStream对象内的数据。再来看看宽字符与多字节字符的“博客园”字符串在内存中的实际数据:

#include <iostream>

#include <sstream>

using namespace std;

int main() {

char* pStr = "博客园";

wchar_t* pWStr = L"博客园";

}

上面的图是wchat_t*类型的,下面的图是char*类型的。通过这几幅图,可以看到outStrStream对象内的字符串仍然是多字节字符类型的字符串,只不过每个字节扩展成了两个字节。这根本不是宽字符类型的字符串,所以即使不调用wcout.imbue也能正确输出中文。

[C/C++]宽字符与控制台程序的更多相关文章

  1. C运行时库(C Run-time Library)详解(提供的另一个最重要的功能是为应用程序添加启动函数。Visual C++对控制台程序默认使用单线程的静态链接库,而MFC中的CFile类已暗藏了多线程)

    一.什么是C运行时库 1)C运行时库就是 C run-time library,是 C 而非 C++ 语言世界的概念:取这个名字就是因为你的 C 程序运行时需要这些库中的函数. 2)C 语言是所谓的“ ...

  2. VC++中的C运行时库浅析(控制台程序默认使用单线程的静态链接库,而MFC中的CFile类已暗藏了多线程)

    1.概论 运行时库是程序在运行时所需要的库文件,通常运行时库是以LIB或DLL形式提供的.C运行时库诞生于20世纪70年代,当时的程序世界还很单纯,应用程序都是单线程的,多任务或多线程机制在此时还属于 ...

  3. win32控制台程序 宽字符与短字符转化

    由于vs各版本之间存在字符设置不兼容问题,特总结char与tchar的互相转换函数,如下,在之后的工程中可以使用. void TcharToChar(const TCHAR * tchar, char ...

  4. C语言小程序——推箱子(窄字符和宽字符)

    C语言小程序——推箱子(窄字符Version) 推箱子.c #include <stdio.h> #include <conio.h> #include <stdlib. ...

  5. windows控制台程序——关于UNICODE字符的总结(转)

    前言:从Windows NT/2000开如,Windows系统已经是一个标准的UNICODE系统,系统内部所有字符串存储及操作均使用UNICODE编码.因此Win32 API都是UNICODE版本的, ...

  6. C# 控制台程序(命令行程序)设置字体颜色,窗口宽高,光标行数

    控制台程序(命令行程序)设置窗口宽度高度,如下代码: Console.WriteLine(Console.WindowHeight); Console.WriteLine(Console.Buffer ...

  7. c++ 在控制台用 wcout输出宽字符的问题

    在我的电脑上要想通过 std::wcout输出 宽字符 需加入以下代码 #include <io.h> #include <fcntl.h> void main() { _se ...

  8. 控制台程序的中文输出乱码问题(export LC_CTYPE=zh_CN.GBK,或者修改/etc/sysconfig/i18n为zh_CN.GBK。使用setlocale(LC_CTYPE, "");会使用默认办法。编译器会将源码做转换成Unicode格式,或者指定gcc的输入文件的编码参数-finput-charset=GBK。Linux下应该用wprintf(L"%ls/n",wstr))

    今天发现用securecrt登陆时,gcc编译出错时会出现乱码,但直接在主机的窗口界面下用Shell编译却没有乱码.查看了一下当时的错误描述,发现它的引号是中文引号,导致在SecureCRT中显示出错 ...

  9. 零基础逆向工程27_Win32_01_宽字符_MessageBox_win32调试输出

    1 多字节字符 ASCII码表:0 ~ 2^7-1 扩展ASCII码表:2^7 ~ 2^8-1 什么是GB2312:1980年,两个字节存储一个汉字:不通用,别国会有乱码. UCICODE:只有一个字 ...

随机推荐

  1. JMeter并发性测试

    JMeter并发性测试 一.JMeter简介 JMeter是apache公司基于java开发的一款开源压力测试工具,体积小,功能全,使用方便,是一个比较轻量级的测试工具,使用起来非常简单.因为jmet ...

  2. 动态背景的CSS3登录表单

    在线演示 本地下载

  3. sql server 数据库复制实现数据同步常见问题(不定期更新)

    sql server2008数据库复制实现数据同步常见问题 在原作者基础上追加 sql server2008数据库复制实现数据同步常见问题 23.发布 'xx' 的并发快照不可用,因为该快照尚未完全生 ...

  4. spring data redis的使用jar包版本冲突问题

    spring data redis 与spring 版本之间会有不兼容,要求spring 最低版本为4.2.6,这里推荐的一个版本 spring 4.3.2  spring data redis 1. ...

  5. spring整合redis配置

    第一步:添加需要的jar包 <!-- redis --> <dependency> <groupId>redis.clients</groupId> & ...

  6. spark学习7(spark2.0集群搭建)

    第一步:安装spark 将官网下载好的spark-2.0.0-bin-hadoop2.6.tgz上传到/usr/spark目录下.这里需注意的是spark和hadoop有对应版本关系 [root@sp ...

  7. Word Search, 在矩阵中寻找字符串,回溯算法

    问题描述: Given a 2D board and a word, find if the word exists in the grid. The word can be constructed ...

  8. Java 如何解析由String类型拼接的XML格式

    String xml = new String(a);打印的xml 的值是 <?xml version= 1.0 encoding=gb2312?><weighData>< ...

  9. 判断浏览器是否支持某一个CSS3属性

    判断浏览器是否支持某一个CSS3属性 function supportCss3(style) { var prefix = ['webkit', 'Moz', 'ms', 'o'], i, humpS ...

  10. 一般处理程序ashx中用session存储数据

    如果要使用session的话,在handler的代码中添加System.Web.SessionState的引用,并让这个handler继承IRequiresSessionState接口,一定要继承这个 ...