C语言到底使用什么编码?谁说C语言使用ASCII码,真是荒谬!

C语言是 70 年代的产物,那个时候只有 ASCII,各个国家的字符编码都还未成熟,所以C语言不可能从底层支持 GB2312、GBK、Big5、Shift-JIS 等国家编码,也不可能支持 Unicode 字符集。

稍微有点C语言基本功的读者可能认为C语言使用 ASCII 编码,字符在存储时会转换成对应的 ASCII 码值,这也是错误的,你被大学老师和教材误导了!在C语言中,只有 char 类型的窄字符才使用 ASCII 编码,char 类型的窄字符串、wchar_t 类型的宽字符和宽字符串都不使用 ASCII 编码

wchar_t 类型的宽字符和宽字符串使用 UTF-16 或者 UTF-32 编码,这个在上节已经讲到了,现在只剩下 char 类型的窄字符串(下面称为窄字符串)没有讲了,这就是本节的重点

对于窄字符串,C语言并没有规定使用哪一种特定的编码,只要选用的编码能够适应当前的环境即可,所以,窄字符串的编码与操作系统和编译器有关

但是,可以肯定的说,在现代计算机中,窄字符串已经不再使用 ASCII 编码了,因为 ASCII 编码只能显示字母、数字等英文字符,对汉语、日语、韩语等其它地区的字符无能为力

讨论窄字符串的编码要从以下两个方面下手。

源文件使用什么编码

源文件用来保存我们编写的代码,它最终会被存储到本地硬盘,或者远程服务器,这个时候就要尽量压缩文件体积,以节省硬盘空间或者网络流量而代码中大部分的字符都是 ASCII 编码中的字符,用一个字节足以容纳,所以 UTF-8 编码是一个不错的选择

  • UTF-8 兼容 ASCII,代码中的大部分字符可以用一个字节保存;
  • 另外 UTF-8 基于 Unicode,支持全世界的字符,我们编写的代码可以给全球的程序员使用,真正做到技术无国界。(个人:也就是世界上各个地方的人编写的代码,拿到另一个地方,不会出现乱码的问题)

常见的 IDE 或者编辑器,

  • 例如 Xcode、Sublime Text、Gedit、Vim 等,在创建源文件时一般也默认使用 UTF-8 编码。
  • 但是 Visual Studio 是个奇葩,它默认使用本地编码来创建源文件
所谓本地编码,就是像 GBK、Big5、Shift-JIS 等这样的国家编码(地区编码)针对不同国家发行的操作系统,默认的本地编码一般不同。简体中文本的 Windows 默认的本地编码是 GBK
对于编译器来说,它往往支持多种编码格式的源文件。微软编译器、GCC、LLVM/Clang(内嵌于 Xcode 中)都支持 UTF-8 和本地编码的源文件,不过微软编译器还支持 UTF-16 编码的源文件。如果考虑到源文件的通用性,就只能使用 UTF-8 和本地编码了。

窄字符串使用什么编码

前面讲到,用 puts 或者 printf 可以输出窄字符串,代码如下:

#include <stdio.h>
int main()
{
	puts("将夜书院");
	printf("https://www.baidu.com");
	return 0;
}

"将夜书院""https://www.baidu.com"就是需要被处理的窄字符串,程序运行后,它们会被载入到内存中。你看,这里面还包含了中文,肯定不能使用 ASCII 编码了。

  • 微软编译器使用本地编码来保存这些字符。(个人:这里的保存指的是在内存中存储)不同地区的 Windows 版本默认的本地编码不一样,所以,同样的窄字符串在不同的 Windows 版本下使用的编码也不一样。对于简体中文版的 Windows,使用的是 GBK 编码
  • GCC、LLVM/Clang 编译器使用和源文件相同的编码来保存这些字符:(个人:这里的保存指的是在内存中存储)如果源文件使用的是 UTF-8 编码,那么这些字符也使用 UTF-8 编码;如果源文件使用的是 GBK 编码,那么这些字符也使用 GBK 编码。

你看,对于代码中需要被处理的窄字符串,不同的编译器差别还是挺大的。不过可以肯定的是,这些字符始终都使用窄字符(多字节字符)编码

正是由于这些字符使用 UTF-8、GBK 等编码,而不是使用 ASCII 编码,所以它们才能包含中文

那么,为什么很多初学者会误认为C语言使用 ASCII 编码呢?

不管是在课堂跟着老师学习,还是通过互联网自学,初学者都是从处理英文开始的,对于英文来说,使用 GBK、UTF-8、ASCII 都是一样的,GBK、UTF-8 都兼容 ASCII,初学者根本察觉不出用了哪种编码

另外,很多大学老师和书籍作者也经常会念叨,字符在存储时会被转换成对应的 ASCII 码,在读取时又会从 ASCII 码转换成对应的字符实体,大家需要熟悉 ASCII 编码,它是C语言处理字符的基础,这从很大程度上给初学者造成一种错误印象:C语言和 ASCII 编码是绑定的,C语言使用 ASCII 编码。

总结

  • 对于 char 类型的窄字符,始终使用 ASCII 编码
  • 对于 wchar_t 类型的宽字符和宽字符串,使用 UTF-16 或者 UTF-32 编码,它们都是基于 Unicode 字符集的
  • 对于 char 类型的窄字符串,微软编译器使用本地编码,GCC、LLVM/Clang 使用和源文件编码相同的编码
  • 另外,处理窄字符和处理宽字符使用的函数也不一样:
    • <stdio.h> 头文件中的 putchar、puts、printf 函数只能用来处理窄字符
    • <wchar.h> 头文件中的 putwchar、wprintf 函数只能用来处理宽字符

你看,仅仅是字符的处理,C语言就能玩出这么多花样,让人捉摸不透,不容易学习。这是因为,C语言是一种较为底层和古老的语言,既有历史遗留问题,又有贴近计算机底层的特性。不过,一旦搞明白这些繁杂的底层问题,你的编程内功将精进一个层次,这也许就是学习C语言的乐趣。

【拓展】编码字符集和运行字符集

站在专业的角度讲,源文件使用的字符集被称为编码字符集,也就是写代码的时候使用的字符集;程序中的字符或者字符串使用的字符集被称为运行字符集,也就是程序运行后使用的字符集

  • 源文件需要保存到硬盘,或者在网络上传输使用的编码要尽量节省存储空间,同时要方便跨国交流,所以一般使用 UTF-8,这就是选择编码字符集的标准。(个人:也就是我们使用编辑器打开源码文件时,使用哪种编码方式打开,我们必须使用与保存时相同的编码方式打开,这样解码显示时才不会发生乱码的现象,注意,编辑器这个软件解码显示源文件内容时,将文件里的内容以源文件的编码方式加载存储到内存,然后显示,与将源文件里的代码程序编译后,运行时为程序里的变量,字符类型变量(以哪种编码方式存储到内存)分配内存存储不是一回事)
  • 程序中的字符或者字符串,在程序运行后必须被载入到内存,才能进行后续的处理,对于这些字符来说,要尽量选用能够提高处理速度的编码,例如 UTF-16 和 UTF-32 编码就能够快速定位(查找)字符。(个人:也就是我们实际编译,运行程序时,在内存中,字符以哪种方式进行编码存储在内存中,来进行程序的操作处理)
  • 编码字符集是站在存储和传输的角度,运行字符集是站在处理或者操作的角度,所以它们并不一定相同

 

posted on 2022-05-15 17:40  朴素贝叶斯  阅读(2160)  评论(0编辑  收藏  举报

导航