C++:字符串编码与字符串

1、编码
在讲字符串之前首先说说编码方式。字符串在程序用用数据类型进行存储,同时数据类型存储的也可以是不同编码方式的字符串。总的来说,常用编码方式有以下几种:
ASCII:最古老的编码方式,只使用后7位,可以存储英语大小写、数字及几乎所有常用半角符号。
ISO-8859-1:西欧地区使用的编码方式,兼容ASCII码,在最高位为1时用于描述西文符号。
GB2312/GBK/GB18030:这个是天朝用户专用编码方式,兼容ASCII码,对于英文字符使用1字节进行存储,对于中文使用2字节进行存储,同时两个字节的最高位均为1。值得注意的是,GB2312在Win32开发中常常被称作Ansi编码;其次,GBK为GB2312的扩充,GB18030为GBK的扩充。以前它们是不同的编码方式,但现在也没有严格的划分,通常三者代表同一种编码方式。
BIG-5:也是天朝用户专用编码方式,兼容ASCII码,与GB2312不同的是,它只能编码繁体字,不能编码简体字。
UTF-16/UCS-2:这两个名称所代表的是同一种编码方式,使用两个字节来存储一个中文字符或者一个字母,不兼容ASCII码。这种编码方式也划分为两种不同的子编码方式,分别为UCS-2 Big Endian与UCS2 Little Endian。常说的UTF-16或者UCS-2通常指的是UCS-2 Big Endian。这两种子编码方式的区别为,Big Endian高字节在前,低字节在后;Little Endian低字节在前,高字节在后。这种编码方式在Win32开发中常常被称作Unicode编码,但它属于一种误称;另外,这种编码方式有点浪费存储空间,并且也不能描述世界上所有的符号,相比其他编码,唯一优势是,字符串长度就等于字符个数。
UTF-8:使用最广泛的编码方式,没有之一!几乎所有的网页、XML描述文件、Json数据文件、大多数数据库以及Linux系统均使用的编码方式,相比而言GB2312、UTF-16只有在Windows平台用用而已,仗着Windows平台用的人多,所以也作为常用的编码方式,对于英文字符使用1字节进行存储,因此兼容ASCII编码;它同时也能编码世界上所有的文字,对于汉字而言这种编码方式使用3个字节进行存储,但理论上可以使用2、3、4、5或6字节来编码一个特定字符。
UTF-32/UCS-4:由于UTF-16不能编码所有的编码方式,但发明这编码的人不服,爱搞事,所以发明了4个字节来编码一个字符的编码方式,理论上可以描述世界上所有的字符,但由于一个字母都需要4个字节,过于浪费存储空间,所以这种编码方式几乎没有人使用。
以上是需要了解的编码方式,除了上面几个之外,不同地方也有他们自己的编码方式,以下为不完全统计:
西欧语系:ISO-8859-1
东欧语系:ISO-8859-2
土耳其语:ISO-8859-3
波罗的语:ISO-8859-4
斯拉夫语:ISO-8859-5
阿拉伯文:ISO-8859-6
希腊文:ISO-8859-7
希伯来文:ISO-8859-8
日文:Shift-JIS
韩文:EUC-KR
……

