Windows 程序设计(3) 关于字符串
1. 宽窄字节的区别及重要性
1.1 宽窄字节简介:
C语言/C++语言,使用的字符串指针就是 char* 类型,C++中的字符串是 string,内部也是对 char* 的封装
窄字节
其实最早的系统都是窄字节的,也就是我们很常用的 char 因为都是英文的,英文本身就26个字母,再加上其他的一些标点符号之类的,char 也能表示的下,无符号的 char 最多能表示 255个字符,所以足够用了!
操作系统的国际化,比如:Windows 系统不仅有英文的,也有中文的,韩文的,日文的,所以原来用一个 char 来表示一个英文字符的方式已经无法表达中文的一个汉字了。汉字是很多的,好几万个,单纯的 char 的取值范围已经无法表达的下了。这时候有大牛就想到了,既然用一个char表示不下,那么就用2个char来表示一个汉字,这样就可以解决了,所以窄字节的表示方法就是数字、字母之类的仍然用一个char来表示,一个汉子或者全角字符使用2个 char 来表示。
没错,这样可以解决大多数问题,在中文的系统上能正常的显示中文,在日文的系统上也能正常的显示日文,但是如果把一个在中文系统上写的软件,界面上带有汉字的程序拿到一个日文的Windows操作系统上就会有问题了,乱码了,汉字无法正常显示,同理,把一个界面上带有日文或者韩文的软件拿到中文的系统上也显示乱码!
宽字节
为了解决这个国际化的问题,微软在Windows操作系统中引入了宽字节的功能,即:Unicode,Unicode中规定任意一个字符都占用两个字节的存储空间,即2个char,不管是数字或者字母,还是一个汉字 都占用2个字节。用两个char难免不方面,所以微软直接使用一个新的类型:wchar_t,大家看起来比较陌生,不过他的原型实际上就是 unsigned short,这个大家比较熟悉吧,占用2个字节的存储空间。
所以,微软就是利用Unicode编码来解决这个国际化的问题!
1.2 操作系统及VS编译器对宽窄字节的编码支持:
1.2.1 Windows操作系统提供了两种类型的 API 函数
例如 MessageBox 函数,其实 MessageBox 他只是一个宏,他对应的两个版本的函数分别为:MessageBoxA 和 MessageBoxW
VS编译器中使用的时候系统会根据是否定义了_UNICODE 宏来进行判断当前工程使用的是宽字节的Unicode编码还是窄字节编码,根据这个来决定该使用哪个版本的函数!
如果你的工程没有定义 _UNICODE 宏,也就是非Unicode编码,那么就使用窄字节的 MessageBoxA,
如果定义了,那么就使用宽字节的 MessageBoxW
查看DLL中的导出函数可以使用 depends 这个工具来查看!
备注:Windows 2000 及其以后的 系统都是使用Unicode从头进行开发的,如果调用任何一个Windows API 函数并给它传递一个 ANSI 字符串,那么系统首先要将字符串转换成Unicode,然后将Unicode字符串传递给操作系统。如果希望函数返回ANSI字符串,系统就会先将Unicode字符串转换成ANSI字符串,然后将结果返回给你的应用程序。进行这些字符串的转换需要占用系统的时间和内存。通过从头开始用Unicode来开发应用程序,就能够使你的应用程序更加高效的运行!
1.2.2 编译器对宽窄字节的支持:
VC++ 6.0 默认为窄字节编码
vs2005、vs2008、vs2010、vs2012、vs2013、vs2015、vs2017 等默认都是Unicode编码,当然可以进行工程的设置从而进行编码的转换
备注:从vs2013开始,如果要让工程从默认的Unicode编码转到窄字节编码,需要安装vs2013的多字节补丁才行!
具体在工程的属性对话框中的如下位置可更改编码类型:
1.3 宽窄字符串的优缺点:
宽字节也有缺点,并非一味的用Unicode宽字节。
因为宽字节的占用空间比窄字节多了一倍,所以如果是单纯在本机的话还好,如果是进行字符串的网络传输,那么传输量就会是窄字节的二倍
一般来说只是涉及到界面,或者是跟字符串操作相关的建议大家使用宽字节,其他地方还是可以用窄字节。
微软也为我们提供好了相应的 API 函数,可用于宽字节和窄字节的转换
1.4 复杂的宽窄字节数据类型:
- 窄字节
C/C++ | char | char* | const char* |
---|---|---|---|
微软 | CHAR | PCHAR、PSTR、LPST | LPCSTR |
- Unicode 宽字节:
C/C++ | wchar_t | wchar_t* | const wchar_t* |
---|---|---|---|
微软 | WCHAR | PWCHAR、PWSTR、LPWSTR | LPCWSTR |
- T 通用类型:
TCHAR | TCHAR * 、PTCHAR、PTSTR、LPTSTR | LPCTSTR |
---|---|---|
其中:P代表指针的意思,STR代表字符串的意思,L是长指针的意思,在WIN32平台下可以忽略,C代表const常量的意思,W代表wide宽字节的意思,T大家可以理解为通用类型的意思
可以根据工程中是否定义_UNICODE 宏,来判断当前工程的编码类型是宽字节还是窄字节,之后分别定义成不同的类型,比如:TCHAR 类型,如果工程中定义了_UNICODE 宏,那么就表明工程是宽字节编码,他最终就被定义成 wchar_t 类型,如果工程中没有定义_UNICODE 宏,就表明工程当前是窄字节编码,那么 TCHAR 被最终定义成 char 类型。
其方便性就是修改了工程的编码格式之后不用修改代码,建议编写程序的时候使用通用类型!
参考:https://www.cctry.com/thread-297534-1-1.html
2. 宽窄字节字符串的使用
2.1 宽窄字符串类型指针的定义:
- 窄字节:char *p_str = "hello";
- Unicode宽字节:wchar_t *p_wstr = L"hello";
- 通用类型:TCHAR *p_tstr = _T("hello"); 或者 TCHAR *p_tstr= _TEXT("hello");
- 动态申请内存:TCHAR *pszBuf = new TCHAR[100];
其中,_TEXT 和 _T 是一样的,定义如下:
define _T(x) __T(x)
define _TEXT(x) __T(x)
__T 的最终定义:
#ifdef _UNICODE
#define __T(x) L##x
#else
#define __T(x) x
#endif
其中,##为连接的意思。
2.2 常用的宽窄字节字符串处理函数:
字符串长度:
Ansi:strlen(char *str);
Unicode:wcslen(wchar_t *str);
通用函数:_tcslen(TCHAR *str);
Ansi:int atoi(const char *str);
Unicode:int _wtoi(const wchar_t *str);
通用函数:_tstoi(const TCHAR *str);
字符串拷贝:
- Ansi:strcpy(char *strDestination, const char *strSource);
- Unicode:wcscpy(wchar_t *strDestination, const wchar_t *strSource);
- 通用函数:_tcscpy(TCHAR *strDestination, const TCHAR *strSource);
以上函数不安全,在vs2003等以上版本的编译器中会有warnning警告提示,以下为安全函数(VC++6.0不支持):
- Ansi:strcpy_s(char *strDestination, size_t numberOfElements, const char *strSource);
- Unicode:wcscpy_s(wchar_t *strDestination, size_t numberOfElements, const wchar_t *strSource);
- 通用函数:_tcscpy_s(TCHAR *strDestination, size_t numberOfElements, const TCHAR *strSource);
numberOfElements
Size of the destination string buffer. 目的缓冲区的大小,以字节为单位,不是字符!
size_t unsigned integer,在MSDN中的解释:Result of sizeof operator,也就是说 size_t 是 unsigned integer 即无符号整数。那为什么会有size_t这个类型呢?
因为不同平台的操作系统(32/64)中 int/long 等类型所占的字节并不一样,而 size_t 在不同的平台下有不同的定义。有点类似于TCHAR类型:
#ifndef _SIZE_T_DEFINED
#ifdef _WIN64
typedef unsigned __int64 size_t; //8个字节,64位
#else
typedef _W64 unsigned int size_t; //4个字节,32位
#endif
#define _SIZE_T_DEFINED
#endif
2.3 sizeof 求宽窄字节字符串的注意事项:
2.3.1 宽、窄字节求占用的字节数
窄字节 | char* p_str = "hello"; | strlen(p_str) + 1 |
---|---|---|
宽字节 | wchar_t p_wstr = L"hello";* | (wcslen(p_str) + 1) * sizeof(wchar_t) |
通用类型 | TCHAR p_tstr = _T("hello");* | (_tcslen(p_tstr) + 1) * sizeof(TCHAR) |
定义:
TCHAR *pszBuf = new TCHAR[100];
字符串数组清空:
memset(pszBuf, 0, 100 * sizeof(TCHAR))
这样做是最安全的,不管当前的 TCHAR 是 char 也好,是 wchar_t 也好,都可以满足。
3. 宽窄字节字符串的转换
所有的字符串都用宽字节的Unicode来表示,比如网络发送的字符串就可以用窄字节的,当对方收到之后默认收到的是窄字节的,因为对方的程序可能用宽字节Unicode来写的界面,所以要显示的时候就要转换成Unicode宽字节的字符串。这样就涉及到宽窄字节的转换,类似的情况很常见
3.1使用微软提供的API函数来实现宽窄字节的转换:
WideCharToMultiByte 实现宽字节转换到窄字节
int WideCharToMultiByte(
UINT CodePage,
DWORD dwFlags,
LPCWSTR lpWideCharStr,
int cchWideChar,
LPSTR lpMultiByteStr,
int cbMultiByte,
LPCSTR lpDefaultChar,
LPBOOL lpUsedDefaultChar
);
MultiByteToWideChar 实现窄字节转换到宽字节
int MultiByteToWideChar(
UINT CodePage,//针对窄字节
DWORD dwFlags,
_In_NLS_string_(cbMultiByte)LPCCH lpMultiByteStr,
int cbMultiByte,
LPWSTR lpWideCharStr,
int cchWideChar
);
封装
char *cctryWideCharToAnsi(wchar_t *pWideChar)
{
if (!pWideChar) return NULL;
char *pszBuf = NULL;
int needBytes = WideCharToMultiByte(CP_ACP, 0, pWideChar, -1, NULL, 0, NULL, NULL);
if (needBytes > 0){
pszBuf = new char[needBytes+1];
ZeroMemory(pszBuf, (needBytes+1)*sizeof(char));
WideCharToMultiByte(CP_ACP, 0, pWideChar, -1, pszBuf, needBytes, NULL, NULL);
}
return pszBuf;
}
wchar_t *cctryAnsiCharToWide(char *pChar)
{
if (!pChar) return NULL;
wchar_t *pszBuf = NULL;
int needWChar = MultiByteToWideChar(CP_ACP, 0, pChar, -1, NULL, 0);
if (needWChar > 0){
pszBuf = new wchar_t[needWChar+1];
ZeroMemory(pszBuf, (needWChar+1)*sizeof(wchar_t));
MultiByteToWideChar(CP_ACP, 0, pChar, -1, pszBuf, needWChar);
}
return pszBuf;
}
4. CString的方便之处及优缺点
4.1CString简介**:
MFC中对于CString字符串的使用可以说是很频繁的,CString类也可以说是MFC中针对字符串的一个封装类。其实CString归属于ATL,不过在MFC中一样使用,而且被重点使用。
4.2 CString类的方便之处:
在 C语言中,对于字符串的操作有字符串数组,字符串指针之类的,比如: char* p_str = "hello"; char szbuf[100] = {'h', 'e', 'l', 'l', 'o'}; 再对字符串进行拷贝、连接、比较 的时候也要借助 strcpy、strcat、strcmp 之类的函数,而且还要考虑目标空间是否够用之列的,很是麻烦,一旦操作不好就会导致数组越界了,造成缓冲区溢出。
有的网友会说了,C语言的字符串处理确实比较麻烦,但是我可以使用C++ STL 中的 string 啊,这样要比C语言的方便很多吧,比如: string str = "hello"; str += " world."; bool is_equal = (str == "123"); 这样就不用担心字符串空间是否够用,连接、比较什么的也十分方便,直接 += 或者 == 就可以判断出来了。
其实 C++ STL 中的 string 还提供了很多其他的算法,例如: http://www.cplusplus.com/reference/string/string/ 虽然 C++ STL 中的 string 照比C语言中的字符串处理方便很多,但是这里我要说,跟成熟的字符串处理还是差很多,起码跟 CString来说就差了不少。
比如: trim 操作:去除掉首尾的不可见字符,比如回车,制表符,空格之类的;
reverse 操作:进行字符串的首尾颠倒反转;
upper操作:将字符串中的英文全变成大写字母;
lower操作:将字符串中的英文全变成小写字母;
right操作:直接返回字符串中结尾的指定字符;
span_including操作:返回包含指定字符串中任意一个字符的子串
span_excluding操作:返回不包含指定字符串中任意一个字符的子串
format:格式化字符串
replace:替换字符串中的指定字符
stricmp:不区分大小写进行字符串比较 等
4.3 CString 对于 TCHAR 的封装
针对当前编码,决定当前内部的指针是 char* 还是 wchar_t,
实际上CString 本身是个模板类,实际上有三种:
typedef ATL::CStringT< wchar_t, StrTraitMFC< wchar_t > > CStringW;
typedef ATL::CStringT< char, StrTraitMFC< char > > CStringA;
typedef ATL::CStringT< TCHAR, StrTraitMFC< TCHAR > > CString;
CStringA 内部使用的是 char,
CStringW 内部使用的是 wchar_t,
CString内部使用的是 TCHAR,
所以 CString 本身会根据当前工程的编码决定使用 char* 还是 wchar_t*,这一点也比较方便。 我们可以先简单使用下 CString,之后改变下工程的编码,看看编译结果就知道了。
4.4 CString 类对于宽窄字节的转换
当前工程编码是宽字节Unicode的前提下,CString支持从char* 类型的窄字节字符串来构造和赋值。所以,是可以方便的从窄字节转换到宽字节。但是反之则没有办法实现。
4.5 使用 CString 类的优缺点:
优点:
①、使用方便,包含了很多实现好的操作,包括:trim、reverse、format、replace 等等;
②、无缝兼容MFC;
③、自适应当前工程编码,匹配宽窄字节;
④、能实现从窄字节到宽字节的自动转换工作;
缺点:
①、在非MFC的工程中无法使用,例如:控制台类型的工程,或者Win32类型的API项目;
②、无法完全实现宽窄字节的任意转换;
③、非C++标准,无法跨平台,在除了VC以外的编译器无法使用,更无法在Linux等其他平台上使用。
4.6 解决方案:
扩展 C++ 标准模板库 STL 中的 string 字符串,使其既方便使用,又可以自由的转换宽窄字节,接下来的课程给大家讲解!
5 扩展C++STL中的string方便使用
跨平台的考虑,支持 Windows系统,也要支持 Linux 系统,就必须使用 C++ 的东西了,不能使用 MFC 之类的,因为不跨平台。
但是C++ 标准模板库 STL 中的 stirng 还比较简陋,用起来确实很不方便,于是也是为了自己方便使用,就新写了个 string 扩展的工具类:string_util,实现对 string 功能的全面扩展。把我们平时常用的一些操作统统都封装进去,需要用的时候直接调用就可以了
Windows 程序设计(3) 关于字符串的更多相关文章
- Windows 程序设计
一.Win32 API /******************************************************************** created: 2014/04/1 ...
- windows程序设计01_utf8编码问题
坚持与妥协 从学程序的第一天老师就给我们说源代码应该使用utf8保存.因为先入为主,"源代码应该使用utf8"的观念已经在"学院派"出身的程序员脑子里根深蒂固. ...
- Windows 程序设计(4) MFC 03 -系列学习
本文整体目录和绝大部门内容来自 [鸡啄米网站]的MFC系列文章,欢迎支持原创 (一)VS2010/MFC编程入门之前言 VC++全称是Visual C++,是由微软提供的C++开发工具,它与C++的根 ...
- 关于《Windows程序设计(第五版)》中一个实例程序的疑问
最近一直在看Charlse Petzold的<Windows程序设计>,作为一个新得不能再新的新手,只能先照着书的抄抄源码了,之前的例子一直都很正常,但昨天遇到一个很诡异的BUG. 先看实 ...
- windows 程序设计自学:添加图标资源
#include <windows.h> #include "resource.h" LRESULT CALLBACK MyWndProc( HWND hwnd, // ...
- Windows内核下操作字符串!
* Windows内核下操作字符串! */ #include <ntddk.h> #include <ntstrsafe.h> #define BUFFER_SIZE 1024 ...
- windows程序设计笔记
2014.05.06 新建一个visual C++ -- 常规 -- 空白 的项目,用.c后缀名指定这是一个用C语言来写的windows项目.和C语言的hellworld程序做了一个比较,按照wind ...
- 《Windows程序设计第5版》学习进度备忘
书签:另外跳过的内容有待跟进 __________________学习资源: <Windows程序设计第5版珍藏版> __________________知识基础支持: _________ ...
- MFC Windows程序设计源代码免费下载
本人近期在网上找到了<MFC Windows程序设计>第二版的书内程序的源代码,特意上传CSDN上面,供学习MFC的程序猿们免费下载. 源代码下载: http://download.csd ...
随机推荐
- PLC可编程控制器的结构和工作原理
PLC的可编程控制器由的功能结构由cpu中央处理器,存储器和输入输出借口三部分组成 CPU Cpu的功能是完成plc所有的的控制和监视, Cpu中央处理去由控制器,寄存器,运算器.通过数据总线,地址总 ...
- layui 数据表格按钮事件绑定和渲染
先看效果图 使用两种渲染方法: 1.toolbar引入模板 顶部的添加和删除按钮,右侧的三个筛选,打印,导出按钮 基础参数属性:toolbar:'#demo2', //创建 删除 添加按钮模板 < ...
- protocbuf的简单理解
之前通信协议替换为protocbuf!新老交替,很多不同看法,也提出来一些负面因数: 1.老的内部通信协议体已经有一段时间了,稳定熟悉! 2.通过通信结构体进行交互,实际上并没有序列化和反序列化的过程 ...
- C# 基础之参数修饰符
参数传参的时候一共有四种传递方式: 一.无修饰符传参 也就是说没有传参修饰符,这种情况传过去的是一个副本,本体是不会被改变的 二.out传参修饰符 在传参的参数全面加一个out: public voi ...
- Maven快速入门(二)手动创建maven项目hellomaven
之前讲过Maven介绍及环境搭建,介绍了maven的作用和如何搭建maven环境.接下来就以一个helloworld的例子来说一说如何创建maven项目以及maven项目的项目结构,最后讲maven如 ...
- 被缠上了,小王问我怎么在 Spring Boot 中使用 JDBC 连接 MySQL
上次帮小王入了 Spring Boot 的门后,他觉得我这个人和蔼可亲.平易近人,于是隔天小王又微信我说:"二哥,快教教我,怎么在 Spring Boot 项目中使用 JDBC 连接 MyS ...
- bypass disable_function的方法及蚁剑插件bypass-php-function使用
bypass disable_function的方法及蚁剑插件bypass-php-function使用 在学习php时,发现有许多函数会对网站或系统造成很大危险隐患,常见的危险函数有: phpinf ...
- Rocket - debug - DMI
https://mp.weixin.qq.com/s/70BoeS7z4aBZK24zxdZzXA 简单介绍DMI的实现. 1. DMIConsts 定义DMI使用的常量: 其中: a. dmiDat ...
- 高性能可扩展mysql 笔记(四)项目分区表演示
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 登录日志的分区 如何为Customer_login_log表分区? 从以下两个业务场景入手: 用户每次登 ...
- OkHttp,一次无奈的使用
一次使用OKHTTP的心痛历程 最近由于一些不得已的原因,接触到了OKHttp,说起来也挺Dan疼的,之前同事将生产附件上传地址配置成了测试地址,还好数量不多,没有造成太大的影响,况且的是这位同事又离 ...