Windows核心编程之核心总结(第二章 字符和字符串处理)(2018.5.27)

学习目标

第二章是学习字符和字符串处理,为了更好理解这一章的内容,我自行添加了其他辅助性内容:存储模式(大端存储和小端存储)、字符编码方案(一看就懂)。以下是这一章的学习目标:
1.大端存储和小端存储
2.字符编码方案
3.ANSI和Unicode字符、字符串,Windows自定义数据类型(为了兼容ANSI和Unicode)
4.Windows的ANSI函数和Unicode函数
5.C运行库的ANSI和Unicode函数
6.C运行库的安全字符串函数
7.C运行库的安全字符串函数(进阶版)
8.字符串比较函数
9.宽字符和ASCII字符之间的转换函数

必备知识-大端存储和小端存储

如何理解Big Endian(大端存储)和Little Endian(小端存储)?
举个例子:
int a = 1;
a这个数本身的16进制表示是0x00 00 00 01
在内存中怎么存储呢?
如果你的CPU是intel x86架构的(基本上就是通常我们说的奔腾cpu),那么就是0x01 0x00 0x00 0x00 , 这也就是所谓的little-endian, 低字节存放在内存的低位.
如果你的CPU是老式AMD系列的(很老很老的那种,因为最新的AMD系列已经是x86架构了), 它的字节序就是big-endian, 其内存存储就是 0x00 0x00 0x00 0x01在内存中从高字节开始存放。
现在世界上绝大多数的CPU都是little-endian。

字符编码

发展流程:ASCII-扩展ASCII-GB2312-GBK-GB18030
美国是最先开始使用计算机,一个字节有八位二进制,能够组合出256种不同的状态,他们将控制字符、空格、标点符号、数字、大小写字母分别用连续的字节状态表示,一直编码到了第127号,这样计算机就可以用不同字节来存储英语文字了。这时,他们这个方案就叫做ANSI的ASCII编码。后来计算机普及到了世界,因为127种不能表示其他国家的字符、文字,接着他们就想扩展ASCII,使用127后面的位数来其他的字符和文字,一直编码到状态255,从126到255这一页的字符集称作扩展字符集。等到我们中国使用计算机,那么仅仅依靠ASCII完全不够存储,中国就自己想法子解决这一问题,我们这样规定:一个小于127的字符意义还是与原来的ASCII编码相同,但两个大于127的字符连在一起时,就表示一个汉字。意思是说:一个英文字母还是一个字节存储,而一个汉字用两个字节来表示,低8字节存储在低地址位置,高8字节存储在高地址位置,如果在低地址位置存储的低8字节的第8个二进制位大于1,在高地址位置存储的高8字节的第8个二进制位也大于1,那么就会被识别为一个汉字。这样我们大概就可以组合出大约7000多个简体汉字了,在这些编码种,我们还将数学符号、连ASCII原本就有的数字、标点、字母都统统重新编码了,这就是我们遇到的“全角”字符,而原来在127号以下的就叫“半角”字符。之后,中国就叫这个汉字编码方案称为“GB2312”。后来,中国文化博大精深,有些汉字还没完全编码,所以就决定干脆只要高字节代表的字符大于127就表示该字符为汉字,然后称这个编码方案为 GBK 标准。当用GBK解码时,若高字节最高位为0,则用ASCII编码码解码;若高字节最高位为1,则用GBK编码表解码。GBK之后又有GB18030标准,因GB18030较GBK又多了几千汉字,码位不足,GB18030使用了2byte与4byte混合编码方式,这又给软件增加了难题,所以虽然GB18030推出了近5年,仍然没有得到广泛应用。前面讲的一堆汉字编码,我们总称为“DBCS”,即双字节字符集。经过前面编码的发展,逐渐出现一个严重的问题,就是当时各个国家都像中国这样搞出一套自己的编码标准,结果互相之间谁的计算机都不认识对方,谁也不支持对方的编码。后来,先辈们就想到,废除所有的地区性编码方案,重新搞一个包括了地球上所有文化、文字和符号的编码方案,他们称这个编码方案为“Unicode编码”。
Unicode编码有以下几种:
UTF-8:一个字节一个字符,有些字符是2个字节,有的字符是3个字节,还有的字符是4个字节。
UTF-16:大部分字符都是2个字节。Windows平台下默认的Unicode编码为Little Endian的UTF-16。
UTF-32:所有字符都是4个字节。

