字符编码的前世今生和数据类型大小

前言

今天面试又遇到以前一直忽略的问题,即字符编码方式和不同数据类型在不同位数操作系统的所占的字节大小,或许是在考察我学习是否细致、认真、深入。借此机会,我在网上查找资料,用自己的适合的记忆方式对该知识点以及辐射出的知识内容的进行了一个总结,形成自己的理解。

编码

编码前奏曲

我相信很多人都和我遇到过相同的问题和相似的疑问,为什么Java中的字符是2个字节,而C/C++中又为什么是1个字节?为什么在不同位数的机器上面指针型为什么是固定的某个值?为什么我明明没干什么“坏事”,怎么会出现乱码呢?

对于第一和第二个问题,如果只是想知道结果,动手验证即可,当然这也是学习的第一步。对于问题三,虽然也可以不求甚解的找个解决方案,但是遇到类似的情况时你就不明白从哪个角度找出问题的所在并解决它。

为什么需要编码?

先给出一个官方的字符编码定义:
字集码是把字符集中的字符编码指定为集合中某一对象(例如:比特模式、自然数序列、8位组或者电脉冲),以便文本在计算机中存储和通过通信网络的传递。

我理解的角度是,计算机用二进制数据描述信息的过程叫做编码,编码的方式决定了什么样的二进制数值表示什么字符,编码的长度(字节数的大小)则是影响存储空间大小,间接影响着传输某一信息所需要的流量。

编码方案发展

我相信通过编码方案的发展历程,我们就能搞清楚为什么Unicode如此受欢迎,乱码问题本质是什么。

  • ASCII:开始计算机只在美国使用,8位的字节一共可以组合出28=256种不同状态,至今用了128种状态,前32种状态(编号为0——31)规定了特殊的用途,后面世界上所有计算机都用ASCII方案保存英文文字。ASCII全称为American Standard Code for Information Interchange,即美国信息交换标准代码
  • EASCII:EASCII在ASCII上做的一个扩展,利用起后面128种状态(128——255),EASCII码比ASCII码扩充出来的符号包括表格符号、希腊字母等,具体问搜索引擎
  • GB2312:GB是国标的意思,这是我们国家标准总局发布的编码方案,不仅把我们的很多汉字进行了编码,还对日文的假名等和ASCII本来就有的数字、标点、字母都用2个字节进行编码,这就是输入法中所说的“全角”符号了,而127以下的ASCII编码的称为半角符号
  • GBK:扩展GB2312后的编码方案称为GBK标准,少数民族文字的加入又扩成了GB18030,它们都是双字节字符集的
  • Unicode:各个国家和地区使用各自的编码,对于交流来说是极其不便的,最喜欢制作标准的ISO(国际标准化组织)发明了Unicode编码,全称是Universal Multiple-Octet Coded Character Set.ISO规定必须用2个字节表示所有字符,而对于“半角”的英文字符来说,保存英文文本时多浪费了一倍的空间,因为高8位全为0. Unicode下,无论是英文字母还是汉字,它们都是“一个字符”,“字节”是一个8位的物理存贮单元,“字符”是一个文化相关的符号
  • UTF-8等:假设Unicode统一规定,每个符号用3或4个字节表示,那么每个英文字母必有第2,3个字节为0,存储的文本比原来大2,3倍,这是难以接受的.互联网的出现,为解决Unicode如何在网络上传输无国界,诞生了UTF标准。UTF-8是每次传输8位数据,UTF-16是每次传输16位,两者编码哪个更有效率要看字符分布的范围。UTF-8是变长编码,即字符在ASCII编码范围内则用1个字节表示,unicode中文字符占2个字节,而UTF-8中文字符占3个字节。下面简单的展示UTF-8的编码
1
2
3
4
5
6
7
0xxxxxxx
110xxxxx 10xxxxxx
1110xxxx 10xxxxxx 10xxxxxx
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
...

UTF-8是网页,电子邮件中常用的信道编码,乱码问题其实是不同编码字符占用的字节数不一样,而计算机读取的时候如果不按照对应的规则(字节数)读取,那么读取来的就不是之前编码的字符,从而出现了乱码.

