Skip to main content

计算机基础

本节旨在为计算机初学者提供有关计算机组成和编程语言发展历史的背景知识,以便更好地理解计算机语言中的各种概念和设定。如果您已经熟悉计算机的结构,可以跳过本节内容。

图灵机

自古以来,人们就不断探讨如何利用机器来辅助数学计算。比如算盘就是一种比较典型的辅助计算工具,它可以提高使用者的计算速度。然而,算盘的功能相对简单,仅能在基本的加减乘除运算中发挥作用。近两个世纪以来,人们设计出了功能更强大的计算机器。其中,对后世影响最大的当属图灵机(Turing Machine),由数学家艾伦·图灵(Alan Turing)在1936年提出。尽管图灵机并非实体机器,而是一种模型,但它从理论上证明了机器计算的可行性,为现代计算机的诞生与发展奠定了理论基础。

图灵机的基本构成如下:

  • 无限长的纸带:纸带上有无数方格,每个方格中可以书写一个符号。
  • 读写头:读写头可以移动到纸带上的任意方格,读取或修改符号。
  • 操作规则:图灵机可以保存当前的状态,并根据当前状态和纸带上的符号决定下一步操作,例如移动到新的位置,进行读或写。

尽管图灵机的结构简单,但只要给定一个问题(根据规则写在纸带上的一串符号),它就能计算出问题的结果(更新后的纸带)。图灵证明了任何“可计算问题”都可以使用图灵机来求解。

图灵机是一种理论模型,而现代计算机可以视作这种模型的实际应用。现代计算机通过内存或硬盘读取和写入数据,并由中央处理器(CPU)控制读写过程。虽然现代计算机看起来比图灵机复杂得多,但它们在本质上是相同的。

冯·诺依曼架构

根据图灵机模型,制造一台实际的机器仍有多种方法。比如可以用不同的设备替代纸带;数据在纸带上的组织方式也可以不同;可以用齿轮或三极管驱动读写头等。

现代计算机的基本框架采用的是冯·诺依曼架构,由数学家约翰·冯·诺依曼(John von Neumann)于 1945 年提出。冯·诺依曼架构的核心思想是:使用二进制逻辑,将数据和程序指令(对数据的操作)存储在同一个存储器中,并由中央处理单元(CPU)按顺序读取和执行。这种相对简洁的设计为现代通用计算机奠定了基础。

冯·诺依曼架构的主要组成部分包括:

  • 中央处理单元(CPU):负责解释和执行指令,协调计算机各部分的工作。
  • 存储器:用于存放程序指令和数据。
  • 输入输出设备:如键盘和显示器等,用于与计算机交互。
  • 数据总线:用于传输数据。

除了以上硬件部分,计算机还需要配套的软件。每台计算机都安装有操作系统,用于管理计算机的硬件和软件资源。此外,还需要安装各种系统软件和应用软件来执行特定任务,例如浏览网页、播放视频等。

冯·诺依曼架构的一个主要问题是:CPU 与存储器之间的数据传输速度限制了系统性能。在存储器中读写数据的速度远慢于 CPU 的处理速度。为了应对这一问题,许多编程技巧和硬件优化方案被开发出来,这也会是我们在编程学习过程中经常遇到的挑战。

编程语言

在日常生活中,我们主要使用十进制来表示数据。但计算机不同,它采用二进制。这是因为二进制是使用电路来表示数据的最简单方式:高电平表示 1,低电平表示 0。也就是说,在计算机内部,所有信息都以二进制数字 0 和 1 的形式存在。由于人类直接理解这些二进制数据非常困难,我们需要一种人类与计算机之间的交流工具,这便是编程语言。

在计算机诞生初期,人们只能迁就计算机,直接使用二进制的数据来控制计算机。这种方式对人类极为不友好,于是,汇编语言应运而生,它使用有意义的字符串代替机器语言中的二进制数。这大大提高了程序的可读性,但汇编语言程序的结构仍然与具体的机器紧密相关。

随着计算机的普及,开发者对编程语言提出了各种不同的要求。为了进一步提升编程效率,并应对各种不同的应用场景,各种高级编程语言被发明出来。高级编程语言通过抽象底层硬件的实现细节,使语法更接近人类语言,从而让程序员可以通过更简洁、易读的语法结构来编写代码。当前最流行的编程语言,比如 Python 和 JavaScript 等,都是高级语言的典型代表。