ANSI和Unicode字符、字符串,Windows自定义数据类型

ANSI字符就是C语言用char数据类型代表一个8位的字符。ANSI字符串是多个char数据类型组成的数组,代表多个字节的字符串。例如:

char a=‘a‘;//‘a‘这个常量字符在常量存储区存储为1个字节。而a在栈区存储为1个字节。
char szBuffer[10]="abcdefg";//"abcdefg"这个常量字符在常量存储区存储为8个字节。而szBuffer在栈区存储为10个字节。

在以前,Unicode字符用wchar_t代表一个两字节的宽字符(Unicode字符),以前C头文件有这样的定义:typedef unsigned short wchar_t,说明wchar_t其实也只是一个无符号短整型而已。后来C编译器将wchar_t定义为与Int一样是基本数据类型,这时候,在高版本一点的编译器你是找不到typedef unsigned short wchar_t这条语句的了。如果想表示常量字符和常量字符串为Unicode版本,那么就要在前面加个L。例如:

wchar_t c=L‘a‘;//L‘a’这个常量字符在常量存储区存储为2个字节。而c在栈区存储为2个字节。
wchar_t szBuffer[10]=L"abcdefg";//L"abcdefg"这个常量字符在常量存储区存储为16个字节。而szBuffer在栈区存储为20个字节。

为了与C语言稍微一些区分,并且为了兼容ANSI和Unicode字符或字符串,Windows自定义了一些数据类型:TCHAR数据类型、TEXT宏。
而对于TCHAR数据类型和TEXT宏的头文件定义如下:

#ifdef  UNICODE                     // r_winnt

#ifndef _TCHAR_DEFINED
typedef WCHAR TCHAR, *PTCHAR;
typedef WCHAR TBYTE , *PTBYTE ;
#define _TCHAR_DEFINED
#endif /* !_TCHAR_DEFINED */

typedef LPWCH LPTCH, PTCH;
typedef LPCWCH LPCTCH, PCTCH;
typedef LPWSTR PTSTR, LPTSTR;
typedef LPCWSTR PCTSTR, LPCTSTR;
typedef LPUWSTR PUTSTR, LPUTSTR;
typedef LPCUWSTR PCUTSTR, LPCUTSTR;
typedef LPWSTR LP;
typedef PZZWSTR PZZTSTR;
typedef PCZZWSTR PCZZTSTR;
typedef PUZZWSTR PUZZTSTR;
typedef PCUZZWSTR PCUZZTSTR;
typedef PZPWSTR PZPTSTR;
typedef PNZWCH PNZTCH;
typedef PCNZWCH PCNZTCH;
typedef PUNZWCH PUNZTCH;
typedef PCUNZWCH PCUNZTCH;
#define __TEXT(quote) L##quote      // r_winnt

#else   /* UNICODE */               // r_winnt

#ifndef _TCHAR_DEFINED
typedef char TCHAR, *PTCHAR;
typedef unsigned char TBYTE , *PTBYTE ;
#define _TCHAR_DEFINED
#endif /* !_TCHAR_DEFINED */

typedef LPCH LPTCH, PTCH;
typedef LPCCH LPCTCH, PCTCH;
typedef LPSTR PTSTR, LPTSTR, PUTSTR, LPUTSTR;
typedef LPCSTR PCTSTR, LPCTSTR, PCUTSTR, LPCUTSTR;
typedef PZZSTR PZZTSTR, PUZZTSTR;
typedef PCZZSTR PCZZTSTR, PCUZZTSTR;
typedef PZPSTR PZPTSTR;
typedef PNZCH PNZTCH, PUNZTCH;
typedef PCNZCH PCNZTCH, PCUNZTCH;
#define __TEXT(quote) quote         // r_winnt

