学习笔记:计算机科学 (9645) - 软件与系统基础
你好!欢迎来到计算机科学的基石:软件。在本章中,我们将超越物理组件(硬件),深入探索赋予计算机生命的指令与逻辑。无论你是正在编写第一个程序,还是想了解操作系统的工作原理,理解软件——它是如何设计、构建和执行的——都至关重要。我们将涵盖从编程中使用的基本数据类型,到管理计算机内存及运行应用程序的复杂进程等所有内容。让我们开始吧!
1. 硬件与软件的关系 (3.6.1)
计算机系统完全依赖于物理部件与其指令之间的相互作用。
- 硬件 (Hardware): 计算机系统的物理电子组件(例如:CPU、内存、硬盘)。
- 软件 (Software): 通过硬件执行的指令序列。没有软件,硬件只是一堆电路盒子!
1.1 软件分类 (3.6.2)
软件大体上分为两大类:
系统软件 (System Software)
此类软件负责管理和控制计算机硬件,使应用软件能够运行。它充当了用户/应用程序与物理机器之间的中介。
最重要的系统软件是操作系统 (OS)。
- 隐藏复杂性: 操作系统的核心作用是对用户和其他软件隐藏硬件的具体细节。
- 实用程序 (Utility Programs): 为系统管理提供额外功能的辅助程序。(示例:病毒扫描程序、压缩软件、磁盘碎片整理程序)。
- 库 (Libraries): 可供其他程序使用的预编写函数或子程序的集合。
- 翻译程序 (Translators): 将源代码转换为机器码(处理器可执行代码)的程序(如编译器或解释器)。
操作系统的主要功能:
- 调度 (Scheduling): 决定接下来运行哪些任务或进程,以及运行多长时间,从而高效地管理 CPU。
- 内存分配 (Memory Allocation): 管理主存 (RAM),确保不同的程序在互不干扰的情况下获得所需的空间。
- 输入/输出设备管理 (I/O Device Management): 处理输入输出设备(如打印机、键盘、显示器)。
- 中断处理 (Interrupt Handling): 响应来自硬件或软件、需要立即处理的信号(中断)。
应用软件 (Application Software)
这是为用户执行特定任务而设计的软件。(示例:文字处理软件、网页浏览器、游戏、电子表格。)
快速总结: 系统软件管理计算机;应用软件为用户执行任务。
2. 编程基础:数据与指令 (3.1.1, 3.1.2)
当我们编写软件时,实际上是在使用指令操作数据。在操作数据之前,必须先定义数据类型。
2.1 数据类型 (3.1.1)
数据类型 (Data type) 告诉计算机为一段数据分配多少内存,以及可以对它执行什么样的操作。
- 整数 (Integer): 整数(正数、负数或零)。(示例:5, -100)
- 实数/浮点数 (Real / Float): 带有小数部分的数字。(示例:3.14, 0.5)
- 布尔值 (Boolean): 只能为 True(真) 或 False(假) 的逻辑值。
- 字符 (Character): 单个字母、数字、标点符号或符号。(示例:'A', '5', '?')
- 字符串 (String): 字符序列。(示例:"Hello World")
- 日期/时间 (Date/Time): 表示特定日期和/或时间的值。
记忆技巧: 把数据类型看作容器。你不会用麻袋(字符串容器)来装水,你需要一个水壶(类似于浮点数容器)!
2.2 变量与标识符 (3.1.2)
- 变量声明 (Variable Declaration): 预留内存空间并赋予其名称(标识符)。
- 赋值 (Assignment): 将值存入该变量。
- 有意义的标识符命名: 使用能清楚表达变量用途的名称非常重要,这会让代码更易于阅读和维护。(例如:使用 TotalScore 而不是 x)。
2.3 输入、输出与注释 (3.1.2)
程序必须能够与用户交互(输入)并展示结果(输出)。
- 输入 (Input): 从用户处获取数据(例如:询问他们的年龄)。
- 输出 (Output): 显示结果(例如:打印 "你的最终得分为 95")。
- 注释 (Comments): 添加到代码中的解释性说明。它们会被翻译程序忽略,但对于人类程序员理解代码的工作方式以及为何做出某些决策至关重要。
快速总结: 数据类型定义数据;变量存储数据;标识符使数据易读;输入输出处理用户交互。
3. 控制流:逻辑与运算 (3.1.2.1-3.1.2.3)
控制流语句决定了指令执行的顺序。
3.1 算术运算 (3.1.2.1)
这是标准的数学运算:
- 加法 (+)、减法 (-)、乘法 (*)。
- 实数/浮点除法: 标准除法,结果为浮点数(例如:7 / 2 = 3.5)。
- 整数除法 (DIV): 除法运算,仅保留整数部分(商)。(示例:7 DIV 2 = 3)。
- 取余 (MOD): 返回整数除法后的余数。(示例:7 MOD 2 = 1)。
- 幂运算 (Exponentiation): 数字的乘方。
我们也提供调整数值结果的函数:
- 四舍五入 (Rounding): 将数字向上或向下调整到最接近的整数(或指定的十进制位)。
- 截断 (Truncation): 直接切除数字的小数部分(始终向零方向取整)。
3.2 控制结构:选择与迭代 (3.1.2)
选择 (Selection/Decision Making): 允许程序根据条件选择不同的执行路径(例如:IF, ELIF, ELSE, CASE 语句)。
迭代 (Iteration/Looping): 重复执行一段代码。
- 定数迭代: 运行固定且预定次数的循环(例如:FOR 循环)。
- 不定数迭代: 运行直到满足特定条件的循环(例如:WHILE 或 REPEAT UNTIL 循环)。
- 嵌套结构: 将一个选择或迭代结构完全置于另一个结构之内。这在处理二维数据(如网格中的行和列)时非常常见。
3.3 关系与布尔运算 (3.1.2.2, 3.1.2.3)
关系运算 (Relational Operations): 比较两个值并得到一个 布尔值 (True/False):
- 等于 (= 或 ==)
- 不等于 (\( \neq \) 或 !=)
- 小于 (<), 大于 (>)
- 小于等于 (\( \le \)), 大于等于 (\( \ge \))
布尔运算 (Boolean Operations/Logical Operators): 合并布尔值:
- NOT: 反转真假值(True 变为 False)。
- AND: 仅在两个条件都为 True 时才为 True。
- OR: 只要至少有一个条件为 True,结果即为 True。
- XOR (异或): 仅当只有一个条件为 True(不同时为 True)时结果为 True。
优先级顺序: 当语句有多个布尔运算符时,它们按特定顺序计算:
NOT (最高) > AND > OR (最低)。(括号总是能改变计算顺序)。
快速总结: 程序通过运算和控制结构(选择/迭代)来决定下一步采取什么步骤。
4. 模块化与过程(子程序)(3.1.2.7)
大型程序过于复杂,无法一次性写完。我们将它拆解成更小、可管理的块,称为子程序 (subroutines)(有时称为函数或过程)。
4.1 子程序与参数
- 定义: 子程序是一个已命名、“外置”的代码块,用于执行特定任务。
- 调用/执行: 只需在程序语句中写下其名称即可执行(调用)。
- 优势:
- 模块化: 将问题拆解 (Decomposition)。
- 可重用性: 代码块可以在程序中多次使用,无需重复编写。
- 调试更容易: 较小的块更容易测试和修复。
- 返回值: 子程序可以计算结果并将其返回给调用它的主程序。
- 参数: 调用子程序时传入的数据,允许其操作特定数值。
4.2 变量作用域(局部 vs 全局)
变量的作用域 (scope) 定义了程序中哪些部分可以访问该变量。
- 全局变量 (Global Variables): 在整个程序中均可访问的变量。
- 局部变量 (Local Variables): 在子程序内部声明的变量。
- 它们仅在子程序执行期间存在。
- 它们仅在子程序内部可访问。
最佳实践: 尽可能限制变量的作用域(优先使用局部变量而非全局变量),因为这可以防止程序其他部分意外修改重要数据。
4.3 栈帧 (Stack Frame)
当调用子程序时,计算机需要一种方式来保存执行位置并管理该调用的数据。它使用一块称为栈 (Stack) 的内存区域。每次子程序调用都会创建一个栈帧,用于存储:
- 返回地址: 子程序完成后需要返回的下一条指令的内存地址。
- 参数: 传入子程序的值。
- 局部变量: 在子程序内部声明的变量。
快速总结: 子程序通过局部/全局作用域提升代码质量并管理数据访问,同时通过栈帧在执行期间处理内存。
5. 数据结构 (3.2, 3.2.1-3.2.4)
数据结构 (Data structure) 是一种在计算机中组织和存储数据的方式,以便能高效地访问和修改。
5.1 静态与动态结构
- 静态数据结构: 大小固定,在程序编译或首次运行时决定。(示例:数组)
- 优点: 访问速度快;内存分配简单。
- 缺点: 如果未填满则浪费空间;需要更多空间时无法增长。
- 动态数据结构: 大小可在程序运行期间改变(可以增长或收缩)。(示例:链表实现的列表、队列、栈)
- 优点: 内存利用率高;能处理不可预见的数据量。
- 缺点: 内存管理较复杂;访问速度有时较慢。
5.2 数组、列表与记录 (3.2.1, 3.2.2)
- 数组/列表: 在单个标识符下存储的相同数据类型的项集合。我们可以使用二维数组(数组的数组,类似于网格)来存储复杂数据,如棋盘或电子表格。
- 记录 (Records): 相关数据值(称为字段/fields)的集合,它们的数据类型可能不同,被视为单个实体进行组合和操作。
示例:一个 '学生记录' 可能包含字段:姓名 (字符串)、学号 (整数)、等级 (字符)。
5.3 栈 (LIFO) (3.2.4)
栈 (Stack) 是一种动态数据结构,最后添加的项是第一个被移除的。这称为后进先出 (Last-In, First-Out, LIFO)。
类比:一叠盘子——你总是取走最上面(最新)的一个。
栈操作:
- 压栈 (Push): 将项添加到栈顶。
- 弹栈 (Pop): 移除并返回栈顶项。
- 窥视 (Peek/Top): 返回栈顶项的值而不移除它。
栈对于管理子程序调用(前文提到的栈帧)和检查表达式中括号的平衡性至关重要。
实现: 栈通常使用一维数组实现,需要检查栈是空还是满。
5.4 队列 (FIFO) (3.2.3)
队列 (Queue) 是一种动态数据结构,最先添加的项是第一个被移除的。这称为先进先出 (First-In, First-Out, FIFO)。
类比:商店排队的人群——先到的人先被服务。
队列操作:
- 入队 (Enqueue): 将项添加到队列尾部。
- 出队 (Dequeue): 从队列前端移除并返回该项。
队列在操作系统中用于打印缓冲和处理数据缓冲区等任务。
实现:
- 线性队列: 使用数组的简单实现。问题:项出队后,“前端”会向前移动,导致数组开头出现未使用的空空间。
- 循环队列: 通过将队列索引绕回到数组开头来解决线性队列的问题。这允许在项出队后高效地重用空间。
快速总结: 栈是 LIFO(后进先出),队列是 FIFO(先进先出)。
6. 软件设计与开发 (3.3)
软件开发遵循逻辑结构,以确保最终产品符合需求且易于维护。
6.1 结构化编程方法 (3.3.1)
结构化方法侧重于清晰度、质量和控制。关键特性包括:
- 模块化编程: 将程序分解为独立的子程序/模块(如第 4 节所述)。
- 使用参数和返回值: 为模块定义明确的输入和输出。
- 使用局部变量: 限制作用域以防止意外的副作用。
使用诸如层次结构图(展示总体程序结构)和结构图(展示模块、参数和数据流)等设计工具。
6.2 抽象与拆解 (3.3.2)
- 拆解 (Decomposition): 将大型、复杂的问题分解成更小、可识别的子问题。这通常通过使用子程序来实现。
- 抽象 (Abstraction): 从问题中移除不必要的细节,专注于解决问题所需的基本特性。(示例:在设计赛车模拟时,你抽象掉具体的发动机螺栓尺寸,仅关注速度、燃料和位置。)
6.3 软件开发周期 (3.3.4)
软件通常通过几个阶段创建。这可能是一个迭代(重复)的过程,通常使用原型设计或敏捷开发方法。
阶段 1:分析
明确定义问题。通过与预期用户交互确定系统的需求。这会创建功能规范和数据模型。
阶段 2:设计
在编写任何代码之前规划解决方案。这包括:
- 设计数据结构(如数组和记录)。
- 设计算法(使用伪代码或流程图)。
- 设计模块结构(子程序)。
- 设计人机界面 (HUI)。
重心可以从关键路径开始——即解决方案中其他一切所依赖的部分。
阶段 3:实现
将模型和算法转换为计算机可以处理的代码(指令)。
阶段 4:测试
检查实现是否存在错误。必须使用精选的测试数据:
- 正常数据 (Typical): 在通常条件下预期的有效数据。
- 边界数据 (Boundary): 正好处于可接受输入极限的数据(例如,如果范围是 1 到 100,则使用 1 或 100)。
- 错误数据 (Erroneous): 系统应该拒绝的无效数据(例如,期望数字时输入字母)。
阶段 5:评估
根据预定标准评判成品系统:
- 正确性: 系统是否符合需求?(它解决问题了吗?)
- 效率: 运行速度有多快(时间),以及它占用多少内存(空间)?
- 可维护性: 程序员以后修复、更新或修改代码的难度如何?
快速总结: 好的软件是使用结构化方法构建的,侧重于拆解并遵循分析、设计、实现、测试、评估循环。
7. 编程语言与翻译 (3.6.3)
计算机只理解机器码(二进制),但人类使用高级语言编写软件。翻译程序架起了这座桥梁。
7.1 低级语言
这些语言非常接近硬件,并且是处理器指令集特定的。
- 机器码 (Machine Code): CPU 可直接执行的二进制指令(0 和 1)。这是计算机唯一真正理解的语言。
- 汇编语言 (Assembly Language): 使用助记符(如 ADD, LOAD 等易读的简写)来代表机器码指令。
低级语言 (L-L-L) 的优点:
- 程序员对硬件和内存有精确的控制。
- 生成非常快且高效的代码。
低级语言 (L-L-L) 的缺点:
- 编写困难且耗时。
- 不可移植(为一种类型的 CPU 编写的代码无法在另一种 CPU 上运行)。
7.2 高级语言 (HLL)
这些语言更接近人类语言,使用起来更简单。
- 指令式高级语言 (Imperative HLL): 一种命令明确描述执行任务所需遵循的*过程*(步骤序列)的语言。(最常见的语言如 Python, Java, C++ 都是指令式的。)
高级语言 (HLL) 的优点:
- 更容易阅读、编写和调试。
- 可在不同的硬件平台上移植。
7.3 程序翻译程序类型 (3.6.3.2)
翻译程序将源代码(人类可读)转换为目标代码(机器码/可执行文件)。
| 翻译程序 | 角色与过程 | 何时使用 |
| 汇编器 (Assembler) | 将汇编语言转换为机器码。 | 当需要控制特定硬件组件时。 |
| 编译器 (Compiler) | 在执行之前将整个源代码翻译成机器码。创建一个可执行文件。 | 当需要最终、快速且安全的产品时(例如:商业软件)。 |
| 解释器 (Interpreter) | 在运行时逐行翻译和执行代码。不会创建单独的可执行文件。 | 在开发和测试期间,或者在可移植性/快速更改至关重要的系统上运行时。 |
中间语言(如字节码/Bytecode): 一些编译器生成中间语言而不是直接生成机器码。
- 为什么要使用它们? 它们比机器码更可移植,并允许在执行前进行安全检查。
- 如何使用? 它们由虚拟机(解释中间代码)或即时编译器 (JIT)(在执行前立即将其转换为机器码)运行。
快速总结: 程序员为了简便和可移植性使用高级语言,依靠编译器(为了速度)和解释器(为了灵活性)来生成最终的机器码。
8. 面向对象与函数式范式
虽然大多数 AS 编程侧重于指令式/过程式方法,但现代软件使用不同的方法,称为范式 (paradigms)。
8.1 面向对象编程 (OOP) (3.9)
OOP 被使用是因为它能更好地模拟现实世界实体,从而促进重用性和安全性。
OOP 核心概念:
- 类 (Class): 定义对象共同特性(属性/attributes)和行为(方法/methods)的蓝图或模板。(例如:"汽车"类。)
- 对象 (Object): 类的一个实例。对象通过构造函数 (constructor)(初始化对象状态)进行实例化 (instantiation) 来创建。(例如:"我的丰田" 是"汽车"类的一个对象。)
- 封装 (Encapsulation): 对其他类隐藏类的内部运行方式和表示数据的方式。这保护了数据的完整性。访问通常通过获取器 (getter) 和 设置器 (setter) 方法控制。
- 继承 (Inheritance): 一种关系,其中新类(子类/subclass 或派生类)是现有类(基类/base class 或父类)的更专业化版本。(例如:"卡车"子类继承了"车辆"基类的属性。)
- 重写 (Overriding): 当从基类继承的方法在子类中被重新定义,以使子类具有不同的行为时。
- 关联 (Association): 两个对象之间的一种较弱关系,其中一个对象仅使用另一个对象。(例如:"团队"对象使用"玩家"对象。)
8.2 函数式编程 (FP) (3.12)
FP 专注于使用数学函数计算结果,将函数视为一等对象(意味着它们可以作为参数传递或从其他函数返回)。
- 函数应用 (Function Application): 为函数提供特定的输入(参数)以获得结果。
- 函数组合 (Composition of Functions): 合并两个函数以获得新函数。(如果运行函数 f,然后将函数 g 运行在 f 的结果上,这就是 g of f。)
- 高阶函数 (Higher-Order Functions): 将另一个函数作为参数,或者返回一个函数作为结果,或者两者兼有的函数。
- 映射 (Map): 将函数应用于列表中的每个元素,返回结果的新列表。
- 过滤 (Filter): 处理列表,生成仅包含符合给定条件元素的新列表。
- 折叠 (Fold/Reduce): 通过重复应用组合函数,将值列表缩减为单个值。(注意:foldl 向前工作(从左到右),foldr 向后工作(从右到左))。
快速总结: OOP 使用类和对象模拟现实世界对象;FP 将函数视为数据,并使用高阶函数高效地操作列表。
9. 其他编程实用程序
9.1 字符串处理与转换 (3.1.2.4)
字符串是字符序列,各种操作允许我们处理它们:
- 长度 (Length): 查找字符数。
- 位置 (Position): 查找字符或子字符串开始的位置。
- 子字符串 (Substring): 提取字符串的一部分。
- 连接 (Concatenation): 将两个字符串连接在一起。
- 转换 (Conversion): 在数据类型之间移动(例如:字符串 到 整数,或 字符 到其数值代码)。
9.2 异常处理 (3.1.2.6)
这是一种处理程序执行期间发生的意外事件(异常/exceptions)的编程技术,例如尝试除以零或尝试打开不存在的文件。
如果起初觉得这很复杂,别担心!异常处理仅仅是为出错情况做好计划,并确保你的程序不会崩溃。
快速回顾: 软件是硬件执行的指令。它是使用模块化结构(子程序)、定义好的数据类型和逻辑控制流(选择和迭代)构建的。设计过程确保了正确性、效率和可维护性。