1.5. 什么是编程?#
1.5.1. 计算机组成#
Computer 一词最早出现于 1613 年,指人类「计算员」,使用计算器做算术运算,直到 20 世纪中期,才被用于指「可编程的数字电子计算机」,简称「计算机」,中文里也被翻译成「电脑」。
一台计算机由硬件(hardware)和软件(software)组成,「硬件」指直接可见可触摸的设备,「软件」指存储、运行在硬件中的不能直接可见可触摸的数据和程序。
1.5.1.1. 硬件#
计算和控制:最典型的是中央处理器(CPU,Central Processing Unit),主流的 CPU 家族有 X86、ARM。CPU 主要有如下元件组成:
算术逻辑单元(ALU, Arithmetic Logic Unit):负责算术和逻辑运算,包含加法器、乘法器、除法器等电路;
控制单元(CU, Control Unit):负责解释指令并控制其它部件执行;
寄存器(Registers):存储 CPU 当前运行用到的指令、数据;
缓存(Cache):在寄存器和内存之间缓存指令和数据;
存储器
主存储器、内存(memory):一般指断电即丢失数据的随机访问存储( RAM,Random Access Memory);
辅存储器、外存(storage):包括软盘、硬盘、光盘等,断电不会丢失数据的存储设备;
输入设备(Input Device):键盘、鼠标、麦克风、扫描仪、网卡等,用于外界输入数据给计算机处理;
输出设备(Output Device):屏幕、扬声器、打印机、指示灯、网卡等,用于计算机输出结果给外界;
总线(Bus):连接各个设备的线缆,传输数据和控制信号;