#endif /* UNICODE */                // r_winnt
#define TEXT(quote) __TEXT(quote) 

从头文件定义中,我们可以看出TCHAR数据类型有两种可能,如果定义了UNICODE,则是WCHAR(其实就是wchar_t,宽字符),如果定义了非UNICODE(多字节字符集),则是char(窄字符)。我们知道,当我们打开vs编译器,默认采取的是Unicode字符集,其实这个选项代表我们写的程序序加了这句代码:#define UNICODE。那说明我们写TCHAR,其实就是wchar_t。而如果我们在选项中更改字符集为多字节字符集,那么就相当于定义了非UNICODE,那说明我们写TCHAR,其实就是char。而对于TEXT宏也同样道理,如果是UNICODE字符集,那么就转定义为L##quote(代表在quote前面添加L,quote可以是字符,也可以是字符串),如果是多字节字符集,那么就转定义为quote(代表什么都不添加)。下面举个例子:

//Unicode字符集
TCHAR c=TEXT(‘a‘);//TEXT(‘a‘)相当于L’a‘,在常量存储区存储为2个字节。而c在栈区存储为2个字节。
TCHAR szBuffer[10]=TEXT("abcdefg");//TEXT("abcdefg")相当于L"abcdefg",在常量存储区存储为16个字节。而szBuffer在栈区存储为20个字节。
//多字节字符集
TCHAR c=TEXT(‘a‘);//TEXT(‘a‘)相当于’a‘,在常量存储区存储为1个字节。而c在栈区存储为1个字节。
TCHAR szBuffer[10]=TEXT("abcdefg");//TEXT("abcdefg")相当于"abcdefg",在常量存储区存储为8个字节。而szBuffer在栈区存储为10个字节。

Windows是不是很智能?兼容了ANSI和Unicode,通过TCHAR和TEXT宏可以自动采用对应编码方式进行编码。