数据类型

我一直在想,C/C++数据类型大小与什么因素有关,从CPU,OS,Compiler三方面考虑。先了解一些基本概念:

  • CPU指的是处理器,不同位数(如32位,64位)的处理器我们称为不同位数的机器;
  • OS指运行在机器上的操作系统,这里关注的是操作系统位数,64位的机器上也可以运行32位的操作系统;
  • Compiler即编译器,即使是32位win32平台编译器中可以定义64位长整型的__int64.

int字长规定:

  • C/C++规定int字长和机器字长相同;
  • 操作系统字长和机器字长未必一致;
  • 编译器根据操作系统字长定义int字长;

因此,对于C/C++,无操作系统的嵌入式计算机系统下,int长度与处理器字长一致;
有操作系统时,编译器根据操作系统的字长定义int字长。比如说64位机器上运行32位系统,编译器中的int为32位。

数据类型所占字节数

C/C++编译器

数据类型 32位 64位
short int 2 2
int 4 4
unsigned int 4 4
float 4 4
double 8 8
long 4 4
long long 8 8
unsigned long 4 4
指针类型变量 4 8

动手实践一番吧,在下的环境是win10 x64位系统,处理器为64位,编译环境分别选择vs的32位和64位debug模式。先给出代码,最后给出运行结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include "stdio.h" 
#include "iostream"
using namespace std;

class A {
private: int i;
private: char c;
public: void* fun(){
}
};

int main()
{
char c1[10];
char* c2[10];
string s[10];

printf("sizeof(shor int):%d\n", sizeof(short int));
printf("sizeof(shor int*):%d\n", sizeof(short int*));
printf("\n");

printf("sizeof(int):%d\n", sizeof(int));
printf("sizeof(int*):%d\n", sizeof(int*));
printf("sizeof(int**):%d\n", sizeof(int**));
printf("sizeof(unsigned int):%d\n", sizeof(unsigned int));
printf("sizeof(unsigned int*):%d\n", sizeof(unsigned int*));
printf("\n");

printf("sizeof(long):%d\n", sizeof(long));
printf("sizeof(long*):%d\n", sizeof(long*));
printf("\n");

printf("sizeof(long long):%d\n", sizeof(long long));
printf("sizeof(long long*):%d\n", sizeof(long long*));
printf("sizeof(unsigned long):%d\n", sizeof(unsigned long));
printf("sizeof(unsigned long*):%d\n", sizeof(unsigned long*));
printf("\n");

printf("sizeof(float):%d\n", sizeof(float));
printf("sizeof(float*):%d\n", sizeof(float*));
printf("\n");

printf("sizeof(double):%d\n", sizeof(double));
printf("sizeof(double*):%d\n", sizeof(double*));
printf("\n");

printf("sizeof(char):%d\n", sizeof(char));
printf("sizeof(char*):%d\n", sizeof(char*));
printf("sizeof(c1):%d\n", sizeof(c1));
printf("sizeof(c2):%d\n", sizeof(c2));
printf("\n");

printf("sizeof(string):%d\n", sizeof(string));
printf("sizeof(string*):%d\n", sizeof(string*));
printf("sizeof(s):%d\n", sizeof(s));
printf("\n");

printf("sizeof(void*):%d\n", sizeof(void*));
printf("\n");

printf("sizeof(A*):%d\n", sizeof(A*));
printf("\n");

return 0;
}

32位debug模式下:

64位debug模式下:

从中可以发现,string类型在不同的编译环境中大小发生了变化,指针型变量选择32位编译时,为32位大小(4个字节);选择64位时为64位大小(8个字节),验证了类型大小与compiler有关的结论。

Java数据类型
java中采用unicode编码,数据类型大小在任何平台都不改变,这就是Java具有强大的移植性能原因。

数据类型 Java
byte 1
boolean -
char 2
short 2
int 4
long 8
float 4
double 8

参考资料

喜欢通过听故事的方式学习知识的朋友可以看看知乎这篇文章,讲得挺生动有趣的:Unicode和UTF-8有何区别?
维基百科词条:字符编码
基本类型C++