1.5.1.2. 软件#
代码 (Code):用编程语言写成的文本,人可以阅读和修改;
操作系统(OS, Operating System):狭义指直接管理硬件的操作系统内核(Kernel) ,广义指硬件上的基础软件。主流的 OS 有 Windows、Linux、BSD、macOS、Android、iOS;
程序(Program):也称为应用(application),可供操作系统运行;
Shell:指操作系统提供的最基本的人机交互界面应用,用于输入命令、查看结果,例如 Linux、BSD 上的 sh 命令行工具,Windows、macOS 上提供的桌面、任务栏、程序列表、文件管理器等图形工具;
编辑器(Editor):一般指文本编辑器(Text Editor),可以用来编辑代码保存到外存里;
编译器(Compiler)、汇编器(Assembler)、链接器(Linker)、加载器(Loader):
编译器:指将高级编程语言翻译到汇编语言的程序;
汇编器:指将汇编语言翻译到机器码的程序;
链接器:指将多个机器指令的程序片断合并到一起的程序;
加载器:指操作系统用来加载外存上的程序进入内存的程序;
解释器(Interpreter):指直接运行代码,不用显式的经过编译、汇编、链接、加载四个阶段的一类程序;
1.5.2. 高级编程语言和汇编语言#
下面是三种语言定义函数的语法,以及机器码的十六进制表示,Python 和 C 都属于高级语言。
Python 语言:
def square(n):
return n * n
C 语言:
int square(int n)
{
return n * n;
}
x86-64 Intel 语法的汇编语言,右边的注释是对应的十六进制机器码:
square:
push rbp ; 55
mov rbp, rsp ; 48 89 e5
mov DWORD PTR [rbp - 0x4], edi ; 89 7d fc
mov eax, DWORD PTR [rbp - 0x4] ; 8b 45 fc
imul eax, eax ; 0f af c0
pop rbp ; 5d
ret ; c3
1.5.3. 编程语言的实现和编写流程#
编程语言有三种实现方式:
显式编译后执行,如 C、C++ 语言;
直接解释执行,如 Python;
编译到中间形式(还没到机器码)再解释执行,如 Java。
不同的编程语言实现,其编写程序的流程也不一样,也分成三种:
「编辑 → 编译 → 执行」循环,典型如 C/C++ 和 Java。
#include <iostream>
int main(int argc, char** argv)
{
std::cout << "Hello world!\n";
return EXIT_SUCCESS;
}
$ g++ -o hello hello.cpp
$ ./hello
Hello world!
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello world!");
}
}
$ javac HelloWorld.java
$ java HelloWorld
Hello world!
「编辑 → 执行」循环,典型如 Python。Java 也支持这种方式用于简单程序。
#!/usr/bin/env python3
print("Hello world!")
$ python3 hello.py
Hello world!
# 或者
$ chmod a+rx hello.py
$ ./hello.py
Hello world!
# Java 也可以不用显式编译
$ java HelloWorld.java
Hello world!
「读取 → 执行」循环,也称为 REPL(read-evaluate-print-loop,print 指输出表达式的值),典型如 Lisp 和 Smalltalk。Python 和 Java 也支持这种方式用于简单测试。
$ sbcl
* (format t "Hello world!~%")
Hello world!
NIL
$ python3
>>> print("Hello world!")
Hello world!
$ jshell
jshell> System.out.println("Hello world!")
Hello world!
从 Java 的例子可以看到「编程语言」和「编程语言的实现」是两个不同的概念,前者指语法和语义,后者指编译、执行形式。
1.5.4. 编程风格的分类#
编程风格(style)也常被称为编程范式(paradigm),指如何组织数据和代码。一种编程语言可以以某种风格为主,同时支持多种风格,常见的编程风格如下:
命令式(Imperative),也经常狭义指代「过程式」,明确指定计算机一步步「怎么做(how)」;
过程式(procedural),如 Fortran、Basic、Pascal、C;
面向对象式(object-oriented)
基于类(class-based),如 C++、Java;
基于原型(prototype-based),如 JavaScript、Io;
声明式(declarative),只给出「要做什么(what)」,让计算机(实际是设计好的算法)给出「怎么做」的具体步骤;
函数式(functional),如 Haskell、OCaml;
逻辑式(logical),如 Prolog;
响应式(reactive),如 Verilog,Elm;
截止 2024 年,主流使用的编程语言以过程式、面向对象式为主。
1.5.5. 选择编程语言#
自从计算机诞生以来,人们发明了上千种编程语言,如下是一些有名的语言:
汇编语言(Assembly):最古老的语言,已经很少人直接写汇编代码了;
Fortran: 1957 年诞生,常用于科学计算;
Lisp:1960 年诞生,早年常用于人工智能;
Pascal:1970 年诞生,1980 年代最火的编程语言;
C:1972 年诞生,常用于写操作系统、编译器、数据库等基础软件;
C++:1985 年诞生,扩充了 C 语言;
Python:1991 年诞生,常用于科学计算、人工智能;
Java:1995 年诞生,常用于服务器端的服务开发;
JavaScript:1995 年诞生,常用于浏览器端的动态网页开发;
各种编程竞赛中常用 C++、Java、Python 三种语言,C++ 的性能最快但最难学,Python 的性能最慢但最易学。
1.5.6. 选择编辑器#
编辑器也非常多,部分常见的如下:
Lem:使用 Common Lisp 编写的 Emacs 仿品;
micro:使用 Go 编写的代码编辑器;
Fresh:使用 Rust 编写的代码编辑器;
Visual Studio Code: 最流行的轻量级集成开发环境;
Zed:新兴的集成 AI 的代码编辑器;
Sublime Text:轻量级的代码编辑器;
Gedit:Linux GNOME 桌面环境曾经的默认编辑器(已换成更简单的 GNOME Text Editor);
CudaText:使用 Free Pascal 编写的代码编辑器;
Notepad–: 国产跨平台代码编辑器;
Geany、CodeLite、CodeBlocks、Redpanda C++: 轻量级的集成开发环境;
VIM 和 Emacs 功能丰富但比较难,Visual Studio Code、Zed、Kate 功能丰富且易用。
1.5.7. 基本语法#
编程语言很多,语法各异,但都有一些类似的基本语法。
1.5.7.1. 数据类型#
从 CPU 的机器指令视角看,数据类型只有两大类:
标量(scalar)
整数
byte(字节):存储单位,一个字节包含 8 比特(bit),分有符号字节,范围 0 ~ 255,和无符号字节,范围 -128 ~ 127;
word(字):计算单位,表示 CPU 一条整数指令能计算的数据长度,在 32 位 CPU 中是 4 字节(也就是 32 比特),在 64 位 CPU 中是 8 字节(也就是 64 比特);
浮点数
float(单精度浮点数):4 字节;
double(双精度浮点数):8 字节;
long double(扩展精度浮点数):12 或 16 字节;
向量(vector):也就是数组(array),在内存中连续存储的多个标量;
从编程语言的语法角度看,使用变量和常量指代数据,数据类型表现的更多样,以 C/C++ 为例:
整数:char, short, int, long, long long, int8_t, int16_t, int32_t, int64_t 等,以及对应的 无符号(unsigned)版本;
char 既表示字节,也表示 ASCII 字符,区分只在输入、输出上;
布尔类型 (_Bool, bool) 也是整数,但只有两个取值 true 和 false;
指针类型,本质也是个整数,用来表示内存地址;
浮点数:float, double, long double,符合浮点算术标准 IEEE 754;
由于长度限制,整数能表达的范围是有限的,N 比特能表达 2N 个整数,所以无符号整数范围是 0 ~ 2N - 1,有符号整数范围是 -2N ~ 2N - 1。因为有长度限制,运算时会发生溢出回绕,例如 signed char 的 127 + 1 = -128, -128 - 1 = 127,unsigned char 的 255 + 1 = 0,0 - 1 = 255。有的编程语言如 Python 的默认整数类型是利用多个机器整数拼起来的,可以表达任意小和任意大的整数,不会发生溢出回绕。
类似的,由于长度限制,浮点数能表达的范围和精度是有限的,特别是精度问题,浮点数不应该用 “==” 判断相等,例如 0.1 + 0.2 == 0.3 的结果是 false,并不相等。有的语言支持分数类型,可以用来做精确的四则运算,结果依然为分数。
除了整数和浮点数两种基本类型,大多编程语言还有以下复合类型:
结构体(struct) 、类(class)、元组(tuple)
枚举(enum)
联合(union)
字符串(string)
向量(vector)、数组(array)
链表(list)
映射(map)、词典(dictionary)、集合(set)
1.5.7.2. 控制结构#
代码语句的执行次序分成顺序、分支、循环、递归、异常五种,顺序就是一句句往下执行,递归是函数调用自身,分支、循环、异常有自己的语法,以 C++ 为例:
分支(branch)
if … else
continue, break
goto
循环(loop)
while, do … while
for
异常(exception)
throw, try … catch … finally
1.5.7.3. 函数#
常用的代码语句可以提取到函数(function)里,方便在代码的其它地方按名字调用,不再重复写,在上面已经展示了 square 函数。函数在不同语言里有不同称呼,如在 Perl 中称为 subroutine(子例程),在 Pascal 中按有无返回值分为 function 和 procedure( 过程)。
1.5.7.4. 模块#
除了函数是组织代码的一种方式,另一种更高层的方式是把常用的函数和复合数据类型提取到模块(module) 供多个程序复用,在有的语言里用包(package)指代模块,进一步的,多个模块可以放在一起成为库(library),方便分发用途相关的多个模块。
1.5.8. 数据结构 + 算法 = 程序#
《Data Structures + Algorithms = Programs》是 Pascal 语言之父、著名计算机科学家尼古拉斯·沃斯(Niklaus Wirth,1934年2月15日—2024年1月1日)编写的一本名著,揭示了编程的要点:设计一个程序,就是对输入的数据,考虑如何组织数据以及如何操作数据,最终得到期望的输出。
常用的数据结构:
array, vector: 一般内置在编程语言里;
string: 一般内置在编程语言里;
单向链表、双向链表:比 array 和 vector 能更高效的在开头和中间插入和删除元素;
stack, queue, dequeue, heap
tree, hash table
graph
matrix
常用的算法:
查找,包括字符串查找和数组、树的元素查找和插入;
数组的排序;
集合运算;
图的拓扑排序、最短路径;
1.5.9. 常用库#
日常编程中,所有语言都有类似的常用库:
文件读写,包括记录日志;
网络读写,最常用的是发送 HTTP 请求;
JSON 数据格式解析;
关系数据库的读写;
图形用户界面(GUI);
命令行选项解析;
配置文件读写;
1.5.10. 项目构建工具#
一个项目(project)由多个源文件组成,当文件很多而且要考虑在多种操作系统下编译时,手动写命令会很麻烦,因此需要专门的构建工具来:
检测所需的库、编译器等;
编译、链接、测试;
打包、安装;
以 C/C++ 为例,有最基本的 make ,更高级的 Autotools、CMake、Boost Build,更新潮的 xmake、Meson,注重封闭(hermetic)构建的 Bazel、Buck 2、Please、Pants。
1.5.11. 版本控制系统#
开发过程中需要记录文件的版本变更,以方便回溯历史,下面是常见的版本控制系统(Version Control System):