Windows的ANSI函数和Unicode函数

  1. 在windows中,有UNICODE类型的函数和ASCII类型的函数,例如CreateWindowEx函数。
    在WinUser.h中,有如下定义:

    #ifdef UNICODE
    #define CreateWindowEx  CreateWindowExW
    #else
    #define CreateWindowEx  CreateWindowExA
    #endif // !UNICODE

    根据上面头文件,我们就知道了,CreateWindowExW函数是支持Unicode字符的,而CreateWindowExA是支持ANSI字符的。原来Windows函数也会考虑到ANSI和Unicode字符串问题,所以为了兼容这两者,就归为CreateWindowEx函数了,会自动根据情况自行选择正确的函数。其实,还有一个内部原理:CreateWindowExA函数内部实现的其实只是一个转换层,它负责分配内存,以便将ANSI字符串转换为Unicode字符串,然后内部代码会调用CreateWindowExW,并向它传递转换后的字符串,CreateWindowExW返回时,CreateWindowExA会释放它的内存缓冲区,并返回窗口句柄。这个内部原理,总结一句话就是虽然我们调用的是CreateWindowExA,但实际函数内部是先将ANSI字符串转换为Unicode字符串,再调用CreateWindowExW,最后释放内存,接着CreateWindowExA返回内部调用的CreateWindowExW返回的窗口句柄。

    C运行库的ANSI和Unicode函数

    C运行库提供了一些字符串操作函数来处理ANSI字符和Unicode字符。例如:strlen和wcslen函数,分别支持ANSI字符串和Unicode字符串。

    //字符集为Unicode字符集
    char szBuffer1[5]="abcd";
    printf("%d\n", strlen(szBuffer1));
    
    TCHAR szBuffer2[5] = TEXT("abcd");
    printf("%d\n", wcslen(szBuffer2));

    而C运行库为了能智能兼容ANSI和Unicode,提供了_tcslen函数,这个函数需要头文件tchar.h,并且定义了_UNICODE。
    tchar.h头文件定义了以下宏:

    #ifdef _UNICODE
    #define _tcslen wcslen
    #else
    #define _tcslen strlen
    #endif

    如果包含了头文件tchar.h,并且字符集设置为Unicode字符集,那么就已经定义了_UNICODE,我也不知道为什么设置Unicode字符集就会自动定义_UNICODE,然后就可以直接使用_tcslen,也许是因为设置字符集这个操作内部就有#define _UNICODE这行代码吧。下面举个例子:

    //已经设置字符集为Unicode字符集了
    #include<windows.h>
    #include<tchar.h>
    int main()
    {
    TCHAR szBuffer3[5] = TEXT("abcd");
    printf("%d\n", _tcslen(szBuffer3));
    system("pause");
    return 0;
    }

    C运行库的安全字符串函数

    我们编程的时候,尽量使用安全字符串,例如strcpy就是一个非安全函数,当你再程序中使用这个函数的时候,你就会发现,编译器会出现警告,同时给出建议,请遵守。编译器会提示我们使用strcpy_s函数,此时,我们可以查找这个函数,并找到这个函数的TCHAR.h版。具体使用方法不是很难,你要使用那个字符串,就在MSDN中找相应的安全字符串函数即可。不过,对于strlen、wcslen和_tcslen等函数没有问题,可以放心使用,因为它们不会修改传入的字符串。

    C运行库的安全字符串函数(进阶版)

    C运行库还新增了一些函数,用于在执行字符串处理时提供更多的控制。例如:StringCchLength、StringCchPrintf等函数,更多函数请参考MSDN。
    下面是StringCchPrintf函数的说明:
    StringCchPrintf函数用于把格式化字符串写入指定的缓冲区中,与wsprintf函数不同之处在于,该函数还另外需要提供目标缓冲区的大小,确保不会发生越界访问。(因为wsprintf函数,若缓冲区大小不足以存储格式化字符串,则不允许写入,而且会发生崩溃。但是StringCchPrintf函数指定了目标缓冲区大小,意味着,缓冲区大小不足以存储格式化字符串,也可以截断,只存储参数1(缓冲区大小)的长度的字符串),这样就避免发生了奔溃。头文件 strsafe.h。

函数原型:
HRESULT StringCchPrintf(
Out LPTSTR pszDest,
In size_t cchDest,
In LPCTSTR pszFormat,
In ...
);

参数1:指定将要被写入的缓冲区
参数2:限制缓冲区大小
参数3:格式化字符串
参数4:可变参数

    TCHAR szBuffer[10];
    wsprintf(szBuffer, TEXT("%s"), TEXT("woainiaifbgfbfgbfgbgf"));//当目标缓冲区不够存储源缓冲区内容,则会溢出崩溃
    StringCchPrintf(szBuffer, 10, TEXT("%s"),TEXT("wwoainiaifbgfbfgbfgbgf"));//新的安全字符串函数增加了一个缓冲区大小参数,如果超过目标缓冲区大小则会自动截断,避免了溢出崩溃

下面是StringCchLength函数的说明:
StringCchLength函数用于确定字符串是否超过了规定长度。与lstrlen函数的区别在于,该函数指定了待检查的字符串的最大允许的字符数量。注意,如果待检查的字符串(双引号)长度大于最大允许的字符数量,参数3置为0。如果待检查的字符串(单引号),没有字符串结束符,则无论设置cchMax为多少,都会置参数3为0.

