ASCII是用来表示英文字符的一种编码规范。每个ASCII字符占用1个字节,因此,ASCII编码可以表示的最大字符数是255(00H—FFH)。

其实,英文字符并没有那么多,一般只用前128个(00H-7FH,即0x0000 0000-0x0111 1111,最高位为0),其中包括了控制字符、数字、大小写字母和其它一些符号。

而另128个字符(80H—FFH,即0x1000 0000-0x1111 1111,最高位为1)被称为“扩展ASCII”,一般用来存放英文的制表符、部分音标字符等等的一些其它符号。

中文编码规范“GB2312—80,其实就是利用把一个中文字符用两个扩展ASCII字符来表示,以区分ASCII码部分。

这个方法有问题,最大的问题就是中文的文字编码和扩展ASCII码有重叠。而很多软件利用扩展ASCII码的英文制表符来画表格,这样的软件用到中文系统中,这些表格就会被误认作中文字符,出现乱码。

要真正解决这个问题,不能从扩展ASCII的角度入手,而必须有一个全新的编码系统,这个系统要可以为每一种文字的每个字符均分配一个单独的编码,Unicode为此诞生!

Unicode也是一种字符编码方法,它占用两个字节(0000H—FFFFH),容纳65536个字符,这完全可以容纳全世界所有语言文字的编码。

在Unicode里,所有的字符被一视同仁,汉字不再使用“两个扩展ASCII”,而是所有的文字都按一个字符来处理,它们都有一个唯一的Unicode码。

    使用Unicode编码可以使您的工程同时支持多种语言,使您的工程国际化。

    Windows NT是使用Unicode进行开发的,整个系统都是基于Unicode的。如果调用一个API函数并给它传递一个ANSI(ASCII字符集以及由此派生并兼容的字符集,如:GB2312,通常称为ANSI字符集)字符串,那么系统首先要将字符串转换成Unicode,然后将Unicode字符串传递给操作系统。进行这些字符串的转换需要占用系统的时间和内存。如果用Unicode来开发应用程序,就能够使您的应用程序更加有效地运行。

    下面例举几个字符的编码以简单演示ANSI和Unicode的区别:(注意:中文字符的编号有了变化)
字符  A  N  和
ANSI码  41H  4eH  cdbaH
Unicode码  0041H  004eH  548cH

对宽字符的支持其实是ANSI C标准的一部分,用以支持多字节表示一个字符。宽字符和Unicode并不完全等同,Unicode只是宽字符的一种编码方式

1、宽字符的定义

    在ANSI中,一个字符(char)的长度为一个字节(Byte)。使用Unicode时,一个字符占据一个字(2 Bytes),C++在wchar.h头文件中定义了最基本的宽字符类型wchar_t:

typedef unsigned short wchar_t; // 所谓的宽字符就是无符号短整数

2、常量宽字符串

  对C++程序员而言,构造字符串常量是一项经常性的工作。那么,如何构造宽字符字符串常量呢?很简单,只要在字符串常量前加上一个大写的L就可以了,比如:wchar_t *str1 = L" Hello";

这个L非常重要,只有带上它,编译器才知道你要将字符串存成一个字符一个字(即一个字符两字节)。还要注意,在L和字符串之间不能有空格。

3、宽字符串库函数

    为了操作宽字符串,C++专门定义了一套函数,比如求宽字符串长度的函数是:

size_t __cdel wchlen(const wchar_t*);

    为什么要专门定义这些函数呢?最根本的原因是,ANSI下的字符串都是以’\0’来标识字符串尾的(Unicode字符串以“\0\0”结束),许多字符串函数的正确操作均是以此为基础进行。而我们知道,在宽字符的情况下,一个字符在内存中要占据一个字的空间(即一个字符两字节),这就会使操作ANSI字符的字符串函数无法正确操作。

以”Hello”字符串为例,在宽字符下,它的五个字符是:0x0048 0x0065 0x006c 0x006c 0x006f

在内存中,实际的排列是:48 00 65 00 6c 00 6c 00 6f 00

于是,ANSI字符串函数,如strlen,在碰到第一个48后的00时,就会认为字符串到尾了,用strlen对宽字符串求长度的结果就永远会是1!

在许多多字节字符集中,0x00 到 0x7F 范围内的每个字符都与 ASCII 字符集中具有相同值的字符相同。

