现代C++新特性 新字符类型char16_t和char32_t

2023-11-02 13:50

本文主要是介绍现代C++新特性 新字符类型char16_t和char32_t,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 文字版PDF文档链接:现代C++新特性(文字版)-C++文档类资源-CSDN下载

 在C++11标准中添加两种新的字符类型char16_t和char32_t,它们分别用来对应Unicode字符集的UTF-16和UTF-32两种编码方法。在正式介绍它们之前,需要先弄清楚字符集和编码方法的区别。

字符集和编码方法

通常我们所说的字符集是指系统支持的所有抽象字符的集合,通常一个字符集的字符是稳定的。而编码方法是利用数字和字符集建立对应关系的一套方法,这个方法可以有很多种,比如Unicode字符集就有UTF-8、UTF-16和UTF-32这3种编码方法。除了Unicode字符集,我们常见的字符集还包括ASCII字符集、GB2312字符集、BIG5字符集等,它们都有各自的编码方法。字符集需要和编码方式对应,如果这个对应关系发生了错乱,那么我们就会看到计算机世界中令人深恶痛绝的乱码。不过,现在的计算机世界逐渐达成了一致,就是尽量以Unicode作为字符集标准,那么剩下的工作就是处理UTF-8、UTF-16和UTF-32这3 种编码方法的问题了。

UTF-8、UTF-16和UTF-32简单来说是使用不同大小内存空间的编码方法。

UTF-32是 简单的编码方法,该方法用一个32位的内存空间(也就是4字节)存储一个字符编码,由于Unicode字符集的 大个数为0x10FFFF(ISO 10646),因此4字节的空间完全能够容纳任何一个字符编码。UTF-32编码方法的优点显而易见,它非常简单,计算字符串长度和查找字符都很方便;缺点也很明显,太占用内存空间。

UTF-16编码方法所需的内存空间从32位缩小到16位(占用2字节),但是由于存储空间的缩小,因此UTF-16 多只能支持0xFFFF个字符,这显然不太够用,于是UTF-16采用了一种特殊的方法来表达无法表示的字符。简单来说,从0x0000~0xD7FF以及0xE000~0xFFFF直接映射到Unicode字符集,而剩下的0xD800~0xDFFF则用于映射0x10000~0x10FFFF的Unicode字符集,映射方法为:字符编码减去0x10000后剩下的20比特位分为高位和低位,高10位的映射范围为0xD800~0xDBFF,低10位的映射范围为0xDC00~0xDFFF。例如0x10437,减去0x10000后的高低位分别为0x1和0x37,分别加上0xD800 和0xDC00的结果是0xD801和0xDC37。

幸运的是,一般情况下0xFFFF足以覆盖日常字符需求,我们也不必为了UTF-16的特殊编码方法而烦恼。UTF-16编码的优势是可以用固定长度的编码表达常用的字符,所以计算字符长度和查找字符也比较方便。另外,在内存空间使用上也比UTF-32好得多。


后说一下我们 常用的UTF-8编码方法,它是一种可变长度的编码方法。由于UTF-8编码方法只占用8比特位(1字节),因此要表达完数量高达0x10FFFF的字符集,它采用了一种前缀编码的方法。这个方法可以用1~4字节表示字符个数为0x10FFFF的Unicode(ISO 10646)字符集。为了尽量节约空间,常用的字符通常用1~2字节就能表达,其他的字符才会用到3~4字节,所以在内存空间可以使用UTF-8,但是计算字符串长度和查找字符在UTF-8中却是一个令人头痛的问题。表1-1展示了UTF-8对应的范围。

代码范围 十六进制UTF-8 二进制注释
000000~00007F 128个代码0zzzzzzzASCII字符范围,字节由零开始

000080~0007FF 1920个代

110yyyyy 10zzzzzz第1字节由110开始,接着的字节由10开始

000800~00D7FF 00E000~

00FFFF 61440个代码

1110xxxx 10yyyyyy

10zzzzzz

第1字节由1110开始,接着的字节由10开始

010000~10FFFF 1048576

个代码

11110www 10xxxxxx

10yyyyyy 10zzzzzz

将由11110开始,接着的字节从10开始

使用新字符类型char16_t和char32_t

对于UTF-8编码方法而言,普通类型似乎是无法满足需求的,毕竟普通类型无法表达变长的内存空间。所以一般情况下我们直接使用基本类型char进行处理,而过去也没有一个针对UTF-16和UTF-32的字符类型。到了C++11,char16_t和char32_t的出现打破了这个尴尬的局面。除此之外,C++11标准还为3种编码提供了新前缀用于声明3种编码字符和字符串的字面量,它们分别是UTF-8的前缀u8、UTF-16的前缀u和UTF-32的前缀U:

char utf8c = u8'a';             // C++17标准
// char utf8c = u8'好'; 
char16_t utf16c = u'好';
char32_t utf32c = U'好';
char utf8[] = u8"你好世界";
char16_t utf16[] = u"你好世界";
char32_t utf32[] = U"你好世界";

在上面的代码中,分别使用UTF-8、UTF-16和UTF-32编码的字符和字符串对变量进行了初始化,代码很简单,不过还是有两个地方值得一提。

char utf8c = u8'a'在C++11标准中实际上是无法编译成功的,因为在C++11标准中u8只能作为字符串字面量的前缀,而无法作为字符的前缀。这个问题直到C++17标准才得以解决,所以上述代码需要C++17的环境来执行编译。

char utf8c = u8'好'是无法通过编译的,因为存储“好”需要3字节,显然utf8c只能存储1字节,所以会编译失败。

