当前位置: 首页 > news >正文

Windows API --- Unicode简介 2.1

文章目录

  • Unicode简介
    • 字符集简史
    • char数据类型
    • 更宽的字符
    • 宽字符库函数
    • 维护一个源代码文件(一个很巧妙的设计)

Unicode简介

字符集简史

对于Unicode的发展不再缀叙

  • 简而言之: 随着计算机的发展与不断普及,ASCII码已经不足以应对计算机全球化,因此Unicode编码应运而生。Unicode编码是16位,而ASCII码是8位,因此Unicode可以存储更多信息,以应对应对全球化并提供一个标准。 另外一提 Unicode 被认为是“宽字符”,且Unicode只有一个字符集(因此避免了"二义性")。
  • 注意: 在发展史上,并不是ASCII码无法适应全球化后就直接出现了Unicode,实际上这期间还有例如 “扩展 ASCII” ,”ANSI字符集” , “DBCS”等等。
  • 宽字符 并不一定是 Unicode。 Unicode只是宽字符编码的一种实现,但我们后续的重点是为了研究和学习了解 Windows API , 因此后续我们会将 宽字符和Unicode划等号

char数据类型

为了后续对宽字符的研究,这里对char数据类型进行一个介绍,相信学过c/c++的各位对char数据类型都有自己的认识,但是还请看下去,或许会有一些收获

  • 下面声明定义和初始化一个包含单一字符的变量
    • char c = 'A';
      变量c会申请一个字节大小的存储空间而且会用十六进制0X41来初始化这个存储空间。也就是ASCII字母A的符号。
  • 讲解 字符串的指针字符数组
    • 字符串指针: char* p = "Hello!"; 在32位机器上,指针的大小为4个字节,这里的 指针p指向了 字符串 “Hello!”

      • 注意: 这里是 指针p指向了 字符串”Hello!” ,这个字符串”Hello!”是存储在静态内存中并使用7个字节的存储空间----其中6个字节存储字符串而另一个字节存储表示字符串结尾的’\0’
    • 字符数组: 我们定义一个char a[10]; 这种情况下编译器为数组a保留了10个字节的存储空间。sizeof(a); 表达式返回10。

      • 如果这个数组是全局的,我们可以属于静态字符串对其进行初始化:char a[] = "Hello!";
      • 如果这个数组是一个局部变量,那么它必须定义为静态变量:static char a[] = "Hello!";
        • 当其是局部变量时,只有把它设为静态的(static),编译器才在正式进入程序前会为其申请内存。
          如下图,静态局部和局部的区别:
          在这里插入图片描述
    • 总结:无论是以上那种情况,字符串始终被存储在静态内存中,并有一个’\0’在最后,一共需要7个字节的存储空间。

更宽的字符

使用Unicode 或者是 宽字符 并不会改变 C语言中的字符数据类型,char类型仍然为1个字节的存储空间,而且sizeof(char);继续返回1.

  • C语言的宽字符是基于 wchar_t 数据类型的。 这个数据类型被定义中多个头文件中,包括WCHAR.Hwchar_t的本质是 对 unsigned short 的封装,即: typedef unsigned short wchar_t; 因此我们也可以看到,wchar_t 实质上是一个无符号的16位数据类型。

  • 定义一个包含单个宽字符的变量

    • wchar_t c = 'A';
    • 变量 c 是一个两个字节的值0X0041,这是Unicode中字母A的代表。
    • 然而,因为Intel微处理器存储多字节数组是总是最低有效数字有限,说要这些字节在内存是以这样的形式顺序存储的:0X41,0X00说人话就是 这里是“小端存储”。 如果要检查Unicode文本的内存存储,务必要记住这点
    • 下图是验证c的内存大小是16位,而不是8位。
      在这里插入图片描述
  • 定义并初始化一个指向宽字符串的指针 以及 数组

    • 指向宽字符串的指针: wchar_t* p = L"Hello!";

      • 注意: 大写字母L(表示长整型)紧接左引号。这是向编译器表面这个字符串将用宽字符存储-----也就是说,每个字符占2个字节。指针变量p还是运用4个字节的存储空间,但是这个字符串占14个字节的存储空间----每个字符需要2个字节,再加上最后的0需要两个字节。
    • 定义数组:
      static wchar_t a[] = "Hello!";

      • 尽管前面有个L很怪,但是这个L是非常重要的,且L和引号之间不能有空格。只要有了这个L编译器才能知道你想要创建的是宽字符数组。
    • 验证 宽字符的存储为 两个字节 , 且在小端存储下,是先存有效数字。

      • 如下图
        在这里插入图片描述

        这里第一个字节存储了0X41 , 第二个字节存储0X00

    • 单个字符的定义其实也可以加L,即 wchar_t c = L’A’; 也是可以的,这是这个就没有那么必要了。