4、用宏实现对ANSI和Unicode通用的编程

    可见,C++有一整套的数据类型和函数实现Unicode编程,也就是说,您完全可以使用C++实现Unicode编程。
    如果我们想要我们的程序有两个版本:ANSI版本和Unicode版本。当然,编写两套代码分别实现ANSI版本和Unicode版本完全是行得通的。但是,针对ANSI字符和Unicode字符维护两套代码是非常麻烦的事情。为了减轻编程的负担,C++定义了一系列的宏,帮助您实现对ANSI和Unicode的通用编程
  C++宏实现ANSI和Unicode的通用编程的本质是根据”_UNICODE”(注意,有下划线)定义与否,这些宏展开为ANSI或Unicode字符(字符串)。

#ifdef _UNICODE
    typedef wchar_t TCHAR; // 定义了_UNICODE宏的情况下,TCHAR是两个字节的字符
    #define __T(x)  L##x  

    // ##是ANSI C标准的预处理语法,它叫做“粘贴运算符”,即将前面的L与宏参数合在一起。
    #define _T(x)   __T(x) // _T(x)一个下划线的变成__T(x)两个下划线的
#else
    #define __T(x) x       // 未定义_UNICODE宏的情况下,TCHAR是一个字节的字符
    typedef char   TCHAR;  // 非宽字符的字符串常量
#endif
*.几个预编译指令的用法

  #        字符串化运算符,其主要效果是把参数的名字转换为字符串。

  Example:   // 1. *.h中定义

#define STRINGLIZE(ivalue) #ivalue

// *.cpp中定义

CString strTmp = STRINGLIZE(2);

AfxMessageBox(strTmp);

// 结果是:弹出消息框中显示2,说明可以变成字符串

// 2.

#define STRINGLIZE(ivalue) printf(#ivalue " is: %d", ivalue)
             // 使用
             STRINGLIZE(2);

// 结果是:2 is: 2,将ivalue的值与后面的字符串合并成一个字符串了

// 注:以下这情况使用时的结果会有不同

int a = 2;

STRINGLIZE(a);

// 1. 结果是:弹出消息框中显示a

// 2. 结果是:a is: 2

注意:预处理的意思就是在编译运行前按字面处理,

  ##       粘贴运算符,即它先进行宏替换,再进行连接。

  Example:   #define MACR1 printf("MACR1 is invoked.")

#define MACR2 printf("MACR2 is invoked.")
             #define MAKE_MACR(n) MACR ## n

// 使用时

MAKE_MACR(2); // -->相当于调用了宏MACR2

// 结果是:MACR2 is invoked.

// 2.

#define STRINGLIZE(ivalue) TRACE("ivalue is: %d", ivalue##ivalue)

STRINGLIZE(2);

// 2. 结果是:ivalue is: 22

// 3.

int a = 2;

STRINGLIZE(a);

// 3. 结果是:error C2065: 'aa' : undeclared identifier

  #@       字符化运算符

  Example:   #define CHARIZEIT(x) #@x

// 使用

char c = CHARIZEIT(z);

// 结果是:c = 'z'

  #include 包含一个源代码文件

Example: #include /#include "my.h"/#include "t.c"

  #define  定义宏

  Example: #define MAX_NUM 10/#define max(x,y) (x) > (y) ? (x) : (y);

#define可以替代多行的代码,例如MFC中的宏定义:
             #define MACRO(arg1, arg2) do { \
               语句; \

}while(条件) 
           关键是要在每一个换行的时候加上一个"\"。

  #undef   取消已定义的宏

  #if      如果给定条件为真,则编译下面代码

  #ifdef   如果宏已经定义,则编译下面代码

  #ifndef  如果宏没有定义,则编译下面代码

  #elif    如果前面的#if给定条件不为真,当前条件为真,则编译下面代码

  #endif   结束一个#if/#ifdef/#ifndef...#else条件编译块

  #error   停止编译并显示错误信息

#line    指令可以改变编译器用来指出警告和错误信息的文件号和行号。

#pragma  指令没有正式的定义。编译器可以自定义其用途。

C++为字符串函数也定义了一系列宏,只例举几个常用的宏:

未定义_UNICODE(ANSI字符) 定义了_UNICODE(Unicode字符)
_tcschr  strchr  wcschr
_tcscmp  strcmp  wcscmp
_tcslen  strlen  wcslen

四、使用Win32 API进行Unicode编程

Win32 API中定义了一些自己的字符数据类型。这些数据类型的定义在winnt.h头文件中。例如:

