Programming Paradigms
A-Level Computer Science Topic 20 13:48 English narration · English + 中文 subtitles burned in
Chapters
Transcript
Here is one job: list every student with an A grade.
这里有一件事要做:列出所有拿到 A 的学生。
One programmer writes a loop — start at the first record, check the grade, add the name to a list, move on, and repeat to the end.
一位程序员写了一个循环——从第一条记录开始, 检查成绩,把名字加进列表,再往下走,一直重复到末尾。
Another writes a single line: select name from students where grade equals A.
另一位只写了一行: 从 students 表里选出成绩等于 A 的 name。
No loop, no counter, no moving on.
没有循环,没有计数器,也不用往下走。
Both give exactly the same answer.
两者给出的答案完全一样。
The first one says how to get it; the second says what you want.
第一种说的是"怎么拿到",第二种说的是"你想要什么"。
That difference has a name — a programming paradigm.
这个差别有个名字——编程范式。
Programming paradigms, and two practical skills that go with them.
编程范式,以及与之相伴的两项实用技能。
Today: the four paradigms, the pillars of object-oriented programming, how to update a file safely, and how to handle exceptions instead of crashing.
今天我们讲:四种编程范式、面向对象编程的几大支柱、 如何安全地更新一个文件,以及如何处理异常而不是让程序崩溃。
Let's begin.
让我们开始吧。
There are four paradigms in this syllabus.
这份大纲里有四种编程范式。
Low-level programming works close to the hardware, in assembly or machine code, where each instruction maps to something the processor actually does — it is fast and compact, but tied to one architecture and painful to maintain.
低级编程贴近硬件,用汇编或机器码来写,每一条指令都对应处理器 真正会做的事——它又快又紧凑,但绑死在一种架构上,而且维护起来很痛苦。
Imperative programming, also called procedural, is a sequence of commands that change the program's state: variables, assignments, loops and functions.
命令式编程, 也叫过程式编程,是一连串改变程序状态的命令:变量、赋值、循环和函数。
Object-oriented programming builds the program out of objects that bundle data together with the operations on that data.
面向对象编程把程序拆成一个个对象,每个对象都把数据和作用在这些数据上的操作打包在一起。
And declarative programming says what you want, not how to get it — that covers functional programming, made of pure functions, and logic programming, made of facts and rules.
而声明式编程说的是"你想要什么",而不是"怎么拿到"——它涵盖由纯函数组成的函数式编程, 以及由事实和规则组成的逻辑编程。
Modern languages often mix several of these.
现代语言常常把好几种混着用。
Zoom into low-level programming.
把低级编程放大来看。
You work close to the hardware in machine code or assembly language, where each instruction maps to what the central processing unit actually runs.
你贴近硬件,用机器码或汇编语言来写,每一条指令都对应中央处理器 真正会执行的操作。
You get direct access to registers and memory addresses, and you choose addressing modes: immediate, direct, indirect, indexed, and relative.
你可以直接访问寄存器和内存地址,并选择寻址方式:立即寻址、 直接寻址、间接寻址、变址寻址和相对寻址。
The payoff is speed and a tiny footprint.
好处是速度快、体积小。
The cost is that the code is tied to one architecture, tedious to write, and hard to maintain.
代价是代码绑死在 一种架构上,写起来费劲,也难维护。
Real uses include device drivers, firmware, and bootloaders — places where total control of the hardware matters more than comfort for the programmer.
真实用途包括设备驱动、固件和引导程序—— 在这些地方,对硬件的完全控制比程序员写起来舒不舒服更重要。
Imperative programming, also called procedural, is the style you already know from Python and from C.
命令式编程,也叫过程式编程,就是你在 Python 和 C 里已经熟悉的那种风格。
The programmer writes a sequence of commands that change the program's state.
程序员写出一连串改变程序状态的命令。
Variables hold the state; assignments, conditionals, loops and function calls change it.
变量保存状态;赋值、条件、循环和函数调用改变状态。
Code is organised into procedures and functions — that is also called structured programming, or structural programming.
代码被组织成过程和函数——这也叫结构化编程。
It is strong when the algorithm has clear sequential steps: read input, process, write output.
当算法有清晰的先后步骤时,它很合适: 读入、处理、写出。
That is why Topics nine and eleven in this course live mostly in this paradigm.
所以这门课里第九和第十一专题,大体都落在这个范式里。
Declarative programming says what to compute, not how — the runtime works out the steps.
声明式编程说的是要算什么,而不是怎么算——运行环境自己想出步骤。
Two kinds sit under this umbrella.
这个伞下有两种。
Functional programming is built from pure functions: no side effects, so the same input always gives the same output, and you compose those functions together.
函数式编程由纯函数组成:没有副作用,所以相同输入总给出相同输出,你再把这些函数组合起来。
Languages like Haskell and Lisp lean this way.
像 Haskell 和 Lisp 就偏向这一边。
Logic programming states facts and rules; the engine answers a goal — a query — by inference.
逻辑编程陈述事实和规则;引擎通过推理来回答一个目标—— 也就是一条查询。
Prolog is the classic example.
Prolog 是经典例子。
A familiar declarative tool is SQL — structured query language: select star from customer where country equals UK.
一个你熟悉的声明式工具是 SQL——结构化查询语言: 从 customer 表里选出 country 等于英国的全部行。
That line says what you want; it never says how to walk the records.
那一行说的是你想要什么; 它从不说该怎样一条条遍历记录。
Compare them by strength and typical language.
按优势和典型语言来比较它们。
Low-level gives maximum control and speed — assembly.
低级给出最大的控制和速度——汇编。
Imperative is direct and intuitive — C and Python.
命令式直接、直觉—— C 和 Python。
Object-oriented is modular and models real entities — Java, C sharp, Python.
面向对象模块化,适合为真实实体建模——Java、C sharp、Python。
Functional is clear and free of side effects — Haskell and F sharp.
函数式清晰且没有副作用——Haskell 和 F sharp。
Logic is about inference and rules — Prolog.
逻辑式关于推理和规则——Prolog。
Database queries sit with structured query language.
数据库查询落在结构化查询语言这边。
Modern languages often mix paradigms: Python alone supports procedural, object-oriented and functional styles.
现代语言常常混合范式:单是 Python 就同时支持 过程式、面向对象和函数式风格。
Pick the one that fits the problem, not the one you like most.
选适合问题的那一种,而不是你最喜欢的那一种。
Object-oriented programming has four pillars.
面向对象编程有四大支柱。
Encapsulation: an object's data is hidden behind its methods, so outside code cannot reach in and break it.
封装:对象的数据藏在它的方法后面,外部代码没法伸手进去把它弄坏。
Inheritance: a subclass takes everything from its superclass and then adds to it or replaces parts of it — a manager is an employee.
继承:子类把父类的一切都拿过来,然后在上面添加,或者替换掉其中的一部分——经理也是一名员工。
Polymorphism: different objects answer the same method call in their own way.
多态:不同的对象对同一个方法调用,各自用自己的方式作答。
And abstraction: show a simple interface, hide the messy details.
还有抽象: 对外给出一个简单的接口,把杂乱的实现细节藏起来。
Learn these four names with one short example each — that is exactly how the exam asks for them.
把这四个名字连同一个简短的例子一起记住—— 考试问的正是这个形式。
Here is encapsulation drawn as a bank account.
这是画成银行账户的封装。
The balance is private data — outside code cannot touch it directly.
余额是私有数据——外部代码不能直接碰它。
The only way in is through the public methods deposit and withdraw, which can enforce a rule such as never go below zero.
唯一的入口是公有方法 存款和取款,它们可以强制一条规则,比如余额永远不能低于零。
That protects the object, and it lets the internals change later without breaking callers.
这保护了对象,也让内部实现 以后可以改动,而不弄坏调用方。
In exam language: data is hidden behind methods; callers use the public interface only.
考试用语:数据藏在方法后面;调用方只用公有接口。
Inheritance models an is-a relationship.
继承建模的是"是一个"关系。
Look at the employee superclass at the top.
看上方的员工父类。
Below it, part-time and full-time are subclasses, each joined by a hollow-triangle generalisation arrow.
下面是兼职和全职两个子类, 各自用空心三角的泛化箭头连到父类。
Each subclass inherits the attributes and methods of employee, then adds or overrides its own — for example its own pay rate or hours.
每个子类继承员工的属性和方法,再添加或重写自己的—— 比如自己的薪资或工时。
A manager is an employee; a circle is a shape.
经理是一名员工;圆是一种形状。
The subclass specialises the superclass without rewriting what already works.
子类在父类上做特化, 而不必重写已经可用的部分。
Polymorphism means the same method call runs different code for each object.
多态的意思是:同一个方法调用,在不同对象上跑的是不同的代码。
Every shape has a method called area.
每个形状都有一个叫面积的方法。
Call area on a circle and it computes pi times radius squared.
对圆调用面积,就算圆周率乘以半径的平方。
Call area on a rectangle and it computes width times height.
对矩形调用面积,就算宽乘高。
The caller never needs to know which concrete type it holds — it just asks for area.
调用方根本不必知道自己拿的是哪种具体类型——它只问面积。
Same call, different implementation.
同一个调用,不同的实现。
That is the mark-winning one-line definition plus example.
这就是能拿分的一句话定义加例子。
Make that concrete.
我们把它落到实处。
A class is the blueprint; an object is one thing built from that blueprint — objects are instances of classes.
类是图纸;对象则是照着这张图纸造出来的一个具体东西—— 对象是类的实例。
A bank account class hides its balance as private data, and offers deposit and withdraw as public methods — so no code anywhere can set the balance to minus a million, because the methods enforce the rule.
一个银行账户类把余额作为私有数据藏起来,只对外提供存款和取款这两个公有方法—— 所以任何地方的代码都没法把余额设成负一百万,因为规则由这些方法来把关。
That is encapsulation.
这就是封装。
Now take a shape class with a method called area.
再看一个形状类,它有一个叫"面积"的方法。
A circle and a rectangle both have that method, but each works it out its own way, and the calling code never needs to know which one it is holding.
圆和矩形都有这个方法, 但各自用自己的方式算出结果,而调用它的代码根本不需要知道自己手上拿的是哪一个。
That is polymorphism — the same call, different code.
这就是多态——同一个调用,不同的代码。
A class diagram is a three-part box.
类图是一个三格盒子。
The top holds the class name — here, Shape.
最上面是类名——这里是形状。
The middle holds attributes, marked private with a minus: name, area, perimeter.
中间是属性,用减号标成私有: 名称、面积、周长。
The bottom holds methods, marked public with a plus: set shape, calculate area, calculate perimeter.
下面是方法,用加号标成公有:设置形状、计算面积、计算周长。
Private means only the object itself may touch those fields; public methods are the interface other code is allowed to call.
私有表示只有对象自己可以碰这些字段;公有方法才是其他代码允许调用的接口。
Reading a diagram like this is a standard exam skill for object-oriented design.
读懂这样的图,是面向对象设计里一项标准的考试技能。
A few more terms the paper likes.
试卷还喜欢几个词。
A constructor is a special method that runs when an object is created, to set up its attributes.
构造函数是对象被创建时运行的特殊方法,用来初始化它的属性。
Getters and setters read and write an object's attributes — its properties — through methods, so you still control access.
取值器和赋值器通过方法来读、写对象的属性——也就是它的特性——于是你仍然控制访问。
Aggregation and containment build an object from other objects: a has-a relationship, not is-a.
聚合和包含用别的对象来搭建一个对象:这是"有一个"关系,不是"是一个"。
A car has an engine; a class has students.
汽车有一个引擎;班级有学生。
Object-oriented code shines in large systems, graphical interfaces, simulations and games, where you model entities that keep their own data and behaviour together.
面向对象代码在大型系统、图形界面、模拟和游戏里很出彩, 因为你在为那些把数据和自身行为打包在一起的实体建模。
File processing extends the file work from Topic ten.
文件处理延续了第十专题的文件内容。
You handle serial, sequential and random — that is, direct-access — files.
你要处理串行、顺序和随机——也就是直接访问——文件。
The pseudocode operations are open file for read, write or append; read file into a variable; write file a value; close file; and end of file, which is true when you have reached the end.
伪代码操作有:以读、写或追加方式打开文件;把一行读进变量;把一个值写入文件;关闭文件; 以及文件结束,到达末尾时为真。
Read opens an existing file.
读打开已有文件。
Write creates or overwrites.
写会创建或覆盖。
Append adds to the end.
追加加在末尾。
Mixing write with append is the classic wipe-everything trap.
把写和追加搞混,就是那个把整份文件清空的经典陷阱。
Always close when you finish, or data may never leave the buffer onto disk.
用完一定要关闭, 否则数据可能还停在缓冲区里,没落到磁盘上。
Two standard patterns.
两个标准套路。
To read a whole file: open for read, then while not end of file, read the next line and process it, then close.
读完整文件:以读方式打开,然后在未到文件结束时循环,读下一行并处理,最后关闭。
To search: set found to false, open for read, then while not end of file and not found, read a line and if it matches the target set found to true, then close.
搜索:把已找到设为假,以读方式打开,然后在未到文件结束且尚未找到时循环,读一行, 若匹配目标就把已找到设为真,最后关闭。
Stop when you find it — do not keep reading past a match.
找到就停——不要在匹配之后还继续读。
And never hard-code an absolute path such as slash users slash admin slash data dot text; use a relative constant so the program still works on another machine.
也绝不要把绝对路径写死,比如斜杠 users 斜杠 admin 斜杠 data 点 text; 用相对路径常量,程序在另一台机器上才能照样跑。
Now a practical skill: changing one line in a text file.
接下来是一项实用技能:修改文本文件里的某一行。
You cannot simply overwrite that line, because the lines are different lengths — a longer replacement would run into the next record, and a shorter one would leave part of the old line behind.
你不能直接把那一行覆盖掉, 因为每行的长度都不一样——换成更长的会撞进下一条记录,换成更短的又会留下旧行的残余。
So use the copy pattern.
所以要用复制这个套路。
Open the original for reading, and a temporary file for writing.
把原文件以读的方式打开,再把一个临时文件以写的方式打开。
Read every line in turn, writing the new version for the line that changes and the original line for all the others.
依次读取每一行,需要改的那一行写入新版本,其余各行原样写入。
Close both, then replace the original with the temporary file.
两个文件都关掉, 然后用临时文件替换掉原文件。
Every line gets written — writing only the changed record and losing the rest of the file is the classic slip.
每一行都要写出去——只写那条改动的记录、 把文件其余部分弄丢了,是最经典的失误。
Two more traps: opening for write when you meant append wipes the file, and forgetting to close a file can lose your data.
还有两个坑:本想追加却用了写入模式,会把文件清空; 而忘记关闭文件,可能让你的数据丢失。
The same copy pattern handles deleting and inserting.
同一个复制套路也能处理删除和插入。
To delete a line, skip it when you write the temporary file — every other line is copied across.
要删一行,写入临时文件时跳过它——其余各行照抄过去。
To insert, write the extra line at the right place as you go.
要插入,就在正确位置多写那一行。
Either way you still open the original for read, write a full temporary file, close both, and replace.
无论哪种,你仍然以读打开原文件、写满一份临时文件、 两边都关掉,再替换。
A worked example from the paper: change one member's phone number in a text file of members.
试卷上的例题:在会员文本文件里改某一位的电话。
Lines differ in length, so you never overwrite in place — you rebuild the whole file through the temporary copy.
各行长度不同,所以绝不能原地覆盖——要通过临时副本重建整份文件。
Last, exception handling.
最后是异常处理。
An exception is an error that happens while the program runs — a file missing, a division by zero, an index off the end of an array.
异常是程序运行过程中发生的错误——文件不见了、除以零、数组下标越界。
Some of these simply cannot be prevented up front; a file can be deleted between your checking it and your opening it.
其中有一些你根本无法事先预防;在你检查文件存在和真正打开它之间,文件可能就被删掉了。
So instead of guarding every line, you wrap the risky code in a try block.
所以与其给每一行都加防护,不如把有风险的代码包进一个 try 块里。
If it fails, control jumps straight to the matching except block, which handles it.
一旦它失败, 控制流会直接跳到匹配的 except 块,由它来处理。
A finally block runs either way, which is perfect for cleanup like closing a file.
finally 块无论如何都会执行, 这非常适合做关闭文件之类的收尾工作。
A subroutine that spots a problem can also raise an exception and let its caller decide what to do.
发现问题的子程序也可以抛出一个异常, 让调用它的人来决定怎么办。
Handle it close by if the fix is simple, or higher up if only the outer code knows what to do.
如果处理起来很简单,就地处理;如果只有外层代码才知道该怎么做, 就往上交。
And Don't swallow exceptions silently — at the very least, log it.
还有,绝不要把异常悄悄吞掉——至少也要把它记进日志。
Trace the flow.
跟着流程走。
The try block holds the code that might fail — open a file, read a line, output it.
try 块放可能失败的代码——打开文件、读一行、输出。
If an exception is raised, control jumps to the first matching except block, for example file not found or read error, which prints a friendly message.
如果抛出了异常, 控制流跳到第一个匹配的 except 块,比如文件未找到或读错误,打印一句友好的提示。
If nothing goes wrong, the except blocks are skipped.
如果一切顺利,except 块被跳过。
Either way, the finally block runs, then the program continues.
无论哪种情况,finally 块都会执行,然后程序继续。
Real languages also offer a catch-all except.
真实语言还提供一个兜底的 except。
Wrapping risky work this way separates the normal path from the error path, so the main code stays readable.
这样包住有风险的工作,就把正常路径和错误路径分开, 主代码仍然好读。
A subroutine that detects an error can raise an exception so the caller handles it.
发现错误的子程序可以抛出异常,让调用方来处理。
Picture a divide procedure: if the second number is zero, raise divide by zero; otherwise return the integer quotient.
设想一个除法过程:如果第二个数是零, 就抛出除以零;否则返回整数商。
Handle close to the error when the response is simple — a message or a retry.
当应对很简单时——一句提示或重试——就在错误附近处理。
Handle higher up the call stack when only the outer code knows what to do — a top-level interface logs the error and shows a friendly dialog.
当只有外层代码才知道该怎么做时——顶层界面记日志并弹出友好对话框——就往调用栈上方交。
Common names to recognise: file not found, input output error, division by zero, index out of range, invalid argument, null reference, out of memory.
要认得的常见名字:文件未找到、输入输出错误、除以零、下标越界、非法参数、空引用、内存不足。
The goal is a program that degrades gracefully instead of crashing.
目标是程序优雅降级,而不是直接崩溃。
Three marks to lock in.
三个要拿稳的分。
First, name the paradigms and say when each one suits a problem.
第一,说出这些编程范式的名字,并说清楚每一种适合什么样的问题。
Second, for object-oriented programming, define class, object, inheritance, encapsulation and polymorphism, each with a one-line example.
第二,对于面向对象编程,要能定义类、对象、继承、封装和多态,每个都配一句话的例子。
Third, to change a file, copy every line to a temporary file and then replace the original.
第三,要修改一个文件,就把每一行都复制到临时文件里,然后替换掉原文件。
Nail these, and this topic — and this whole course — is yours.
掌握这些,这个专题——乃至整门课——就都是你的了。