mirror of
https://github.com/Aexiar/c.git
synced 2024-10-22 14:05:45 +02:00
c
This commit is contained in:
parent
fda419fbc0
commit
d1aebd4f7b
BIN
docs/notes/01_c-basic/03_xdx/assets/53.png
Normal file
BIN
docs/notes/01_c-basic/03_xdx/assets/53.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 71 KiB |
@ -2439,8 +2439,13 @@ man ascii
|
|||||||
> * UTF-16 可以看做是 UTF-8 和 UTF-32 的折中方案,它平衡了存储空间和处理效率的矛盾。对于常用的字符,用两个字节存储足以,这个时候 UTF-16 是不需要转换的,直接存储字符的编码值即可。
|
> * UTF-16 可以看做是 UTF-8 和 UTF-32 的折中方案,它平衡了存储空间和处理效率的矛盾。对于常用的字符,用两个字节存储足以,这个时候 UTF-16 是不需要转换的,直接存储字符的编码值即可。
|
||||||
> * ② 总而言之,**UTF-8** 编码兼容性强,适合大多数应用,特别是英文文本处理。**UTF-16** 编码适合处理大量亚洲字符,但在处理英文或其他拉丁字符时相对浪费空间。**UTF-32**编码简单直接,但非常浪费空间,适合需要固定字符宽度的特殊场景。
|
> * ② 总而言之,**UTF-8** 编码兼容性强,适合大多数应用,特别是英文文本处理。**UTF-16** 编码适合处理大量亚洲字符,但在处理英文或其他拉丁字符时相对浪费空间。**UTF-32**编码简单直接,但非常浪费空间,适合需要固定字符宽度的特殊场景。
|
||||||
> * ③ 在实际应用中,UTF-8 通常是最常用的编码方式,因为它在兼容性和空间效率之间提供了良好的平衡。
|
> * ③ 在实际应用中,UTF-8 通常是最常用的编码方式,因为它在兼容性和空间效率之间提供了良好的平衡。
|
||||||
> * ④ Windows 内核、.NET Framework、Java String 内部采用的都是 `UTF-16` 编码,主要原因是为了在兼顾字符处理效率的同时,能够有效处理多种语言的字符集,即:历史遗留问题、兼容性要求和多语言支持的需要。
|
|
||||||
> * ⑤ 不过,UNIX 家族的操作系统(Linux、Mac OS、iOS 等)内核都采用 `UTF-8` 编码,主要是为了兼容性和灵活性,因为 UTF-8 编码可以无缝处理 ASCII 字符,同时也能够支持多字节的 Unicode 字符,即:为了最大限度地兼容 ASCII,同时保持系统的简单性、灵活性和效率。
|
> [!IMPORTANT]
|
||||||
|
>
|
||||||
|
> * ① Windows 内核、.NET Framework、Java String 内部采用的都是 `UTF-16` 编码,主要原因是为了在兼顾字符处理效率的同时,能够有效处理多种语言的字符集,即:历史遗留问题、兼容性要求和多语言支持的需要。
|
||||||
|
> * ② 不过,UNIX 家族的操作系统(Linux、Mac OS、iOS 等)内核都采用 `UTF-8` 编码,主要是为了兼容性和灵活性,因为 UTF-8 编码可以无缝处理 ASCII 字符,同时也能够支持多字节的 Unicode 字符,即:为了最大限度地兼容 ASCII,同时保持系统的简单性、灵活性和效率。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- `Unicode 字符集`和对应的`UTF-8 字符编码`之间的关系,如下所示:
|
- `Unicode 字符集`和对应的`UTF-8 字符编码`之间的关系,如下所示:
|
||||||
|
|
||||||
@ -2535,3 +2540,232 @@ source /etc/default/locale
|
|||||||
```
|
```
|
||||||
|
|
||||||
![](./assets/52.gif)
|
![](./assets/52.gif)
|
||||||
|
|
||||||
|
## 3.3 在 C 语言中使用中文字符
|
||||||
|
|
||||||
|
### 3.3.1 概述
|
||||||
|
|
||||||
|
* 大部分 C 语言文章或教材对中文字符的处理讳莫如深,甚至只字不提,导致很多初学者认为 C 语言只能处理英文,而不支持中文。
|
||||||
|
* 其实,这是不对的。C 语言作为一门系统级别的编程语言,理应支持世界上任何一个国家的文字,如:中文、日文、韩文等。
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> 如果 C 语言不支持中文,那么简体中文 Windows 操作系统将无从谈起,我们只能被迫使用英文 Windows 操作系统,这对计算机的传播而言将会是一种巨大的阻碍。
|
||||||
|
|
||||||
|
### 3.3.2 中文字符的存储
|
||||||
|
|
||||||
|
* 要想正确的存储中文字符,需要解决如下的两个问题:
|
||||||
|
* ① 足够长的数据类型:char 的长度是 1 个字节,只能存储拉丁体系的问题,并不能存储中文字符,所以至少需要 2 个字节的内存空间。
|
||||||
|
* ② 包含中文的字符集:C 语言规定,对于中文、日文、韩文等非 ASCII 编码之外的单个字符,需要有专门的字符类型,也就是需要使用宽字符的编码方式。而常见的宽字符的编码有 UTF-16 和 UTF-32,它们都是基于 Unicode 字符集的,都能够支持全球的文字。
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> 上文提及过,在现代编程中,`窄字符`通常与 `UTF-8` 编码关联,特别是在处理文本输入、输出和网络传输时。尽管 `UTF-8` 是变长编码,由于其高效的空间利用和对 `ASCII` 的优化,通常与`窄字符`概念关联。而`宽字符`通常与 `UTF-16` 编码或 `UTF-32`编码关联,这些编码使用更大的固定或半固定长度来表示字符,适合处理更大的字符集。
|
||||||
|
|
||||||
|
* 在真正实现的时候,微软的 MSVC 编译器采用 UTF-16 编码,即:使用 2 个字节来存储一个字符,使用 unsigned short 类型就可以容纳。而 GCC、LLVM/Clang 采用 UTF-32 编码,使用 4 个字节存储字符,用 unsigned int 类型就可以容纳。
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> 不同的编译器可以使用不同的整数类型,来存储宽字符,这对于跨平台开发来说,非常不友好。
|
||||||
|
|
||||||
|
* 为了解决上述的问题,C 语言推出了一种全新的类型 `wchar_t` 类型,用来存储宽字符类型。
|
||||||
|
* 在微软的 MSVC 编译器中,它的长度是 2 个字节。
|
||||||
|
* 在 GCC、LLVM/Clang 中,它的长度是 4 个字节。
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> * ① `wchar_t` 中的 `w`是 wide 的首字母,`t` 是 type 的首字母,所以 `wchar_t` 就是宽字符类型,足够见名知意。
|
||||||
|
> * ② `wchar_t` 是用 typedef 关键字定义的一个别名,后文讲解,`wchar_t` 在不同的编译器下长度不一样。
|
||||||
|
> * ③ `wchar_t` 类型位于 `<wchar.h>` 头文件中,它使得代码在具有良好移植性的同时,也节省了不少内存,以后我们就用它来存储宽字符。
|
||||||
|
|
||||||
|
* 对于普通的拉丁体系的字符,我们使用 `''` 括起来,来表示字符,如:`'A'`、`'&'` 等。但是,如果要想表示宽字符,就需要加上 `L` 前缀了,如:`L'A'`、`L'中'`。
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> 宽字符字面量中的 `L` 是 `Long` 的缩写,意思是比普通的字符(char)要长。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
* 示例:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
|
||||||
|
/* 存储宽字符,如:中文 */
|
||||||
|
wchar_t a = L'中';
|
||||||
|
wchar_t b = L'中';
|
||||||
|
wchar_t c = L'中';
|
||||||
|
wchar_t d = L'中';
|
||||||
|
wchar_t e = L'中';
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3.3 中文字符的输出
|
||||||
|
|
||||||
|
* 对于宽字符,就不能使用 `putchar` 函数和 `printf` 函数来进行输出了,需要使用 `putwchar` 函数和 `wprintf` 函数。
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> * ① `putchar` 函数和 `printf` 函数,只能输出窄字符,即:`char` 类型表示的字符。
|
||||||
|
> * ② `putwchar` 函数可以用来输出宽字符,用法和 `putchar` 函数类似。
|
||||||
|
> * ③ `wprintf`函数可以用来输出宽字符,用法和 `printf` 函数类型,只不过格式占位符是 `%lc` 。
|
||||||
|
> * ④ 在输出宽字符之前,还需要使用 `setlocale` 函数进行本地化设置,告诉程序如何才能正确地处理各个国家的语言文化。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
* 示例:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include <locale.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <wchar.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
|
||||||
|
/* 存储宽字符,如:中文 */
|
||||||
|
wchar_t a = L'中';
|
||||||
|
wchar_t b = L'国';
|
||||||
|
wchar_t c = L'人';
|
||||||
|
wchar_t d = L'你';
|
||||||
|
wchar_t e = L'好';
|
||||||
|
|
||||||
|
// 将本地环境设置为简体中文
|
||||||
|
setlocale(LC_ALL, "zh_CN.UTF-8");
|
||||||
|
|
||||||
|
// 使用专门的 putwchar 输出宽字符
|
||||||
|
putwchar(a);
|
||||||
|
putwchar(b);
|
||||||
|
putwchar(c);
|
||||||
|
putwchar(d);
|
||||||
|
putwchar(e);
|
||||||
|
putwchar(L'\n'); // 只能使用宽字符
|
||||||
|
|
||||||
|
// 使用通用的 wprintf 输出宽字符
|
||||||
|
wprintf(L"%lc %lc %lc %lc %lc\n", a, b, c, d, e);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3.4 宽字符串
|
||||||
|
|
||||||
|
* 如果给字符串加上 `L` 前缀,就变成了宽字符串,即:它包含的每个字符都是宽字符,一律采用 UTF-16 或者 UTF-32 编码。
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> * ① 输出宽字符串可以使用 <wchar.h> 头文件中的 wprintf 函数,对应的格式控制符是`%ls`。
|
||||||
|
> * ② 不加`L`前缀的窄字符串也可以处理中文,我们之前就在 `printf` 函数中,使用格式占位符 `%s` 输出含有中文的字符串,至于为什么,看下文讲解。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
* 示例:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include <locale.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <wchar.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
|
||||||
|
/* 存储宽字符,如:中文 */
|
||||||
|
wchar_t a[] = L"中国人";
|
||||||
|
wchar_t *b = L"你好";
|
||||||
|
|
||||||
|
// 将本地环境设置为简体中文
|
||||||
|
setlocale(LC_ALL, "zh_CN.UTF-8");
|
||||||
|
|
||||||
|
// 使用通用的 wprintf 输出字符串
|
||||||
|
wprintf(L"%ls %ls\n", a, b);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3.4 C 语言到底使用什么编码?
|
||||||
|
|
||||||
|
### 3.4.1 概述
|
||||||
|
|
||||||
|
* 在 C 语言中,只有 `char` 类型的`窄字符`才会使用 ASCII 编码。而 `char` 类型的`窄字符串`、`wchar_t` 类型的`宽字符`和`宽字符串`都不使用 ASCII 编码。
|
||||||
|
* `wchar_t` 类型的`宽字符`和`宽字符串`使用 UTF-16 或者 UTF-32 编码,这个在上文已经讲解了,现在只剩下 `char` 类型的`窄字符串`没有讲解了,这也是下文的重点。
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> * ① 其实,对于`char` 类型的窄字符串,C 语言并没有规定使用哪一种特定的编码,只要选用的编码能够适应当前的环境即可。换言之,`char` 类型的窄字符串的编码与操作系统以及编译器有关。
|
||||||
|
> * ② 但是,`char` 类型的窄字符串一定不是 ASCII 编码,因为 ASCII 编码只能显示拉丁体系的文字,而不能输出中文、日文、韩文等。
|
||||||
|
> * ③ 讨论窄字符串的编码要从以下两个方面下手。
|
||||||
|
|
||||||
|
### 3.4.2 源文件使用什么编码?
|
||||||
|
|
||||||
|
* 源文件用来保存我们编写的代码,它最终会被存储到本地硬盘,或者远程服务器,这个时候就要尽量压缩文件体积,以节省硬盘空间或者网络流量,而代码中大部分的字符都是 ASCII 编码中的字符,用一个字节足以容纳,所以 UTF-8 编码是一个不错的选择。
|
||||||
|
* UTF-8 兼容 ASCII,代码中的大部分字符可以用一个字节保存。另外,UTF-8 基于 Unicode,支持全世界的字符,我们编写的代码可以给全球的程序员使用,真正做到技术无国界。
|
||||||
|
* 常见的 IDE 或者编辑器,如:Sublime Text、Vim 等,在创建源文件的时候一般默认就是 UTF-8 编码。就算不是,我们也会推荐设置为 UTF-8 编码,如下所示:
|
||||||
|
|
||||||
|
![](./assets/53.png)
|
||||||
|
|
||||||
|
* 对于 C 语言编译器来说,它往往支持多种编码格式的源文件。微软的 MSVC 、GCC 和 LLVM/Clang 都支持 UTF-8 和本地编码的源文件。
|
||||||
|
|
||||||
|
### 3.4.3 窄字符串使用什么编码?
|
||||||
|
|
||||||
|
* 前文提到,可以使用 puts 函数或 printf 函数来输出窄字符串,如下所示:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
|
||||||
|
// 存储字符串
|
||||||
|
char str[] = "我";
|
||||||
|
char *str2 = "爱你";
|
||||||
|
|
||||||
|
puts(str); // 我
|
||||||
|
puts(str2); // 爱你
|
||||||
|
|
||||||
|
// 存储字符串
|
||||||
|
char str3[] = "你";
|
||||||
|
char *str4 = "是好人";
|
||||||
|
|
||||||
|
printf("%s\n", str3); // 你
|
||||||
|
printf("%s\n", str4); // 是好人
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* 像 `"我"`、`"爱你"`、`"你"`、`"是好人"`就是需要被处理的窄字符串,当程序运行的时候,它们会被加载进内存。并且,这些字符串中是包含中文的,所以一定不会使用 ASCII 编码。
|
||||||
|
* 其实,对于代码中需要被处理的窄字符串,不同的编译器差别还是挺大的:
|
||||||
|
* 微软的 MSVC 编译器使用本地编码来保存这些字符。对于简体中文版的 Windows,使用的是 GBK 编码。
|
||||||
|
* GCC、LLVM/Clang 编译器使用和源文件相同的编码来保存这些字符:如果源文件使用的是 UTF-8 编码,那么这些字符也使用 UTF-8 编码;如果源文件使用的是 GBK 编码,那么这些字符也使用 GBK 编码。
|
||||||
|
|
||||||
|
### 3.3.4 总结
|
||||||
|
|
||||||
|
* ① 对于 `char` 类型的窄字符,在 C 语言中,使用的是 `ASCII` 编码。
|
||||||
|
* ② 对于 `wchar_t` 类型的`宽字符`和`宽字符串`,在 C 语言中,使用的 `UTF-16` 编码或者 `UTF-32` 编码,它们都是基于 Unicode 字符集的。
|
||||||
|
* ③ 对于 `char` 类型的`窄字符串`,微软的 MSVC 编译器使用本地编码,GCC、LLVM/Clang 使用和源文件编码相同的编码。
|
||||||
|
* ④ 处理窄字符和处理宽字符使用的函数也不一样,如下所示:
|
||||||
|
* `<stdio.h>` 头文件中的 `putchar`、`puts`、`printf` 函数只能用来处理窄字符。
|
||||||
|
* `<wchar.h>` 头文件中的 `putwchar`、`wprintf` 函数只能用来处理宽字符。
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
>
|
||||||
|
> * ① C 语言作为一门较为底层和古老的语言,对于字符的处理,之所以有这么多种方式,是因为历史遗留的原因和早期计算机资源有限的背景密切相关。
|
||||||
|
> * ② 现代化的编程语言,如:C++ 、Java、Python 等都对字符串处理进行了改进和抽象,如:C++ 中的 `std::string` 和 Java 中的 `String`。并且,现代编程语言的字符串类型通常会自动管理内存,这样开发者就不需要手动处理字符串的内存分配和释放,从而减少了内存泄漏和缓冲区溢出等问题。当然,现代编程语言通常内置了对各种字符编码的支持,能够方便地处理不同语言的字符,如:Java 的 `String` 类和 Python 的 `str` 类型都默认支持 Unicode,可以轻松处理中文等多字节字符。
|
||||||
|
|
||||||
|
### 3.3.5 编码字符集和运行字符集
|
||||||
|
|
||||||
|
* 源文件使用的字符集,通常称为`编码字符集`,即:写代码的时候所使用的字符集。
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> 源文件需要保存到硬盘,或者在网络上传输,使用的编码要尽量节省存储空间,同时要方便跨国交流,所以一般使用 UTF-8,这就是选择编码字符集的标准。
|
||||||
|
|
||||||
|
* 程序中的字符或者字符串使用的字符集,通常称为`运行字符集`,即:程序运行时所使用的字符集。
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> 程序中的字符或者字符串,在程序运行后必须被载入到内存,才能进行后续的处理,对于这些字符来说,要尽量选用能够提高处理速度的编码,如:UTF-16 和 UTF-32 编码就能够快速定位(查找)字符。
|
||||||
|
|
||||||
|
* 编码字符集是站在存储和传输的角度,而运行字符集是站在处理或者操作的角度,所以它们并不一定相同。
|
||||||
|
Loading…
Reference in New Issue
Block a user