typedef char           CHAR;
    typedef unsigned short WCHAR;           // wc, 16-bit UNICODE character
    typedef CONST CHAR     *LPCSTR, *PCSTR;
    Win32 API在winnt.h头文件中定义了一些实现字符和常量字符串的宏进行ANSI/Unicode通用编程。同样,只例举几个最常用的:

    #ifdef UNICODE // 注意此处没有没有下划线

        typedef WCHAR         TCHAR, *PTCHAR;

        typedef LPWSTR        LPTCH, PTCH;

        typedef LPWSTR        PTSTR, LPTSTR;

        typedef LPCWSTR       LPCTSTR;

        #define __TEXT(quote) L##quote        // r_winnt

    #else                        // r_winnt

        typedef char          TCHAR, *PTCHAR;

        typedef LPSTR         LPTCH, PTCH;

        typedef LPSTR         PTSTR, LPTSTR;

        typedef LPCSTR        LPCTSTR;

        #define __TEXT(quote) quote           // r_winnt

    #endif                       // r_winnt

API的字符串操作函数和C++的操作函数可以实现相同的功能,所以,如果需要的话,建议您尽可能使用C++的字符串函数,没必要去花太多精力再去学习API的这些东西。

Win32 API实际上有两个版本。一个版本接受MBCS字符串,另一个接受Unicode字符串。

例如:其实根本没有SetWindowText()这个API函数,相反,有SetWindowTextA()和SetWindowTextW()。后缀A表明这是MBCS函数,后缀W表示这是Unicode版本的函数。这些API函数的头文件在winuser.h中声明,下面例举winuser.h中的SetWindowText()函数的声明部分:

    #ifdef UNICODE
        #define SetWindowText  SetWindowTextW
    #else
        #define SetWindowText  SetWindowTextA
    #endif // !UNICODE
    细心的读者可能已经注意到了UNICODE和_UNICODE的区别,前者没有下划线,专门用于Windows头文件;后者有一个前缀下划线,专门用于C运行时头文件。换句话说,也就是在ANSI C++语言里面根据_UNICODE(有下划线)定义与否,各宏分别展开为Unicode或ANSI字符,在Windows里面根据UNICODE(无下划线)定义与否,各宏分别展开为Unicode或ANSI字符。

实际使用中我们同时定义_UNICODE和UNICODE,以实现UNICODE版本编程。

微软提供了一些ANSI和Unicode兼容的通用数据类型,我们最常用的数据类型有_T ,TCHAR,LPTSTR, LPCTSTR。

LPCTSTR和const TCHAR*是完全等同的。其中L表示long指针,这是为了兼容Windows 3.1等16位操作系统遗留下来的,在Win32 中以及其它的32位操作系统中,long指针和near指针及far修饰符都是为了兼容的作用,没有实际意义。P(pointer)表示这是一个指针C(const)表示是一个常量T(_T宏)表示兼容ANSI和UnicodeSTR(string)表示这个变量是一个字符串。综上可以看出,LPCTSTR表示一个指向常固定地址的可以根据一些宏定义改变语义的字符串。

比如: TCHAR  *szText = _T("Hello!");

         TCHAR   szText[] = _T("I Love You");

         LPCTSTR lpszText = _T("大家好!");
    使用函数中的参数最好也要有变化,比如:MessageBox(_T("你好"));其实,在这条语句中,即使您不加_T宏,MessageBox函数也会自动把“你好”字符串进行强制转换。还是推荐您使用_T宏,以表示您有Unicode编码意识

一些字符串操作函数需要获取字符串的字符数(sizeof(szBuffer)/sizeof(TCHAR)),而另一些函数可能需要获取字符串的字节数sizeof(szBuffer)。您应该注意该问题并仔细分析字符串操作函数,以确定能够得到正确的结果。
    1. ANSI操作函数:          以str开头,如strcpy(),strcat(),strlen();
    2. Unicode操作函数:       以wcs开头,如wcscpy,wcscpy(),wcslen();
    3. ANSI/Unicode操作函数: 以_tcs开头 _tcscpy(C运行期库);
    4. ANSI/Unicode操作函数: 以lstr开头 lstrcpy(Windows函数);
考虑ANSI和Unicode的兼容,我们需要使用以_tcs开头或lstr开头的通用字符串操作函数

很多时候程序中既需要Unicode,又需要使用ASCII,这时需要用到操作系统的2个API:

WideCharToMultiByte用来将Unicode字符串转化为MBCS的;

MultiByteToWideChar用来将MBCS字符串转化为Unicode的;

函数原型:

// 将宽字符转换成多个窄字符

int WideCharToMultiByte(UINT    CodePage,        // code page

                        DWORD   dwFlags,         // performance and mapping flags

                        LPCWSTR lpWideCharStr,   // wide-character string

                        int     cchWideChar,     // number of chars in string

                        LPSTR   lpMultiByteStr,  // buffer for new string

                        int     cbMultiByte,     // size of buffer

                        LPCSTR  lpDefaultChar,   // default for unmappable chars

                        LPBOOL  lpUsedDefaultChar); // set when default char used