2、C语言中的字符串
编码方式就不细究了,这个讲不完。接下来进入正题:字符串的处理
C++中的字符串基于C语言的字符串,所以先说说C语言中的字符串。
以上几种常用编码方式,,有点多了,我重点只说说三种:
2.1、GB2312编码
使用char*或者char[]来存放一个字符串,Win32开发中以大写字母A结尾的API或者C语言自带函数均为这种编码方式的字符串。比如strlen(C语言获取字符串长度用的API)、MessageBoxA(Win32用于显示提示框的API)。通常使用双引号 "" 来表示一个字符串。
2.2、UTF-16编码
使用wchar_t*或者wchar_t[]来存放一个字符串,Win32开发中以大写字母W结尾的API使用这种编码方式的字符串。比如MessageBoxW(Win32用于显示提示框的API)。通常使用L+双引号的形式 L"" 来表示一个字符串。
2.3、在讲第三种编码方式前,重点说说以上两种编码方式
首先,Win32中API定义了很多字符串数据类型:LPSTR、LPWSTR、LPCSTR、LPCWSTR、LPTSTR、LPCTSTR……等等等等,看起来好复杂的样子……其实原理非常简单,下面我说说这个的原理:
字符串以P或LP开头,以STR结尾。STR的意思大家都懂吧,字符串的英文string的简写;P的意思是“指针”,Pointer,学过C语言的应该都知道指针的含义。LP的写法源于历史原因:在计算机只有16位的时候,惜KB如金的年代,指针占两个字节有些浪费,所以,16位的系统通常有两种指针,一种为短指针short pointer,占1字节,代表偏移量-128~127,另一种为长指针long pointer,占2字节,用于指示内存中一个固定区域。然后计算机到达32位,不再那么需要节约一两字节内存,所以指针都以4个字节表示,但同时也继承了程序猿的习惯,有P与LP两种写法,都代表同一个含义。
对于最基本的字符串类型LPSTR,表示的数据类型为char*;然后,P/LP与STR之间,一般有三个字母,T、W与C。C代表const,比如LPCSTR的实际类型为const char*;W代表UCS-2编码方式字符串,比如LPWSTR代表wchar_t*;T代表这个类型根据宏来动态解释。比如LPTSTR如果定义了UNICODE宏,那么代表wchar_t*类型;反之代表char*类型。这种写法有一个好处就是,同一套代码可以生成两种不同编码方式的程序,也是推荐的写法。
对于字符串来讲,GB2312与UCS-2使用不同的写法,这时候可以引用一个头文件tchar.h,然后,统一用_T ("")的写法,那么就可以在Unicode或Ansi被分别解析了。
如果不想使用这头文件也可以自己定义一个:

1
2
3
4
5
#ifdef UNICODE
#define _T(str) L##str
#else
#define _T(str) str
#endif

两个#号代表连接前后两个符号,因此Unicode环境下_T ("")写法与L""写法没有区别。
然后是常用字符串函数,比如字符串复制,推荐使用lstrcpy写法,它在有无UNICODE宏的情况下也是分别解析为lstrcpyA与lstrcpyW,非常方便。常用字符串函数中有一个坑就是_s结尾的函数。Windows那帮人认为C语言的很多东西不安全,于是自己改写了一堆库,使用_s结尾,具体含义为,如果以_s结尾的API,那么对于字符串参数来说,都需要传入一个整数代表字符串的长度。如果有这方面的“安全”需要,那么函数都加上_s,否则,定义一个如下的宏:

1
#define _CRT_SECURE_NO_WARNINGS

定义了这个宏之后,就不会报那种编译错误了。
2.4、UTF-8编码
这个就麻烦了,Linux平台还好,常用字符串就是UTF-8格式,Win32平台就麻烦了,既不能直接定义,也不能直接使用。比如需要访问一个网页,下载下来的内容直接显示的话绝壁是乱码。这个就涉及到编码转换了。在 Windows下编码转换 这篇文章中有提到编码的转换,转为Win32通用编码方式后才能显示。
如果需要直接定义的话,那么通常方式是定义以下宏:

1
#pragma execution_character_set("utf-8")

然后,所有定义""这种风格的字符串的编码方式全部为UTF-8了。值得注意的是,这种编码使用char*存储,适合用C风格字符串函数比如lstrcpyA、lstrcatA等等,但不适用于Windows函数,比如MessageBoxA,如果传入UTF-8编码字符串并且带中文那么一定会乱码。

3、说完了C语言的字符串,接下来说说C++中的字符串
Windows上C++字符串通常分三种,标准C++字符串,MFC字符串与COM+字符串。
3.1、标准C++字符串,用std::string或std::wstring表示。由于没有通用的宏,所以通常使用前,用户会自定义一个宏来代表字符串。比如:

