什么是计算机

虽然在这里参考《计算机组成原理》一书也许有点为时尚早。。。

一般意义下,计算机是一种能够接受输入信息,并进行处理产生输出结果的机器。
生活中常见的台式电脑、笔记本电脑、平板、手机都是计算机。

冯·诺依曼计算机

现在应用的大多数计算机遵循 冯·诺依曼体系结构 ,即 冯·诺依曼计算机 ,全称为 冯·诺依曼存储程序数字计算机 ,其最大的特点是 将程序存储起来,并且程序和数据保存在相同的存储器中,并不加以区分
除此之外,其他类型的计算机还有诸如模拟计算机、神经计算机、量子计算机、生化计算机等,它们处理信息的方式与存储程序计算机完全不同,这些概念不在本教程讨论范围内。

冯·诺依曼计算机有如下基本结构:
image.png|650
5大组成分别负责以下职责:

  • 存储器:用于存储程序指令和数据
  • 运算器:负责执行所有的算术运算和逻辑运算
  • 控制器:管理指令的执行流程,控制程序运行
  • 输入设备:用于向计算机输入信息(主要包括键盘和鼠标)
  • 输出设备:用于输出计算机的运行结果(包括图形、文字、声音等各种形式)

下图是存储程序计算机的结构图(截自《计算机组成原理》):
image.png|450

需要注意的是,现代计算机和传统的冯·诺依曼计算机有着不小的差别,例如现代计算机将运算器和控制器集成为 CPU等,这些内容并不是我们现在关注的重点,它们应该放到计算机组成原理课程上去学习。

软件和硬件

一个完整的计算机系统由硬件系统和软件系统组成。

  • 软件系统:由在硬件上运行的程序和程序所操作的数据组成,包括系统软件(负责系统控制)和应用软件(负责实际应用计算)
  • 硬件系统:组成计算机的各种物理设备,包括处理器、内存、硬盘、电源等等各种设备
    在一般的程序开发中,我们更多地关注软件系统,而一定程度上忽略硬件系统的组成细节。

计算机的发展历史

  1. 在电子计算机之前:算盘、机械计算器,主要用于算数运算,不能进行编程。
  2. 1642年,法国数学家布莱兹·帕斯卡(Blaise Pascal)发明了帕斯卡计算器,能够借助发条执行加减运算,仍然不能进行编程。
  3. 差分机:英国数学家查尔斯·巴贝奇(Charles Babbage)设计了差分机,能够自动计算构造数学表所需多项式的值。
  4. 分析机:巴贝奇设计了分析机,具有重要的历史地位,被认为是通用计算机的先驱。
  5. 机电式计算机:仍使用机械部件,但是使用电控制,典型的机器有马克 I 号等。
  6. 早期电子计算机:大名鼎鼎的ENIAC,是一台十进制真空管计算机,只能通过硬连线编程。
  7. 微机与PC机:再之后,计算机体量逐渐缩小,直到如今的家用电脑(微型机)。

什么是计算机程序

计算机程序是构成计算机软件系统的组成部分,计算机程序运行在硬件设备上。
计算机输入信息、处理计算信息、输出信息的所有过程全部依靠各种计算机程序进行控制。

  • 系统程序:对计算机系统进行控制,以保证系统的正常工作,并对应用程序的运行进行支持。
  • 应用程序:负责完成计算机实际需要进行处理的任务,例如科学计算、音视频渲染、文件处理等任务。

计算机程序需要程序员进行编写,编写好的程序就可以在特定的计算机(硬件)上运行并产生结果。

什么是计算机语言