从编程语言的发展脉络来看,明显的趋势是越来越抽象,越来越接近人类自然语言。未来,人类很可能会直接使用自然语言与机器交流,而不再需要依赖编程语言作为桥梁。

为什么有那么多的编程语言?

很难统计这世界上到底有多少种编程语言了。有时,甚至在计算机课程中,学生们也会被要求设计出一门新的编程语言。即便只考虑那些有正式规范,已经被广泛使用的编程语言,也至少有上千种。为什么我们需要这么多编程语言?我们能否只维护一种或少数几种编程语言,完成所有工作呢?

这是几乎不可能的。理想情况下,我们希望一种编程语言具备以下特征:

  • 易学易用:初学者可以直接上手,并完成工作。
  • 功能强大:能够完成任何工作,即便是超大型项目。
  • 运行效率高:资源消耗最少,程序执行速度最快。
  • 开发效率高:能够快速搭建任何项目。
  • 安全可靠:能够预防程序出错,并防范恶意利用。
  • 灵活、可扩展:稍作改动即可赋予程序新功能。
  • 跨平台、可移植:同一程序能够在不同硬件平台上运行,例如手机、手表、服务器等。

然而,这些需求之间往往存在矛盾。例如,功能越多,学习成本就越高。因此,没有任何一种编程语言能够完美满足所有需求。正因为如此,人们开发了各种不同的编程语言,针对特定方面进行优化。

不仅如此,每当新的一类问题出现,我们就会需要一些新的语言特性来应对,而改进一个现有的编程语言的成本却非常高多数情况下,创造一门新的编程语言比改进已有语言更经济。例如,当遇到一个新的需求时,现有编程语言无法满足时,如果我们去修改一个现有的编程语言,让它适应新的需求,这很有可能就会导致已经使用这门语言编写的程序都无法再运行了。这样的后果是无法接受的,所以,安全的做法是不要改动已有的语言,而是创建一门新语言来满足新的需求。于是,新的编程语言总是层出不穷。比如,虽然 C 语言已经很流行了,但是为了支持面向对象编程,人们又开发了 C++、Java 等。

当然,这并不是说编程语言一旦被设计好,就只能一成不变。实际上有生命力的编程语言都是要不断进化。只是这些改进不能够影响任何已有的功能。比如,C++ 在发布之后,又经历了数次大改进:支持模板、完善STL、加入 lambda 函数等等。现在 C++ 的编程思想与早年已经大相径庭了。早期使用 C++ 开发项目时,可能主要精力都消耗在查找内存泄漏和野指针等编程语言本身的问题上;而如今,C++ 也可以自动管理内存了,程序员也可以把主要经历放在业务逻辑上,而不是放在管理底层资源上。尽管经历了如此大的变化,C++ 依然是同一语言,因为它的每次改进都是向前兼容的,绝对不会破坏已有的程序:早年编写的程序拿到现在的 C++ 编译器上一样可以编译成功。

有时,编程语言的设计者可能会发现一些无法容忍的设计失误,甚至不得不破坏兼容性进行修改。例如,从 Python 2.0 升级到 3.0,版本升级主要为了修正少数几处设计缺陷,比如支持 unicode,改变整数除法行为等,大多数的 Python 代码还是不受影响的。即便如此,就样的小变动也花费了 Python 十多年的时间才彻底升级成功。

Python 在最初设计时,更侧重于易学易用,因此放弃了一些复杂功能,如内存控制等;更侧重于灵活性,因此放弃了一些安全性,如静态数据类型检测;更侧重于开发效率,因此放弃了运行效率。然而,现如今的 Python 也在极力克服这些缺点,比如通过第三方库弥补了缺失的功能,利用 C 语言预编译的模块提升运行效率等。也正因如此,Python 才得以被应用在越来越广泛的领域里。

编译型语言和解释型语言