// 将多个窄字符转换成宽字符

int MultiByteToWideChar(UINT   CodePage,       // code page

                        DWORD  dwFlags,        // character-type options

                        LPCSTR lpMultiByteStr, // string to map

                        int    cbMultiByte,    // number of bytes in string

                        LPWSTR lpWideCharStr,  // wide-character buffer

                        int    cchWideChar);   // size of buffer

这个是我们需要转化的MBCS字符串:char sText[20] = {"多字节字符串!OK!"};

而我们需要知道转化后的UNICODE字符串需要多少个数组空间?直接定义一个20 * 2UNICODE字符的数组,将会发现其中有浪费内存情况!

我们只需要将MultiByteToWideChar()的第四个形参设为-1,即可返回所需的短字符数组空间的个数:

DWORD dwNum = MultiByteToWideChar (CP_ACP, 0, sText, -1, NULL, 0);

    接下来,我们只需要分配响应的数组空间:

    wchar_t *pwText = NULL;

    pwText = new wchar_t[dwNum];

    if (!pwText)

    {

        delete []pwText;

    }

    再接着,我们就可以着手进行转换了。在这里以转换成ASCII码做为例子:

MultiByteToWideChar(CP_ACP, // ANSI code page

                        0,      //

                        sText,  // MBCS字符串

                        -1,     // 返回UNICODE字符串包括'\0'的长度

                        pwText, // UNICODE字符串数组

                        dwNum); // UNICODE字符串数组元素个数

    最后,使用完毕当然要记得释放占用的内存:delete []pwText;

    同理,宽字符转为多字节字符的代码如下:

    wchar_t wText[20] = {L"宽字符转换实例!OK!"};

    DWORD dwNum = WideCharToMultiByte(CP_OEMCP, // OEM code page

                                      0,        //

                                      wText,    // UNICODE字符串

                                      -1,       // 返回MBCS字符串包括'\0'的长度

                                      NULL,     //

                                      0,        //

                                      NULL,     //

                                      FALSE);   //

    char *psText = NULL;

    psText = new char[dwNum];

    if (!psText)

    {

        delete []psText;

    }

    WideCharToMultiByte (CP_OEMCP, 0, wText, -1, psText, dwNum, NULL, FALSE);

    delete []psText;

最后给一个实例代码:

void CTMUDlg::OnBnClickedButton1()
{

DWORD   dwNum = 0;

wchar_t wText[7]  = L"宽字符串示例";

char    sText[13] =  "窄字符串示例";

wchar_t *pwText = NULL;

char    *psText = NULL;

// 先显示一下

MessageBoxW(this->m_hWnd, wText, L"显示常量宽字符串", MB_OK);

MessageBoxA(sText, "显示常量窄字符串", MB_OK);

// 转换一下

dwNum = MultiByteToWideChar (CP_ACP, 0, sText, -1, NULL, 0);

pwText = new wchar_t[dwNum];

if (!pwText)

{

delete[] pwText;

}

MultiByteToWideChar(CP_ACP, // ANSI code page

0,      //

sText,  // MBCS字符串

-1,     // 返回UNICODE字符串包括'\0'的长度

pwText, // UNICODE字符串数组

dwNum); // UNICODE字符串数组元素个数

MessageBoxW(this->m_hWnd, pwText, L"显示窄转宽字符串", MB_OK);

delete[] pwText;

dwNum = WideCharToMultiByte(CP_OEMCP, // OEM code page

0,        //

wText,    // UNICODE字符串

-1,       // 返回MBCS字符串包括'\0'的长度

NULL,     //

0,        //

NULL,     //

FALSE);   //

psText = new char[dwNum];

if (!psText)

{

delete[] psText;

}

WideCharToMultiByte (CP_OEMCP, 0, wText, -1, psText, dwNum, NULL, FALSE);

MessageBoxA(psText, "显示宽转窄字符串", MB_OK);

delete[] psText;
}

