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) 关于字符串的更多相关文章

  1. Windows 程序设计

    一.Win32 API /******************************************************************** created: 2014/04/1 ...

  2. windows程序设计01_utf8编码问题

    坚持与妥协 从学程序的第一天老师就给我们说源代码应该使用utf8保存.因为先入为主,"源代码应该使用utf8"的观念已经在"学院派"出身的程序员脑子里根深蒂固. ...

  3. Windows 程序设计(4) MFC 03 -系列学习

    本文整体目录和绝大部门内容来自 [鸡啄米网站]的MFC系列文章,欢迎支持原创 (一)VS2010/MFC编程入门之前言 VC++全称是Visual C++,是由微软提供的C++开发工具,它与C++的根 ...

  4. 关于《Windows程序设计(第五版)》中一个实例程序的疑问

    最近一直在看Charlse Petzold的<Windows程序设计>,作为一个新得不能再新的新手,只能先照着书的抄抄源码了,之前的例子一直都很正常,但昨天遇到一个很诡异的BUG. 先看实 ...

  5. windows 程序设计自学:添加图标资源

    #include <windows.h> #include "resource.h" LRESULT CALLBACK MyWndProc( HWND hwnd, // ...

  6. Windows内核下操作字符串!

    * Windows内核下操作字符串! */ #include <ntddk.h> #include <ntstrsafe.h> #define BUFFER_SIZE 1024 ...

  7. windows程序设计笔记

    2014.05.06 新建一个visual C++ -- 常规 -- 空白 的项目,用.c后缀名指定这是一个用C语言来写的windows项目.和C语言的hellworld程序做了一个比较,按照wind ...

  8. 《Windows程序设计第5版》学习进度备忘

    书签:另外跳过的内容有待跟进 __________________学习资源: <Windows程序设计第5版珍藏版> __________________知识基础支持: _________ ...

  9. MFC Windows程序设计源代码免费下载

    本人近期在网上找到了<MFC Windows程序设计>第二版的书内程序的源代码,特意上传CSDN上面,供学习MFC的程序猿们免费下载. 源代码下载: http://download.csd ...

随机推荐

  1. thymeleaf将对象ModelList数据抛到HTML页面

  2. spring——bean自动装配

    注意:自动装配功能和手动装配要是同时使用,那么自动装配就不起作用. beans.xml <?xml version="1.0" encoding="UTF-8&qu ...

  3. ngnix随笔一

    nginx安装及启动 1.用yum源安装,首先配置yum源 在“/etc/yum.repo.d/”下创建yum源文件 例如:cd /etc/yum.repo.d/nginx.repo [nginx-s ...

  4. 自定义cursor鼠标 图片

    1.CSS3自定义鼠标样式 最近想要使用自定义鼠标样式,看了cursor的样式不好看,就想到cursor属性能不能自定义图片,翻看了下CSS3文档,发现是可以的 格式为:cursor:url('图片u ...

  5. 读Pyqt4教程,带你入门Pyqt4 _003

    编程中的一个重要事情是布局管理,布局管理是如何在窗体上摆放窗口组件.可以有两种方式进行管理:绝对定位或使用布局类. 绝对定位 程序员用像素指定每个控件的位置和尺寸.使用绝对定位时,你必须理解几件事情. ...

  6. Java的基本数据类型及其封装类

    Java的基本数据类型及其封装类 一.8种基本数据类型 二.基本数据类型的包装类及大小 三.基本数据类型和封装类的区别 定义不同.封装类是对象,基本数据类型不是: 使用方式不同.封装类需要先new初始 ...

  7. Chisel3 - Tutorial - ByteSelector

    https://mp.weixin.qq.com/s/RQg2ca1rwfVHx_QG-IOV-w   字节选择器.   参考链接: https://github.com/ucb-bar/chisel ...

  8. PYTHON-函数调用函数名

    def fun1(x): print('......................') x() def fun2(): print('**********************') fun1(fu ...

  9. 学Linux驱动: 应该先了解驱动模型

    [导读] Linux设备林林总总,嵌入式开发一个绕不开的话题就是设备驱动开发,在做具体设备驱动开发之前,有必要对Linux设驱动模型有一个相对清晰的认识,将会帮助驱动开发,明白具体驱动接口操作符相应都 ...

  10. 题解 P5329 【[SNOI2019]字符串】

    用栈的做法来水一发. 首先我们有一个暴力的做法,枚举每个被删除的字符,然后排序输出,时间复杂度:$ O ( N \times N \times LogN ) $ . 然后我们观察一下数据,发现有一个数 ...