计算机编程语言与我们使用的自然语言(中文、英文等),虽然有明显差异,但也有相似之处。它们均由按照特定语法排列的文字(或图形符号)构成。然而,计算机处理器无法直接理解这些文字或符号:它仅识别二进制的指令和数据。在运行我们编写的程序之前,首先需要把这些文本或图形形式的程序转换成为处理器可识别的二进制指令和数据。每种编程语言都需经历这个转换过程。根据翻译的过程的不同,编程语言大致分为两类:编译型和解释型。

在编译型语言中,整个项目的源代码需要全部编写完成后,再整体翻译成二进制机器语言,然后才能执行。这个翻译过程被称为“编译”。像 C、C++ 这样的编译型语言拥有编译器,负责将我们写的程序编译成二进制的可执行文件文件。

而解释型语言则无需预先进行编译。它们一边读取代码,一边将其翻译成二进制机器语言,并同时运行翻译好的代码。这一过程称为“解释”:每读取一行代码,就解释并执行这一行。Python 和 JavaScript 是典型的解释型语言。使用解释型语言编写的代码有时也被称为脚本。

这两类编程语言各有优劣。编译型语言的优势在于运行速度快,因为程序已经预先编译好,运行时直接执行编译后的二进制机器语言。此外,将程序代码作为一个整体编译还可以进行许多优化,进一步提升效率。其缺点是,任何一处代码更改都需要重新编译整个程序。

解释型语言因为程序运行时先要解释才能执行,解释过程也是运行的一部分,因此运行速度相对较慢。但它们更具灵活性:部分代码的修改不会影响其它部分的代码的执行,这在某些情况下非常有用。比如,在编写程序的时候,可以一边写一边执行,以确保程序正确。若某句出错,可以单独修改并运行该句,而无需重新执行整个程序,这大大提高了开发效率。

在 2020 年之前,编译型语言占据了程序开发的主导地位。解释型语言没那么受欢迎,是因为那时候它们确实太慢了。然而,如今情况已经变了,解释型语言经过诸多改进,在速度上的缺陷已经不那么明显了。目前,最消耗计算机算力,最考验运行效率的任务,恐怕非训练人工神经网络模型莫属了。然而,却正是 Python 这个解释型语言在此领域表现的最为出色。其原因在于,运行 Python 程序时,并非所有代码都以解释方式执行。实际上,只有主文件中的代码是解释执行的。最耗时的逻辑通常在模块中实现,例如 AI 模型训练逻辑常在诸如 PyTorch 这类模块中实现。这个模块本身已经被编译好了,运行时会直接执行二进制机器语言。而主程序中的代码通常只负责数据传递和结果显示,这些操作本就非常快。因此,无论是用 C 语言还是 Python 训练 AI 模型,在速度上几乎没有差别。

不仅 Python 自带或第三方模块如此,我们自己编写的模块也会被 Python 预先翻译并保存在 *.pyc 文件中,使得再次运行时无需再解释,直接使用 pyc 文件即可,这无疑极大地提升了程序运行速度。

Python 语言的解释器

Python 的解释器不止一种。我们平时讨论最多的解释器叫做 CPython。它是 Python 官方发行的版本,用C语言开发的,所以叫 CPython。除了这个解释器,我们上文也提到过,有把 Python 程序翻译成 JavaScript 程序的解释器,比如 pyscript;有翻译成 Java 字节码的解释器 Jython;有翻译成 .NET 字节码的 IronPython;还有对 Python 程序进行编译的 PyPy 等等。这些解释器或者编译器产生的运行结果可能会与官方的 CPython 略有不同,本书在介绍程序运行效果的时候,会以 CPython 为准。

Python 的版本

Python 一直在改进中,每隔几个月就会有一个较大的版本更新。作为学习使用,建议尽量使用 Python 的最新版本。如果想查看你的 Python 版本,在计算机的命令行终端可以使用如下命令:

python --version

然后会看到类似 Python 3.11.2 的输出,它表示了 Python 的版本是 3.11.2

如果是在 Python 的编辑环境中,可以运行如下 Python 代码查看其版本:

import sys
print(sys.version_info)
print(sys.version)

这会显示一些更详细的版本信息,比如:

sys.version_info(major=3, minor=11, micro=2, releaselevel='final', serial=0)
3.11.2 (main, May 3 2023, 04:00:05) [Clang 17.0.0]

除了版本号,还会显示版本发布的日期,编译平台等信息。