VC++下的Unicode编程的更多相关文章

  1. 【转】VC下的Unicode编程

    转自http://www.leewei.org/?p=1304 UniCode简述 在Windows下用VC编程,如果编写的程序要在多种语言环境下运行(比如日文.中文.葡萄牙文等),使用VC默认的MB ...

  2. VC的UNICODE 编程

    简介 如果你编写的程序是针对非英语国家的用户,如中国.日本.东欧和中东地区,那么你一定要熟悉 UNICODE 字符集.尤其是用 Visual C++/MFC 编写针对上述国家和地区的用户的程序时,如果 ...

  3. 【转】VC++的Unicode编程

    转自http://www.cnblogs.com/kex1n/archive/2010/03/15/2286510.html 原始出处http://www.vckbase.com/document/v ...

  4. VC++的Unicode编程

    本文来自:http://tech.ddvip.com/2007-03/117395585321221.html 一.什么是Unicode 先从ASCII说起,ASCII是用来表示英文字符的一种编码规范 ...

  5. 【Windows编程】系列第四篇:使用Unicode编程

    上一篇我们学习了Windows编程的文本及字体输出,在以上几篇的实例中也出现了一些带有“TEXT”的Windows宏定义,有朋友留言想了解一些ANSI和Unicode编程方面的内容,本章就来了解和学习 ...

  6. 在VC下采用ADO实现BLOB(Binary)数据的存储,读取,修改,删除。

    在VC下采用ADO实现BLOB(Binary)数据的存储,读取,修改,删除. 作者:邵盛松 2009-09-05 前言 1关于的BLOB(Binary)数据的存储和读取功能主要参考了MSDN上的一篇& ...

  7. LINUX下C语言编程基础

    实验二 Linux下C语言编程基础 一.实验目的 1. 熟悉Linux系统下的开发环境 2. 熟悉vi的基本操作 3. 熟悉gcc编译器的基本原理 4. 熟练使用gcc编译器的常用选项 5 .熟练使用 ...

  8. DELPHI下的SOCK编程(转)

    DELPHI下的SOCK编程      本文是写给公司新来的程序员的,算是一点培训的教材.本文不会涉及太多的编程细节,只是简单讲解在DELPHI下进行Winsock编程最好了解的知识. 题外话:我认为 ...

  9. linux 操作系统下c语言编程入门

    2)Linux程序设计入门--进程介绍 3)Linux程序设计入门--文件操作 4)Linux程序设计入门--时间概念 5)Linux程序设计入门--信号处理 6)Linux程序设计入门--消息管理  ...

随机推荐

  1. [Z]QPS、PV和需要部署机器数量计算公式

    QPS = req/sec = 请求数/秒 [QPS计算PV和机器的方式] QPS统计方式 [一般使用 http_load 进行统计]QPS = 总请求数 / ( 进程总数 *   请求时间 )QPS ...

  2. 在线程中调用其它主界面的模块,因为中间有休息1000ms,所以调用前要检查DateTimeRun变量;在From_load 启动线程;在From_closing From_closed 设置DateTimeRun=false

    //系统启动后,自动启动时钟 void jishi_kernel() { try { while (DateTimeRun) { Thread.Sleep(); if (myRunning) Runn ...

  3. **解释器全局锁(Global Interpreter Lock)

    解释器全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的限制,说白了就是一个核只能在同一时间运行一个线程. [解决办法就是多进程和协程(协程 ...

  4. 【290】Python 函数

    参考:Python 函数 参考:7.3 给函数参数增加元信息(增加参数的数据类型) 目录: 一.语法 二.说明 三.参数传递 四.参数 4. 1 必备参数 4.2 关键字参数 4.3 缺省参数 4.4 ...

  5. ElasticSearch中如何让query should等同于filter should

    bool query must The clause (query) must appear in matching documents. should The clause (query) shou ...

  6. 使用heap profiler进行内存占用分析

    最近在项目中用到了google的heap profiler工具来分析内存占用,效果非常显著,因此在这里写一篇博客记录一下使用过程中遇到的一些问题. heap profiler依赖于tcmalloc,所 ...

  7. uml 时序图

    1.时序图的概念 时序图定义 : 描述了对象之间传递消息的时间顺序, 用来表示用例中的行为顺序, 是强调消息时间顺序的交互图; 时序图描述的事物: 时序图描述系统中类和类之间的交互, 将这些交互建模成 ...

  8. SmartGit过期后破解方法

    根据自己的操作系统,进入相应的文件夹 ,可能还有一个版本号的文件夹,再进入 Windows: %APPDATA%\syntevo\SmartGit\OS X: ~/Library/Preference ...

  9. 关于使用PL/SQL连接本地oracle时报错:ORA-12514: TNS: 监听程序当前无法识别连接描述符中请求的服务解决

    转自:https://blog.csdn.net/a657281084/article/details/49490069 问题:Oracle主服务和监听器服务已经启动,使用SQL Plus能够正常连接 ...

  10. Unity代码里的Position和界面上的Position

    代码里的Position = 世界坐标 this.gameObject.transform.position 界面上的Position = localPosition