转自: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. IIS DirectoryEntry

    DirectoryEntry是.Net给我们的一大礼物,他的名字我们就知道他的功能--目录入口.使用过ADSI的人都知道操作IIS,WinNT这些时,我们还需要提供他们的Path,操作IIS时,这个P ...

  2. Linux系统下Git操作命令整理

    1.显示当前的配置信息 git config --list 2. 创建repo从别的地方获取 git clone git://git.kernel.org/pub/scm/git/git.git 自己 ...

  3. Gentoo系统安装步骤详解

    下载镜像 一般我都是用国内的镜像源,不管是centos,ubuntu还是gentoo在国内的镜像来说肯定比国外快 #下载地址mirrors.163.com/gentoo/#我用的x86的http:// ...

  4. HDU 5704

    题意:n个人参加一个游戏,每个人选择0-100范围的数.m为选择的所有数的平均数*2/3,选择的数<=m且距离m最近的人获胜,若同时有多人满足条件则随机抽取胜者.如果一个人选的数,比m小,且相距 ...

  5. sql报字段过大的错误解决方法

    set global max_allowed_packet = 2*1024*1024*10

  6. devstack环境中不能创建cinder volume

    刚安装好的devstack环境中无法成功创建cinder volume,创建的volume的status为error:在cinder scheduler中看到失败log:2015-10-15 14:1 ...

  7. <<Hadoop基础教程》之初识Hadoop【转】

    Hadoop一直是我想学习的技术,正巧最近项目组要做电子商城,我就开始研究Hadoop,虽然最后鉴定Hadoop不适用我们的项目,但是我会继续研究下去,技多不压身. <Hadoop基础教程> ...

  8. Python 用Redis简单实现分布式爬虫

    Redis通常被认为是一种持久化的存储器关键字-值型存储,可以用于几台机子之间的数据共享平台. 连接数据库 注意:假设现有几台在同一局域网内的机器分别为Master和几个Slaver Master连接 ...

  9. OSI七层模型及应用

    应用层:提供访问网络服务的接口.例如telnet. 表示层:提供数据格式转化服务.例如压缩和解压缩. 会话层:提供回话实体的连接服务,进行访问验证和会话管理.例如服务器验证用户登录和断点续传. 传输层 ...

  10. [MYSQL]时间毫秒数转换

    java中常用bigint字段保存时间,通常将时间保存为一大串数字,每次取出需要在程序里转换,有时候程序里不方便,可以使用MYSQL自带的函数FROM_UNIXTIME(unix_timestamp, ...