C++之编码问题(Unicode,ASCII,本地默认)
本篇文章试图回答的问题:
1、char* pStr="我aa";这句代码执行后,pStr指向的内存区域中存储的字节到底是根据什么码表而来的呢?该字符串占几个字节?
2、将一个VS2010的Windows程序设置了“使用Unicode字符集”到底意味着什么?
3、现在有一个文件,其存储内容未知(可能是文本,可能是图像,可能是视频),要求是:在文件最前面插入一串Unicode文本,插入完成后以文本程序打开该文件,插入的文本不会显示为乱码(该文件本身的内容不考虑)。——如何做到?
本人能力、精力有限,所言所感都基于自身的实践和有限的阅读、查阅,如有错误,欢迎拍砖,敬请赐教——博客园:钱智慧。
一:
用VS2010新建一个win32控制台应用程序TestChar,代码如下:
#include <iostream>
using namespace std;
int main()
{
char* pStr="我aa";
cout<<sizeof("我aa")<<endl;
cout<<hex<<pStr[]-;
cout<<pStr[]-<<endl;
return ;
}
打印结果如下:
分析:存储介质(内存、外存等)上存储的都是二进制数据,而对于字符信息的存储,先查码表进行解码,再把码以二进制信息存储,即pStr指向的这段内存中存储的都是字符们的编码:一个中文字符,一个英文字母,一个全角字母。要得到”我“的编码,默认会查找本地码表,本人是中文Win7系统,查找的是GB2312码表,而对照GB2312码表可发现,"我"的编码正是ced2,与打印一致(f是符号位,可无视)。另外GB2312是不会对英文字母进行编码的,因为英文字母属于半角字符,这类编码由ASCII码表负责,GB2312中的任何字符都占用两个字节,空字符也由ASCII负责编码,这就是为何上面的字符串占用的字节数目是6。可见,上面一句代码,其实涉及到了两张码表:ASCII码表和GB2312。
二:
新建一个MFC对话框程序,名为TestUnicodeChar。默认情况下,VS2010建的项目都是基于Unicode的,即打开项目的属性,在”字符集“设置中都是”使用Unicode字符集“,这到底是什么意思呢?莫非在程序中用的中文都是以Unicode字节进行存储的?由上面的TestChar程序可以看出并非如此,pStr中的"我”是以GB2312进行存储的。又或者,源文件(h文件、cpp文件等)在磁盘上是以Unicode进行编码存储的?也不是,源文件存储默认也是以本地GB2312进行存储的。验证方式:用UE编辑器随便打开一个设置了“使用Unicode字符集”项目的某个源文件,比如打开本项目中的TestUnicodeCharDlg.cpp文件,在UE中以16进制的形式查看该文件内容,可以发现其前两个字节并非FF FE(这是Unicode文件的标识),你随便找一个中文,对照其16进制找到其编码,然后跟GB2312码表中的该中文的编码对照即可验证。这是一个GB2312码表的网页链接:
http://tool.xker.com/gb2312tbl.php
我们知道,在C++中,有char和string,为了支持Unicode字符,还有wchar_t和wstring,我们可以认为string是基于char的,而wstring是基于wchar_t的。为什么要引入Unicode呢,只用char和string难道不能保存含有中文的字符(串)吗?由TestChar程序,我们知道完全可以,并且采用的是GB2312编码,问题是你无法确定一个类似pStr的混合串的字符数目,比如:string str="我a",你调用string的length方法不能准确得到str的长度,这给编程带来了不便。所以你可以选用wstring和wchar:wstring wstr=L"我a",这时你再调用wstring的length方法就能准确得到了,其中L前缀可以使后面紧跟的字符串解释成宽字符串(Unicode)。因为Unicode对任何字符都采用2个字节进行编码,所以length的实现想必也很简单:每两个字节算一个字符,进而可以方便得到字符串的长度,这便是Unicode优于多字节编码的地方:试想,如果采用多字节编码,那么要实现基于这种编码的字符串类的length方法会非常头疼。Unicode浪费了存储空间但带来了编程上的简便。(关于这方面的详细内容可以参考《Windows程序设计 第5版》第1章)
在Windows中,有这样一些宏:_T,TEXT,TCHAR,CString,它们根据不同的设定有不同的含义。先看一下它们的使用:
在TestUnicodeCharDlg.cpp的OnPaint方法中加上如下代码:
TCHAR * pStr=TEXT("a我");//_T与TEXT的含义是一样的
CString str1=TEXT("a我");
CString str2=L"a我";
如果程序定义了UNICODE宏,则_T和TEXT(二者含义和用法完全一样)便会把括号内的字符串解释为UNICODE字符串,TCHAR便会替换为wchar_t,CString便会替换为CStringW,否则(即没有定义UNICODE宏),都会解释为相应的char版本。而L前缀不是宏,类似强转:不管有没有定义UNICODE宏,都把后面的字符串解释为UNICODE字符串。而设置“使用UNICODE字符集”就相当于定义UNICODE宏,即该设置仅仅是影响了一些宏的行为。若把该项目的“使用Unicode字符集”设置改为“未使用”,则编译会出错,因为此时str2就是一个CStringA实例,你不能把一个L前缀的字符串(Unicode字符串)赋值给它。
三:
第三个问题本质上就是往一个文件中写Unicode字符串的问题。涉及到编码问题的文件操作始终牢记一点:以什么编码写,就以编码读。在TestUnicodeChar程序的OnInitDialog函数中加入如下代码:
BOOL CTestUnicodeCharDlg::OnInitDialog()
{
CDialogEx::OnInitDialog(); // 将“关于...”菜单项添加到系统菜单中。 // IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
} // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标 // TODO: 在此添加额外的初始化代码
CFile myFile; if ( myFile.Open( _T("c:\\myfile.txt"), CFile::modeCreate |
CFile::modeReadWrite ) )
{ CString str=TEXT("a我");
//myFile.Write("\xff\xfe",2);
myFile.Write( str, str.GetLength()*sizeof(TCHAR) );
myFile.Flush();
} return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
运行程序,然后用写字板、记事本、NotePad++、UE(UEdit)分别打开mfile.txt发现,有的能正常显示,有的则乱码。要知道,你往myfile.txt写进去的是 两个字符的Unicode编码,用某文本程序去打开myfile.txt,倘若该程序默认情况下读取文本时是按Unicode来解析的,则不会乱码,否则就乱码。我们把注释的那行代码的注释拿掉,用任何支持Unicode的文本程序去打开myfile.txt就不会出错了,因为一个文本中的前两个字节FF FE便向试图打开该文本的程序表明该文本应该用Unicode进行解析(你可以用NotePad++新建几个Unicode格式的文本,随便保存几个字符,然后用UE以16进制格式查看便可知Unicode文本的前两个字节都是FF FE)。
另外,经常遇到有人问这样的问题:CString如何转换为char*?问这个问题之前,最好问下自己:我的目的是什么,为何要进行这样的转换,当前项目有没有设置Unicode。要知道,如果设置了Unicode,则CString存储的是Unicode字符串,转换为char*后,你如果直接显示这个char*或者写到文件中(没有把FF FE写到文件开始处)然后打开,则会(假如打开文件的程序默认不以Unicode进行解析)出现乱码,所以,这种情况下,转换为char*的意义不大——这不是说不能把Unicode串转为char*,这完全是可行的,本质上这只是在把一个Unicode字符串的内存内容"活生生”取出来而已。不管怎样,下面的代码重新修改了OnInitDialog函数,演示了几种情况:
BOOL CTestUnicodeCharMFCDlg::OnInitDialog()
{
CDialogEx::OnInitDialog(); // 将“关于...”菜单项添加到系统菜单中。 // IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
} // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标 // TODO: 在此添加额外的初始化代码
CFile myFileW,myFileA,myFileCharArrow,myFileWOrA; if ( myFileW.Open( _T("c:\\myfileW.txt"), CFile::modeCreate |
CFile::modeReadWrite ) &&
myFileA.Open( _T("c:\\myfileA.txt"), CFile::modeCreate |
CFile::modeReadWrite ) &&
myFileCharArrow.Open( _T("c:\\myfileCharArrow.txt"), CFile::modeCreate |
CFile::modeReadWrite ) &&
myFileWOrA.Open( _T("c:\\myFileWOrA.txt"), CFile::modeCreate |
CFile::modeReadWrite ))
{ CString strW=TEXT("a我");//因为本项目设置了Unicode字符集,所以我们知道CString会被替换为CStringW CStringA strA(strW); myFileW.Write( strW, strW.GetLength()*);
myFileW.Flush(); myFileA.Write(strA,strA.GetLength());
myFileA.Flush();
//CString的GetString返回的const类型指针,要么在右边强转,要么左边用const类型的char*去接
//注意指针命名:p是pointer,c是const,如果有t则是TEXT,w是wide,l是long
char* pstr=(char*)strA.GetString();
/*下面这行代码若不注掉,会报错,因为本程序是Unicode程序,
strW会是一个CStringW类型的字符串,它的GetString返回的是LPCWSTR类型指针
当然不能赋值给LPCSTR类型指针了,一个是const wchar_t*,另一个是const char*
*/
//const char* pcstr1=strW.GetString(); myFileCharArrow.Write(pstr,strlen(pstr));
myFileCharArrow.Flush(); //假设我们在编程中,不知道有没有使用Unicode设置,为了通用,我们可以尽量使用宏及通用版本的相关函数(如_tcslen)
CString strWOrA=TEXT("a我");
//注意这里的TCHAR不一定就是wchar_t,这取决于程序是否设置了Unicode
const TCHAR* pctstr=strWOrA.GetString();//CString的GetString返回的是const指针
myFileWOrA.Write(ptstr,_tcslen(pctstr)*sizeof(TCHAR));
myFileWOrA.Flush(); } return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
用UE察看几个文件的内容,如图:
其中,CED2是”我“的GB2312编码,6211(注意字节高低次序)是”我“的Unicode编码,我们可知,CStringA strA(strW)这行代码,一定进行了码表间的转换。结合代码,文件内容应该不难理解。
还有些让人容易头晕的字符串指针宏,下面列举出来:
关于char*的:
LPCSTR: long pointer const string,可看成const char*,与PCSTR相似
LPSTR:可看成char*,与PSTR相似
关于wchar_t*的:
LPCWSTR,PCWSTR,LPWSTR,PWSTR
通用版本(根据是否配置了Unicode有不同的宏替换):
TCHAR*
LPTSTR,LPCTSTR (T有点类似TEXT宏的意思)
C++之编码问题(Unicode,ASCII,本地默认)的更多相关文章
- 聊聊计算机中的编码(Unicode,GBK,ASCII,utf8,utf16,ISO8859-1等)以及乱码问题的解决办法
作为一个程序员,一个中国的程序员,想来“乱码”问题基本上都遇到过,也为之头疼过.出现乱码问题的根本原因是编码与解码使用了不同而且不兼容的“标准”,在国内一般出现在中文的编解码过程中. 我们平时常见的编 ...
- 字符编码笔记:ASCII,Unicode 和 UTF-8个人理解
一.ASCII 码 我们知道,计算机内部,所有信息最终都是一个二进制值.每一个二进制位(bit)有0和1两种状态,因此八个二进制位(字节(Byte )是计算机信息技术用于计量存储容量的一种计量单位,作 ...
- 字符编码笔记:ASCII,Unicode和UTF-8
很久很久以前,有一群人,他们决定用8个可以开合的晶体管来组合成不同的状态,以表示世界上的万物.他们看到8个开关状态是好的,于是他们把这称为"字节". 再后来,他们又做了一些可以处理 ...
- 字符编码笔记:ASCII,Unicode和UTF-8 转
本文出处 http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html 只是为了记录一下省得要去搜. 今天中午,我突然想搞清楚 ...
- [转]字符编码笔记:ASCII,Unicode和UTF-8
转自:http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html 作者: 阮一峰 日期: 2007年10月28日 今天中午, ...
- 字符编码笔记:ASCII,Unicode和UTF-8(转载)
作者: 阮一峰 日期: 2007年10月28日 今天中午,我突然想搞清楚Unicode和UTF-8之间的关系,于是就开始在网上查资料. 结果,这个问题比我想象的复杂,从午饭后一直看到晚上9点,才算初步 ...
- 字符编码笔记:ASCII,Unicode和UTF-8【转载】
作者: 阮一峰 日期: 2007年10月28日 今天中午,我突然想搞清楚Unicode和UTF-8之间的关系,于是就开始在网上查资料. 结果,这个问题比我想象的复杂,从午饭后一直看到晚上9点,才算初步 ...
- 【转】字符编码笔记:ASCII,Unicode和UTF-8
今天整理笔记,关于NSString转NSData时,什么时候使用NSUTF8StringEncoding,或者NSASCIIStringEncoding,或者 NSUnicodeStringEncod ...
- 字符编码笔记:ASCII,Unicode和UTF-8,附带 Little endian和Big endian的解释
作者: 阮一峰 日期: 2007年10月28日 今天中午,我突然想搞清楚Unicode和UTF-8之间的关系,于是就开始在网上查资料. 结果,这个问题比我想象的复杂,从午饭后一直看到晚上9点,才算初步 ...
- 字符编码笔记:ASCII、Unicode、UTF-8、UTF-16、UCS、BOM、Endian
转载:http://witmax.cn/character-encoding-notes.html 今天中午,我突然想搞清楚Unicode和UTF-8之间的关系,于是就开始在网上查资料. 结果,这个问 ...
随机推荐
- mac下搭建react-native环境
1.安装Homebrew 2.安装node(最好安装4.x以上版本这样就自带了一个npm) 3.安装npm(node的包管理工具) 一般高版本的npm在安装node的时候已经具有了 4.安装react ...
- compass(sass)+seajs+frozenui+frozenjs+svn主干分支
1.compass框架 sass编译 1.compass create 项目名 2.cd目录,执行compass watch 2.frozen框架 js(frozen.js),css(global.c ...
- 修改ECSHOP,支持图片云存储化(分离到专用图片服务器)
为了提高页面加载速度和适应中国复杂的网络环境,我决定把所有商品图片都分离到专业的云存储服务器上,具有CDN加速功能. 首先,生成一个域名 img.xxxx.com 并映射到自己的云存储别名,然后把全部 ...
- building Utils {{ant+ivy}、{maven}}怎么样手动将下载下来的 JAR 包添加到 Maven、ivy 的本地仓库
mvn install:install-file -Dfile=jar包的位置 -DgroupId=上面的groupId -DartifactId=上面的artifactId -Dversion=上面 ...
- linux编译相关知识
(1)用g++编译程序时,-l 与-L各是什么意思 http://bbs.chinaunix.net/thread-107364-1-1.html 感谢作者 -l 表示:编译程序到系统默认路进搜索,如 ...
- 一步步学习ASP.NET MVC3 (1)——基础知识
请注明转载地址:http://www.cnblogs.com/arhat 首先在这里我想声明一下,这个ASP.NET MVC3系列是我在授课过程中的一些经验,有什么不对的地方,请大家指出,我们共同的学 ...
- 一步步学习ASP.NET MVC3 (6)——@helper,@functions
请注明转载地址:http://www.cnblogs.com/arhat 在前一章中,我们讲述了View如何从Action中获得数据,并显示出来,但随着需求的变化,我们可能要对View中显示的数据作出 ...
- Source Insight 显示中文乱码
Source Insight 3.X utf8支持插件震撼发布 继上次SI多标签插件之后,因为公司内部编码改为utf8编码,因此特意做了这个Source Insight 3.X utf8插件. 下载地 ...
- android开发之---文字居中---android中去掉标题栏
1. 让textView里面的内容水平居中 : android:gravity="center_horizontal" 2. 让textView控件在它的父布局里水平居中 ...
- DEPRECATED: Use of this script to execute hdfs command is deprecated.
DEPRECATED: Use of this script to execute hdfs command is deprecated. 本人安装的hadoop版本是2.4.0的,但每次执行命令时都 ...