字符历史
https://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
ASCII American Standard Code for Information Interchange 7 位,是最老的 ANSI American National Standards Institute 1 字节,即 8 位 是微软搞的,前 128 位和 ASCII 一样
这个"ANSI"这个单词,有时候也能够表示本地化的 MBCS,就是记事本打开 txt 文件存储为 ANSI 格式
这里的 ANSI 实际上就是指 GBK,注意 GB18030 不是 GBK
MBCS多字节字符集 不同国家对 ASCII 进行了扩充,比如中国 GBK 系列 两个字节代表一个汉字 但是问题是,国际上,一个编码值,不同解释,有不同的字
Unicode宽字节字符集
有很多种编码方式,utf8 是其中一种编码方式
https://blog.csdn.net/weixin_39671621/article/details/111119560
源文件的编码:
UTF-8 without BOM 是最推荐的。这也是 Linux 上源文件默认的标准。
不说具体有没有 BOM,直接说 UTF-8,就指代没有 BOM。
C++字符类型
然后是 C/C++里,和 char 有关的变体
基本的有两个,即自带的类型
(wchar_t 其实好像定义在了<wchar.h>中,C 语言,C++是<cwchar>)
char :一定是 1 字节 wchar_t :2~4,根据平台决定
t 的意思是 type,wchar_t 能够灵活的表示 MBCS 或者 Unicode,这根据宏的设置。
wchar_t 分配到的内存,在 Windows 通常是 16 位,使用 UTF16,在 Unix/Linux 通常是 32 位。
注意:wchar_t 本身的大小是固定的,只不过是表示的 Unicode 一个字,可以占不同位,一个类型必须是固定大小才能编译嘛
typedef wchar_t WCHAR; // wc, 16-bit UNICODE character (winnt.h)这可以使用sizeof(wchar_t)来判断
但是,话又说回来,有一个编译选项是“将 WChar_t 视为内置类型”,它默认是开启
开启后,wchar_t 的地位如图 int。不开启,就是 typedef wchar_t short 之类的(这其实不推荐)
问题:对于 utf-8,有的字符占用了四个字节,而 windows 只给了两个字节的空间?
首先,我们要分清楚码点和具体某种编码方式所产生的字节序列,这不是一致的
常见的中文,都在 BMP 基本多文种平面 中,因此一个中文“中”的码点是,U+4E2D。
“中”这个字,在 UTF-16 下,是 4E 2D 没问题,但是在 utf-8 下是 E4 B8 AD。
一个 utf-8 的中文,占 3 字节,罕见字占 4 字节。没有占 2 字节的中文。
一个 wchar_t 无法表示超过两个字节的 utf-8,在 Windows 上。
如果硬是要表示😀,则需要用代理项对。
因为 GBK 是无法表示 emoji 的,所以打出😀
如果你是 GBK 源文件,VS 会告诉你保存为 Unicode。如果是 Unicode,则可以放在 const 的字面量里,而不报错。
VSCode 里用 gbk 编写 emoji,是能显示出来,但是保存再打开,我发现 emoji 消失了
以下例子基于“你”这个汉字
“你”的编码为:
Unicode 码点 U+4F60 (对应 10 进制数字20320),因此“你”属于 BMP(U+0000 到 U+FFFF)
UTF8 E4 BD A0 注意这里是三个字节
GBK C4 E3 (对应 10 进制数字 50403)
UTF16 LE 60 4F 对于 BMP 内的字符,UTF16 直接存其码点,Windows 按小端存
输出 C4 这个字节,以有符号整数,它的二进制是 1100 0100 ,然后计算机一般是补码,所以它是负的,然后取反加一得 0011 1100 算出来是 60。所以这个数是 -60
50436 在 Unicode 里是쓣这个东西。而 GBK 中不存在 604F 的汉字。
工程实际
(1)VS 字符集设置:使用 Unicode 字符集/使用多字节字符集。通知编译器使用指定的字符集,帮助解决本地化问题。
首先,这个选项并不会修改你源文件的编码方式。它只决定 UNICODE 和 _UNICODE 是否被定义。
而这个宏影响 Win32 API 的调用,即 MessageBox 在 Unicode 下是 MessageBoxW,ANSI 下是 MessageBoxA。
也影响 TCHAR 系列的解释,但是如果显式的写 wchar_t,则没有任何影响,因为 wchar_t 和 UNICODE 宏是无关的。
(2)源码保存方式
(3)L
windows 本身默认了 UTF16 编码,所以和你的源代码保存方式也没关系。
源代码的保存方式,只决定了编译器能不能看懂源代码,就是 cl.exe 本身是用系统默认代码页解析。
但是解析到字符串字面量的时候,只要写了 L,就解释为 UTF16。
如果不写 L,那么就是按系统默认代码页解析(即原始字节直接拷过去)
在默认都写 L 的情况下,编译器会把字面量解释为 UTF-16,然后编译二进制文件里
(4)char 和 wchar_t
开始编程,进行配置
设置字符集宏(VS 中设置,不用显示写)
用 Unicode,必须定义UNICODE 和 _UNICODE
用 MBCS 则什么都不用定义
注意,这两个宏,是影响了
TCHAR 等宏的解释方式,以及 API 调用(比如底层调用 MessageBoxW 还是 MessageBoxA)
貌似和源代码用什么方式编辑,没关系
(源代码保存为 utf-8,选文件->高级保存选项)
固有宏
和 TCHAR 有关的宏在 #include <tchar.h> 中 (这玩意是一个 windows 特有的)
可变类型宏:
TCHAR 可根据编译开关(UNICODE 宏),解释为 char 或者是 wchar_t
LPCTSTR(long pointer to a const tchar string) 即 const char* 或 const wchar_t*
LPTSTR 即 char* 或 wchat_t*
固定类型宏:
CHAR = char
WCHAR = wchar_t
LPCSTR = const char*
LPCWSTR = const wchar_t*
固定指针类型宏:
LPSTR = char*
LPCSTR = const char*
LPWSTR = wchar_t*
LPCWSTR = const wchar_t*
特点:LP + 空/C + 空/W/T + STR
字符串常量宏:
字符串前缀,也可以使用在字符前面。 L 表示宽字符,则后续内容将采用 wchar_t 来解释。
不写 L,则解释为 char,写 L 则解释为 wchar_t。wchar_t 的也必须写 L 才行。
L:
L"你好,世界" L 适用于宽字符,生成一个 const wchar_t *类型的字面量
即应该
const wchar_t* wideStr = L"你好,世界";T 和_T:
这两个是一回事,根据 UNICODE 宏,来判断是 L 还是没有
LPCTSTR wideStr = T("你好,世界");值得注意的是 T 宏,将字符串用括号包裹了,而 L 不用
TEXT:
TEXT 的用法和 T 完全一致,可以替换。只不过只适用于 Windows
总结:其实走到这里,以上的东西,都是适用于 C 语言的,还没到 C++呢。wchar_t 是灵活的表示 MBCS 还是 Unicode,而 TCHAR 是灵活的表示 char 和 wchar_t。
C++字符串处理
std::wstring
标准库给我们提供了一个处理 wchar_t 的类,功能类似于 string 可以处理 char
可以通过 const wchar_t* 初始化
std::string file_name = "box_model.step";
std::wstring file_name = L"box_model - 副本。step";
std::wstring file_name2(L"box_model - 副本。step");头文件的引入是#include <string>,不存在<wstring>,string 中定义了 string 和 wstring。这两个的关系是
typedef basic_string<char> string;
typedef basic_string<wchar_t> wstring;
除此之外还有 u16string 和 u32string,这里按下不表。
到目前为止,没有一个类,来存放 TCHAR,要么 char 要么 wchar_t。
CString
CString 是 MFC 的字符串类型
引入 #include <afx.h> 这是 MFC 的基本类,包括 CString。但是无法跨平台。
而 #include <afxwin.h> 则更多和窗口有关系,用来写完整的 MFC 程序
#include <stdafx.h> 是一个预编译头文件,VS2017(不含)以后换成 pch.h
CString 是一个基于 TCHAR 的类,并提供了字符串的常见算法,以及重载了+号
QString
QString 是 Qt 的字符串类型,基于 QChar。QChar 是一个两字节的变量类型。
个人理解
使用 TCHAR 是因为,旧的纯英文代码逐渐国际化,老函数的接口可能还是 char 的,比如比较函数,要求你传 char 指针。但是你新增的国际化字符串,肯定不想保存在 char 里,肯定选 wchar_t 存,那么再用老接口的时候就要换。如果你确定了,就是要从头开始一个 Unicode 编程,那么一开始就用 wchar_t,不碰 TCHAR 似乎也行。
实战演练
STEPControl_Reader::ReadFile(const Standard_CString filename)
typedef const Standard_Character* Standard_CString;
typedef char Standard_Character;
如何给这个函数传递一个中文的文件名?
1、首先,文件名最开始要用 wchat_t 来存,因此考虑 wstring
2、wstring 不能直接转换为 char*
3、string 使用 c_str 可以转换为 char *指针,此指针仅在字符串未修改的情况下有效,由 string 自动管理
4、将 wstring 中的 UTF16 内容,转换为 UTF8 的内容存在 string 里
展望
目前,似乎倾向是,不管 Linux 还是 Windows
源代码全用 UTF-8 Without BOM 存,然后二进制也存 UTF-8 Without BOM(即告诉 cl.exe 存 utf-8)
然后调 Win32 的时候,才去转换,不用 wchar_t,因为它跨平台费劲,不一样长
C++也是意识到了,放弃 wchar_t,推出 char8_t char_16_t 等
虽然没有任何一个类型,可以表示 utf-8 这种变长的编码,因为一个类型编译前需要确定大小
但是 char8_t 大小在任何平台上,必须是 1 个字节(虽然和 char 一样大,但是类型安全,你不需要猜测了,知道它就是 utf-8,而不是其它的)
然而不要迷信这个东西,C 语言没有 char8_t,所以你懂的,很多项目继续直接用 char 来表示字节流,继续用 std::string 来放 utf-8
这种做法在逻辑上已经接近 Rust 的 String
其它语言
目前大概是三种
C/C++ 偏底层所以方式比较多也比较混乱
Java、JS、C# 年纪大,都是 UTF-16
GO/Rust 年轻,都是 UTF-8(牺牲随机访问)
Python3 动态管理,如果全英文,是 ASCII,有中文,会提升,有 Emoji,会再次提升,反正很复杂。但是对程序员来说,[] 永远是人眼逻辑上的字符
去重
C++ 开启编译优化后,字符串字面量会被合并到同一个静态区
现在的 C++字符串都不会共享(很早之前 gcc 会,但是多线程很蠢,所以放弃了)
现在的 std::string 追求的是确定性:每个 string 拥有自己独立的内存
SSO(短字符串优化): 如果字符串很短(通常小于 15-22 个字节),std::string 不会去堆上分配内存,而是直接存在对象内部的缓冲区里