和人们写作一样,编写计算机程序同样需要使用特定的语言,这类语言称为 编程语言。(人们交流的语言是 自然语言
随着计算机发展,编程语言也从 机器语言汇编语言高级语言逐步发展。

机器语言

最早由于电子计算机的特点,特定的计算机指令必须由一串二进制代码表示,这样计算机才能进行识别并执行代码。
因此计算机程序就由二进制代码直接构成(例如高电平1低电平0,符合电路特点)。为了编写可以运行的程序,就必须根据特定的硬件电路去直接构造二进制代码。

这种由二进制串构成的代码被称为 机器语言,程序员需要根据计算机硬件支持的指令来直接构造程序,这个过程异常复杂,并且机器代码与特定硬件直接关联,几乎不可移植。

汇编语言

为了解决机器语言的问题,将二进制代码进行符号化,即将某一串二进制指令由一串符号(英文单词、数字)进行代替,例如 add ax,1 表示将 ax 寄存器的值加1。

这样,程序员无需直接构造二进制程序,可以使用汇编语言提供的“助记符”来编写程序,然后由特定的程序来转换成可运行的二进制程序。

高级语言

尽管汇编语言大大简化了程序编写,但是它仍然十分低级,程序员仍然需要亲自考虑计算机的运行逻辑,效率依然低下,同时仍然有大量汇编指令需要记忆,移植性也依然很低。

人们希望能够将注意力更多地集中到解决问题本身,而不需要将程序设计的大量时间浪费在考虑操作计算机的运行逻辑上,高级编程语言就此出现。

所谓高级语言,指的是语法非常接近自然语言的编程语言,高级语言的表达更接近于一个正常人的思维。程序员可以根据自己的逻辑进行程序的编写。在程序编写完成后,使用特定的程序将其“翻译”为机器语言即可。

高级语言隐藏了计算机运行时复杂的底层逻辑和过程,让程序员能够将注意力更多地放在需要解决的问题本身上,大大提高了效率,而随着计算机的发展,编程语言也在不断地更新、演化。

本教程所讲述的 C语言 就是高级语言(其语法接近自然语言)的一种,也是最为重要的一门语言。

计算机存储单位

二进制有着其独特的性质,简单到可以由高低电平直接表示,因此极其适合应用于计算机。同样,计算机的数据存储也使用二进制。
数据是可以量化的,二进制数据有着特定的存储单位。

bit-比特

一个十进制数有0-9共十种状态,类似地,一个二进制数只有0或1这两种,因此只需要一个简单的标志即可进行区分,例如“开关的开合”、“电压的高低”、“硬币的正反”等等。

这一个标志的信息被称为 bit(比特) ,1 bit信息表示一个“非正即反”的数据。
在计算机中,比特 常常被称为 1bit信息1位信息
二进制数据正式由若干个比特组成的,但是比特这个单位太小了,当然,有着更大的数据单位。

byte-字节

字节(byte)是计算机中最基本的存储单位,一个字节由8个相邻的二进制位(bit)组成。同时,字节是计算机中最小的操作单位(或者说是最小的可寻址单位)。

容易计算:1bit信息有 2 种状态,则1byte就有 2^8=256 种状态。
因此,假设用一字节数据存储无符号数(即非负整数),那么可存储的范围是 0~255
注意:字节的缩写为大写的 B,而比特则常写为 bit

更大的存储单位

从字节再向上,每 2^10(即1024) 一个单位,从小到大依次为KB、MB、GB、TB等等(还有更大的EB等等,此处省略),也就是说:
1024B(Byte)=1KB,1024KB=1MB,1024MB=1GB,1024GB=1TB
一般来讲,我们平时能用到的最大单位就是TB,例如1TB的固态硬盘指的就是这个单位。

计算机中各设备的存储容量

以2024年的情况举例:

  • 现在的个人计算机,大多为64位机器,即地址长度为64位(8字节宽度)
  • 64位机器的CPU中,一个寄存器的大小一般为64位(8字节)
  • 一台微型计算机的内存大小一般在8GB~64GB上下
  • 一张固态硬盘的大小一般在256GB~2TB上下

此外,在计算机中,各种单位都是以2的幂次方增长的,而不是以我们熟悉的十进制,这样是为了让计算机更容易地进行处理。

进制转换

人们日常使用的是十进制,即逢十进一,使用0~9这十个数字;
而在计算机科学中,一般使用二进制,即逢二进一,使用0~1这两个数字;
除此之外,由于二进制数过于冗长,还经常使用八进制(逢八进一,使用0~7这八个数字)或者十六进制(逢十六进一,使用0~9,A~F共十六个字符)计数;

不同进制之间可以根据特定的规则进行转换。无论几进制,无非是表示方式不同,在进制转换的过程中,数的大小是保持不变的。

k进制数的特征

考虑十进制数 1234(10)
显然:1234(10)=1103+2102+3101+41001234(10)=1*10^3+2*10^2+3*10^1+4*10^0,即从右边最低位(记为第0位)开始,第 i 位的数乘以10的 i 次方,各项之和即为总的数
每一位数乘以的 10i10^i 被称为这一位的 权重,其中的 10 正是这个数所使用的进制。

类似地,考虑二进制数 101(2)
同样:101(2)=6(10)=122+021+120101(2)=6(10)=1*2^2+0*2^1+1*2^0
其中101(2)就是十进制的6,这很容易得出。

这样的分解适用于任何k进制数,当然,分解后的多项式是基于十进制的。

十进制转k进制

十进制转二进制,使用除二求余的方法,每次除法得到的余数即为二进制数的每一个位。
注:一个数后跟括号,括号中的数字表示这个数使用的进制,例如 7F(16) 表示这是一个十六进制数 7F。

6(10) 转二进制为例:
将其以2的幂次方和分解,显然有:6=122+121+0206=1*2^2+1*2^1+0*2^0,因此, 6(10)=110(2)
如果使用 倒除法 表示这一过程,就是:

image.png|400

事实上,如果要把十进制数转换为k进制数,只需要将倒除法的除数换为 k 即可,仍然将余数从下到上逆序拼接得到对应的k进制数。例如要转为16进制,则将上图中的除数2换成16即可。

k进制转十进制

110(2) 转十进制为例:
类似地就有 110(2)=122+121+020=6(10)110(2)=1*2^2+1*2^1+0*2^0=6(10) 这样将其进行分解并求和,即可得到对应的十进制数6

事实上,如果要把k进制数转换为10进制数,只需要将各项权重的底数换为 k 即可,将多项式求和即可得到对应的十进制数。例如要将k进制数转换为十进制数,则使用类似 num(k)=x0k0+x1k1+...num(k)=x_0*k^0+x_1*k^1+... 的分解即可。、

8421法则

16,8,4,2,1这几个数有着巧妙的关系:它们都是2的幂次方。基于这个特性,我们在将二进制数转换为八进制或者十六进制的时候,可以直接将二进制串进行分组,分组直接转换为对应的八进制或者十六进制数,然后各组直接拼接即可。

例如:
二进制数 10100101(2) 转换为16进制数时,四位一组进行分组得到 1010|0101 ;第一组直接得到 1010(2)=8+0+2+0=10(10)=A(16),第二组直接得到 0101(2)=0+4+0+1=5(10);然后直接拼接即可得到对应的十六进制数 A5(16)

再例如:
二进制数 10100101(2) 转换为8进制数时,三位一组进行分组得到 010|100|101 (高位补0);第一组直接得到 010(2)=0+2+0=2(10)=2(8),第二组直接得到 100(2)=4+0+0=4(10)=4(8),第三组直接得到 101(2)=4+0+1=5(10)=5(8) ;然后直接拼接即可得到对应的八进制数 245(8)

这就是 8421法则421法则,利用了源进制和目标进制都是2的幂次方的特点,并且由于权重很小,所以直接即为8、4、2、1即可,这样大大简化了2的幂次方进制数之间的转换。

事实上,在计算机中,由于二进制数太过繁琐,因此几乎总是使用对应的十六进制数进行表示,因此彼此之间的转换应该烂熟于心。

字符编码

在计算机科学中,字符编码定义了字符的表示、编码和处理方式。了解字符标准对于编写能够处理文本数据的程序至关重要。

ASCII编码

最早和最广泛使用的字符编码标准是美国信息交换标准代码(American Standard Code for Information Interchange,简称ASCII)。ASCII使用7位二进制数来表示128个不同的字符,包括:

  • 大写和小写英文字母(A-Z,a-z)
  • 数字0-9
  • 空格
  • 标点符号
  • 控制字符(一些特殊字符,用来控制计算机)

ASCII编码示例:

  • 空格字符 的ASCII码是32。
  • 大写字母'A'的ASCII码是65。
  • 小写字母'a'的ASCII码是97。

实际上,由于计算机中一个字节为8位二进制数,因此一个ASCII字符一般使用一个完整的字节来存储(例如C语言中保存单个字符的char类型就是1个字节)。

注:完整的ASCII码表详见:ASCII - 维基百科,自由的百科全书

Unicode编码

随着全球化的发展,仅仅支持英文字符的ASCII逐渐不能满足需求,因为它无法表示世界上所有的字符(例如中文、日文字符)。统一字符集(Unicode)被创建用以支持更多的字符。Unicode是一个更全面的标准,它能够表示世界上几乎所有的书写系统的字符。

Unicode以下包括多种编码形式,其中最常用的是:

  • UTF-8:一种变长编码方式,使用1到4个字节表示一个字符,它与ASCII码兼容。
  • UTF-16:使用2个或4个字节表示字符。
  • UTF-32:使用固定4个字节表示每个字符。

上面三种编码,在不同的场景有着不同的选择,例如Java语言使用UTF-16编码存储字符;而Golang则使用UTF-8作为默认编码;Linux操作系统默认使用UTF-8编码,等等。

其他编码

在不同的平台、国家或地区,有着许许多多的其他字符编码,例如:

  • GBK:汉字内码扩展规范,收录有两万多个汉字等。
  • 大五码:繁体中文字符集标准。
    这些字符都在特定场合有着用武之地,当然,一般情况下ASCII和UTF-8即可满足我们大部分的需求。