宽字符库函数

  • 如果用strlen来计算 宽字符串 的长度, 那么结果会是怎么样的呢?
    • 首先我们知道对于 char* p = “Hello!” 而言,strlen(p);的结果是 6 , 可是对于 wchar_t wp = L”Hello!”; 而言 , strlen(wp); 会先报个error,这个error说明了 strlen接受的参数一个是一个指向char的指针,而不是一个指向unsigned short 的指针。但是我们仍然可以强制编译,但是结果会显示1。
      为什么这里的结果会是1呢?
      • 这是因为对于 宽字符串“Hello!” 而言,它的16位值如下
        0X0048 0X0065 0X006C 0X006F 0X0021
      • 而由于小端存储的原因,逐个字节来看其存储如下
        48 00 65 00 6C 00 6F 00 21 00
      • 而对于strlen函数,它是以一个字节为单位进行检索的,在检索到0时停止,因此它在计算第一个字符后就停止了,返回1。
  • 看到这里,我们可以知道我们必须对C语言库中那些有字符串参数的函数进行重新,不过幸运的是 这些函数的重写已经被完成了,而不需要我们对其进行重写。
  • 宽字符版本的strlen函数被称为wcslen(宽字符的长度)函数,被定义在STRING.H(也就是定义strlen被定义的地方)和WCHAR.H中。
    • strlen函数的声明如下:
      size_t _cdecl strlen(const char*);
    • wcslen函数的声明如下:
      size_t _cdecl wcslen(const wchar_t*);
    • 所以我们可以通过wcslen函数来计算wp的长度,wcslen(wp);,返回值为6。也就是字符个数为6.
    • 注意:在使用宽字符时,宽字符串的长度并没有改变,改变的只是字节长度

维护一个源代码文件(一个很巧妙的设计)

上文我们可以知道,对于宽字符的大小是大于普通字符的,宽字符运行库函数比正常的函数要大,为此可能会想要写两个版本的程序,一个用ASCII字符串,而另一个用Unicode字符串。那么最好的方法是什么呢? 最好的办法是 维护一个单一的源代码文件,但是可以编译成ASCII或者Unicode!!! 【这个设计十分的巧妙,笔者感觉已经是有 泛型 的雏形了,但是对于C语言这个面向过程的语言,如何实现这个设计?,下面我们就要看大神的设计实现和演示了,相信各位会受益,至少笔者是感觉受益良多的】

  • 一个问题
    因为运行库函数具有不同名称,字符变量的定义也不同,而且宽字符串字面之前必须要加L,如何解决呢?

  • 解决方案
    一个答案被包含在TCHAR.H头文件中,这个头文件是ANSI C标准的一部分,因此其中定义的每个函数和宏都有一个下划线前缀。

    • TCHAR.H为那些需要字符串参数的普通运行库函数(例如:_tprintf_tcslen )提供了一系列的替代名称。 这些函数有时被称为“通用”的函数名字。因为它们可以指Unicode或非Unicode版本的函数。
  • 具体实现方式

    • 如果一个命名为_UNICODE的标识符被定义了(即存在宏_UNICODE)了,并且TCHAR.H头文件被包含到程序中,_tcslen就被定义为wcslen

      • 定义如下:
        #define _tcslen wcslen
    • 如果_UNICODE并没有被定义,那么_tcslen就被定义为strlen

      • 定义如下:
        #define _tcslen strlen
    • 下图对其进行验证。#undef _UNICODE是取消宏定义_UNICODE
      在这里插入图片描述

      在这里插入图片描述

    • 以此类推。TCHAR.H也用一个命名为TCHAR的新的数据类型解决了两个字符数据类型的问题。

      • 如果_UNICODE标识符被定义了,TCHAR就是wchar_t
        定义如下:
        typedef wchar_t TCHAR
      • 否则的话,TCAHR就是一个简单的char
        定义如下:
        typedef char TCHAR
    • 好了,现在该解决 宽字符串字面量前L的问题了。这个问题通过一个宏__T来解决的。

      • 或许读者对于宏方面的不是太过了解,对于符号##的解释如下:
        A##B 是将A和B 合成一个符号,即L##X在预处理后,表示的是LX

      • 如果_UNICODE表示被定义,一个叫__T的宏是如下定义的:
        #define __T(X) L##X

      • 如果_UNICODE标识符没有被定义,宏__T的定义如下:
        #define __T(X) X

      • 而宏__T的写法还是不太简介,因此有了下面两个宏定义:

        #define _T(X) __T(X)
        #define _TEXT(X) __T(X)
        
      • Win32控制台程序下使用哪一种取决于你的需求。基本上必须用下列方式将字符串字面定义为_T或_TEXT宏内:
        _TEXT("Hello!");

        • 这样做的结果就是: 当_UNICODE被定义时,字符串就被解释为由宽字符组成。否则,被解释为 由普通字符组成)
          演示如下图:
          在这里插入图片描述

        在这里插入图片描述


http://www.mrgr.cn/news/57003.html

相关文章:

  • 代码工艺:写代码的好习惯
  • 文件(下)
  • 【设计模式】深入理解Python中的桥接模式(Bridge Pattern)
  • SpringBoot集成Spring security 2024.10(Spring Security 6.3.3)
  • 飞凌嵌入式FET527N-C核心板已适配OpenHarmony4.1
  • Leecode热题100-25.K个一组反转链表
  • python--pyQt 单选按钮控件 -QRadioButton
  • Java面试题库——网络编程
  • 洛谷 P3130 [USACO15DEC] Counting Haybale P
  • 科大讯飞AI开发者大赛颁奖典礼,万码优才荣获前三甲!
  • vue项目中pinia和vuex的使用
  • Android 默认去掉URL网络校验,设置不进行网络校验
  • 代码工艺:写代码的好习惯
  • arco-design 自定义table和for循环自定义form-item并添加自定义校验
  • Linux系统基础-进程间通信(4)_模拟实现进程池
  • 智慧楼宇平台,构筑未来智慧城市的基石
  • 聊一聊电的产生和输送联接到桌面PDU插座的那些事儿
  • Shiro授权
  • OpenHarmony4.0配置应用开机自启
  • 高效休息法
  • CSS背景
  • 【Java SE 】抽象类 和 接口 详解
  • 高标准农田信息化推动农业产业链升级
  • Scala的内部类
  • uniapp学习(007-3 壁纸项目:系统高度等信息的操作)
  • 线程池常见面试题