wchar_t存在的问题

在C++98的标准中提供了一个wchar_t字符类型,并且还提供了前缀L,用它表示一个宽字符。事实上Windows系统的API使用的就是 wchar_t,它在Windows内核中是一个 基础的字符类型:

HANDLE CreateFileW(LPCWSTR lpFileName,…
);CreateFileW(L"c:\\tmp.txt", …);

上面是一段在Windows系统上创建文件的伪代码,可以看出Windows为创建文件的API提供了宽字符版本,其中LPCWSTR实际上是 const wchar_t的指针类型,我们可以通过L前缀来定义一个 wchar_t类型的字符串字面量,并且将其作为实参传入API。

讨论到这里读者会产生一个疑问,既然已经有了处理宽字符的字符类型,那么为什么又要加入新的字符类型呢?没错,wchar_t确实在一定程度上能够满足我们对于字符表达的需求,但是起初在定义wchar_t时并没有规定其占用内存的大小。于是就给了实现者充分的自由,以至于在Windows上wchar_t是一个16位长度的类型(2字节),而在Linux和macOS上wchar_t却是32位的(4字节)。这导致了一个严重的后果,我们写出的代码无法在不同平台上保持相同行
为。而char16_t和char32_t的出现解决了这个问题,它们明确规定了其所占内存空间的大小,让代码在任何平台上都能够有一致的表现。

新字符串连接

由于字符类型增多,因此我们还需要了解一下字符串连接的规则:如果两个字符串字面量具有相同的前缀,则生成的连接字符串字面量也具有该前缀,如表1-2所示。如果其中一个字符串字面量没有前缀,则将其视为与另一个字符串字面量具有相同前缀的字符串字面量,其他的连接行为由具体实现者定义。另外,这里的连接操作是编译时的行为,而不是一个转换。

源代码

等同于

源代码

等同于

源代码

等同于

u"a" u"b"

u"ab"

U"a" U"b"

U"ab"

L"a" L"b"

L"ab"

u"a" "b"

u"ab"

U"a" "b"

U"ab"

L"a" "b"

L"ab"

"a" u"b"

u"ab"

"a" U"b"

U"ab"

"a" L"b"

L"ab"

需要注意的是,进行连接的字符依然是保持独立的,也就是说不会因为字符串连接,将两个字符合并为一个,例如连接"\xA" "B"的结果应该是"\nB"(换行符和字符B),而不是一个字符"\xAB"。

库对新字符类型的支持

随着新字符类型加入C++11标准,相应的库函数也加入进来。C11在中增加了4个字符的转换函数,包括:

size_t mbrtoc16(char16_t* pc16, const char* s, size_t n, mbstate_t* ps);
size_t c16rtomb(char* s, char16_t c16, mbstate_t* ps); 
size_t mbrtoc32(char32_t* pc32, const char* s, size_t n, mbstate_t* ps);
size_t c32rtomb(char* s, char32_t c32, mbstate_t* ps);

它们的功能分别是多字节字符和UTF-16编码字符互转,以及多字节字符和UTF-32编码字符互转。在C++11中,我们可以通过包含<cuchar>来使用这4个函数。当然C++11中也添加了C++风格的转发方法wstring_convert以及codecvt。使用类模板 wstring_convert和codecvt相结合,可以对多字节字符串和宽字符串进行转换。不过这里并不打算花费篇幅介绍这些转换方法,因为它们在C++17标准中已经不被推荐使用了,所以应该尽量避免使用它们。

除此之外,C++标准库的字符串也加入了对新字符类型的支持,例如:

using u16string = basic_string;
using u32string = basic_string; 
using wstring = basic_string;

char8_t字符类型

使用char类型来处理UTF-8字符虽然可行,但是也会带来一些困扰,比如当库函数需要同时处理多种字符时必须采用不同的函数名称以区分普通字符和UTF-8字符。C++20标准新引入的类型char8_t可以解决以上问题,它可以代替char作为UTF-8的字符类型。char8_t具有和unsigned char相同的符号属性、存储大小、对齐方式以及整数转换等级。引入char8_t类型后,在C++17环境下可以编译的UTF-8 字符相关的代码会出现问题,例如:

char str[] = u8"text";  // C++17编译成功;C++20编译失败,需要char8_t 
char c = u8'c';

当然反过来也不行:

char8_t c8a[] = "text"; // C++20编译失败,需要char 
char8_t c8 = 'c';

另外,为了匹配新的char8_t字符类型,库函数也有相应的增加:

size_t mbrtoc8(char8_t* pc8, const char* s, size_t n, mbstate_t* ps);
size_t c8rtomb(char* s, char8_t c8, mbstate_t* ps);
using u8string = basic_string;

后需要说明的是,上面这些例子只是C++标准库为新字符类型新增代码的冰山一角,有兴趣的读者可以翻阅标准库代码,包括

<atomic>、<filesystem>、<istream>、<limits>、<locale>、<ostream>、<string>以及<string_ view>等头文件,这里就不一一介绍了。

这篇关于现代C++新特性 新字符类型char16_t和char32_t的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/331035

相关文章

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现

自定义类型:结构体(续)

目录 一. 结构体的内存对齐 1.1 为什么存在内存对齐? 1.2 修改默认对齐数 二. 结构体传参 三. 结构体实现位段 一. 结构体的内存对齐 在前面的文章里我们已经讲过一部分的内存对齐的知识,并举出了两个例子,我们再举出两个例子继续说明: struct S3{double a;int b;char c;};int mian(){printf("%zd\n",s