*函数原型:
HRESULT StringCchLength(
In LPCTSTR psz,
In size_t cchMax,
Out size_t
pcch
);
参数1:指向待检查的字符串
参数2:psz参数里最大允许的字符数量。
参数3:字符串的字符数,不包括‘\0‘**

    size_t iTarget1,iTarget2,iTarget3;
    TCHAR szBuffer1[10] =TEXT("但是我依然很开心呀");
    StringCchLength(szBuffer1, 5, &iTarget1);//如果待检查的字符串(双引号)长度大于最大允许的字符数量,参数3置为0。不会报错。
    TCHAR szBuffer2[3] = { L‘a‘, L‘b‘, L‘c‘ };
    StringCchLength(szBuffer2, 5, &iTarget2);//如果待检查的字符串(单引号),没有字符串结束符,则无论设置cchMax为多少,都会置参数3为0.不会报错。
    TCHAR szBuffer3[10] = TEXT("但是我依然很开心呀");
    StringCchLength(szBuffer3, 10, &iTarget3);//成功了

总结,StringCch*系列的函数是安全的,因为可以指定如何截断,不会发生崩溃。

字符串比较函数

int CompareString(
  __in  LCID Locale,
  __in  DWORD dwCmpFlags,
  __in  LPCTSTR lpString1,
  __in  int cchCount1,
  __in  LPCTSTR lpString2,
  __in  int cchCount2
);
int CompareStringOrdinal(
  __in  LPCWSTR lpString1,
  __in  int cchCount1,
  __in  LPCWSTR lpString2,
  __in  int cchCount2,
  __in  BOOL bIgnoreCase
);

CompareStringOrdina和语言无关,速度更快!!!建议使用!!!因为字符串操作函数在实际应用中可以查询MSDN,我以后再填补回来。

宽字符和ASCII字符之间的转换函数

int MultiByteToWideChar(
  __in   UINT CodePage,
  __in   DWORD dwFlags,
  __in   LPCSTR lpMultiByteStr,
  __in   int cbMultiByte,
  __out  LPWSTR lpWideCharStr,
  __in   int cchWideChar
);
int WideCharToMultiByte(
  __in   UINT CodePage,
  __in   DWORD dwFlags,
  __in   LPCWSTR lpWideCharStr,
  __in   int cchWideChar,
  __out  LPSTR lpMultiByteStr,
  __in   int cbMultiByte,
  __in   LPCSTR lpDefaultChar,
  __out  LPBOOL lpUsedDefaultChar
);

因为字符串操作函数在实际应用中可以查询MSDN,我以后再填补回来。

原文地址:http://blog.51cto.com/12731497/2120935

时间: 2024-08-03 19:50:24

Windows核心编程之核心总结(第二章 字符和字符串处理)(2018.5.27)的相关文章

Python核心编程第三版第二章学习笔记

第二章 网络编程 1.学习笔记 2.课后习题 答案是按照自己理解和查阅资料来的,不保证正确性.如由错误欢迎指出,谢谢 1. 套接字:A network socket is an endpoint of a connection across a computer network,Sockets are often represented internally as simple integers, which identify which connection to use. 套接字是网络通信的

Windows Forms编程实战学习:第二章 欢迎使用Visual Studio

第二章 欢迎使用Visual Studio 1,AssemblyInfo文件 包含程序集的属性,向应用程序添加元数据 [assembly:<attribute>(<setting>)] AssemblyInfo常用属性 属性 描述 AssemblyTitle 程序集标题 AssemblyDescription 程序集描述 AssemblyCompany 程序集公司名 AssemblyProduct 程序集产品名 AssemblyCopyright 程序集的版权字符串 Assembl

Windows核心编程之核心总结(第一章 错误处理)(2018.5.26)

前沿 学习Windows核心编程是步入Windows编程殿堂的必经之路,2018年寒假重温了计算机操作系统知识,前阵子又过学习Windows程序设计方面的基础,正所谓打铁要乘热,所以我又入了Windows核心编程的坑啦,哈哈~ 学习目标 每一章的学习都要明确一个目标,就是你学完这一章之后你能做些什么?好的,我们一步步来学习第一章节错误处理.以下是这一章节的学习目标:1.了解Windows函数的错误机制2.了解GetLastError和SetLastError函数的使用3.了解FormatMess

Windows核心编程之核心总结(第三章 内核对象)(2018.6.2)