1
2
3
4
5
#ifdef UNICODE
typedef std::wstring string_t;
#else
typedef std::string string_t;
#endif

当然,如果涉及到其他比如文件操作或者字符串流操作,还得分别定义,不过胜在“标准”。
这种字符串还有一个问题就是出现时间很早,所以很多通用的简写都没有,比如格式化一个字符串,还得调用C的库sprintf等,所以,在下收集了一些网上的轮子,做成了一个简单的字符串处理库 C++中std::string实现trim、format等函数,可以实现format等操作。
3.2、MFC字符串,也就是大众所熟知的CString,这个类分两个版本,CStringA与CStringW,分别对应Ansi与Unicode环境。封装了format等简单的操作,但有个问题就是,MFC由于近期更新太少,属于接近被淘汰的库,但以前很多比较老的大型项目都在用,并且跨版本不兼容。所以这就有点尴尬了,同样是M$大大的东西,VC6上的代码还不能直接放在VS上编译。这东西,能不沾就尽量别沾吧,以免升级环境时麻烦。
3.3、COM+字符串。众所周知M$大大习惯一个系统分很多开发组然后让他们互相竞争,这就给开发者造成了一个麻烦比如COM+上面的所有字符串都不是上面常见的字符串。比如_bstr_t格式字符串。不过还好这种字符串支持直接转换char与wchar_t字符串类型。它内部使用BSTR格式来存储字符串,所谓BSTR实际上就是wchar_t*。然后是COM+操作的接口,很多是变体类型:VARIANT或者_variant_t类型,后者好说,是一个类可以自由转换,前者就稍微有点麻烦了,需要手工判断类型然后分别处理。

4、没想到吧这儿还有一小节,用来讲传说中的System::String
C++/CLR是一个奇特的存在,诞生于微软的.NET战略,用于将C#、VB.NET等语言编译为IL中间语言,但通用框架怎么能少了C++?另外C++也无法直接进入托管环境,所以微软强推了一种新的C++托管框架,名为C++/CLR,或者C++/CLI,或者C++.Net,反正都代表一个框架。这个框架还是挺好用的,从C++开发者角度来说,可以随时进入托管环境调用托管库,也可以随时调用非托管代码,相当方便的框架,由于语法比较奇特,所以被一些不懂C++的人喷。所以,下次如果遇到有人喷C++/CLR,那么说明他不懂C++,准确度超过90%,哈哈~
托管字符串基本语法为:

1
System::String ^str = gcnew System::String(L"aaa");

由于托管字符串使用了托管的指针,与C++标准指针不兼容,所以使用^符号来声明托管指针;其次托管里面只有指针不能直接定义字符串;另外托管的new和delete与C++中的不兼容,所以M$大大创造了两个关键字gcnew与gcdelete,用于托管指针的分配与释放;最后,托管字符串内部是使用UCS-2编码进行存放,所以传递C风格字符串前最好传入UCS-2编码字符串。
托管字符串的使用算是比较简单的,除了奇特的托管语法外,其他都和C#用法一样,不明白的童鞋看看C#教程也就懂了托管的用法了。
还有比较重要的一点是,托管字符串与非托管字符串之间的互相转换:

1
2
3
4
5
6
7
8
9
10
11
#include <vcclr.h>
 
//……
 
// 托管转非托管
System::String ^clrstr = gcnew System::String (L"aaa");
pin_ptr<const wchar_t> wch = PtrToStringChars (clrstr);
wchar_t *wstr = wch;
 
// 非托管转托管
clrstr = gcnew System::String (wstr);

原因讲过,由于CLR内部使用wchar_t字符串结构存储,所以只能与UCS-2编码互转,如果需要与GB2312或者UTF-8编码互转,那么依旧是参考 Windows下编码转换 这个~

发布者

fawdlstty

又一只萌萌哒程序猿~~

发表评论

电子邮件地址不会被公开。 必填项已用*标注