学习目标 第三章内核对象的概念较为抽象,理解起来着实不易,我不断上网找资料和看视频,才基本理解了内核对象的概念和特性,其实整本书给我的感觉就是完整代码太少了,没有多少实践的代码对内容的实现,而且书本给的源码例子,有太多我们不知道的知识,并且这些知识对本章主要内容来说是多余的,所以我们理解起来也非常困难.为了更好的学习这章,我补充了一些辅助性内容.这一章的学习目标:1.Windows会话和安全机制2.什么是内核对象?3.使用计数和安全描述符4.内核对象句柄表5.创建内核对象6.关闭内核对象7.跨进

Windows核心编程之核心总结(第四章 进程(一))(2018.6.8)

学习目标 第四章进程的学习可谓是任重而道远,虽然不难,但知识量很多,也比较零散,需要多总结,脑海里才有进程的框架.所以,我把本章分为几个小节来讲完.我还是一如既往的添加辅助性内容,希望对于小白有所帮助.而比我流弊的大有人在,大神们可以跳过辅助性内容.本小节的学习目标如下:1.C/C++程序编译过程2.C/C++命令行参数的使用3.什么是进程4.Windows的入口点函数5.进程实例句柄(可执行文件实例句柄或者DLL文件实例句柄) C/C++程序编译过程 C/C++的编译.链接过程要把我们编写的一

Windows核心编程之核心总结(第四章 进程(二))(2018.6.17)

学习目标 上一节我们了解了进程.入口函数和进程实例句柄等内容,在进入进程的命令行学习前,有一个全局变量初始化问题需要测试一波.本节的学习目标如下:1.测试C/C++运行库启动函数初始化哪些全局变量2.进程的命令行3.进程的环境变量4.进程的当前驱动器和目录5.判断系统版本6.创建进程(CreateProcess函数详解) 测试启动函数初始化哪些全局变量 我们知道C/C++运行库的启动函数会做一些事后再调用我们的入口函数,而入口函数的参数都是在调用前就初始化好了的.那么我就产生了一个疑问,全局变量

Windows核心编程读书笔记-第四章进程

1.进程组成 一个内核对象,操作系统用它来管理进程. 一个地址空间,其中包含所有可执行文件或DLL模块的代码和数据.此外,它还包含动态内存分配,比如线程堆栈和堆的分配. 2.一个进程可以有多个线程,所有线程都在进程的地址空间中"同时"执行代码.每个进程至少要有一个线程来执行进程地址空间包含的代码. 3.用Microsoft Visual Studio来创建一个应用程序项目时,集开发环境会设置各种链接器开关,使链接器将子系统的正确类型嵌入最终生成的可执行文件.对于CUI程序,这个链接器开

【编程珠玑】【第二章】问题B

问题B:将一个n元一维向量向左旋转i个位置.例如,当n = 8且i = 3时,向量abcdefgh旋转为defghabc. 方法一.使用一个字节的额外空间开销. 采用每次向左移一位的方法,循环i次.当然也可以使用向右移动的方法,循环length - i次.以向左移动为例,共需要移动i趟,首先把str[0]赋值给临时变量temp,剩余的字符向左移动一位,即str[k]=str[k+1],移动完成后把临时变量temp赋值给str[n-1]. 该方法比较笨,但是也是最容易想到的,它空间开销小,但是时间

C#高级编程第11版 - 第二章

导航 C# 全版本特性一览 全书目录 第二章 Core C 2.1 C#基础 29 2.2 变量 31 2.2.1 初始化变量 31 2.2.2 类型推断 32 2.2.3 变量的作用域 33 2.2.4 常量 34 2.3 预定义数据类型 35 2.3.1 值类型和引用类型 35 2.3.2 .NET 类型 36 2.3.3 预定义的值类型 36 2.3.4 预定义的引用类型 40 2.4 程序流控制 42 2.4.1 条件语句 42 2.4.2 循环 44 2.4.3 跳转语句 47 2.5