斯坦福-CS105-计算机科学导论笔记-全-

斯坦福 CS105 计算机科学导论笔记(全)

L1.1:比特、字节与二进制:0和1的世界 🔢

在本节课中,我们将要学习计算机如何表示和处理信息的基础。一切数字信息,从你看到的文字到播放的视频,其根源都归结为最简单的两种状态:0和1。我们将探讨为什么计算机使用二进制系统,以及比特和字节这两个核心概念。

计算机使用二进制系统

计算机使用二进制数字系统。让我们看看二进制数字是什么样子。我们将从比较十进制数字开始,这是我们习惯的数字系统。

在十进制数字系统中,有10个不同的数字:0到9。所有你习惯看到的数字都由这10个不同的数字组成。

我们有时也将这个数字系统称为 基数为10 的系统。

二进制系统:只有0和1

相比之下,计算机使用二进制数字系统。在二进制数字系统中,只有两个数字:0和1。

以下是一些使用二进制数字系统可以构成的不同类型数字的例子:

  • 101
  • 1101
  • 100101

我们将在另一个视频中详细探讨这些数字如何工作以及它们的含义。但现在,只需认识到二进制数字系统中的所有数字仅由两个数字0和1组成。

正因为只有两个数字,我们有时将使用这个数字系统的数字称为 基数为2 的数字。

为什么计算机使用二进制?

接下来,我们来看看为什么计算机使用二进制数字系统。

如果我们打开计算机并查看计算机内存,我们会发现计算机内存实际上由一大堆独立的电子开关组成。我们可以把这些电子开关想象成和你的电灯开关没有太大不同。

就像墙上的电灯开关可以打开或关闭一样,计算机内部的开关也可以打开或关闭。当开关关闭时,我们认为它代表数字 0;当开关打开时,我们认为它代表数字 1

因此,我们的每个开关都对应一个单个数字。由于这些数字只能是0或1,所以它们是二进制数字。我们将“二进制数字”缩写为 比特

所以,当你听到有人谈论比特时,他们谈论的就是一个0或1,或者是计算机内部用于存储那个0或1的内存。

从比特到字节

现在,仅用一个比特我们做不了太多事情。因此,我们将比特组合成组。比特的主要分组是一组 8个比特,这被称为一个 字节

字节是计算机存储的主要度量单位。

所有数字媒介都存储比特和字节

虽然我一直在描述计算机主内存内部的情况,但所有计算机数据最终都以比特和字节的形式存储。

我们使用各种不同的媒介来存储数字信息。在每种情况下,该数字媒介都需要被设置为存储0和1,即比特和字节。

  • 固态硬盘:大多数笔记本电脑内部使用的就是固态硬盘。它基本上使用电子方式存储信息,这与我们前面描述的非常相似。
  • 机械硬盘:如果你打开一个机械硬盘,你会发现磁盘本身有被磁化的独立扇区。根据磁极方向,如果磁极朝一个方向,它被视为0;如果磁极朝相反方向,它被视为1。
  • 光盘:如果我们仔细观察CD、DVD或蓝光光盘,我们会发现光盘由许多不同的区域组成,这些区域要么被称为“凹坑”,要么被称为“平面”。根据光盘该区域是凹坑还是平面,我们最终用这些凹坑和平面来表示0和1。

所以,再次强调,计算机使用的所有信息最终都将存储为这些0和1,即比特,并且它们将被组织成称为字节的8位一组。

总结与预告

本节课中,我们一起学习了计算机信息表示的基础。我们了解到计算机使用二进制系统,仅由0和1两个数字构成。计算机内存中的电子开关用开/关状态对应这两个数字,每个开关的状态就是一个比特。为了存储更复杂的信息,8个比特被组合成一个字节,这是计算机存储的基本单位。无论是内存、硬盘还是光盘,所有数字媒介最终都以比特和字节的形式存储数据。

虽然我们的第一个视频相当简短,但本讲座中的其他视频会更长。在下一个视频中,我们将深入了解二进制数字的实际工作原理。

L1.2:比特、字节与二进制

在本节课中,我们将要学习计算机信息表示的基础:比特、字节与二进制数系统。我们将了解二进制如何工作,它与我们熟悉的十进制系统有何不同,以及为什么它在计算机科学中如此重要。

概述

计算机内部使用二进制系统处理所有信息。本节将解释二进制数的基本原理,包括如何计数、如何与十进制数转换,以及比特组合如何表示各种数据。

从十进制到二进制

在上一课中,我们了解了计算机为何使用二进制数系统。本节中,我们将仔细看看二进制数的实际工作原理。虽然这会涉及一些数学,但理解二进制数如何工作,以及它们可能代表什么,对于后续学习计算机中的数据表示非常有用。二进制系统在计算机中无处不在,数据的底层表示都使用这些二进制数。

回顾十进制计数

为了理解二进制计数,首先回顾我们熟悉的十进制计数会很有帮助。十进制系统使用0到9这十个数字。

我们从0开始,加1得到1。再加1得到2,依此类推,直到9。当我们尝试将1加到9时,会遇到一个问题:没有第十一个数字。因此,我们执行“进位”操作:将个位的9重置为0,并向十位进1,最终得到10。

这个过程会持续下去。例如,19加1时,个位9加1再次需要进位,我们得到20。99加1时,个位和十位都无法再容纳更大的数字,所以我们从十位向百位进位,最终得到100。

二进制计数原理

现在让我们来看看它在二进制中是如何工作的。二进制系统只有两个数字:0和1。

我们同样从0开始,加1得到1(0 + 1 = 1)。这与十进制类似。但下一步,当我们尝试计算 1 + 1 时,问题出现了。二进制中没有数字“2”,只有0和1。这类似于十进制中 9 + 1 的情况。

因此,我们需要执行进位操作:将1加到1的结果是 10(读作“一零”)。我们可以继续:

  • 10 + 1 = 11
  • 11 + 1 时,又需要进位,得到 100

以下是二进制数系统的前几个数字(对应十进制0到15):

  • 0 -> 0
  • 1 -> 1
  • 10 -> 2
  • 11 -> 3
  • 100 -> 4
  • 101 -> 5
  • 110 -> 6
  • 111 -> 7
  • 1000 -> 8
  • 1001 -> 9
  • 1010 -> 10
  • 1011 -> 11
  • 1100 -> 12
  • 1101 -> 13
  • 1110 -> 14
  • 1111 -> 15

我们有时使用下标来澄清数字使用的进制,避免混淆。例如:

  • 6₁₀ 表示十进制数字6。
  • 110₂ 表示二进制数字“一一零”,而不是十进制的一百一十。

二进制与十进制的转换

了解不同进制数字之间的等价性很重要。让我们看看如何进行转换。

十进制数的位值原理

首先,回顾十进制系统。数字 1891 中,每个位置代表10的不同次幂:

  • 最右边是个位:1 × 10⁰ = 1
  • 然后是十位:9 × 10¹ = 90
  • 接着是百位:8 × 10² = 800
  • 最左边是千位:1 × 10³ = 1000
  • 总和:1 + 90 + 800 + 1000 = 1891

二进制数的位值原理

二进制系统使用相同的位值原理,但基数是2。以二进制数 1101 为例:

  • 最右边是第0位:1 × 2⁰ = 1
  • 然后是第1位:0 × 2¹ = 0
  • 接着是第2位:1 × 2² = 4
  • 最左边是第3位:1 × 2³ = 8
  • 总和:1 + 0 + 4 + 8 = 13

因此,二进制数 1101₂ 等价于十进制数 13₁₀

比特的组合与信息表示

理解了二进制数本身后,一个关键问题是:给定数量的比特(二进制位)可以表示多少种不同的信息组合?这决定了我们能存储或表示多少种不同的东西。

以下是不同数量比特可表示的组合数:

  • 1位:可以表示 01 两种状态。常用来表示 真/假是/否
  • 2位:可以表示 00011011 四种组合。
  • 3位:可以表示八种组合(000111)。
  • 4位:可以表示十六种组合(00001111),对应十进制0到15。

这里有一个通用公式:如果有 n 位,就可以表示 2ⁿ 种不同的组合

这些组合不仅可以表示数字(正数、负数),还可以表示任何我们定义的事物。例如:

  • 用4位可以表示加拿大的10个省和3个地区(共13种,小于16种)。
  • 用4位无法表示美国的50个州(需要至少6位,因为 2⁶ = 64 > 50)。

程序员在设计程序时,必须决定为某项信息分配多少比特,以及如何用这些比特序列来编码信息(如数字、省份、宿舍等)。

计算机存储中的2的幂

你可能注意到,计算机存储设备的容量常常是128、256、512等数字,而不是100、250、500。这是因为这些数字是2的幂(2⁷=128, 2⁸=256, 2⁹=512),与计算机内部的二进制处理方式更匹配。

同样,存储单位也使用基于2的幂的命名:

  • 1 千字节 ≈ 2¹⁰ = 1024 字节(约一千)
  • 1 兆字节 ≈ 2²⁰ = 1,048,576 字节(约一百万)
  • 1 吉字节 ≈ 2³⁰ = 1,073,741,824 字节(约十亿)
  • 1 太字节 ≈ 2⁴⁰ = 1,099,511,627,776 字节(约一万亿)

需要注意的是,在某些领域(如网络带宽、硬盘制造商),可能指10的幂(1000, 1,000,000, 1,000,000,000),这有时会造成混淆。但在计算机内存和大多数软件语境中,通常指的是2的幂。

总结

本节课中,我们一起学习了:

  1. 二进制计数:二进制只有0和1,遵循“逢二进一”的规则。
  2. 进制转换:利用位值原理(数字 × 基数^位置)可以在二进制和十进制间转换。
  3. 比特与组合n 位二进制可以表示 2ⁿ 种不同组合,这些组合可被赋予各种含义(数字、状态、代码等)。
  4. 2的幂的应用:计算机存储容量和单位常基于2的幂,这与二进制系统高效协同。

理解比特、字节和二进制是理解计算机如何表示和处理一切信息——从数字、文本到图像、声音——的基石。在接下来的课程中,我们将继续探索如何用这些基本的二进制位来表示更复杂的数据。

L1.3:比特、字节与二进制:一场由16位引发的火箭事故 🚀

在本节课中,我们将学习计算机如何用比特和字节表示信息,并探讨当数据表示出现错误时可能引发的严重后果。我们将通过一个真实的火箭事故案例,深入理解“溢出”等核心概念。

概述

本节课程将从一个代价高昂的真实错误案例开始——1996年欧洲航天局阿丽亚娜5号火箭的发射失败。我们将分析其根本原因:程序员选择了错误的位数来存储数据。通过这个案例,我们将学习二进制表示、溢出错误,并了解与之相关的“千年虫”问题。最后,我们会探讨计算机在处理小数时可能出现的精度问题。

阿丽亚娜5号火箭事故

上一节我们介绍了二进制的基本原理,本节中我们来看看当数据表示出现问题时会发生什么。

1996年,欧洲航天局发射了新型火箭阿丽亚娜5号。这枚火箭搭载了四颗总价值3.7亿美元的卫星。发射初期看似顺利,但在飞行37秒后,火箭开始疯狂转向并最终解体,不得不被摧毁。

问题出在哪里?问题根源在于,控制火箭的一个程序试图用16位来存储一个信息。

理解16位存储的限制

正如你可能记得的,16位可以存储的最大数字是 2^16,这意味着我们可以存储 0到65,535 之间的数字。

虽然这个数字对于其前身阿丽亚娜4号来说足够大,但阿丽亚娜5号的性能特征使其运行得更快。它试图存储到一个16位位置的值,已经超出了该位置能容纳的范围。

当程序尝试将一个过大的值放入16位时,最终发生的情况称为“溢出”。

溢出与千年虫问题

溢出时可能发生的情况是:我们有一个非常大的数字,然后给它加一,最后却得到零;或者给一个大的正数加一,最后却得到一个大的负数。

这种现象还与另一个著名的问题有关,即“千年虫”问题。

“千年虫”问题的起因是:从上世纪50年代开始,程序员为了在计算机程序中节省空间,在存储年份时,没有用所有四位数字存储全年,而是只存储了后两位数字。

以下是其工作原理的一个示例:

  • 假设程序员要存储年份1952,他们只存储“52”。
  • 当从计算机内存中读取“52”时,程序会知道需要为其加上“19”。
  • 这种模式持续了多年,例如1984年存为“84”,1995年存为“95”。

但当越来越接近2000年时,程序员意识到,如果只存储“00”而不修复程序,就会出问题。程序会认为“00”年代表1900年,而不是2000年。这就是“千年虫”问题。最终,我们不得不花费数百万美元重写计算机程序,以确保当2000年到来时,计算机不会认为我们回到了1900年代。

溢出是如何工作的

让我们看看溢出实际上是如何工作的。我将向你展示两种不同版本的溢出。

首先,看看当我们只存储正整数时会发生什么。我将使用8位来演示,这样更容易理解。

在8位中,如果只存储正数,我们可以存储从 0到255 的数字。假设我们将数字255存储在一个特定的位置。

数字255是8位中能存储的最大可能数字(所有位都是1)。然后我尝试给它加一。

如果你记得我们上一个关于如何在二进制中加法的视频,那么自然会发生的是:我们会产生进位。我们不断进位,直到最左边。你会看到,第九位被翻转为1,但我们没有第九位,我们只有八位。因此,第九位被丢弃了。

结果,我们从一个由所有位都是1组成的非常大的值(255),变成了一个由所有位都是0组成的非常低的值(0)。

所以,从十进制来看发生的情况是:我们有255,我们给255加1,但实际上我们最终得到了0。这就是溢出。

有符号整数溢出

对于下一个示例,我们将看看当我们有一个很大的正数,但将它存储在允许存储正数和负数的空间中时会发生什么。

同样,在这种特殊情况下,我将所有数据存储在一个字节中,这允许我们存储 -128到127 之间的数字。

在计算机上存储负数有许多不同的方法。最常见的一种叫做“二进制补码”。所有这些方案的关键点是:最左边的位将代表我们存储的是正数还是负数。

所以,我们现在要做的是,从最大的正数开始。最左边的位是0,代表这是一个正数。所有其他七位都是1,这代表正127。

然后我们继续给它加一。你可以看到这里发生了什么:最左边的位翻转为1,这意味着我们得到了一个负数。使用二进制补码表示,这实际上代表负128。

所以发生的情况是:我们有一个非常大的正数127,我们给它加了1,得到了128,但由于溢出,它变成了负128。这又是一个溢出的例子。

因此,当你使用计算机程序时,可能认为一切正常,但突然你的银行账户被重置为零,或者你的一百万美元变成了负一百万。这里可能发生的情况就是溢出,这可能意味着编写该程序的人没有做好充分的错误处理。

二进制小数的精度问题

我还有另一种错误想向你介绍。严格来说,这并非技术性错误,但我们将看到,当我们处理二进制数时,我们对结果应该是什么的直觉不一定正确。

我的意思是,我们将一些数字加在一起,结果在使用十进制时对我们来说应该非常明显,但我们会看到计算机对答案应该是什么有不同的想法。

我运行的是Python解释器,这是一种可以交互式输入Python编程语言命令的方法。CS105的学生将在本季度晚些时候使用这种编程语言。

我在这里要添加一些数字。让Python解释器计算0.1加上0.2。0.1加0.2自然是0.3。让我们看看Python解释器是否同意我们的意见。

再试另一个。让Python解释器将0.7加到0.1。这很清楚应该是0.8,对吧?让我们看看Python解释器得到了什么。

这里发生的事情是:我们可以用一组小数位数表示的数字,在十进制(基数为10)和二进制(基数为2)中是不同的。

你可能还记得,有些数字我们无法用固定的小数位数精确表示。例如,我让你写出三分之二的十进制等价物。你可能会说它是0.67,但这并不准确。0.66667是更准确的表示,实际上正确的表示是0.6(6循环),其中6上方的横杠代表一个无限循环的小数。

我们在这里遇到的问题,是二进制数具有与十进制数系统不同的循环特性。尤其是数字0.1,在十进制中它是有限的(0.1),但它在二进制中没有有限的表示。在二进制中,它是0.0(0011循环)。例如:0.0001100110011...,并且小数点后没有有限的位数。

所以这里发生的事情是:我们正在取这些数字(如0.7加0.1),并且我们得到了一个没有自然终点的数字表示。这就是为什么这些数字会略微偏离的原因。因此,程序员被警告在表示货币等需要精确计算的场合不要使用浮点数,因为你可能会得到这些细微的变化,从而导致错误。

显然,还有很多计算可能会出错,我们会在本季度晚些时候看到其中的一些。但现在,这只是对我们处理二进制数时可能会出错的地方进行一些初步了解。

总结

本节课中,我们一起学习了数据在计算机中的表示方式及其潜在风险。我们通过阿丽亚娜5号火箭的失败案例,深入理解了“溢出”错误,即当数据超出其存储位的容量时会发生的情况。我们还回顾了“千年虫”问题,它也是由于数据表示位数不足引发的。最后,我们探讨了二进制系统在处理小数时固有的精度限制问题。理解这些基本概念,对于编写健壮、可靠的程序至关重要。

L1.4:比特、字节与二进制:计算机中的象形文字表示 🧮

在本节课中,我们将要学习计算机如何表示文本信息。我们将从基础的ASCII编码开始,探讨其局限性,并最终了解现代统一编码标准Unicode如何解决多语言字符表示的问题。

到目前为止,我们一直专注于如何在计算机上表示数字。但许多人花在打字和写作上的时间多于计算的时间。因此,本节我们将换个角度,谈谈如何在计算机上表示文本。

我们在之前的课程中简要讨论了如何使用不同的位组合来代表不同的事物,例如某人可能居住的州或省。一种自然的思考方式是,我们可以使用不同的字符组合来表示文本。

ASCII编码表

接下来我要向你展示一种叫做ASCII表的东西。在ASCII表中,不同的位组合可以用来表示特定的字母、数字甚至计算机上的标点符号。

让我们仔细看看这是如何在ASCII表的右上角完成的。在这里,我们可以看到abcdef的大写和小写表示。

假设计算机要看到位组合000001(或 off off off off on),我们可以看到这实际上代表了大写字母A。相反,如果它看到位序列110101(或 on off off on off on),它实际上代表小写字母e

现在我应该提醒你,这些位模式的特定解释仅适用于旨在处理文本的计算机程序。记住,特定序列的位可以代表不同的东西,这取决于查看这些位的特定程序的设计目的。

例如,如果我们正在使用一个处理文本的程序,如文字处理器,000001将被解释为字母A。但是,如果我们正在使用一个设计用于处理数字的程序,这实际上可以解释为数字65

为什么?让我们快速看一下这里的数学。我们有八位,从右边开始,我们的零位这代表二的零次方,即1。接下来我们有二的第一个幂是2,第二个幂是4,第三个幂是8。我们有163264。所以,如果我们看这个,我们有1(2的0次方)和1(2的6次方,即64),所以我们总共有65

我认为当我们查看这些位时,重要的是要记住,在一个层面上,就计算机而言,位只是位。决定如何解释这些位的是与它们一起工作的程序。只要处理这些特定位的程序将它们视为ASCII,那么它将被解释为字母A。还有其他可能的解释,它可能是一个数字、一个声音或一张图片。但在这种特殊情况下,我们将假设我们的程序知道这是一个ASCII字符,因此它会在ASCII表中查找并将其显示为A

控制字符与行尾

让我们再看看这里的ASCII表。现在关注左侧栏,您会注意到左侧栏实际上有标点符号和数字09,当它们表示为文本文档的一部分时。

现在重点在这张表的左上角,你会注意到这里的数字有点奇怪。如果你仔细看,你会注意到我们的左上角不是从数字零开始的。您可以看到它以0100000开头。如果我们继续将其切换为十进制,这将更加明显。您可以在此处看到,代表ASCII字符的代码以数字32开头,并继续高达127

为什么它以32开始?数字32以下发生了什么?事实证明还有另一组代码从031,代表所谓的控制代码。如果你看一下键盘,尤其是Windows或Unix键盘,您会看到有一个控制键。通过按住该控制键并键入不同的字符,我们会创建不同的控制字符。

下表显示了一些更有趣的控制字符。

以下是部分控制字符及其含义:

  • Control H:表示退格。
  • Control M:表示回车。
  • Control G:实际上会响铃。

您可以看到其中一些最初用于回到我们拥有电传打字机的那一天。在这种特殊情况下,我一直很喜欢的Control G实际上会响铃。在相当长的一段时间内,如果您在文本编辑器中工作,您会按下Control G,您的计算机会实际上响铃。但这里的基本思想是,这些不同的控制字符允许我们使用相同的ASCII字符集传输控制信息。

您有时会遇到的控制字符的其他方面是,计算机制造商未就正确的字符使用达成一致。行尾是Unix计算机、Microsoft Windows和pre-OS X Macintoshes都使用不同的字符来表示行尾。

这里涉及的主要两个字符是:

  • Control M:代表字符转向(Carriage Return)。
  • Control J:历史上是换行(Line Feed)。

Control J换行将获取当前在电传打字机中的一张纸,并将该电传打字机向上移动一行。Control M将从当前在线的任何位置返回打印墨盒,并将其返回到行的开头。

Microsoft Windows假设您需要执行两个操作:您需要执行Control J操作以向下移动到下一行,您需要执行Control M以移动到当前行的开头。Unix只是使用换行符,即使用Control J。而pre-OS X Macintosh只使用字符转向,即Control M。

这可能导致奇怪的情况。例如,一个在Macintosh上创建的文本文件,当在PC文本编辑器上打开时,可能会显示错误。幸运的是,大多数现代编辑器对结束一行的各种可能方式都足够熟悉,它们会为我们翻译。大多数高端文本编辑器,特别是那些为程序员设计的,将允许我们设置我们想要使用的约定。

扩展字符集与Unicode

另一个地方我们将如何表示这些不同操作系统的行尾的区别是,当我们使用文件传输程序时。文件传输程序区分文本文件和二进制文件。所以问题之一是,为什么我们有这种区别?文件传输程序询问是否要将特定文件视为文本文件或二进制文件,因为它需要知道这两者之间的区别。如果是一个文本文件,它将遍历文本文件,并查找这些行尾序列中的每一个,并且它将从它在文件中看到的任何内容进行转换为您正在传输的计算机的正确序列。

如果您仔细查看ASCII表,您可能会意识到一些事情。您可能会注意到的第一件事是我们的每个字符只有7位。正如您所知,计算机通常使用8位或一个字节的集合,因此这实际上意味着有一大堆我们没有利用的组合。我们只利用了2的7次方(128种)组合。

另一个问题是ASCII是一个非常以英语为中心的表格。即使只看欧洲,也有很多不同的字符需要表示,而它们没有在那里列出。显然,还有一大堆其他不是欧洲的语言,它们根本没有列出字符。

那么我们将如何处理这个问题?我们可以做的一件事是,我们可以利用我们在一个字节中有256种组合的事实。可以说,其中128个留作英文字符,剩下的128个我们可以用于不同的目的。这就是一个叫做ISO 8859的标准。有许多相关的ISO 8859标准,适用于不同语言,我们可以将语言所需的所有字母包含在256个字符中。

这个特定标准的工作方式是我们必须告诉计算机程序,这些字符集中的哪一个是我们正在使用的。我们需要做的下一件事是我们需要在这里查看我们的八位。如果高端位是0,这意味着我正在使用标准的ASCII表。如果高端字符是1,这意味着我使用的是ISO 8859。那么特定的字符将取决于我使用的是ISO 8859-1、-2、-3等等。

我们确实需要正确设置ISO字符集。例如,如果我有一个用法语写的网页,我更改了正在使用的字符集。如果我没有正确告诉Web浏览器要使用哪个字符集,并且它认为这是西里尔文或阿拉伯文,您可以看到该网页将出现不同和错误的显示。

现在非西方语言呢?这些也有许多不同的标准,其中大多数使用16位。在16位中,我们可以表示2的16次方个不同的字符,我们称之为65,536。这足以表示大多数的字符,例如中文。所以这些将用于不同的语言。我们需要有一个程序来理解它们,我们需要程序来理解正在使用的特定字符集。

例如,我从Pottermore网站上获得的《哈利波特和魔法石》的日语副本。在最右边我们可以看到这些是日语章节的名称,但我们在左边的内容基本上得到了垃圾。这里发生的事情是,当我从Pottermore得到这些文件后,我将它们下载到我的电脑上,它期望字符只有8位长,而Pottermore文件使用的字符是16位,我的电脑不知道如何处理它们,所以它基本上改变了文件的名称,变成垃圾。

所以我们在这里看到的问题是,不同的位组合代表不同的东西,这取决于特定的编码。我们可以有一个字符组合来代表英语中的e,而相同的字符组合可能代表日语中的tsu。所以我们需要的是一个更统一的系统。

Unicode:统一的解决方案

事实证明,实际上有一个更统一的系统:Unicode。Unicode每个字符使用1到4个字节。如果你计算2的32次方种可能的组合(因为有32个不同的位在四个字节中),允许有4,294,967,296个组合。这是一大堆组合。

Unicode不仅允许我们存储我们已经见过的所有不同的语言,它还具有一些组合,这将使我们能够表示古语言,如埃及象形文字、苏美尔楔形文字,甚至迈锡尼线性B。它也包括诸如音乐字符和表情符号之类的东西。

所以我们要做的是,我们将快速了解您如何使用此Unicode。例如一个历史文件,假设我正在写一篇历史论文,我需要在我的历史剧本中使用埃及象形文字。

我需要做的第一件事是我需要掌握一种可以显示埃及象形文字的字体。请记住,Unicode只指定特定的位序列来表示这些字符,它保证有一个独特的位序列对于我的埃及象形文字,不能与中文字符或英文字母混淆。但这并不意味着每种字体都有这40亿个组合中的一个可能已经计算出来,所以它知道如何绘制它。

例如,如果您使用了Times New Roman字体,它是为英语和欧洲语言设计的,它不是为了显示迈锡尼线性B。所以,在Times New Roman中看到迈锡尼线性B字符的位组合,字体就像“我不知道那是什么”,所以它只是要画一个块。所以你经常会看到有问题的块或小菱形,它们中的标记。这些是不同的迹象,表明您使用的字体知道存在这种独特的位模式,但它实际上没有能力显示这种独特的字形。

因此,如果我们想在这里用埃及象形文字写我们的论文,我们需要做的第一件事是,我们需要继续找到一种支持埃及象形文字的字体。你可以去谷歌搜索“埃及象形文字unicode字体”,你会得到一堆不同的热门选择。计算机及其过程将根据您拥有的计算机类型而有所不同。

我们接下来要做的就是,找出我们想要用于在我们的论文中重现的特定字符的特定位模式。这样,我们就可以查找表格。维基百科有许多Unicode支持的不同语言的表格。这是维基百科上关于埃及象形文字Unicode的表格。我要做的是查看左侧的列和顶部的行,然后将这两个列结合起来形成一个唯一的数字。这些实际上是十六进制的。这是一个与二进制非常密切相关的编号系统,您可以将其视为一种非常紧凑的二进制形式。

所以我要找出十六进制数是什么,然后我要继续去Microsoft Word。我要设置字体,因为再次记住,如果我使用像Arial这样的标准字体之一,它们实际上没有埃及字符的图纸,所以我需要切换到我的字体,里面有埃及字符。然后我要插入符号,这就是Microsoft Word允许你插入任何Unicode字符的方式。切换到插入选项卡,在最右边有插入符号,然后一直点击到底部,它会显示“更多符号”,然后会弹出一个对话框。我将继续输入维基百科表格中的Unicode代码,将其输入到对话框中,然后继续将我的埃及字符插入到我的文档中。这样,我就知道如何在计算机上输入埃及象形文字了。

总结与展望

本节课中我们一起学习了计算机如何表示文本。我们从基础的ASCII编码开始,了解了它如何用7位表示英文字符和控制代码。接着,我们探讨了其局限性,以及为了表示更多语言而出现的扩展字符集(如ISO 8859)和多字节编码。最后,我们介绍了Unicode这一统一的编码方案,它使用1到4个字节为世界上几乎所有的字符系统提供了唯一的数字标识,从而解决了跨语言文本表示的难题。

现在,我们已经看过计算机如何表示一些基本数据类型。在接下来的几节课中,我们将转向一些更复杂的数据类型。我们将首先看看计算机如何表示图像。本课将很有用,无论您打算购买一台新电脑或一台新电视,我们将讨论底层技术和术语。如果您打算创建任何类型的数字图像并且需要知道正确的工具以及存储文件的正确格式,这也将非常有用。之后,我们将研究计算机如何表示音乐和声音。在数字图像和声音讲座之间,我们将仔细研究对数字和模拟之间差异的批判性理解中的关键差异。

L2.1:数字图像基础 🖼️

在本节课中,我们将要学习数字图像的基础知识,特别是计算机显示器的工作原理。我们将了解屏幕如何通过像素网格来显示图像,并探讨屏幕分辨率、纵横比等核心概念,以及它们如何影响我们的观看体验。

显示器的工作原理 🔍

上一节我们介绍了课程概述,本节中我们来看看计算机显示器是如何工作的。

计算机屏幕实际上是由一个巨大的网格组成的。在黑白图像的情况下,网格中的每个元素都可以被打开或关闭。这些独立的网格元素被称为图片元素,简称像素

每个像素都由计算机内存中的一个来控制。如果该位为“开”,则相应的像素点亮;如果该位为“关”,则像素熄灭。

核心概念:像素状态由内存中的位控制。

像素状态 = 内存位值
如果 位 == 1,则 像素亮
如果 位 == 0,则 像素灭

屏幕分辨率 📏

了解了像素的基本原理后,接下来我们需要认识一个选购设备时的重要参数:屏幕分辨率。

屏幕分辨率指的是显示器在宽度和高度上各有多少像素。它通常表示为“宽度像素数 x 高度像素数”。

以下是不同设备屏幕分辨率的示例:

  • Surface Book 15英寸:3000 x 2000 像素
  • MacBook Pro 15英寸:3072 x 1920 像素
  • 34英寸超宽显示器:3440 x 1440 像素
  • 55英寸高清电视:1920 x 1080 像素

一些常见的分辨率有特定名称,例如:

  • 640 x 480:旧电脑和电视使用的VGA标准,也称为480i或480p。
  • 1920 x 1080:常被称为高清电视1080p

屏幕纵横比 📐

除了分辨率,另一个关键质量指标是屏幕的纵横比。它描述了屏幕宽度与高度的比例关系。

计算纵横比时,我们用宽度像素数除以高度像素数,并将其简化为最简整数比。

例如:

  • 一个分辨率为 1024 x 768 的显示器。计算过程为:1024 ÷ 256 = 4,768 ÷ 256 = 3。因此,其纵横比为 4:3
  • 一台 1920 x 1080 的高清电视。计算过程为:1920 ÷ 120 = 16,1080 ÷ 120 = 9。因此,其纵横比为 16:9

核心概念:纵横比计算公式。

纵横比 = 宽度像素数 : 高度像素数 (化简为最简整数比)

需要注意的是,纵横比相同的屏幕,其像素总数(即清晰度)可能大不相同。例如,一个27英寸的老式CRT电视(640x480)、一个12.9英寸的iPad Pro(2732x2048)和一个7.9英寸的iPad mini(2048x1536)都具有4:3的纵横比,但像素数量差异巨大。

如何选择合适的纵横比? 📱🖥️

那么,哪种纵横比最好呢?这实际上取决于设备的用途和尺寸。

对于工作而言,宽屏显示器通常更佳。

  • 例如,一台27英寸的16:9显示器非常宽,可以舒适地并排查看两个文档,提高工作效率。

对于平板电脑,不同的纵横比适合不同的任务。

  • 16:10或16:9的宽屏平板(如三星Galaxy Tab)更适合观看视频等娱乐活动。
  • 4:3比例的平板(如iPad)在竖屏状态下能显示更多文档内容,因此可能更适合阅读和处理文档。

纵横比与影视内容 🎬

在观看电影和电视时,内容的纵横比与屏幕的纵横比是否匹配至关重要。影视内容的纵横比通常用小数表示,如2.35:1或1.85:1。

当内容与屏幕的纵横比不匹配时,会出现两种情况:

  1. 信箱模式:内容位于屏幕中央,上下出现黑条。这通常发生在宽屏内容在较“矮”的屏幕上播放时。
  2. 邮筒模式:内容位于屏幕中央,左右出现黑条。这通常发生在较“方”的内容在宽屏上播放时。

让我们看一些例子:

  • 动作片(如《美国队长3:内战》,比例约2.35:1)在16:9的高清电视上播放时,上下会有轻微的黑条(信箱模式);在4:3的iPad上播放时,上下黑条会更粗。
  • 电视剧/剧情片(如《理智与情感》,比例约1.85:1)与16:9的高清电视几乎完美匹配,观看体验最佳。
  • 老式电视节目(如《老友记》,比例4:3)在4:3的iPad上完美播放,但在16:9的高清电视上,左右会出现黑条(邮筒模式)。
  • 经典老电影(如《绿野仙踪》,比例约1.37:1)同样更适合4:3的屏幕,在宽屏电视上会以邮筒模式播放。

总结 📝

本节课中我们一起学习了数字图像的基础知识。我们了解到计算机屏幕由像素网格构成,每个像素由内存中的位控制。我们学习了屏幕分辨率纵横比这两个核心概念,知道了如何计算和解读它们。最后,我们探讨了不同纵横比如何影响工作和娱乐体验,以及当影视内容与屏幕纵横比不匹配时会发生什么。理解这些基础知识,将帮助你在选择电子设备和享受数字媒体时做出更明智的决定。

在下一个视频中,我们将更深入地研究颜色是如何在数字设备上生成和显示的。

L2.2:数字图像:颜色与数据 🎨

在本节课中,我们将要学习数字图像如何表示颜色。我们将从计算机屏幕的物理结构开始,了解像素和子像素的概念,然后深入探讨RGB颜色模型及其在计算机中的表示方式。最后,我们会对比显示器和打印机在颜色生成上的不同原理。

屏幕的微观世界 🔬

上一节我们介绍了数字图像的基本概念,本节中我们来看看计算机屏幕是如何显示颜色的。

回到上一堂课,我们观察了笔记本电脑屏幕。当我们使用放大镜仔细观察计算机屏幕时,会发现每个单独的像素都不是由一个正方形组成。它实际上由许多所谓的子像素组成。

现在这些子像素的确切几何配置会因计算机而异,主要取决于大多数液晶屏使用的特定计算技术。子像素实际上是条形。

如果我们仔细观察一个老式阴极射线管或CRT,我们实际上会看到一个红色条、一个绿色条和一个蓝色条。实际上有三个不同的圆圈,但同样会有一个红色圆圈、一个绿色圆圈和一个蓝色圆圈。

RGB颜色模型 🟥🟩🟦

你会听到计算机科学家经常谈论RGB,其中R代表红色,G代表绿色,B代表蓝色。所以大多数颜色在计算机中是使用RGB完成的。我们将在几分钟内了解打印机的工作原理,它们实际上有一个名为CMYK的不同系统,我们稍后再看一下。

但让我们再次看一下我们的红绿蓝。这里的想法是,通过混合红、绿、蓝的数量,我们可以创造出各种颜色。

让我们来看看我几年前写的一个小计算机程序。这个特定的程序实际上是用Python编写的,它是CS105学生将在本季度晚些时候学习的编程语言。尽管我们不会完全达到制作像这样的轻微图形用户界面的地步,但这个特定程序显示了当我们现在将红、绿和蓝色混合在一起时会发生什么。

我已经将红、绿和蓝色都设置为零,所以根本没有灯。我要在这里做的是我继续并在此处调高红色,您可以看到我正在慢慢变得越来越红。我可以将其最大程度提高到255,这是计算机可以发出的最大红色量。

让我们继续前进,你可以看到我可以用绿色做同样的事情。最后是蓝色。当然有趣的是当我们继续尝试混合它们时,我有一点点蓝色,大约30%。我可以继续混合一些绿色,你可以看到我在这里得到不同深浅的绿色,取决于我有多少绿色强度。我可以开始混合一些红色,并通过混合这些不同的颜色,并改变红色、绿色和蓝色的强度,我实际上能够创造出许多不同的颜色。所以这实际上是计算机本身将如何生成颜色。它有红、绿和蓝色条,或者正如我们在CRT中看到的那样,它有那些红、绿和蓝色圆圈。通过增加这些单个条的强度,它实际上是混合的颜色。我们看不到单独的颜色,因为它们真的很小,所以我们的眼睛看到的只是组合在一起的光。但事实上,如果我们把显微镜放在电脑屏幕上,我们实际上会看到以不同强度点亮的各个条。

颜色示例与表示 🎨

让我们来看看一些实际的颜色示例。

以下是几种常见颜色的RGB构成:

  • 红色:由红色组成,一直向上,没有绿色和蓝色。尽管我们可以通过增加或减少红色的强度来创建不同的红色阴影,从最大数量到较少数量的红色,或通过稍微混合一些绿色或一些蓝色。
  • 蓝色:没有红色,没有绿色和最大数量的蓝色。然后我们可以开始混合。
  • 紫色:如果我们把红色子像素调到最大强度的一半,把绿色子像素关掉,把蓝色子像素调到最大强度的一半。如果我们把红色一直调高,蓝色一直向上,让绿色关闭,就会变成紫色。
  • 紫红色:如果我们将绿色变成蓝色的一半,红色的一半关闭红色,我们实际上最终会得到紫红色。
  • 蓝绿色:如果我们将绿色调高,蓝色一直保持,红色仍然关闭,我们实际上会得到蓝绿色。

我们可以获得更多异国情调的颜色。所以这里有一些样本来自我们将在本季度晚些时候讨论的HTML5规范。你可以在这里看到绿色组成,绿色但不是绿色在最大强度下,只有255种中的139种,然后混合了一些红色和一些蓝色,但大多数都非常低。记住这是一个从0到255的比例。结实的木头由很多红色组成,相当数量绿色和相当数量的蓝色,但少于绿色和红色的数量。还有一个番茄,你知道,主要是红色。红色在这里几乎是最大值,但也混合了一些绿色和一些蓝色。所以这些是不同的例子,我们可以使用这个系统创建的颜色类型。事实上,我们可以使用这个特定的系统创建1670万种颜色。

我是如何达到1670万种颜色的?让我们仔细看看这些颜色在计算机中是如何实际表示的。

这里是颜色的方式。基数红色在计算机内部表示,我们可以看到有24位或24个开关用于表示这种特定的颜色。这24位被分成八组,您会记得将其称为字节。因此我们留出了一个字节对于红色,在此图中为绿色预留了一个字节,为蓝色预留了一个字节。您可以看到我在这里放置了十进制等价物。红色的组合在远处切换,最左边的红色位组合相当于十进制140。中间八位的组合对应绿色的数量,即十进制21。右边的组合,再次与开关相同,绿色再次代表十进制数21。所以计算机知道它应该将红色设置为最大强度的一半以上,然后它应该添加一点绿色和一点蓝色。

现在因为红色的数量由8位表示,你会记得在我们之前的讲座中,我们可以用8位表示2到8个组合,即256。我们希望这些组合中的一个代表零,代表没有光出来那个特定的子像素。所以不是从1到256,而是从0到255。所以我们有红色0到255,绿色0到255,和蓝色0到255。这就是你在上一个例子中看到的,当我滑动时,在一个小Python程序上找到滑块。

因为我有红色的两种到第八种可能的组合,绿色的两种到第八种可能的组合,以及蓝色的两种到第八种可能的组合,我实际上总共有两种到24种可能的组合。事实证明,如果你计算出所有24位的所有可能组合,我会得到1670万多一点。所以我们通常说在24位颜色中,我们可以表示1670万种颜色。

现在这个24位颜色系统是当今计算机上使用的最常见的颜色系统之一。但有一个更高级的变体,它使用一个额外的字节。这是32位颜色系统中的32位颜色系统。我们实际上有一个额外的字节,而这个额外的字节实际上被搁置了,代表一个叫做Alpha的东西。Alpha代表一个特定对象的不透明度或透明度。

如果我们将一个对象滑动到另一个我们想要显示的对象的顶部,我们会使用它。如果它下面的对象你通常可以看到,当我们在操作系统周围移动窗口时使用的这种技术。

Alpha通道与透明度 🌫️

让我们仔细看看我们的Alpha是如何工作的。

我在这里得到的是我在这里有一对图像。我下面有一张图片,它停在卢浮宫附近,然后我在左上角有一张伦敦塔的图像。我将公园与伦敦塔重叠。我将Alpha设置为更高的百分比。Alpha设置为100%,伦敦桥图像将完全不透明,我们将无法看透它。但是当我们降低不透明度时,我们将能够看到它下面越来越多的巴黎公园。

Alpha设置为255中的192,即75%,你可以看到公园开始显现出来。看到更多下面的公园,你也可以看到我们伦敦塔的那部分不在巴黎公园上的部分开始看起来有点褪色。然后最后,在我们这里的最后一张图片中,我将Alpha设置为25,即64/255。你可以看到,我们几乎可以完全看到公园,伦敦塔的图像几乎完全褪色。如果我们进一步降低Alpha,我们将Alpha设置为零,我们将无法看到塔。

实际应用:选购电视 📺

让我们把我们新发现的知识应用到一些实际的事情上。例如,假设我们有兴趣购买一台新电视。这是百思买网站上三星新电视的网页。所以,如果我们快速浏览一下这个网页,对我来说最突出的第一件事是你可以以329.99的价格购买一台全新的高清电视。为什么我不认真地拥有其中一个呢?反正这些价格真的很低。

让我们来看看这个电视的规格。我们将重点放在标题行上。我们可以看到这是三星制造商,它是55英寸,这是从显示屏的一个角到对角的距离。我们可以看到它是LED,这是在谈论显示器所使用的技术,与等离子电视或老式阴极射线管电视不同。

所以这是2160p。2160指的是从上到下的像素数。P实际上代表渐进式。所有现代显示器都是渐进式。为了了解渐进式是什么,我们需要看一下使用隔行信号的旧技术。所以例如,一个老式的录像机磁带是隔行扫描的。这里的隔行信号的想是,嗯,而不是更新每一行在电视上,我们会每隔一行更新电视上的信息。假设我们的信号正在输入,我们能够每秒获取30次信息。隔行扫描信号会发生,在电视上每奇数行的前30秒将被更新,然后在下一个30秒内,每条偶数线将被更新,然后每条奇数线将被更新,每条偶数线将被更新。并且渐进式电视每条线都会同时更新。所以这是一方面,这是一件非常好的事情。这实际上是隔行扫描的VCR磁带和渐进式DVD之间的巨大差异。所以你知道如果你曾经玩过这两种技术,这是一个巨大的改进。

但作为购买者,我们对它并不是特别感兴趣一台新电视,因为所有新电视都是渐进式。“智能的”指的是它可以直接连接到互联网,而且还有呃,它具有执行显示等操作的内置功能,例如Netflix或Hulu或迪士尼Plus。

“4K”指的是像素数。在这种情况下,从左到右有3840个像素。而“UHD电视”指的是超高清电视,再次指的是它有很多像素宽和很多像素高。所以这是超高清电视,而不是高清电视。高清电视是1920x1080像素。所以我们实际上可以容纳四个高清电视显示在这台电视上,因为如果我们继续乘以从左到右和从上到下的像素数,它的像素数是它的四倍,这是高清电视上的四倍。

我认为这是最后一部分,“HDR”实际上是最有趣的。所以HDR指的是高动态范围。显示技术已经有了许多重要的改进。例如,DVD被定义为差异化之一。推论是我们每英寸的像素要高得多。所以,你知道我们刚刚看到它有很多像素高,很多像素宽。实际上我们的像素是高清电视的四倍。但另一个区别是现代电视有更好的对比度。所以最暗和最亮的灯光之间的差异比以前大得多。我们现在也有更高的整体亮度。为了利用最暗和最亮的灯光之间的这种对比,我们需要一个用于定义颜色的新系统。

HDR与更多颜色 🌈

我们之前已经讨论过现代计算机技术如何使用24位颜色。因此,8位代表红色,8位代表绿色,8位代表蓝色。嗯,24位整体导致总共1670万种颜色。HDR的作用是什么?实际上增加了每个单独像素的位数。它增加了每个颜色通道的位数。

有许多不同的竞争标准。DS有一种标准对颜色通道使用10位,另一种标准对颜色通道使用12位。我将讨论10位用于颜色通道的示例,因为这似乎更为普遍。因此,每个颜色通道10位之一标准是这个HDR10。对于HDR10,我们将为红色预留10位,为绿色预留10位,为蓝色预留10位。这将允许我们为每个设置0到255之间的强度,在这三个颜色通道中,我们将能够存储0到1023强度之间的强度。因此,我们能够拥有更多的渐变。事实上,如果我们继续进行数学计算,请记住在我们拥有224之前,给我们1670万种颜色。现在我们有230,这给了我们超过10亿种颜色。所以这只会给我们更多的颜色变化,更能代表实际图像。所以这实际上是一个漂亮、整洁的技术,应该会极大地改善您在电视机上的图像。

打印机与CMYK 🖨️

之前我提到打印机使用与显示器不同的颜色技术,所以让我们快速浏览一下。这里的主要思想是打印的页面和绘画以与我们的笔记本电脑显示器不同的方式创建颜色。

让我们快速浏览一下。我们的笔记本电脑或计算机或电视使用所谓的加色来创建颜色。这些不同的显示技术产生的颜色会创建颜色并将它们以对比的方式映入我们的眼睛。如果我们考虑绘画或印刷页面的工作方式,我们想要的是我们想要明亮的白光。白光会照在我们的印刷页面或我们的绘画上。发生的情况是该印刷页面中的不同墨水或该绘画使用的油漆将吸收其中的一部分白光。

因此白光包含色谱中的所有不同颜色,其中一些颜色会被印刷品吸收页面或绘画,以及剩下的东西被发送到我们的眼睛或被反射到我们的眼睛。因此这被称为减色法。因此这是一种与我们在显示器上看到的颜色产生根本不同的方式。

因此实际上,用于打印技术的一组不同的原色。正如我们之前看到的,计算机屏幕使用RGB红、绿和蓝色作为我们的原色。而事实证明打印机使用CMYK。因此它们使用青色、洋红色、黄色和黑色。所以如果您要购买打印机墨盒,你不会看到打印机墨盒有红、绿和蓝色。他们实际上有青色、洋红色、黄色和黑色。你实际上不需要黑色,在技术上可以创建混合一大堆青色、洋红色和黄色的黑色墨水。但事实证明,这会使用大量墨水,有时会给您带来混乱的结果。

关于RGB与CMYK的另一件事要注意的是,事实证明存在您可以在一种配色方案中生成而在另一种配色方案中无法生成的颜色。因此,您真的应该在创建图像时做出决定,该图像是用于计算机屏幕还是印刷品页面。所以你可以做的是你可以选择某些颜色,因为你知道它们在其中一种配色方案中效果很好,而不是在另一种配色方案中。

你也会发现我们稍后会讨论关于如何设计网站和网站配色方案的季度。以及您可能为网站选择颜色的方法之一是您将使用色轮。结果发现,艺术家使用了不同的色轮。艺术家色轮自然地使用原色,使用减色进行绘画。而网络色轮则基于RGB的加色使用。

总结 📝

本节课中我们一起学习了数字图像中颜色的表示。我们从计算机屏幕的物理结构出发,了解了RGB颜色模型如何通过混合红、绿、蓝三原色的光来生成各种颜色,并用24位(或32位带Alpha通道)数据来表示。我们还探讨了HDR技术如何通过增加每个颜色通道的位数来提供更丰富的色彩。最后,我们对比了显示器使用的加色法(RGB)与打印机使用的减色法(CMYK)之间的根本区别。接下来,我们将研究程序如何表示和操作图像,以及用于存储图像信息的不同格式。

L2.3:数字图像:位图与对象 🖼️

在本节课中,我们将要学习数字图像的两种基本存储方式:位图(光栅图形)和对象(矢量图形)。我们将探讨它们的工作原理、各自的优缺点以及适用场景。

概述

之前的视频中,我们关注了计算机显示器的实际工作方式。本节中,我们将切换视角,看看图像实际上是如何存储在计算机上的。

区别在于硬件如何工作与我们想要使用的文件如何工作以存储我们的图像。结果证明有两种基本方法可以将图像存储在文件中。

位图(光栅图形)表示法

第一种方法与我们在计算机显示工作方式方面看到的非常相似。我们看到计算机显示每个单独的像素都有一个支持它的位。如果我们有彩色图像,我们有一个黑白图像或一组位或字节。所以我们看到有24位颜色,其中每个单独的像素后面有24位。我们看到32位颜色等等。我们可以做的是我们可以只存储每个单独的像素值,并将它们存储在一个文件中。

所以这里有一个图像。假设我们想将它存储在一个文件中,我们将从我们的位图表示开始。它与显示工作方式相似。你可以看到我们所做的是我们在这里得到了所有单独的像素值,我们只是要单独存储每个值。

所以我可以说,在左上角我有像素(0,0),那是RGB(255,255,255)。这意味着红色、绿色和蓝色最大,这意味着我们将有白色。我可以将下一个像素的值存储在像素(0,1)上,这也是RGB(255,255,255)等等。

我们向下走几行,我们将存储每一行中的每个单独像素。我们向下几行,我们到达那个圆圈的顶部。你可以看到,像素(9,4)意味着x等于9,y等于4(9从左边,4从顶部)仍然是白色的RGB(255,255,255)。然后我们转到下一个像素(10,4),这个像素实际上是红色的,所以RGB是(255,0,0)等等。然后我们可以继续填充所有这些单独的像素值。

迟早我们要到达那里的矩形。你可以看到你知道的矩形就在第21行的矩形之前。这是y等于12,x等于21,那个仍然是白色的RGB(255,255,255)。我们到达下一个像素,在x上等于22,y等于12。然后这是一种不同的颜色。所以你可以看到这主要是蓝色,蓝色247,但那里有一些红色和绿色:红色87,绿色84,蓝色247。然后我将下一个像素存储在那个像素上,相同的颜色等等。我只是一个接一个地存储所有单独的像素值。

现在这绝对有效,这与显示器的工作方式完全匹配。但还有其他方法可以存储相同的信息。因此这种特定表示再次被称为位图或光栅图形。

对象(矢量图形)表示法

我们将看看另一种表示信息的方式。下一种表示信息的方式是我们所说的对象,或有时称为矢量图形。

你可以在这里看到我们得到了完全相同的图像,但我们将不存储所有单个像素值,而是将它们视为几何形状。所以我可以想,我有一个圆,这里是中心的x和y坐标,半径为8个像素。作为笔颜色宽度的笔画是一个像素,笔画颜色实际上是红色RGB(255,0,0)。然后我有了我的矩形,我继续在左上角开始xy位置,我继续存储宽度和高度,然后我继续存储填充颜色。您可以看到这也存储了完全相同的图像,但它以不同的方式存储了图像。

因此从根本上说,我们有两个选择:我想要将某物存储为位图(也称为光栅),还是我想将事物存储为对象(有时也称为矢量图形)?

所以我们现在要做的是看看这两种方法的优缺点。

比较:存储空间占用

我们要比较的第一件事是这两种方法占用多少空间。

所以我们在这里得到的是在我们的左边,我们将尝试估计我们的小图像在这里占用了多少空间。当我们将事物存储为具有24位颜色的位图时,我们需要为图像中的每个像素存储三个字节。

现在这里的图像实际上大约是64像素乘48像素。我知道它看起来大得多,但那是因为我放大了所有内容。因此,当您将3个字节乘以64 x 48个像素时,我们可以仔细查看所有内容,您最终得到的只是9000多个字节。

现在让我们来看看我们需要多少空间对于我们的对象表示。它取决于我们将为每个对象存储的每个单独的值。例如,我们正在存储中心,我们将开始一个x和y值。这些xy值有多大?它们每个占一定字节数。您知道我们可能会为矩形存储更多属性,而不是圆形。这些对象将要占用的空间会有所不同,但我估计我们每个对象需要大约8到12个字节。假设我们存储它的图像相对较小。如果它是一个更大的图像,我们需要存储更大的图像,我们的x和y的值会更大。所以在这种特殊情况下它确实会有所不同。我是说在这个特定图像中我们每个对象需要大约8到12个字节。我只有两个对象,所以你可以看到占用16到24个字节。

所以它真的不是很多。在一个例子中比较,对象的存储我有16到24个字节,而位图我们有9216个字节。所以你可以看到这里的明显赢家是将事物存储为我们的矢量图或对象图形。

比较:缩放效果

我想看看当我们使用这两种技术中的一种获得图像时,我们继续放大,会发生什么。

所以我得到的是我们将看看我们正在使用的两个不同的程序。将从我们的位图表示开始。所以我们在这里看到的是Microsoft Paint,它是一个标准的绘画程序,包含在Microsoft Windows中。我在这里绘制几个不同的对象,然后我放大。这里主要要寻找的是,注意,因为我放大的东西开始看起来有点参差不齐。

所以这里发生的事情是当我放大时,我没有得到更多的像素。发生的事情是位图表示中的像素只是显示为更大和越来越大的方块,所以他们变得非常参差不齐。

所以下一个程序是Adobe Animate,这是我在本课程中用于一些简单动画的动画程序。所以我将再次在这里绘制几个不同的对象,现在我要放大。你可以看到我可以放大,就我想要的而言,没有那些锯齿状边缘的迹象。

所以这里发生的事情是使用对象/矢量图形方法,我实际上有一个数学公式。我可以重新计算,因为我越来越近,越来越近。我可以只需使用该公式,我就可以重新计算需要放置的位置,并且可以根据需要进行放大,并且该几何形状仍然看起来很平滑。

比较:编辑与操作能力

所以我现在想谈论的是,当我们查看这两种可能的表示时,我们在创建文档后编辑或操作文档的能力是什么。

所以我们将一次又一次地从我们的位图表示开始。这是微软的油漆。你可以看到我已经绘制了许多不同的对象在我的文档中。如果我尝试编辑此文档,我们将发现我无法在此处编辑单个形状。因此您知道我在将这些对象绘制在屏幕上一旦我将它们放入我的文档中,它们就不再被视为单个几何形状。而是程序认为它们只是“嘿,这里是一堆像素,这些像素是特定颜色”。一旦我完成绘制它们,它们就不再是几何形状的一部分。因此当我操作此文档时,我只能操作单个像素或像素组。我无法操作原始圆形或线条或正方形。

现在下一个应用程序,这是Adobe Illustrator。这是一个为对象或矢量图设计的程序。所以你可以看到我可以继续抓取这些单独的形状。我可以旋转它们,我可以改变它们的颜色,我可以用对象或矢量图形表示改变它们的大小。这些单独的形状在屏幕被认为是几何形状,我可以继续将它们作为几何形状进行操作。因此这与我们的位图表示形成了非常鲜明的对比。

适用场景总结

那么,我为什么要很好地使用位图(光栅)呢?这里有一些事情你根本无法用数学公式表示。所以这是Maddie的照片。我们也许可以想出一些几何形状来表示,例如计算机,也许我们可以表示T恤作为几何形状。但你知道Maddie的皮毛怎么样?这不会发生。所以我们无法想出一个数学公式来代表她的每个单独的毛囊。

所以当我们看照片时,如果我们在头脑中从头开始生成一些东西,比如某种图表或某种标志或某种图形,那么我们用相机拍摄的将被表示为位图。是利用矢量对象表示的好时机。因此您知道何时可以使用矢量对象表示。

但请注意,在很多情况下您只需要使用位图。因此您知道数字绘画、照片,这些都是可以的,不能真的用矢量图形完美表示。

总结

本节课中我们一起学习了数字图像的两种核心存储格式:位图(光栅图形)和对象(矢量图形)。我们了解到:

  • 位图 通过记录每个像素的颜色值(如 RGB(255,0,0))来存储图像,与显示器工作原理一致,但放大时会失真,且后期难以编辑单个几何对象。
  • 对象(矢量) 通过记录几何形状的数学描述(如圆心坐标、半径、颜色)来存储图像,文件体积小,可无限放大而不失真,并且便于后期编辑。

位图适用于照片等复杂、非公式化的图像;而矢量图形则更适用于徽标、图表、插图等由清晰几何形状构成的图像。理解这两种格式的区别,有助于我们在不同场景下选择最合适的工具。

L2.4:数字图像:任务与正确的格式 📸

在本节课中,我们将要学习数字图像的不同文件格式。我们将重点关注在计算机和网络上使用的主要格式,了解它们的工作原理、适用场景以及各自的优缺点。通过对比,你将学会如何为不同的任务选择合适的图像格式。

概述

数字图像有多种存储格式,每种格式都有其特定的设计目标和适用场景。本节我们将探讨几种主流的位图格式,如JPEG、PNG和GIF,并简要介绍SVG、RAW和HEIC等格式。理解这些格式的核心差异,能帮助我们在存储、编辑和分享图像时做出最佳选择。

网络上的主要图像格式

在网络上,主要使用三种位图格式:JPEG、PNG和GIF。此外,还有一种相对较新的对象格式SVG,它与HTML有相似之处。由于我们将在本季度晚些时候讨论HTML,因此关于SVG工作原理的详细内容将推迟到那时再讲解。

SVG格式适用于能用一系列几何形状(如圆形、矩形)描述的图像,这种格式也称为矢量格式。矢量格式非常紧凑,具有易于编辑和无限放大的优点。然而,对于像照片这样复杂的对象,矢量格式并不适用,此时位图格式是更好的选择。

位图格式与压缩原理

位图格式关注构成图像的每个像素的颜色值。你可能会认为,存储所有像素值是最直接的方法。但事实上,有更高效的方式来描述相同的信息,这涉及到“压缩”技术。

让我们通过一个在黑白显示器上显示字母“H”的例子来理解这一点。描述屏幕上所有像素状态(开或关)的最直接方法,是逐行逐列地列出每个像素的值。这种方法虽然准确,但非常冗长且占用空间。

以下是描述“H”图像的更紧凑方法:

  • 第0行:所有像素关闭。
  • 第1行:与第0行相同。
  • 第2行:与第0行相同,但位置(2,2)和(4,2)的像素开启。
  • 第3行:与第2行相同,但位置(3,3)的像素也开启。
  • 第4行及之后所有行:与第0行完全相同。

通过指出某些行与已描述的行相同,我们大大减少了描述图像所需的信息量。这种技术就是压缩。它改变了信息的表示格式,从而减少了其占用的空间量。

JPEG格式:适用于照片的有损压缩

JPEG(联合摄影专家组)格式专为存储照片而设计。大多数消费级相机和手机(在iPhone转向HEIC格式之前)默认使用JPEG格式来保存和分享照片。

JPEG存储24位颜色,这意味着它支持完整的1670万色域。然而,JPEG是一种有损压缩格式。这意味着在压缩过程中,为了显著减小文件大小,它会丢弃一些人眼不太容易察觉的图像信息。

例如,一张威尼斯的大尺寸图像(1.39 MB)和一张经过更高压缩的同分辨率图像(215 KB,仅为原大小的1/6)。在正常观看下,两者差异不大。但放大后查看细节(如栏杆或电线),压缩后的图像会出现模糊或混乱的块状区域,这些就是JPEG压缩伪影

JPEG的优势在于我们可以控制压缩程度。你可以选择高质量(文件大、伪影少)以接近原始图像,也可以选择高压缩(文件小、伪影明显)以便于网络快速传输。

关于其他格式:

  • HEIC:苹果公司使用的新格式,在压缩效率上通常优于JPEG。
  • RAW:高端相机使用的格式,直接存储传感器捕获的原始数据,没有任何压缩或处理,文件体积非常大。

PNG格式:适用于图表图形的无损压缩

PNG(便携式网络图形)格式建立在较旧的GIF格式之上,旨在存储图表、图形等由计算机生成或手动绘制的图像,而非照片。

PNG和GIF使用的压缩技术,与我们之前描述“H”字母的例子非常相似。它们会寻找图像中完全相同的部分(例如,图表中两条相同的线,或一大片纯色区域),并通过引用这些重复模式来减少文件大小。这种压缩是无损的,意味着压缩后的图像可以精确还原为原始图像,没有任何信息丢失。

此外,PNG和GIF支持JPEG所不具备的一个功能:透明度。你可以将图像中的某些像素指定为透明,这样当图像放置在不同背景上时,透明区域会显示出背景内容,而不是图像本身的颜色(通常是难看的白色矩形背景)。在编辑软件中,透明区域通常显示为棋盘格图案。

GIF格式:简单的动画与有限色彩

GIF格式之所以著名,主要是因为它支持创建短小的动画。动画GIF本质上是在同一个文件中打包一系列静止图像,并按顺序循环播放。

虽然现在也有支持动画的PNG格式(APNG),但其浏览器支持相对较新,尚未完全普及。因此,如果你希望所有人都能看到你的短动画循环,目前仍应使用GIF格式。

GIF是一种较旧的格式,它有一个主要限制:仅支持最多256种颜色。这与JPEG或PNG的1670万色相比非常有限。因此,GIF图像(尤其是照片)看起来可能色彩单调或有颗粒感。除了制作动画,GIF在静态图像领域已基本被PNG格式取代。

总结

本节课我们一起学习了数字图像的主要文件格式及其核心特性。

  • JPEG:采用有损压缩,通过丢弃部分信息来大幅减小文件体积,最适合存储照片。其压缩程度可调,需要在文件大小和图像质量之间权衡。
  • PNG:采用无损压缩,通过查找重复模式来减小文件,能精确还原原始图像。它支持透明度,最适合存储图表、图形和徽标等计算机生成的图像。
  • GIF:支持简单的动画,但色彩有限(最多256色)。目前其主要用途是制作在网络上广泛传播的短动画循环。
  • SVG:一种矢量图形格式,用数学公式描述图像,无限放大不失真,适合图标和线条图形。
  • RAW/HEIC:RAW是相机原始数据,HEIC是苹果的高效图像格式,它们通常需要在分享前转换为JPEG等通用格式。

理解这些格式的差异,能帮助你在不同场景下(如网页设计、照片存储、图表制作)选择最合适的工具,从而在保证所需质量的同时,优化存储空间和传输速度。

L3.1:数字音乐:影音录制背后的科学 🎵

在本节课中,我们将要学习数字音乐的基础知识,特别是声音是如何产生、被录制,并最终在计算机上以数字形式存储的。我们将从物理世界的声音波开始,逐步探索录音技术如何捕捉这些波,以及计算机如何表示和处理它们。

声音的产生与传播 🔊

上一节我们介绍了课程的主题,本节中我们来看看声音是如何实际产生的。

首先假设我们在教室里,斯坦福交响乐团正在全班面前演奏贝多芬的第五交响曲。当他们演奏交响曲时,会发生什么?正在发生的是,各种乐器都在产生声波,并且这些声波在空气中传播。它们最终到达我们的耳膜。所以,我们所感知的音乐,是声波击中我们耳膜的结果。

录音的基本原理 🎤

了解了声音的产生后,我们来看看录音是如何实际发生的。

现在假设我们想把斯坦福交响乐团送回家,然后听他们的录音。我们要做的是,和之前一样,让斯坦福交响乐团现场演奏。然后,我们要用一些设备代替我们的耳朵——麦克风。我们有一个单独的麦克风,放置在房间中间。麦克风会在声波撞击它时拾取声波。

不知何故,我们需要存储使用该麦克风产生的声波。我们可以从磁带或老式留声机等设备开始。我们需要存储交响乐团正在播放的波的振幅频率

在立体声的情况下,我们将设置两个麦克风:一个在左边,一个在右边。我们将做同样的事情。声波将在稍微不同的时间到达这两个麦克风。我们继续并再次获取声波的幅度和频率,然后我们继续记录。

从录音到播放 🔊

记录下内容后,我们要做的是将斯坦福交响乐团送回家。我们得到一些扬声器并将它们连接到我们的录音设备。如果扬声器能够产生与斯坦福交响乐团最初创建的完全相同的声波,我们基本上可以完整再现斯坦福交响乐团的表演。

可视化声波 📊

我们实际上可以使用计算机上可用的一些工具来看看这些声波是什么样子。我们接下来要做的是,使用一个叫做 Audacity 的程序。它将让我们看看这些声波。我要演奏一个版本的贝多芬第五交响曲,并用我电脑上的麦克风来接收交响乐团演奏的声音。我们将看到的是声波的图形化表示。

以下是使用 Audacity 观察声波的步骤:

  1. 打开 Audacity 程序。
  2. 使用电脑麦克风录制一段声音(例如播放的交响乐)。
  3. 程序界面将显示声音的波形图。

如果我们将正在观看的声波在 Audacity 中放大,那么 Audacity 将向我们展示音乐播放时的声波形态。我们把它放得更大,就能看到更详细的波形。所以,我们的下一个任务是把我们在这里看到的这个波,以某种方式把它转换成位和字节,这样我们就可以在计算机中存储和处理它。

总结 📝

本节课中我们一起学习了数字音乐录制背后的科学。我们从声音的产生和传播开始,了解了声波如何被麦克风捕捉并记录下来。接着,我们探索了如何通过扬声器重现这些声波来播放音乐。最后,我们使用 Audacity 工具可视化了声波,并引出了将模拟声波转换为数字位和字节的核心任务,为后续学习数字音频格式打下基础。

L3.2:数字音乐:从模拟信号到数字信号 🎵

在本节课中,我们将学习如何将现实世界中的模拟音乐信号(声波)转换为计算机可以存储和处理的数字信号。我们将重点理解两个核心概念:采样率和位深度。

在上一节中,我们了解到音乐本质上是一种声波。本节中,我们将看看如何利用这种波,并使用我们在之前课程中学到的位和字节来表示它。

观察声波 📈

现在让我们仔细观察声波,以确切了解其特性。下图是贝多芬第五交响曲声波的一个特写片段。

横轴(X轴)代表时间的推移。纵轴(Y轴)代表波在特定时间点的大小(振幅)。为了将这些信息存储到计算机中,我们需要在特定的时间点进行测量。

我们将存储一系列数字,这些数字对应于波在不同时间点的高度。

核心概念一:采样率 ⏱️

当我们进行测量时,需要决定采样的频率。这被称为采样率。采样率决定了我们每秒从声波中采集多少个样本。

下图展示了采样过程。红线代表根据我们采集的样本重建出的波形。

你会注意到,红线并不完全遵循原始的浅灰色声波线。如果我们能采集无限数量的样本,那么红线将与原始灰线完全匹配。但我们不能采集无限样本,因为那需要无限量的存储空间。

这里发生的是:我们沿着声波线获取特定点的样本,并尝试用这些样本来重现原始声波。

采样率的影响

让我们看看如果降低采样率会发生什么。在下一张图像中,采样率降低了。

我们看到采集的样本数量少了很多。红线开始严重偏离原始灰线,这个新波形看起来与原始波形大不相同。

问题在于:因为我们没有足够频繁地采样,我们的样本完全错过了原始信号的一些峰值和谷值。

核心概念二:位深度 🎚️

我们可以控制的另一个因素是沿Y轴的测量精度。这控制了我们的动态范围。我们可以改变所谓的位深度,即我们留出多少位来表示沿Y轴的数字。

以下是两个示例:

  • 在第一个示例中,我存储介于+8 和 -8之间的数字。
  • 在第二个示例中,我存储介于+2 和 -2之间的数字。

(注:为了示例简单,这里使用了偶数范围。实际上,对于n位,范围通常是从 -(2^(n-1))+(2^(n-1))-1。)

所以让我们先看+8到-8的示例。您可以看到我正在采集一些样本。这里要记住的是,我分配了给定的位数,因此我不能存储无限数量的可能值。我不能存储波高恰好是7.2562这样的值,我必须将它存储在我已给定的位数范围内。在这个例子中,我假设可以存储从+8到-8的整数值。你可以看到我试图遵循原始的灰线,但我仅限于沿着这些特定的离散整数值存储。

现在让我们看看另一个例子,我只能存储-2和+2之间的值。

您可以立即看到,第二个示例的效果不是特别好。这里发生的情况是:位深度越大,我就能够越准确地表示波形。

权衡:质量 vs. 存储空间 💾

我们已经看到,我们可以控制采样率(即随时间采集的样本数量)和位深度(这决定了动态范围和每个样本点的存储精度)。

显然,我们采集的样本越多越好,我们的位深度越宽越好。那么问题来了:为什么我们不直接为这两者使用巨大的值?

答案当然是:采样率越高、位深度越大,我们的音乐文件占用的存储空间就越大。

以下是常见的标准:

  • CD:使用每秒 44,100 个样本(通常写作 44.1 kHz),每个样本 16 位,并且有两个通道(一个左声道和一个右声道)。
  • DVD:使用非常相似的每秒样本数和位深度。
  • 蓝光:实际上可以增加每个样本的位数。

但这基本上是我们大多数人聆听音乐的质量。然而,我们还没有真正完成,因为在您听到的大部分音乐真正进入您的计算机之前,通常还会有一个额外的步骤(如压缩)。这是因为上述规范适用于实际的物理CD磁盘。在下一个视频中,我们将看看这个额外的步骤是什么样子的。

总结 📝

本节课中,我们一起学习了将模拟音乐信号数字化的核心过程:

  1. 采样:以固定的时间间隔(采样率)测量声波的振幅。采样率越高,对原始波形的还原度越高。
  2. 量化:将每个采样点的振幅值,近似为指定位数(位深度)所能表示的最接近的离散值。位深度越大,表示的动态范围越广,精度越高。
  3. 权衡:更高的采样率和位深度能带来更好的音质,但也会导致数据量(文件大小)显著增加。实际应用(如CD标准)是在音质和存储效率之间取得的平衡。

L3.3:压缩音乐 🎵

在本节课中,我们将要学习数字音乐压缩的核心原理。我们将探讨为什么原始的CD音频格式不适合互联网传输,并详细介绍MP3等压缩格式如何通过一系列技术手段,在保证人耳听感基本不变的前提下,大幅减小音频文件的大小。

上一节我们介绍了如何将模拟声音信号转换为数字的CD音频格式。本节中我们来看看如何对这种数字音频进行压缩,使其更适合存储和网络传输。

CD音频的存储问题

在上一个视频中我们提到,即使CD音频允许我们以数字方式存储音乐,但如果这个标准是现在制定的,那它可能不会被广泛使用。因为互联网音乐标准实际上是在大约20年前制定的。

CD音频存在一个问题:它占用了很多空间。让我们来计算一下一首歌曲需要多少空间。

假设我们有一首五分钟的歌曲,并且我们正试图通过互联网发送它。例如,你的父母在一个乐队中创作了一首五分钟的朋克摇滚歌曲,他们想把它发送给家里的弟弟或妹妹。他们使用我们之前讨论的技术将其转换为数字格式。问题是,这个文件有多大?

让我们来计算一下:

  • 5分钟 × 60秒/分钟 = 300秒
  • 每秒44,100个样本 × 2个通道(左/右)= 88,200个样本/秒
  • 每个样本16位 = 16比特/样本
  • 总比特数 = 300秒 × 88,200样本/秒 × 16比特/样本 = 423,360,000比特
  • 转换为字节(1字节=8比特):423,360,000比特 ÷ 8 = 52,920,000字节 ≈ 50兆字节

按照今天的标准,50兆字节听起来并不可怕。但回到20年前,当你的父母上大学时,他们需要通过互联网将这首歌发送给家里的弟弟或妹妹。问题在于,下载这首五分钟的歌曲需要多长时间?

结果证明,它需要大约五个小时。而当时电话线既用于互联网又用于语音,你必须选择使用哪一个。在五个小时内,弟弟或妹妹正在下载那首五分钟的歌曲,没有其他人可以使用电话。这显然是一个问题。

解决方案:音频压缩格式

解决方案是MP3文件。我们将在本视频中讨论MP3文件,但需要提一下还有其他几种密切相关的格式。例如,如果你在从iTunes购买的Apple设备上听音乐,可能会使用AAC格式。如果你在Windows设备上收听,可能会使用WMA格式。AAC和WMA标准都非常密切地遵循MP3的原理。我在这里谈论的几乎所有关于MP3的内容,对于其他音频格式而言都非常相似。

但有一种较新的格式非常不同,它并不需要进行那么多压缩,我们将在本系列的最后一个视频中讨论这个问题。

MP3压缩的核心步骤

为了在合理的时间内在互联网上传输这些文件,我们需要减少音频文件占用的空间量。我们要做的是执行许多不同的步骤来压缩文件。

以下是MP3压缩涉及的三个主要步骤:

1. 快速傅里叶变换

我们要做的第一件事是执行一个快速的傅里叶变换。我不想讨论这个的数学细节。快速傅里叶变换背后的基本思想是:任何声波都可以分解成成分(零件),其中每个零件都是一个单频。例如,我们可能有一首歌在播放,我们可以把这首歌分解成60赫兹的波、80赫兹的波、120赫兹的波等等。它们都有不同的振幅,但是当它们全部结合在一起时,这就是我们实际听到的内容。所以,我们将执行这个快速傅里叶变换来确定各个组成频率。

2. 心理声学分析与信息丢弃

现在我们要做的是使用一种叫做“心理声学”的东西,消除其中的一些频率。心理声学是研究人类如何实际聆听声音的科学。事实证明,有一堆声音我们根本听不见。

最值得注意的是,有些频率太高了我们听不到。例如,狗哨是有效的,因为当你吹狗哨时会发出非常高的高频声音,狗可以听到,但是人类不能,因为它太高以至于人类听不到。

我们还可以使用其他技巧来从我们的信息中消除某些声音。例如,如果有一个声音很大,然后在附近的频率中有更柔和的声音,人类会完全忽略更柔和的声音。

所以,我们要做的是使用心理声学知识,开始丢弃部分音乐信息。

现在需要注意的一件事是:我们正在丢失信息。之前我们谈到压缩图像时,我们谈到了使用JPEG技术和PNG技术进行压缩。记住,PNG技术是无损的,没有信息丢失,我们能够完全重现原始的位图图像。相比之下,JPEG会丢失信息。MP3将类似地工作,它是一种“有损”压缩。

一种思考方式是:假设我把贝多芬《第五交响曲》的原始CD音频,和我对它进行心理声学分析并压缩后的MP3版本进行比较。如果我压缩得很好,希望音乐对我来说听起来完全一样。但想想我的狗,我的狗正在听原始CD质量的声音,里面有这些美妙的高频音调,我听不见,所以我继续把它们扔掉了。当我用新的MP3格式重播音乐时,我的狗可能会想:“你做了什么?所有那些美丽的高频音符发生了什么?” 所以,我们在这里丢失了信息。

3. 霍夫曼编码

我们还没有完成压缩,尽管我们需要采取另一个步骤,那就是执行霍夫曼编码。

现在我不想详细介绍这个,但基本思想是:这是一种查找频繁出现的序列和不经常出现的序列的编码方式。它要做的是将频繁出现的序列存储在较小的空间中,与不经常出现的序列相比。

我喜欢通过类比来说明这一点。你们中的许多人可能至少熟悉摩尔斯电码的概念。最著名的摩尔斯电码信息可能是SOS(拯救我们的船)。在摩尔斯电码中,SOS的符号是:... --- ...(三点三划三点)。这与我们的讨论有什么关系呢?在摩尔斯电码中,传输一个字母所需的空间量取决于字母的频率。字母E是一个非常常见的字母,我们将只用一个点(.)来传输E。而字母A也很常见,所以我们将通过点划线(.-)传输A。而字母Q不经常出现,所以我们要以“划划点划”(--.-)来传输。因此,传输AQ将占用四倍于传输E的空间或时间。

因此,霍夫曼编码将对我们的音乐做一些非常相似的事情,为常见的数据模式分配短的编码,为不常见的模式分配长的编码。

压缩效果对比

所以我们执行了快速傅里叶变换,进行了心理声学分析,并完成了霍夫曼编码。我们有了歌曲的新版本,它以MP3格式存储。让我们快速看看MP3与原始CD音频相比将占用多少空间。

我们需要记住的第一件事是,MP3是一种有损格式,就像JPEG一样。和JPEG一样,可以设置压缩的“损失量”。因此我可以决定我希望它听起来尽可能接近原始,或者我可以决定我将在健身房听这个,所以我不太关心质量有多好。

一些常见的MP3比特率设置是每秒128千比特(Kbps)和每秒192千比特(Kbps)。让我们假设我们使用128 Kbps的比特率来压缩我们的五分钟歌曲。

计算如下:

  • 5分钟 × 60秒/分钟 = 300秒
  • 128千比特/秒 × 300秒 = 38,400千比特
  • 转换为字节:38,400千比特 × 1024比特/千比特 ÷ 8比特/字节 = 4,915,200字节 ≈ 4.7兆字节

记住,我们原来的CD音频歌曲大约是50兆字节。我们可以通过将其转换为MP3,以大约4.7兆字节的空间来收听完全相同的音乐(对人耳而言)。因此,你可以看到我们已经节省了大量的空间。至于质量损失,我们至少可以通过选择不同的比特率来控制它。

总结

本节课中我们一起学习了数字音乐压缩。我们了解到原始的CD音频格式文件体积庞大,不适合互联网传输。MP3等压缩格式通过三个核心步骤——快速傅里叶变换基于心理声学的信息丢弃霍夫曼编码——在牺牲部分人耳不易察觉的信息的前提下,极大地减小了文件大小。这使得音乐在互联网上的快速分享和存储成为可能。这是一种典型的“有损”压缩,其关键在于巧妙地利用人类听觉系统的特性。

L3.4:数字音乐:数字信号 vs 模拟信号 🎵

在本节课中,我们将要学习数字信号与模拟信号的核心区别。我们将探讨计算机如何将现实世界中连续的模拟信息(如图像和声音)转换为离散的数字数据,以便进行存储和处理。

概述

本周我们已经研究了计算机内部运作与我们现实生活中习惯的事物之间的许多差异。我们看到的第一个差异是数字系统:我们习惯使用十进制数,但计算机使用二进制数。但第二个,也是更重要的区别,是模拟和数字之间的区别。

上一节我们介绍了二进制与十进制的区别,本节中我们来看看模拟与数字的本质差异。

模拟信号与数字信号

当我们观察现实世界时,模拟指的是连续信号。现实世界充满了连续的量。相比之下,数字意味着我们以离散量存储信息。

因此,数字图像和数字音乐的全部内容,就是将真实的模拟世界转换成这些离散量,以便我们可以将其存储在计算机中。当我们想要将任何东西移入计算机时,总会有这个过程:我们必须将真实世界的模拟实体,以某种方式转换为数字世界。

从模拟到数字的转换

图像的数字化

如果我们在看一张照片,例如绿色图书馆前的红色喷泉照片。假设我们实际上站在图书馆前,世界以其所有的荣耀呈现。我们可以问一些问题,比如“现实世界中有多少颜色?”或“现实世界中有多少像素?”。这些问题没有意义,因为现实世界没有离散数量的颜色,也没有离散的像素。

当我们拍摄数码照片时,我们将它从现实世界转换成这些离散的单元(像素),以便存储在我们的计算机内部。我们将现实世界中的真实场景转换为单个像素的网格。在这些单个像素中,我们查看颜色,并将其转换为数字序列。例如,我们可能将该颜色转换为一个由8位(红色)、8位(绿色)和8位(蓝色)组成的序列。这样,我们就在数字化真实世界,以便将其作为离散量存储在计算机中。

声音的数字化

类似地,当斯坦福交响乐团在教室前演奏贝多芬第五交响曲时,他们并不是在生成每秒四万四千一百个数字的数字流,其中每个数字都在负三万二千七百六十八和正三万二千七百六十七之间。这不是他们正在做的事情。他们正在生成一个连续的模拟信号

当我们将这个声音存储到我们的计算机中时,我们需要做的是:我们需要将该连续的模拟信号,转换为离散的数字量,以便我们实际上可以将它们存储起来。

核心概念总结

以下是模拟与数字的核心区别:

  • 模拟信号:在时间和幅度上都是连续的。现实世界中的大多数现象(如声音、光线)最初都是模拟的。
    • 公式表示:信号值 = f(时间),其中 f 是连续函数。
  • 数字信号:在时间和幅度上都被离散化(采样和量化)。这是计算机存储和处理信息的方式。
    • 公式表示:数字信号值 = 量化( f(采样时间点) )

将模拟信号转换为数字信号的过程主要包含两个步骤:

  1. 采样:在连续的时间点上测量信号的值。
    • 代码概念:sample_value = analog_signal(t)
  2. 量化:将每个采样得到的连续幅度值,近似为最接近的离散电平值。
    • 代码概念:digital_value = round(sample_value / quantization_step) * quantization_step

总结

本节课中我们一起学习了数字信号与模拟信号的根本区别。我们了解到,计算机是一个数字系统,它处理的是离散的数据。为了将现实世界中连续的模拟信息(如图像和声音)带入计算机,我们必须通过采样量化的过程将其数字化。理解这一转换过程是理解数字媒体(如图片、音乐、视频)如何在计算机中表示和存储的基础。

L3.5:数字音乐:音乐与格式 🎵

在本节课中,我们将探讨数字音乐的存储格式。我们已经了解了MP3等有损压缩格式,本节将介绍无损压缩格式FLAC以及完全不同的MIDI文件格式,并比较它们的特点与应用场景。

无损音频格式:FLAC

上一节我们介绍了MP3等有损压缩格式,它们通过舍弃一些人耳不易察觉的声音信息来减小文件体积。本节中我们来看看一种不同的思路:无损压缩。

FLAC是一种无损音频编解码器。它通过对原始CD音频数据进行压缩来减小文件大小,但不会丢失任何声音信息。其压缩原理类似于我们在图像处理中见过的PNG文件。因此,一个FLAC文件解码后的音频数据与原始CD音频数据完全一致,音质理论上与CD相同。

苹果和微软也拥有具备类似特性的自有无损音频格式。

以下是FLAC、MP3与原始CD音频的文件大小对比(以一首5分钟的歌曲为例):

  • 原始CD音频:约 50.47 MB
  • FLAC文件:约 35.67 MB
  • MP3文件:约 4.5 MB

可以看到,FLAC在保持音质无损的前提下,相比原始CD音频节省了部分空间,但节省的幅度远小于MP3。对于追求极致音质的听众而言,FLAC是一个很好的选择。

另一种思路:MIDI文件

接下来,我们探讨一种完全不同的音乐存储方式。请先聆听以下两段贝多芬《第五交响曲》的录音,感受它们的区别。

[此处为音频示例对比]

你更喜欢哪一个?第二个录音之所以听起来不同,是因为它并非存储声音波形,而是使用了MIDI格式。

MIDI文件的核心在于存储单个的音符指令,而非记录声音的波形。这带来了几个显著的优点。

首先,从文件体积来看,MIDI文件通常比基于波形的声音文件(如CD音频、MP3、FLAC)小得多。因为存储一系列音符指令所需的数据量,远小于存储每秒数万个音频采样点。

但MIDI文件更重要的意义在于其可编辑性。由于音乐被表示为独立的音符,我们可以方便地对其进行修改和编辑。

如上图所示,在名为“Songworks”的程序中处理MIDI文件时,我们可以清晰地看到并操纵每一个单独的音符。这对于作曲家和音乐家来说非常有用,因此MIDI在音乐创作和制作领域被广泛使用。

课程总结与预告

本节课中我们一起学习了数字音乐的两种重要格式。我们探讨了无损压缩格式FLAC,它能提供与CD相同的音质;也认识了MIDI文件格式,它通过存储音符指令来实现极小的文件体积和强大的可编辑性,适用于音乐创作。

下周,我们将进入新的主题,开始探索计算机硬件。CS106E课程会深入研究中央处理器(CPU)和计算机内存的工作原理,而我们的CS105课程也将从计算机硬件概述开始,进而了解互联网是如何运作的。

L4.1:计算机硬件总览 🖥️

在本节课中,我们将要学习计算机硬件的基础知识。我们将了解硬件与软件的区别,并认识构成计算机的三大核心硬件类别:处理器、内存和输入/输出设备。

硬件与软件的区别

上一节我们介绍了课程概述,本节中我们来看看什么是计算机硬件。首先,我们需要明确硬件与软件的区别。

软件是我们安装到计算机中的程序。例如,我需要录制本季度的所有讲座,我可能会决定获取一个视频编辑程序(如 Premiere Pro)的副本,并将其安装到我的电脑上。这属于应用软件。

然而,计算机中还有很多我们认为是其不可或缺部分的东西,实际上并不属于硬件。例如,我可以从商店购买一台 MacBook Pro,它预装了 macOS 操作系统。但实际上,我可以在这台 MacBook Pro 的硬件上,将 macOS 替换为 Microsoft Windows 或 Linux。这是因为操作系统也属于系统软件,它是一组运行在硬件之上的指令,本身是可替换的。

因此,我们区分了:

  • 硬件:计算机的物理设备。
  • 软件:运行在硬件之上的指令集。
    • 系统软件(如操作系统):管理计算机硬件和资源。
    • 应用软件(如 Premiere Pro):为用户执行特定任务。

我们将在后续讲座中更详细地探讨系统软件和操作系统。现在,让我们专注于硬件本身。

计算机硬件的三大类别

我们已经区分了软件和硬件,接下来我们聚焦于硬件。计算机硬件主要分为三大类。

以下是三大核心硬件类别:

  1. 处理器

    • 主要指中央处理器,负责执行程序指令和进行运算。现代设备(如智能手机)通常还包含图形处理单元,专门处理图形和图像计算。
  2. 内存

    • 内存由两种类型组成:
      • 主存储器:通常被称为 RAM。它的特点是易失性,即断电后其中存储的信息会丢失。它的优点是速度非常快。
      • 辅助存储器:例如固态硬盘、机械硬盘、闪存盘等。它的特点是非易失性,即使完全断电,信息也能持久保存。它的缺点是速度比主存储器慢得多。
  3. 输入/输出设备

    • 这包括显示器、键盘、鼠标、打印机、扫描仪等外部设备。它们负责计算机与外界的信息交换。

注意:虽然以上分类以笔记本电脑和台式机为例,但智能手机和平板电脑的工作原理几乎完全相同。它们同样具备 CPU、GPU、主存(RAM)、辅存(内部存储)以及输入/输出设备(如触摸屏)。此外,还有嵌入式计算机(如汽车内部的电脑),它们也包含这些硬件组件来处理特定任务(如监测发动机温度、控制燃油喷射)。

硬件如何协同工作

我们已经认识了处理、内存和输入/输出这三大类硬件,现在我想看看这些不同的组件是如何在执行基本计算任务时协同工作的。

让我们以“安装并运行一个应用程序”为例。假设我需要安装 Premiere Pro。

  1. 获取与安装:我首先需要从互联网或光盘获取程序的安装文件。这些文件通常是压缩的,以节省空间和传输时间。下载后,计算机需要将其解压缩,然后运行安装程序。因为我想在计算机关闭后依然保留这个程序,所以安装程序会将 Premiere Pro 的指令(程序文件)写入辅助存储器(如硬盘或固态硬盘)。

  2. 运行程序:安装完成后,如果我仅仅是将程序放在电脑里而不运行它,那么指令就静静地待在辅助存储器中。当我双击程序图标启动它时,情况发生了变化:程序指令需要从较慢的辅助存储器复制到速度极快的主存储器中。这是因为 CPU 无法直接访问辅助存储器上的内容,它只能与主存储器高速交互。

  3. 处理文档:同理,当我用程序(如 Microsoft Word)打开一个文档时,该文档原本也存储在辅助存储器中。为了让我能编辑它,文档的内容也需要被复制到主存储器中。我在主存中对文档进行修改,然后通过“保存”操作,将修改后的内容从主存写回到辅助存储器,从而实现永久保存。

如何选择内存

在了解了主存和辅存的区别后,一个自然的问题是:我怎么知道我的电脑需要更多的主内存还是更多的辅助存储空间?

这取决于你的使用需求:

  • 需要更多辅助存储:如果你需要存储大量数据,例如成为视频博主,拥有很多视频文件。你通常不会同时编辑所有视频,但需要将它们都保存下来,这时就需要更大的硬盘或固态硬盘空间。
  • 需要更多主内存:如果你习惯同时运行大量程序(例如同时打开浏览器、多个文档、编程环境和音乐播放器),那么你需要更大的 RAM。如果运行的程序所需内存超过了物理 RAM 的容量,计算机会使用一种叫做“虚拟内存”的技术(利用一部分硬盘空间来模拟内存),但这会显著降低系统速度。因此,增加物理 RAM 容量能让多任务处理更流畅高效。

本节课中我们一起学习了:计算机硬件与软件的基本区别,认识了构成计算机的三大核心硬件(处理器、内存、输入/输出设备),并通过安装运行程序的例子了解了它们如何协同工作。我们还学会了根据实际需求(存储大量文件 vs. 同时运行多个程序)来判断是需要升级辅助存储器还是主存储器。

L4.2:电脑硬件:处理过程近观 🔍

在本节课中,我们将深入探讨计算机硬件的核心——处理过程。我们将了解中央处理器(CPU)的工作原理、多核处理器的优势与挑战,以及图形处理单元(GPU)等专用处理器的作用。

概述

在上一个视频中,我们看到了处理是计算机最重要的组件之一。本节中,我们将仔细看看这个处理组件,特别是中央处理器(CPU),并探讨如何通过多核和专用处理器来提升计算能力。

中央处理器(CPU)

您可能知道,最重要的处理组件是中央处理器CPU。但实际上,这里还有更多内容需要了解。

首先,为了让每个人都清楚我们的内容,这里列举一些你可能会遇到的处理器常用名称:

  • 如果你有英特尔处理器,消费级计算机上最常见的是酷睿 i3、酷睿 i5、酷睿 i7 和酷睿 i9。
  • 如果你有一台配备 AMD 处理器的电脑,那么它们的普通消费级 CPU 是 Ryzen 和 Athlon。

现在,关于 CPU 的一个问题是,CPU 往往是一个很大的瓶颈。正如我们所见,所有处理都通过 CPU 进行。内存可以记住事情,但最终为了让我们的计算机真正充当计算设备,CPU 需要执行所有的工作。

多核处理器

有几种方法可以解决 CPU 瓶颈问题。其中之一是使用多核 CPU。

我们开始获得多核 CPU,并不是因为我们真的需要多核,而是更多因为电气工程师很长一段时间都不擅长让 CPU 速度越来越快。现在我们开始在速度上趋于稳定。所以电气工程师的想法是:我们不能让单个处理器更快,但是我们可以给你两个核心、四个核心、六个核心,甚至八个核心。

关于多核处理器,拥有几个核心很有用,但实际上能够充分利用四核、六核或八核,很大程度上取决于你所做的工作类型。这实际上是程序员面临的一个问题,因为程序员通常不知道如何利用多核处理器中的多个核心。

所以,如果你希望应用程序快速运行,并且你知道自己正在多核计算机上运行,那么理想情况下,你希望你的应用程序能利用尽可能多的内核。我认为程序员在这方面做得更好了。我记得第一个多核视频游戏机出现时,这对游戏程序员来说是一笔大买卖,按顺序利用这八个内核并不总是很清楚。

但是,有些应用程序肯定可以利用多核处理器。以下是多核处理器擅长处理的任务类型:

  • 照片处理:处理器可能非常擅长。你基本上可以将照片分成不同的部分,因此可以让一个核心处理一个部分,另一个核心处理另一部分,第三个核心处理第三部分。

这里有一点演示。假设我们有一张照片(这是我的老狗莫莉),我们想把它从彩色转换成黑白。我们的模拟将展示会发生什么。

  • 使用一个处理器,我们只是继续并一次处理每个像素。
  • 现在如果我们切换到两个处理器,你可以看到将其拆分:图片的上半部分交给一个处理器,下半部分交给另一个处理器。
  • 处理器数量又以更快的速度增加到4个,最后我们使用8个。你可以看到这真的很容易分解这个过程以利用额外的内核,因为有一种划分工作的明确方法。

因此,如果我们再次执行诸如天气模拟之类的任务,就很容易弄清楚如何使用多个核心来将不同的计算任务分配给不同的区域。实际上,超级计算机拥有许多核心,例如数万个核心,因此除了多核处理器之外,它们还经常用于模拟工作。

专用处理器

尽管我们可以通过多核方式解决 CPU 瓶颈,但还有另一种方式,这就是添加专用处理器

因此,现代计算机通常除了可能是多核的主中央处理单元外,还具有图形处理单元(GPU)

图形处理单元可能因消费者创建 3D 图形的能力而广为人知,例如用于游戏。但 GPU 也可以用于其他目的:

  • 也可以用于照片或视频编辑。
  • 也可以用于人工智能和神经网络。
  • 它们已经被臭名昭著地用于比特币挖矿。实际上,比特币矿工使用图形处理单元确实提高了价格,这让游戏玩家感到有些不安。而且事实证明,比特币矿工正在使用大量电力,这对环境来说真的很糟糕。

无论如何,我只是想让你了解一下处理过程中发生的不同事情。

总结

本节课中,我们一起学习了计算机处理过程的核心细节。我们了解到 CPU 是主要的处理单元,但也存在性能瓶颈。为了突破瓶颈,现代计算机采用了多核 CPU 和像 GPU 这样的专用处理器。多核处理器通过并行处理提升效率,但其效能取决于任务是否易于分割以及程序员的优化。专用处理器则针对图形渲染、AI 计算等特定任务进行了优化,极大地提升了计算机在特定领域的性能。理解这些处理组件如何协同工作,是理解现代计算机能力的基础。

L4.3:笔记本电脑硬件剖析 🖥️

在本节课中,我们将拆解一台笔记本电脑,深入了解其内部硬件组件。我们将从外部开始,逐步探索主板、存储设备、处理器等核心部件,并解释它们的功能与工作原理。

概述

我们将拆解一台约15年历史的苹果iBook G4笔记本电脑。通过这个过程,我们将直观地认识构成一台计算机的物理部件,并理解它们如何协同工作。

外部观察与初步拆解

首先,我们看到的是笔记本电脑的外部。这台iBook G4有明显的使用痕迹。我们的第一步是取下键盘。

取下键盘后,我们可以看到内部结构。这台较老的笔记本电脑设计允许用户升级某些部件。例如,在键盘下方有一个可以插入Wi-Fi卡的插槽,以及用于添加内存(RAM)的插槽。

请注意,大部分区域被一块金属板覆盖。这块金属板是电磁屏蔽层,其作用是减少笔记本电脑发出或接收的电磁辐射,防止干扰其他附近的电子设备。

内部组件总览

将笔记本电脑完全拆开后,我们可以看到所有内部组件。以下是主要部件的概览:

  • Wi-Fi防护罩:一个U形金属罩,用于屏蔽Wi-Fi卡。
  • 散热器:一块大型金属部件,用于传导热量。
  • 硬盘驱动器(HDD):用于长期存储数据的设备。
  • 主板:绿色的大型电路板,是计算机的核心。

接下来,我们将逐一详细探讨这些核心组件。

存储设备:硬盘驱动器(HDD)与固态硬盘(SSD)

上一节我们看到了内部布局,本节中我们来看看数据存储的核心设备。

硬盘驱动器(Hard Disk Drive, HDD) 是一种使用磁性存储的机械设备。它内部包含多个高速旋转的盘片(platters)。读写磁头在盘片上方移动,通过磁化盘片上的微小区域来存储数据(位),或通过读取磁性状态来获取数据。

公式表示数据访问数据访问时间 ≈ 寻道时间 + 旋转延迟 + 传输时间

HDD的优点是每字节存储成本较低,适合存储大量数据(如视频监控录像)。缺点是速度相对较慢,且由于包含高速运动的机械部件,更怕震动。

固态硬盘(Solid State Drive, SSD) 则完全不同。它没有活动部件,其内部类似于一块小型主板,上面焊接了许多存储芯片(闪存)。数据通过电信号直接存储在芯片中。

代码类比存储方式

# HDD 访问(类似机械寻址)
def read_from_hdd(sector, track):
    move_arm_to(track)      # 机械移动
    wait_for_disk_rotation(sector) # 等待旋转
    return read_magnetic_data() # 读取数据

# SSD 访问(直接电信号)
def read_from_ssd(memory_address):
    return flash_chips[memory_address] # 直接电子访问

SSD的优点是速度快、抗震性强、更安静。缺点是每字节成本更高。如今,SSD已成为大多数个人电脑的首选。

核心与散热:主板、CPU与散热器

了解了存储设备后,我们来看计算机的“大脑”和“神经系统”。

主板(Motherboard) 是一块印刷电路板(Printed Circuit Board, PCB)。绿色的基底是绝缘材料,上面蚀刻着无数细小的金属导线,用于在各个组件之间传递电信号和数据。所有其他关键部件都连接在主板上。

主板上最重要的两个芯片是:

  1. 中央处理器(Central Processing Unit, CPU):负责执行程序指令和处理数据。
  2. 图形处理单元(Graphics Processing Unit, GPU):专用于处理图像和图形计算。

这些芯片在工作时会产生大量热量。散热器(Heatsink) 通常由导热良好的金属(如铜或铝)制成,并紧密贴合在CPU和GPU芯片上。它的作用是吸收芯片产生的热量。散热器设计有大量鳍片,以增大与空气接触的表面积。通常还会有一个风扇对着散热器吹风,加速热空气的排出,从而有效降低芯片温度。

在台式机中,CPU通常被设计成可插拔升级的。CPU底部有许多引脚,用于连接主板插座,传输电力、数据、地址和控制信号。

公式表示数据流CPU <--[数据引脚]--> 内存CPU --[地址引脚]--> 内存

临时记忆:内存(RAM)

最后,我们来看看计算机的“短期记忆”——内存。

我们在主板上看到了内存模块。无论是笔记本电脑还是台式机,内存模块上都焊接有多颗内存芯片。这些芯片的数量通常是2的幂(如4颗、8颗、16颗),这与计算机二进制工作的本质相符。

内存(RAM)用于临时存储CPU正在处理和即将使用的数据与程序指令。它的读写速度远快于硬盘或SSD,但断电后数据会丢失。CPU通过其地址引脚指定要访问的内存位置,再通过数据引脚进行数据的读取或写入。

总结

本节课中,我们一起学习了笔记本电脑的硬件解剖。我们从外部拆解开始,逐步认识了:

  1. 电磁屏蔽层的作用。
  2. 硬盘驱动器(HDD) 的机械工作原理及其与固态硬盘(SSD) 的电子存储方式的区别。
  3. 主板作为连接核心,CPU作为处理大脑,以及散热系统对于维持稳定运行的重要性。
  4. 内存(RAM) 作为高速临时存储器的角色。

理解这些硬件组件如何协同工作,是理解计算机科学基础的重要一步。每个部件,从机械的硬盘到精密的CPU,共同将电信号和物理操作转化为我们所依赖的数字功能。

L4.4:虚拟内存 💾

在本节课中,我们将要学习计算机硬件中的一个重要概念——虚拟内存。我们将了解它是什么、它如何工作,以及它如何帮助计算机同时运行多个程序。

概述

当计算机同时运行太多程序时,其性能可能会下降。一种解决方案是添加更多物理内存。然而,这并不总是可行或经济。虚拟内存技术提供了一种软件解决方案,它允许计算机使用硬盘空间来模拟额外的内存,从而让用户感觉可以运行比物理内存容量更多的程序。

上一节我们介绍了计算机内存的层次结构,本节中我们来看看虚拟内存这一具体技术。

内存层次结构回顾

让我们从一张图开始回顾。这张图强调了计算机有多种类型的内存。

主内存(RAM)速度很快,但容量有限且成本较高。硬盘驱动器(包括固态硬盘)容量大、成本低,但速度慢。介于两者之间还有其他存储介质。请记住,我们通常可以更轻松地负担得起大容量的硬盘空间。

我还想提醒,这些类型的内存中的每一种都有不同的物理工作原理。

例如,内存模块使用电子电路存储数据。硬盘驱动器使用磁极存储信息。CD驱动器则使用凹坑和平面来存储信息,其过渡处代表二进制的0和1。

物理内存的限制

我们在这里看到的右边方框代表物理内存。假设我有1GB的物理内存,并且有一堆应用程序,每个都占用一部分内存。

在理想情况下,计算机一次只运行一个程序。只要我运行的程序所需内存不超过1GB,一切正常。例如,运行一个需要400KB的程序,或者一个需要300KB的媒体播放器,这都很好。

但我们通常不想一次只运行一个程序,我们想同时运行多个。除了应用程序,系统本身也需要内存。因此,如果我同时打开文字处理器、电子表格、浏览器,并启动一个视频会议软件,这些程序所需的总内存可能会超过实际的物理内存容量。

虚拟内存的工作原理

幸运的是,我们有虚拟内存技术。在这个例子中,我有1GB的物理内存芯片,但我还有一个容量大得多的硬盘驱动器(例如固态硬盘)。虚拟内存技术让我可以将硬盘的一部分空间当作主内存来使用。

操作系统软件会“欺骗”运行中的程序,让它们认为自己拥有足够的、连续的内存空间,而实际上部分数据被存储在速度较慢的硬盘上。它们只是二进制位,与物理内存中的位没有本质区别,但访问这些位的速度不同。

让我们看看具体如何工作。假设我有1GB物理内存,并在硬盘上划出1GB空间来假装是额外的主内存。

现在,我可以同时运行文字处理器、媒体播放器、电子表格和视频会议软件,因为我不再受限于1GB的物理内存。理论上,只要硬盘空间足够,我可以添加更多程序。

活动与非活动状态

理想情况下,只有正在被用户主动使用的应用程序和数据才保留在快速的物理内存中。其他暂时不用的应用程序,其相关数据可以存放在硬盘的虚拟内存区域。

因此,之前我们提到,当程序未运行时,其信息如何管理?为了实际编辑一个文档,程序需要被加载到内存中。我们可以打开许多程序,它们看起来都在运行。但只要用户没有主动与某些程序交互,这些程序就可以驻留在虚拟内存中。

例如,假设我打开了文字处理器、电子表格和浏览器,但我实际上只在用文字处理器工作。那么,电子表格和浏览器的数据可以放在虚拟内存里。当我想切换回电子表格时,计算机系统会意识到我正在主动使用它,于是将所需的数据从硬盘的“虚拟内存”部分移回物理内存,同时可能将文字处理器的部分数据移出到硬盘,以腾出空间。

虚拟内存的潜在问题:颠簸

当你在所有打开的应用程序之间频繁切换时,系统可能就会遇到麻烦。因为每个被激活的应用程序都需要从硬盘换入物理内存,这会导致系统花费大量时间在移动数据,而不是执行计算

这个过程被称为颠簸。颠簸是指系统频繁地在物理内存和硬盘之间来回移动数据页(页是内存管理的基本单位)。如果你过于频繁地切换程序,系统最终将花费大部分时间进行数据交换,而无法有效工作。

因此,当你的计算机开始变慢,并且硬盘灯频繁闪烁时,通常意味着发生了颠簸。系统正在使用虚拟内存,并可能频繁地在内存和硬盘间交换内容。

最佳解决方案

所以,最好的解决方案是,如果你的计算机允许,购买并安装更多的主内存(RAM)。这可以为你带来最直接的性能提升,减少系统对虚拟内存的依赖。

总结

本节课中我们一起学习了虚拟内存。我们了解到,虚拟内存是一种利用硬盘空间扩展可用内存的技术,它允许系统运行比物理内存容量更大的程序。其核心思想是将不活跃的程序数据暂时移至硬盘,当需要时再换入物理内存。虽然虚拟内存很有用,但过度依赖会导致性能下降(颠簸)。因此,增加物理内存容量是提升多任务处理能力的根本方法。

L5.1:计算机网络硬件

概述

在本节课中,我们将要学习计算机网络的基础硬件知识。我们将从了解计算机如何通过物理方式连接在一起形成网络开始,探讨不同的网络拓扑结构和连接介质,并介绍一些关键的硬件设备和网络术语。这些知识是理解互联网如何工作的基础。

网络拓扑与连接介质

上一节我们介绍了单个计算机的工作原理,本节中我们来看看多台计算机如何连接在一起工作。使计算机变得非常有用的一点,特别是考虑到它们当前的情况,是它们能够在网络上协同工作。我们将从查看计算机网络硬件开始。

要形成一个计算机网络,我们需要关注两件不同的事情。第一,我需要担心我的网络拓扑结构,即计算机的几何配置是什么。第二,我需要担心使用什么介质将计算机连接在一起,例如是使用电磁波、光缆还是铜线。

以下是三种经典的网络拓扑结构示例:

  • 星型网络:在中间有一个中心节点,外面连接着一堆其他节点。你们都很熟悉这种网络,因为 Wi-Fi 使用的就是这种类型。例如,家里的多台设备都连接到一个单独的 Wi-Fi 路由器。
  • 环形网络:计算机一个接一个地连接成一个环。这种拓扑比星型网络需要更少的连接介质,常用于光缆网络。它的一个问题是,如果其中一个节点发生故障,消息可能会停止在环中传播。
  • 总线网络:所有计算机都连接到一条公共电缆上。这种网络曾经在教室等环境中很常见。总线网络的一个问题是,如果多台计算机尝试同时发送信号,线路上会产生争用,网络需要制定规则来管理这种情况。

互联网:网络的网络

上一节我们看到了三种不同的网络类型,本节中我们来看看互联网属于哪种类型。互联网本身是一个旅行问题,因为它不是一个单一的网络。它实际上是一个由不同网络组成的网络,我们称之为互联网络

例如,一个大学校园可能有一个光纤环网连接不同建筑,每栋建筑内部可能使用总线网络连接不同楼层,而每个楼层又可能通过 Wi-Fi 路由器(星型网络)连接个人设备。互联网本身就是由这样无数个相互连接的不同网络组成的。

网络连接设备

当我们构建和使用互联网络时,会遇到不同类型的连接硬件。

以下是几种关键的连接设备:

  • 网桥:用于连接两个相同类型的不同网络。例如,连接两个宿舍楼各自的总线网络。
  • 路由器:用于连接两种不同类型的网络。最著名的例子是无线路由器,它连接无线 Wi-Fi 网络和其他有线网络。
  • 网关:网关计算机是一台通用计算机,其功能类似于路由器或网桥,用于将不同的网络连接在一起。它与专用路由器盒子的区别在于,网关计算机是一台承担网络流量处理任务的通用计算机。

带宽、延迟与相关术语

上一节我们介绍了连接网络的硬件,本节中我们来了解衡量网络性能的几个关键概念。当我们谈论不同的连接介质时,出现的问题之一是带宽等。

带宽最初指的是为特定通信预留的频带宽度。频带越宽,能发送的消息就越多。现在,大多数人用带宽来指代网络速度。带宽高意味着可以快速发送大量信息。

您可能还会听到宽带窄带这两个术语。窄带通常指传统的电话线(POTS),带宽非常低。宽带则泛指那些不是窄带的、速度更快的连接。虽然这些术语现在不常被提及,但“宽带”一词仍出现在许多公司名称中。

除了连接速度,网络性能还受其他因素影响。因为互联网是一个网络组成的网络,所以消息需要经过多个网络才能到达目的地。

这里有一些您可能会遇到的术语:

  • 局域网:指一个小型网络,例如一个教室或一栋建筑内的计算机网络。朋友们聚在一起用电脑联机游戏,就是一个“局域网派对”。
  • 广域网:指连接跨城市、大洲甚至全球的城市的网络。
  • 城域网:指一个城市范围内的网络。
  • 跳数:指消息从源到目的地需要经过的网络数量。跳数越多,通常意味着路径越长、越复杂。

每次消息从一个网络传输到另一个网络时,连接设备(如路由器)都需要花时间查看该消息,以确定它应该发送给谁或如何传递到下一个网络,这个过程会产生延迟。延迟是指消息传递缓慢的感觉,例如视频通话时声音和画面不同步。在游戏中,高延迟可能导致玩家的操作指令没有及时到达服务器。

延迟是一个更技术性的术语,通常指传输中涉及的延迟量,但它与“延迟”经常被同义使用。

需要明确的是,延迟和延迟不一定与连接速度(带宽)直接相关。例如,向火星发送消息,无论带宽多高,由于距离遥远,第一个数据位到达都需要很长时间(高延迟)。高带宽只能保证在第一个位到达后,剩余数据能较快传送完;而低带宽则会在高延迟的基础上,还要等待更长的数据传输时间。

因此,网络性能由带宽、距离(导致的传播延迟)和跳数(导致的路由处理延迟)等多个因素共同决定。

内容分发网络

当我们开始考虑为全球用户提供网络服务时,延迟就成为一个实际问题。如果服务器在加利福尼亚,而客户在澳大利亚,消息需要经过很多跳,延迟会很长。

处理此问题的一个更好方法是使用内容分发网络。CDN 的作用是,将位于中心服务器(如在加利福尼亚)的文件,分发到世界各地的大量服务器中。这样,无论用户从何处访问,都能从附近的服务器获取信息,从而减少跳数、降低延迟,让信息更快到达。

总结

本节课中我们一起学习了计算机网络的基础硬件知识。我们了解了星型、环形和总线等不同的网络拓扑结构,认识了互联网是由众多网络互联而成的“网络的网络”。我们还介绍了网桥、路由器和网关等关键网络连接设备,并深入探讨了带宽、延迟、跳数等影响网络性能的核心概念。最后,我们了解了 CDN 如何通过在全球部署服务器来优化内容分发,降低延迟。在下一讲中,我们将讨论如何识别网络上的单个计算机。

L5.2:电脑硬件:命名 📝

在本节课中,我们将要学习计算机网络中如何命名和识别不同的设备。上一节我们介绍了如何将计算机连接成网络,本节中我们来看看如何为网络上的每台计算机和设备分配唯一的标识符,以便准确发送信息。

概述

在教室的Wi-Fi网络中,所有学生的笔记本电脑都连接到了同一个无线路由器。虽然设备已经物理连接,但如果我们想给教室对面的朋友发送一条消息,就需要一种方法来唯一识别他的电脑,而不是将消息广播给所有人。这就是命名系统要解决的问题。

命名方案一:MAC地址 🔢

第一种命名方案是MAC地址,也称为物理地址。MAC代表“媒体访问控制”(Media Access Control)。

一个MAC地址看起来像这样:00:01:42:af:3b:05。它由6组十六进制数字组成,每组两个字符。十六进制是一种基数为16的数字系统,便于转换为计算机使用的二进制。

以下是关于MAC地址的几个关键点:

  • 唯一性:每台网络设备的MAC地址在全球都是唯一的。
  • 制造商编码:地址的前6位(前3组数字)是制造商标识符。例如,以00:00:95开头的地址很可能属于苹果公司的设备。
  • 多接口设备:一台计算机可能有多个MAC地址,例如Wi-Fi适配器、以太网接口和蓝牙模块各有独立的MAC地址。

MAC地址在本地网络(如我们的教室Wi-Fi)中可以直接用于设备间通信。然而,它有一个主要缺点:MAC地址是基于设备制造时分配的,与设备当前所在的物理位置无关。这就像试图用社会安全号码(一个唯一的身份ID)来寄信,邮局无法知道收件人住在哪里。因此,MAC地址不适合在广阔的互联网上路由信息。

命名方案二:IP地址 🌐

为了在互联网上定位计算机,我们使用IP地址。IP代表“互联网协议”(Internet Protocol)。

传统的IP地址(IPv4)格式如下:171.64.20.1。它由4个用点分隔的数字组成,每个数字在0到255之间(对应一个字节的存储范围)。

IPv4地址空间约有42亿个组合,但全球设备数量已远超这个数字,导致地址不足。因此,出现了新版本IPv6

一个IPv6地址示例:2001:0db8:85a3:0000:0000:8a2e:0370:7334。它由8组16位的十六进制数组成,提供了极其庞大的地址空间(约3.4×10³⁸个),足以应对未来需求。

以下是IP地址的核心特点:

  • 由ISP分配:与MAC地址由制造商分配不同,你的IP地址是由你的互联网服务提供商(如校园网、康卡斯特)动态或静态分配的。
  • 具有层次性:IP地址的各个部分具有地理和网络层次信息。例如,地址171.64.xxx.xxx可能指向斯坦福大学,而更后面的数字可以进一步定位到校园内的特定子网或建筑。这种层次结构使得互联网上的路由器能够高效地将数据包转发到目的地。
  • 当前状态:IPv4目前仍被广泛使用,但行业正在向IPv6过渡。

命名方案三:主机名 🏷️

IP地址对计算机很友好,但对人类来说难以记忆。因此,我们使用主机名,也就是常见的网址。

例如:www.stanford.eduwww.nasa.gov

有一个名为DNS的系统专门负责将我们输入的、易于记忆的主机名,翻译成计算机用于实际通信的IP地址。互联网上所有的流量最终都是通过IP地址传输的。

命名方案四:端口号 🚪

之前的命名系统用于识别网络上的计算机,而端口号用于识别单台计算机上运行的具体程序

一台计算机可以同时运行多个网络程序(如浏览器、邮箱、音乐软件)。当数据从网络到达计算机时,操作系统需要知道该将数据交给哪个程序处理。

端口号就是用于这个目的。以下是一些常见端口号:

  • 端口 80:通常用于HTTP网页浏览。
  • 端口 443:用于HTTPS安全网页浏览。
  • 端口 25:用于SMTP邮件发送。
  • 端口 143:用于IMAP邮件接收。

数据包会标有目标端口号。例如,标有端口80的数据会被交给网页浏览器,标有端口143的数据则会被交给邮件客户端。防火墙安全设备常常通过控制特定端口的“开放”或“关闭”来管理进出计算机的网络流量。

与端口相关的另一个概念是套接字,它可以理解为两台计算机之间基于特定IP地址和端口号的连接通道。一个服务器可以同时与多个客户端建立不同的套接字连接。

总结

本节课中我们一起学习了计算机网络中的四种核心命名方案:

  1. MAC地址:设备唯一的物理标识,用于本地网络通信。
  2. IP地址(IPv4/IPv6):设备在互联网上的逻辑地址,具有层次性,用于全球路由。
  3. 主机名:方便人类记忆的地址,通过DNS系统转换为IP地址。
  4. 端口号:用于标识计算机上特定的应用程序或服务。

这些命名方案层层协作,确保了数据能够从源头的某个特定程序,准确无误地抵达目的地网络的另一台计算机上的另一个特定程序。

L6.1:网络协议:什么是协议 📡

在本节课中,我们将要学习计算机网络中的一个核心概念——协议。我们将探讨为什么需要协议,协议是什么,并通过一个现实世界的例子(HTTP协议)来理解协议的具体构成和工作方式。


为什么需要协议?🤔

上一节我们介绍了如何在网络中识别计算机(例如通过IP地址或主机名)。本节中我们来看看,当两台计算机想要通信时,仅仅知道对方的地址是不够的。

假设我想在教室的Wi-Fi网络中给朋友发送一条消息“我喜欢计算机科学”。即使我知道她电脑的地址,直接将这条消息发送过去,她可能只会收到一串随机的字节。她如何知道这条消息是谁发的?消息的格式又是什么?

这里的关键问题是:发送方和接收方必须事先就信息的组织和含义达成一致。例如,双方需要约定:

  • 消息内容本身放在哪里。
  • 如何标识发送者和接收者(例如,是先放“发件人”还是先放“收件人”)。
  • 这些信息之间用什么字符分隔(例如用冒号、空格或特定的“终止符”)。

这种计算机之间关于“如何执行一项任务”的预先约定,就是协议


现实世界的协议示例:HTTP 🌐

为了更具体地理解协议,让我们看看万维网(World Wide Web)使用的 HTTP(超文本传输协议)

想象一个典型的场景:你的笔记本电脑(客户端)通过互联网向一台网络服务器请求一个网页。

以下是HTTP协议需要规定的一些关键部分:

请求的类型

客户端可以对服务器发出几种不同类型的请求,每种请求代表不同的意图:

  • GET:请求获取服务器上的信息(例如,请求一个网页)。
  • POST:向服务器提交数据,可能会修改服务器的状态(例如,提交一个订单表单)。
  • DELETE:请求删除服务器上的资源(通常需要授权)。

请求中的附加信息

除了请求类型,客户端还可以在请求中包含许多其他信息,以便与服务器更有效地沟通。

以下是请求中可以包含的一些信息示例:

  • 字符编码:告知服务器客户端可以处理哪些字符集(如UTF-8)。
  • 缓存控制:告诉服务器“如果文件自某个日期后没有修改,就不必发送新副本”。
  • 接受编码:告知服务器客户端支持哪些压缩格式(如gzip),以便服务器在发送前压缩数据,节省带宽。

服务器的响应

服务器收到请求后,会根据协议格式进行回复。响应主要包含两部分:

  1. 状态码:一个数字代码,快速告知客户端请求的结果。
    • 200:成功。
    • 404:未找到请求的文件。
    • 403:禁止访问(权限不足)。
    • 500:服务器内部错误。
  2. 响应内容与元数据:如果请求成功,会返回请求的数据(如HTML文件),并附带一些描述信息,例如:
    • 内容类型(如text/html)。
    • 内容编码(如gzip)。
    • 文件有效期等。

通过HTTP这个例子,我们可以看到,一个协议需要明确规定:

  • 可以发起哪些类型的交互(请求类型)。
  • 交互中应包含哪些信息。
  • 这些信息的格式是什么。
  • 当出现问题时该如何处理(错误状态码)。

协议与程序的关系 🔗

学生经常对协议和程序之间的关系感到困惑。让我们来澄清一下。

协议是一套规则,而程序是这些规则的实现者

以HTTP协议为例:

  • 谷歌Chrome、火狐Firefox、Safari等网络浏览器,是遵循HTTP协议规则编写的程序。它们知道如何构造HTTP请求并解析服务器的响应。
  • Apache、Nginx等网络服务器软件,同样是遵循HTTP协议规则编写的程序。它们知道如何解析收到的HTTP请求并生成格式正确的响应。

关键在于,只要不同的程序都遵循同一套协议规则,它们就能相互协作。Chrome浏览器可以向Nginx服务器请求网页,Firefox也可以。甚至一些非人类使用的程序(如谷歌的搜索爬虫机器人、为视障人士服务的音频浏览器)也能通过HTTP协议与服务器通信。

协议定义了交互的“语言”和“礼仪”,而程序则是使用这种语言进行对话的“参与者”。


总结 📝

本节课中我们一起学习了网络协议的基础知识。

我们首先探讨了为什么需要协议:为了实现有效通信,计算机之间必须预先约定数据交换的格式和规则。

接着,我们通过HTTP协议这个现实例子,深入了解了协议的具体内容,包括请求类型、附加信息、状态码和响应格式。

最后,我们明确了协议与程序的关系:协议是标准化的规则集,而程序是这些规则的实现;多种不同的程序可以遵循同一协议实现互操作。

在下一节,我们将探讨互联网协议一个更重要的特性:分层。理解分层协议模型,将帮助我们更深入地洞察互联网的工作原理。

L6.2:网络协议:互联网协议 🧩

在本节课中,我们将要学习互联网协议栈的分层结构。我们将通过一个简单的类比来理解每一层的功能,并探讨这种分层设计如何让应用程序开发者无需关心底层网络的复杂性。


概述

互联网协议是分层的。理解互联网的真正运作方式很重要。我喜欢通过类比来教授分层协议。

分层协议类比

让我们从一个假设开始。假设我在斯坦福完成教学后退休,搬到缅因州。我一直很擅长手工活,曾经建立模型。所以我决定制作一些木制玩具,而这些木制玩具最终非常受欢迎。因此,世界各地的人们都问他们是否可以购买我的玩具。斯坦福大学的某个人,加利福尼亚州的一位前学生想要购买我的玩具。

这里有一种可能性:我雇了卡车,我的卡车把玩具带到火车站。我们把玩具转移到火车站,火车开到芝加哥,然后它转乘另一列火车,最终到达洛杉矶的某个地方。然后我租了另一辆卡车将玩具从洛杉矶运到我以前的学生居住的任何地方。因此,假设我可以管理跑步的复杂性,这会将玩具送到我以前的学生手中。一个完整的交通网络,但这似乎不是一个很好的主意。我不想这样做。我想专注于制造玩具,我不想处理任何交通问题。

我想要做的是:我喜欢把玩具拿到联邦快递办公室,然后神奇地让它出现在我以前的学生的家里。我以前尝试过的所有步骤,比如租用卡车、制定火车时刻表、给卡车加满汽油,一切都还在发生。但不同的是,我没有处理它。联邦快递是这样的。

所以,当我们为互联网开发软件时会发生同样的过程。最终,有人在那地方工作在这一层,有人试图弄清楚如何装满卡车或制定火车时刻表。但大多数程序员在互联网上编程应用程序并没有处理这种复杂程度。而是相当于放弃了一些信息在联邦快递办公室,让它神奇地出现在另一端。

互联网协议栈

所以,我们要做的是看看有时被称为互联网协议栈的东西。我们将从用气体层填充卡车的底层,橡胶真正接触道路的地方。我们将看到构建在顶部的不同层,直到我们到达顶层。这是应用程序发生的地方,相当于从联邦快递中删除信息,它神奇地出现在另一端。

所以这是互联网协议栈。然后在底层我们有物理层。那就是层,基本上是我们给卡车装满汽油的库。然后我们在上面有很多层。然后在顶层我们有应用层。那是人们从联邦快递丢包裹的层。

所以,让我们来看看这些层中的每一层,看看它们是如何工作的。

第一层:物理层

第一层,底层是物理层。当我们在物理层工作时,我们需要确切地决定我们如何使用 0 和 1 来传输信息。例如,当我们通过电话传输信息时,我们将有一个 1070 赫兹的音调,我们将有一个 2025 赫兹的音调。其中哪一个代表零,哪一个代表一?我们需要弄清楚物理连接是什么样的。我们需要弄清楚一切将如何组合在一起。

现在关于物理层的事情是:任何时候你想出一个新的网络,你需要定义一个新的物理层。我们将发现并非所有层都是这种情况,而是较低的层。每次我们拥有一种新类型的网络时,我们都需要定义一个新副本。所以如果我决定在太平洋上获取信息的最佳方法是让一群鲨鱼头上有可怕的激光束,我需要弄清楚这些鲨鱼将如何在网络的物理层顶部跨太平洋传输零和一。

第二层:网络层

网络层的协议是互联网协议。这与我们看到 IP 地址时看到的互联网协议相同。而这一层建立在物理层之上。它仍然非常原始。所有通过互联网协议发送的信息都在进行,限制为 64 KB 数据包。这些数据包将包括发件人的 IP 地址、收件人的 IP 地址、校验和(这是一种尝试确定是否意外发生的方法,弄乱了数据包中的一些位),然后是实际数据。

特别使这个网络层有点烦人的事情之一是没有交付保证。互联网协议层说它将“尽最大努力”把包裹送到目的地。所以问题之一是:为什么我们有这个非常原始的层?答案是:因为无论何时我们有一种我们想要定义的新型网络,我们需要定义物理层,我们需要定义网络层。

所以,回到我的鲨鱼与可怕的激光束示例。我需要让这些鲨鱼以某种方式排队以将数据包从太平洋的一侧发送到另一侧。我不想处理更复杂的协议。我想要最简单的协议,因为我在试图让这些鲨鱼对齐时遇到了足够的麻烦。所以因为网络层相对简单,所以对某人来说相对容易想出一个新的网络类型,并在这个新的网络类型上实现网络层。

第三层:传输层

向上一层我们有传输层。实际上有几个协议在这一层工作,但到目前为止最著名的是传输控制协议或 TCP。TCP 协议基本上就像我们的联邦快递一样。我们将能够用传输控制协议来做,它会建立在网络层之上,最终建立在物理层之上。我们将能够在传输层放下包,它们就会神奇地出现在另一端。TCP 和 IP 通常被认为是关键的互联网协议,互联网最重要的协议。

让我们来看看这如何工作。所以我们这里有一个关于指环王的视频:《指环的团契》。它是 2.53 GB。这实际上意味着我在我们之前制作了这个小互动视频,拥有广泛使用的高清电视。因此,这部电影 2.53 GB 太大,无法放入我们单独的 64 KB 数据包中。因此如果我们想发送这部电影,需要将它分成所有这些小数据包。然后,是 TCP 将为我们做的事情。

所以作为建立在 TCP 之上的人,我将电影交给 TCP。TCP 将电影分解成所有这些小的单独数据包。如果你解决了,有超过 663,000 个数据包用于整个电影。TCP 要做的是给每个单独的数据包编号,并用源计算机的 IP 号和目标计算机的 IP 号标记数据包。然后它开始通过互联网使用互联网协议。

正如我们之前讨论的,互联网协议不保证传输,它只是尽最大努力。除其他外,这意味着一些数据包将丢失,并且某些数据包将以错误的顺序到达。现在 TCP 要做的是跟踪不同的数据包。如果数据包以错误的顺序到达,它会将它们重新排序为正确的顺序。甚至更好,它将跟踪数据包的总数。如果某些数据包丢失,它将使用 IP 将请求发送回原始计算机,说:“嘿,该数据包没有出现,您可以向我发送另一个副本。” 然后再发送该副本。

从使用 TCP 的人的角度来看,这完全是隐藏的。就 TCP 用户而言,我们只是从一台计算机到另一台计算机有直接连接,我们可以铲除我们想要的任何信息。这种连接最终会神奇地出现在另一边。

第四层:应用层

最终将我们带到我们的最后一层:应用层。这是你们关心的所有事情发生的层。所以我们有一大堆邮件协议:SMTP、POP、IMAP。网络协议 HTTP 在这里。有文件传输协议,如 FTP 和安全 FTP。基本上网络上的每个程序都需要有某种协议。它们可能是指定的协议,是公开可用的,并且不同的程序可以互操作。或者它们可能是完全专有的。

但最终,正如我们之前看到的,如果两台计算机在网络上并一起运行,那么这两台计算机之间需要某种正式协议。并且正式协议是一种协议。但正如我们所见,应用层是建立在其他层之上的。因此,如果您为互联网上的全新类型的应用程序想出了一些很棒的新想法,您就不必回头并说:“嘿,我需要弄清楚如何移动这些数据包,我需要知道如何重新排序那些数据包。” 完全由 TCP 为您处理。我只是将事情交给 TCP,并且 TCP 会照顾到。你不必担心:“嘿,我是通过 Wi-Fi 网络运行它?我是通过光纤电缆运行它?我是在帕特里克鲨鱼激光束网络上运行它吗?” 完全被较低层隐藏。因此您可以看到这种分层协议确实为顶层人员提供了一些巨大的优势。

分层协议的优势

还有一些其他方便的含义,即下面的所有内容都由数据包处理。请考虑,例如,我坐在我的宿舍里,我正在尝试下载《指环王》。现在,事实证明,来自我们特定楼层的所有互联网流量都通过相同的电缆传输到更广泛的大学互联网。因此,没有数据包,当我正在下载《指环王》时,没有其他人可以做任何事情。但是,因为流量被分解成数据包,我会得到我的 64 KB 小数据包。可能我隔壁的朋友正在看电影,她会从上面拿走她的 64 KB 包。我会从《指环王》那里拿一个包。也许有人真的想完成一些工作,他们会得到一些电子邮件进入。基本上所有数据包都将共享相同的电缆。一些数据包会为我进入,一些数据包会为其他人提供。如果我们要发送,人们将能够在我们的数据包之间工作。一切都将在一个大块中,也许你会得到相同数量的流量,但每个人都必须坐下来等待大块的通过。所以事情在一个数据包中分解的事实实际上非常好。

另一种情况是数据包当它们被用于通信目的时,工作得很好。所以如果你研究电信,你会发现电信有两种不同的方式。你可以拥有什么称为电路交换,传统上是电信发生的方式。或者您可以进行分组交换。实际上一切都在转向分组交换。您可能已经注意到斯坦福的电话通常是思科电话。您可能认为:“我认为思科是一家网络公司,但我不认为他们是一家电话公司。” 我们正在使用思科电话,因为思科电话使用互联网分组技术。结果互联网分组技术比传统电话高效得多。

电路交换 vs. 分组交换

所以让我们看看它的实际工作原理。所以我多次提到我的朋友 Tammy,她很久以前搬到了意大利。所以我们会通过电话交谈,并使用传统的电路交换技术。当我在电话中与 Tammy 交谈时,会发生以下情况:斯坦福和意大利之间有一条特定的电线。所以它可能不是一条线,它实际上是一系列经过不同交换机的线,但最终一路上有一条特定的电线。而我正在和她通电话,她完全专注于我们两个人。如果我们正在交谈,那就太好了,我们正在使用我们的电线。如果我们彼此生气并且说得不好,这些线路仍然完全专用于我们,即使我们什么也没说。所以这不一定是这些电线的最有效用途。这些电线是现在将斯坦福连接到意大利的这些电缆。

您可能已经注意到,你们中的一些人给海外朋友拨打的大部分电话实际上是完全免费的。也许您正在使用诸如 FaceTime 或 Google 环聊之类的东西,或者诸如此类的技术,使用互联网数据包。那么,在这些情况下会发生什么?而不是你之间有一个完全专用的电路,并说你的朋友和南非的银行海外项目正在发生的是:你的声音被切断了这些 64 KB 的小数据包。这些数据包与其他人的数据包一起被推入网络。您的数据包将到达另一端。并且由于线路现在是共享的,因此不像线路专用时那么昂贵。并且老实说,意大利这里的电话系统并不是那么好,以至于拥有一条专用线路必然会带来很好的结果。所以你知道,也许最好的 FaceTime,尽管我没有使用 Skype 给人们带来很好的体验。南非,所以你知道,这在很大程度上取决于互联网连接的情况。

这实际上带来了另一个关于整个数据包情况如何工作的有趣点。因为数据包正在与其他所有内容在线共享,有时数据包会被装瓶随着互联网流量的其余部分。正在发生的事情是在另一端,您收到一个数据包,您有决定:“啊,我错过了这个数据包。但如果我在播放视频之前等待太久,我的用户会认为这变得有点奇怪,所以也许我应该在数据包到达之前继续播放视频。我们只是会有一个小故障。” 所以可能会发生各种有趣的情况,因为这些信息是以数据包的形式发送的。


总结

本节课中我们一起学习了互联网协议栈的分层结构。我们从物理层开始,了解了如何传输0和1;然后探讨了网络层(IP协议)如何尽力传输数据包;接着学习了传输层(TCP协议)如何确保数据的可靠、有序传输;最后到达了应用层,各种应用程序协议在此运行。这种分层设计极大地简化了网络应用的开发,让开发者可以专注于业务逻辑,而无需关心底层网络的复杂细节。我们下周将转移到那里,将开始研究万维网,我们将开始学习如何制作网页。

L7.1:HTML 介绍与 Web 的起源 🌐

在本节课中,我们将要学习万维网(World Wide Web)的起源,了解其背后的两项核心技术——互联网与超文本,并探讨它们是如何结合,最终形成了我们今天所熟知的 Web。


万维网的来源

上一节我们提到了 Web 的诞生背景。本节中,我们来看看万维网具体是如何产生的。

万维网来源于两种基本技术:

  • 第一个是互联网,我们在之前的课程中已经花了很多时间研究它。
  • 第二种是超文本技术。

接下来,我们将开始深入了解超文本。


超文本的基本思想

在了解了 Web 的两大技术支柱后,本节我们来看看超文本背后的基本思想。

超文本的核心思想是:我们如何为人们呈现信息?我们一直在使用书籍,但书籍存在局限性。信息科学家们思考了很长时间,如何用信息技术来替代书籍。这个问题在二战结束前后就开始被探讨。

万维网背后的关键技术是超文本。超文本和超媒体的概念实际上可以追溯到 1963 年。超文本的第一次广泛使用可能是在 CD-ROM 上的电子百科全书中,例如微软的 Encarta 从 1993 年开始提供了一个很好的例子。而万维网则首次出现于 1989 年,早于这些商业产品,但最初只被非常有限的人群使用。


书籍的局限性

我们已经了解了超文本的概念,那么,它要解决的具体问题是什么呢?本节我们来分析传统书籍的局限性。

书籍有什么问题?我们为什么想要替换它?关于书籍的一点是:一本书由页面组成,每一页都按顺序跟随前一页。这看起来很明显,但这可能是一个非常大的限制,具体取决于我们看的是哪种类型的书。

以下是书籍的局限性:

  • 线性结构:书籍有第一页、第二页、第三页等顺序。对于阅读小说,这可能是个不错的选择(除非你想直接翻到最后看结局)。但对于像百科全书这样的参考书,单一的线性顺序就成了问题。
  • 查找与关联困难:印刷的百科全书只有一种信息排序方式。例如,要找一篇关于马丁·路德·金的文章,你可能会发现他被编排在“英格兰的乔治国王”和“西班牙的菲利普国王”之间,而他与这两人并无直接关系。这种线性排序使得查找文章容易,但很难发现不同文章之间的内在联系。

因此,当我们转向计算机时,我们不再局限于信息的单一排序。我们可以创建不同的信息节点,并将任何节点链接到任何其他节点。这样,我们既可以维护便于查找的序列,也可以将“马丁·路德·金”与“民权运动”、“阿拉巴马州塞尔玛”、“20世纪的伟大领袖”等文章关联起来。我们现在拥有的是一个信息网络,“万维网”(World Wide Web)一词正是由此而来。

超文本概念基本上就是说:我们有这些不同的文本节点,并将它们链接起来。这正是万维网的工作方式。在计算机上,我们可以更进一步,因为我们不再局限于文字。我们可以有演讲、音频(如马丁·路德·金的“我有一个梦想”演讲)、照片和电影。这被称为超媒体。以上就是超文本和超媒体背后的基本思想。


互联网与超文本的结合

超文本提供了组织信息的新方式,但这还不足以构成万维网。本节中,我们来看看等式的另一半——互联网,是如何与超文本结合在一起的。

如何将两者最终结合在一起?我们需要将目光转向欧洲。在欧洲,有一个名为 CERN 的物理实验室联盟,它有23个成员国,总部设在日内瓦。它最著名的是拥有一个用于高能物理研究的大型对撞机(一个27公里长的环),与斯坦福的SLAC类似。

在80年代后期,一位在CERN工作的计算机科学家蒂姆·伯纳斯-李,有兴趣找到一种方法,让CERN的物理学家们能够分享研究论文。在CERN,成员国将他们的物理学家派来,有时这些物理学家又会回到各自国家的机构。因此,他正在寻找一种在所有物理学家之间共享论文的方法。

很自然地,他认为应该使用互联网。此外,他还认为应该使用人们已谈论许久的超文本技术(该技术在实验室环境中已被研究了相当长一段时间)。于是,蒂姆·伯纳斯-李在1989年提出了结合互联网和超文本,以便在物理学家之间共享信息的想法。这就是万维网的起源,他因此在2004年被封为爵士。

所以,当你查看万维网并想知道为什么某些事情会以某种方式运作时,很重要的一点是记住:它最初是为了让物理学家共享物理研究论文而发明的。因此,它最初并没有拥有我们可能期望用于大众媒体的所有技术。我们会看到,其中一些技术是后来才被加入的。如果你专门为大众市场和普通消费者设计系统,你可能会采用略有不同的形式主义。为了让网页比物理学论文更丰富多彩、更有趣,必须添加很多东西。


总结

本节课中,我们一起学习了万维网的起源。我们了解到,Web 是互联网(提供全球连接的基础设施)与超文本/超媒体(提供非线性、可链接的信息组织方式)两项技术结合的产物。它最初由蒂姆·伯纳斯-李于1989年在CERN提出,旨在方便物理学家共享研究论文。这一起源也影响了Web早期的一些设计特点。

在下一个视频中,我们将看看 HTML,它实际上是用于创建网页的语言。

L7.2:HTML 介绍:超文本标记语言 🏗️

在本节课中,我们将要学习超文本标记语言的基础知识。我们将了解HTML是什么,它与HTTP协议的关系,以及如何使用标签和元素来构建网页的基本结构。


回顾:HTTP与HTML的关系

上一节我们介绍了万维网的起源和HTTP协议。本节中我们来看看HTML,并理解它与HTTP的关系。

HTTP代表超文本传输协议。它是一个协议,控制着万维网上两台计算机如何交互。一台计算机可以向网络服务器发出请求,请求特定文件,服务器则通过发送文件来响应。

这些文件可以有多种格式,最常见的格式就是HTML文件。HTTP协议本身并不指定文件的格式,它只负责传输。因此,HTML作为一种文件格式,定义了网页的内容和结构。

HTML代表超文本标记语言。我们已经了解了“超文本”的含义,而“标记”则来源于出版行业。


什么是“标记”?

术语“标记”来自出版行业。在传统出版中,作者负责撰写文章的正文内容,而编辑则会介入,决定文章的最终呈现格式,例如标题的字体、照片的位置等。

编辑会使用“标记”在作者的文稿上写下这些格式注释,指导排版人员如何进行版面设计。HTML正是借鉴了这一概念。

在HTML中,我们使用一种计算机能理解的、更正式的“标记”来告诉浏览器如何显示网页内容。


HTML的基本构成:标签与元素

学习HTML工作原理的关键是理解标签元素

假设我们想创建一个网页,上面显示“go Stanford”,并希望“go”以斜体显示,“Stanford”以粗体显示。在HTML中,我们会这样写:

<i>go</i> <b>Stanford</b>

以下是HTML的核心概念:

  • 标签:像 <i></i><b></b> 这样的符号就是标签。它们通常成对出现。
  • 元素:一个元素由开始标签结束标签以及它们之间的所有内容组成。例如,<i>go</i> 构成了一个斜体元素。
  • 容器关系:元素可以嵌套。例如,粗体元素可以包含斜体元素。但必须保持完整的包含关系,不能交叉嵌套。
<!-- 正确的嵌套 -->
<b><i>go</i> Stanford</b>

![](https://github.com/OpenDocCN/cs-notes-pt2-zh/raw/master/docs/stf-cs105-intro/img/d79286cfe7fb5355ee81daf9e2aefe67_14.png)

<!-- 错误的嵌套(非法) -->
<b><i>go</b> Stanford</i>

属性与自闭合标签

有时,标签本身需要提供额外信息。例如,如果我们想将“Stanford”一词链接到斯坦福大学的官方网站,就需要使用锚点标签 <a>,并告诉它链接的目标地址。

<a href="https://www.stanford.edu">Stanford</a>

  • 属性href 被称为属性,它为 <a> 标签提供了附加信息(这里是超链接的地址)。
  • 属性值“https://www.stanford.edu”href 属性的,需要用引号括起来。

某些标签并不成对工作,它们被称为自闭合标签。例如,换行符 <br /> 标签。它没有结束标签,为了清晰表明它是独立的,我们通常在末尾加上一个斜杠 /


空白字符与段落

在HTML中,连续的空白字符(包括空格、制表符、回车)都会被合并为单个空格。如果你想控制文本的换行和段落,需要使用特定的标签。

  • 换行符<br /> 标签会强制换行,但不会在两行之间创建空行。
  • 段落<p> 标签用于定义一个段落。浏览器通常会在段落之间显示一个空行。
<p>这是第一段。</p>
<p>这是第二段。</p>


标题与格式控制

HTML提供了用于创建标题的标签,从 <h1><h6>,其中 <h1> 是最高级(最重要)的标题,<h6> 是最低级的标题。

<h1>这是一级标题</h1>
<h2>这是二级标题</h2>

你可能想知道如何控制颜色、字体或对齐方式。在过去,这些样式信息也通过HTML标签来设置。但现在,我们使用级联样式表来专门处理所有样式问题,这使得HTML可以更专注于网页的结构和内容。


一个完整的HTML文档结构

一个格式良好的HTML文档需要包含一些基本框架标签。

以下是所有网页都需要的基本结构:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>网页标题</title>
  </head>
  <body>
    <!-- 网页的实际内容放在这里 -->
  </body>
</html>

以下是各部分的作用:

  • <!DOCTYPE html>:文档类型声明,告诉浏览器这是一个HTML5文档。虽然现代浏览器对此要求不严,但包含它可以避免浏览器进入“怪异模式”。
  • <html>:根元素,包裹整个HTML文档。
  • <head>:头部部分,包含网页的元信息,如字符编码、标题等,这些内容不会直接显示在网页上。
    • <meta charset=“utf-8”>:指定文档的字符编码为UTF-8,以支持多语言。
    • <title>:定义网页的标题,它会显示在浏览器标签页上。
  • <body>:主体部分,包含所有会在网页上显示的实际内容,如文本、图片、链接等。

HTML的变体:XHTML

HTML有多个版本和变体。当前主流版本是HTML5。此外,还有一种更严格的变体叫做XHTML

XHTML要求代码书写更规范,例如:

  • 所有标签和属性必须使用小写字母
  • 所有属性值必须放在引号内。
  • 自闭合标签(如 <br />)必须有结束斜杠

遵循XHTML的规则可以使代码更统一、更清晰,是专业开发中的常见做法。


总结

本节课中我们一起学习了超文本标记语言的基础知识。我们了解了HTML与HTTP协议的关系,学习了如何使用标签元素来构建网页结构,认识了属性的作用,并掌握了一个标准HTML文档的基本框架。

记住,HTML的核心是定义内容的结构,而样式则由CSS负责。在下一个视频中,我们将动手实践,学习如何创建、保存并在浏览器中查看你自己的HTML文件。

L7.3:HTML 介绍:逐步创建网页 🚀

在本节课中,我们将学习如何从零开始创建一个HTML网页。我们将介绍创建网页所需的基本工具、步骤和最佳实践,包括使用文本编辑器、编写HTML代码、验证语法以及如何在浏览器中预览效果。


概述 📋

HTML是构建网页的基础。本节我们将通过一个简单的流程,引导你完成创建第一个网页的每一步。这个过程包括准备工具、编写代码、检查错误和查看结果。


第一步:准备工具 🛠️

上一节我们介绍了HTML的基本概念,本节中我们来看看创建网页需要哪些工具。

首先,你需要一个文本编辑器。请注意,文本编辑器与文字处理器(如Microsoft Word)不同。文字处理器使用“所见即所得”的方式创建格式化文档,而HTML网页需要在各种不同设备上显示,因此我们需要使用纯文本编辑器来编写代码。

以下是几个推荐的文本编辑器:

  • Visual Studio Code:功能强大且免费,支持Mac、Windows和Linux。
  • Sublime Text:轻量且快速,被许多专业人士使用。
  • Atom:由GitHub开发,开源且可高度定制。

特别注意:Mac用户应避免使用系统自带的“文本编辑”应用,因为它默认支持富文本格式,可能会干扰HTML代码的编写。请确保你使用的是纯文本编辑器。


第二步:显示文件扩展名 📁

在开始编辑文件之前,建议你在操作系统中设置显示文件扩展名。文件扩展名(如 .html.txt)能帮助你清晰地区分文件类型,这在后续的编程工作中非常重要。


第三步:创建与编辑网页的流程 🔄

现在,我们来看看创建和编辑一个网页的基本步骤。我们将遵循一个循环流程来完善我们的网页。

以下是创建网页的核心步骤:

  1. 从初始文件开始:我们将为你提供一个基础的HTML文件模板,你无需记忆所有结构代码。
  2. 在文本编辑器中打开并编辑:使用你的文本编辑器打开初始文件,然后添加或修改内容。注意:不要双击HTML文件,否则它会在浏览器中打开。你应该通过文本编辑器的“文件”菜单或拖放方式在编辑器中打开它。
  3. 验证HTML语法:使用在线验证工具检查代码中是否存在语法错误。这是确保网页在不同浏览器中表现一致的关键步骤。
  4. 在浏览器中加载并查看:将验证无误的HTML文件在网页浏览器(如Firefox)中打开,查看实际效果。
  5. 循环改进:如果你对效果不满意或想添加新功能,就回到第2步编辑代码,然后重复验证和预览的过程。

这个“编辑 -> 验证 -> 预览”的循环被称为编辑调试周期,是程序员的核心工作流程。


第四步:使用HTML验证器 ✅

在将网页加载到浏览器之前,验证HTML代码至关重要。因为网页浏览器被设计为对用户友好,即使代码有错误,它也会尝试显示页面,而不会明确报错。这可能导致网页在不同浏览器或设备上显示不一致。

我们推荐使用万维网联盟提供的在线验证服务。访问 validator.w3.org,通过“文件上传”选项提交你的HTML文件进行检查。

关于验证器结果的几点说明:

  • 不要被错误数量吓倒:有时一个早期错误会导致验证器报告大量后续错误。修复第一个错误后重新验证,很多错误可能会消失。
  • 错误定位:验证器报告的错误行号有时可能比实际出错位置晚一行。请关注列表中的第一个错误,并检查其附近代码。

务必在提交作业前完成验证,以确保代码质量。


第五步:在浏览器中预览 🌐

完成验证并修复所有错误后,就可以在网页浏览器中查看你的作品了。为了课程评分的一致性,我们建议使用最新版本的Firefox浏览器

在浏览器中打开文件的方法不是双击,而是启动Firefox后,通过“文件”菜单中的“打开文件”选项,或者直接将文件图标拖放到浏览器窗口中。


总结 🎯

本节课中我们一起学习了创建HTML网页的完整流程。我们了解了文本编辑器与文字处理器的区别,掌握了“编辑-验证-预览”的核心工作循环,并学会了使用W3C验证器来确保代码质量。记住,编程是一个迭代的过程,耐心和细心是成功的关键。在接下来的作业中,你将有机会亲自实践这一切。

L7.4:HTML 语法与词汇规则 🧩

在本节课中,我们将学习 HTML 的语法和词汇规则。我们将了解计算机语言与人类语言的相似之处,并掌握构成 HTML 文档的基本规则。课程将涵盖 HTML 的词汇量、语法规则(句法),以及如何正确使用标签来构建网页。


计算机语言与人类语言

上一节我们介绍了 HTML 的基本概念,本节中我们来看看计算机语言与人类语言的异同。

计算机语言和人类语言都用于交流。人类语言用于人与人之间的交流,而计算机语言则用于人与计算机之间的交流。

学习一门新的人类语言时,我们需要掌握两样东西:语法规则词汇。学习计算机语言也是如此。

在计算机科学中,语法规则被称为句法,而词汇则与语义的概念相关。对于人类语言,词汇通常比语法更重要。例如,即使语法不完美,人们通常也能理解对方的意思。

然而,对于计算机语言,尤其是 HTML 和后续将学习的编程语言,语法必须接近完美。计算机不会像人类一样去猜测不完美语法背后的意图。

好消息是,与人类语言相比,计算机语言的语法规则通常更简化,词汇量也更有限。


HTML 的词汇量

HTML 的词汇量实际上非常小。整个 HTML 规范包含不到 120 个元素(标签)。

以下是部分 HTML 元素的例子:

  • <bdi><bdo>:用于处理从右到左书写的文本方向,在特定语言环境下很重要。
  • <wbr>:用于指定一个单词中合适的换行位置(例如,supercalifragilist<wbr>icexpialidocious)。
  • <track>:用于为音频或视频元素指定字幕轨道。

其中许多元素非常专业化,在创建普通网页时可能永远用不到。而另一些元素则非常常用和直观,例如:

  • <p>:用于定义段落。
  • <article>:用于定义一篇文章,比如博客中的一篇帖子。
  • <section>:用于定义文档中的一个节。

因此,虽然 HTML 有约 120 个元素,但其中很多并不常用,实际需要掌握的核心词汇量是适中的。


HTML 的语法规则(句法)

我们已经学习了 HTML 的一些语法规则。在计算机科学中,这些规则被称为语法

例如,我们了解到 HTML 使用标记(标签)来定义元素。一个元素通常由开始标签、内容和结束标签组成。

我们还学习了标签组合的规则:标签必须完全嵌套。这意味着如果一个斜体标签 <i> 在一个粗体标签 <b> 内部开始,那么它必须在同一个粗体标签内部结束。不能出现标签交叉的情况。

<!-- 正确:完全嵌套 -->
<b>这是<b>粗体<i>和斜体</i></b>文本</b>

<!-- 错误:标签交叉 -->
<b>这是<b>粗体<i>和斜体</b></i>文本</b>

那么,关于如何嵌套元素,还有哪些更多的语法规则呢?


元素嵌套的类别规则

HTML 元素根据其用途被分为不同的类别,这决定了它们内部可以包含什么内容。在 HTML4 中,规则相对简单,可以作为一个很好的经验法则。

主要有两种类型的标签:

  1. 文本级标签(或称行内标签):用于修饰段落内的几个词或字母。
    • 例如:<b>(粗体)、<i>(斜体)、<sub>(下标)、<sup>(上标)。
  2. 块级标签:用于创建文本块或结构块。
    • 例如:<h1>-<h6>(标题)、<p>(段落)、<table>(表格)、<ul>(列表)。

此外,还有一类结构标签,如 <html><head><body>,本次讨论暂不涉及。

基于此,我们引入两个关键概念:

  • 短语内容:可以包含文本和文本级标签。
  • 流内容:可以包含短语内容以及所有的块级标签(即任何能在页面上创建实际内容的东西)。

由此可以得出一些嵌套规则:

  • 文本级标签只能包含短语内容。这意味着你不能在 <i><b> 标签内放置一个段落 <p>
  • 某些块级标签可以包含流内容。例如,<article> 标签内部可以包含标题、段落、表格等任何内容。
  • 某些块级标签只能包含短语内容。例如,<h1> 标题标签内部只能包含文本和文本级标签(如 <i>),但不能包含其他块级标签(如另一个 <p>)。
<!-- 正确:<h1> 包含短语内容(文本和<i>) -->
<h1>斯坦福 <i>历史</i></h1>

![](https://github.com/OpenDocCN/cs-notes-pt2-zh/raw/master/docs/stf-cs105-intro/img/0918e26280b127ad6e2ef3ab378f8ecd_22.png)

<!-- 错误:<h1> 试图包含块级内容(<p>) -->
<h1>斯坦福 <p>历史</p></h1>

然而,从 HTML5 开始,规则变得更加复杂。HTML5 引入了更多内容类别(如“标题内容”、“节内容”)。因此,要准确知道某个元素能包含什么,最可靠的方法是查阅官方文档或参考资料(如课程讲义附录 A)。


一个重要的语法细节:引号

另一个关键的语法规则是关于引号的使用。

请注意下面两对引号的区别:
“Hello” (弯引号) 与 "Hello" (直引号)

弯引号(“ ”)和直引号(" ")是不同的 Unicode 字符。在 HTML 标签的属性值中,只允许使用直引号

<!-- 正确:使用直引号 -->
<a href="https://www.stanford.edu">Stanford</a>

<!-- 错误:使用弯引号(可能导致链接失效) -->
<a href=“https://www.stanford.edu”>Stanford</a>

弯引号通常由文字处理器(如 Microsoft Word)自动生成。因此,强烈建议使用纯文本编辑器或代码编辑器来编写 HTML,以避免引入这类不可见的语法错误。


验证你的 HTML

HTML 的语法规则看似很多,但底线是:在发布网页前,务必使用验证器进行检查

验证器(如 W3C Markup Validation Service)能够检测出各种语法问题,包括:

  • 使用了非法的弯引号。
  • 元素嵌套不符合 HTML5 规则。
  • 标签未正确闭合。

通过验证器运行你的代码,可以确保其语法正确,从而在不同浏览器中都能正常显示。


总结与预告

本节课中,我们一起学习了 HTML 的语法和词汇规则。我们了解到:

  1. HTML 的词汇量有限,核心元素数量适中。
  2. HTML 语法要求严格,标签必须正确嵌套和使用。
  3. 元素根据类别(文本级、块级)有不同的内容模型规则。
  4. 必须使用直引号来定义属性值。
  5. 使用验证器是保证 HTML 语法正确的关键步骤。

下一节课,我们将开始学习级联样式表。掌握了 CSS 之后,我们将能够为网页添加丰富的样式和进行各种有趣的格式化,让网页真正变得生动起来。

L8.1:CSS 简介 🎨

在本节课中,我们将要学习CSS(级联样式表)的基础知识。CSS是用于控制网页外观和布局的语言,它与HTML协同工作,将网页的内容(语义)与表现形式(样式)分离开来。

概述:为什么需要CSS?

上一节我们学习了如何使用HTML来构建网页的结构。你可能想知道,仅凭HTML是否足以制作出美观的网页?答案是否定的。实际上,要制作出酷炫有趣的网页,你需要掌握第二种语言:CSS,即级联样式表。

我们通过CSS将网页的语义信息(内容是什么)与表示信息(内容如何呈现)分离开来。HTML现在仅用于提供语义信息,而级联样式表则告诉我们如何呈现这些信息。

核心区别示例:

  • 语义信息<p> 标签告诉我们“这里有一个段落”。
  • 表示信息:我们希望这个段落有“一英寸的缩进”或“一个2像素宽的红色边框”。

类似地,对于一个项目列表,语义信息是“这是一个列表”,而表示信息可能是“我不想用项目符号,而想在每个项目前使用一个小斯坦福树徽标”。

将两者分离带来了极大的灵活性。例如,一篇2005年的新闻文章,其语义(标题、段落)不变,但我们可以通过更新CSS,轻松地将其外观从2005年的风格升级为2020年的设计。更重要的是,相同的语义内容可以根据访问设备(如台式机、平板电脑或智能手机)的不同,呈现出最合适的布局和样式。

CSS基础语法

CSS的基本结构由选择器声明块组成。

基本语法公式:

选择器 {
    属性1: 值1;
    属性2: 值2;
}

  • 选择器:用于“选择”网页上你想要样式化的HTML元素。
  • 声明块:包含在一对花括号 {} 中,里面是一条或多条声明
  • 声明:每条声明由一个属性和一个组成,中间用冒号 : 分隔,并以分号 ; 结尾。它定义了被选中元素的呈现方式。

例如,以下规则将所有 <h1> 标题的颜色设置为红色,并居中对齐:

h1 {
    color: red;
    text-align: center;
}

选择器详解

选择器是CSS的核心,它决定了样式规则将应用于哪些元素。以下是几种常见的选择器:

1. 类型选择器

类型选择器直接使用HTML元素标签名。它适用于网页上所有该类型的元素。

示例代码:

/* 将所有<h1>元素变为红色 */
h1 {
    color: red;
}

/* 同时将<h1>, <h2>, <h3>变为红色(用逗号分隔) */
h1, h2, h3 {
    color: red;
}

2. 后代选择器

后代选择器用于选择嵌套在某个元素内部的特定元素。选择器之间用空格分隔。

示例代码:

/* 只选择在<ol>(有序列表)内部的<li>(列表项) */
ol li {
    color: red;
}

这条规则不会影响无序列表 <ul> 中的 <li> 元素。

3. 类选择器

类选择器允许你为HTML元素添加一个“类”属性,然后通过CSS精确地定位具有该类的元素。在CSS中,类选择器以一个点 . 开头。

HTML代码示例:

<h3 class="important">盖茨</h3>
<p class="important">这是一个重要段落。</p>

CSS代码示例:

/* 将所有具有`class="important"`的元素变为绿色斜体 */
.important {
    color: green;
    font-style: italic;
}

![](https://github.com/OpenDocCN/cs-notes-pt2-zh/raw/master/docs/stf-cs105-intro/img/1e86f6367c46cbd2859025aaaea2ee77_40.png)

/* 组合选择器:只将具有`class="important"`的段落加上下划线 */
p.important {
    text-decoration: underline;
}

类可以被多个元素共享,非常灵活。

4. ID选择器

ID选择器与类选择器类似,但用于选择具有特定id属性的唯一元素。在CSS中,ID选择器以井号 # 开头。一个id值在整个网页中应该只使用一次。

HTML代码示例:

<h3 id="superior">斧头</h3>

CSS代码示例:

/* 将`id="superior"`的元素变为蓝色 */
#superior {
    color: blue;
}

ID选择器常用于标识网页中唯一的特定部分。

5. 通用容器:<span><div>

有时,我们想对一段没有特定HTML标签的文本(比如一个单词)应用样式,或者想将一组元素作为一个整体来样式化。这时就需要用到 <span><div> 标签。

  • <span>:是一个内联元素,用于包裹一小段文本或内联内容,本身没有视觉变化,主要用来添加样式。例如,将“Stanford”这个词变为红色:

    欢迎来到<span class="stanford">Stanford</span>大学!
    
    .stanford { color: red; }
    
  • <div>:是一个块级元素,用于将文档分割成独立的、可样式化的区块。它可以包含其他块级或内联元素,常用于页面布局。

    <div class="header">
        <h1>网站标题</h1>
        <p>网站描述</p>
    </div>
    
    .header { background-color: #f0f0f0; }
    

关键区别<span>是内联的,像<b>, <i>一样,不会打断内容流;<div>是块级的,像<p>, <h1>一样,通常会独占一行。

常用CSS属性简介

了解了如何选择元素后,我们来看看可以用哪些属性来改变它们的外观。以下是一些常用类别:

  • 字体属性:控制文本外观。
    • font-family: 字体系列(如 Arial, “Times New Roman”)。
    • font-style: 字体样式(如 italic 斜体)。
    • font-weight: 字体粗细(如 bold 粗体,或数值 700)。
  • 文本属性:控制文本布局。
    • text-align: 水平对齐(如 left, center, right)。
    • line-height: 行高。
    • text-decoration: 文本装饰(如 underline 下划线)。
  • 颜色与背景属性
    • color: 文本(前景)颜色。
    • background-color: 背景颜色。
    • background-image: 背景图像。
  • 盒模型属性:控制元素边框、内边距和外边距。
    • border: 边框(如 border: 2px solid red; 创建一个2像素宽的红色实线边框)。
    • padding: 内边距(内容与边框之间的空间)。
    • margin: 外边距(边框与外部元素之间的空间)。
  • 其他高级属性
    • float: 使元素向左或向右浮动,允许文本环绕它(常用于图片)。
    • position: 控制元素的定位方式(如相对定位、绝对定位)。

使用外部样式表

到目前为止,我们一直将CSS代码直接写在HTML文件的 <style> 标签内。这对于学习是方便的,但在实际项目中,最佳实践是使用外部样式表

为什么要使用外部样式表?
假设你的网站有“大型比赛”和“毕业典礼”两个页面,它们使用相同的设计风格。如果你将CSS内嵌在每个HTML文件中,当你需要修改样式时(比如把主色调从红色改为蓝色),就必须分别修改两个文件。这很容易出错且效率低下。

使用外部样式表,你可以将CSS规则保存在一个独立的 .css 文件中,然后在所有HTML文件中链接它。

操作步骤:

  1. 创建一个新文件,例如 style.css,将所有CSS规则粘贴进去(不要包含<style>标签本身)。
  2. 在HTML文件的 <head> 部分,移除 <style> 标签,改为使用 <link> 标签链接到外部CSS文件。

HTML链接代码示例:

<head>
    ...
    <link rel="stylesheet" href="style.css">
    ...
</head>
  • rel="stylesheet" 定义了当前文档与被链接文档之间的关系是“样式表”。
  • href="style.css" 指定了外部样式表文件的路径。

这样,任何链接了 style.css 的网页都会自动应用其中的样式。只需修改这一个CSS文件,所有相关网页的样式都会同步更新。

理解“级联”

“级联样式表”中的“级联”是什么意思?它指的是当多个CSS规则应用于同一个元素时,浏览器如何决定最终使用哪个规则。

核心原则:更具体的选择器优先级更高。

示例代码:

h3 { color: blue; }          /* 规则1:类型选择器 */
.important { color: yellow; } /* 规则2:类选择器 */
h3.important { color: red; }  /* 规则3:类型+类组合选择器 */

对于一个同时是 <h3>class="important" 的元素:

  • 规则1说它是蓝色。
  • 规则2说它是黄色。
  • 规则3说它是红色。

由于 h3.important 这个选择器比单独的 h3.important 都更具体(它同时指定了元素类型和类),所以最终这个元素会显示为红色

浏览器内部有一套计算选择器“特异性”的规则(通常涉及ID、类、元素类型的计数),特异性更高的规则会覆盖特异性较低的规则。这就是“级联”的含义——样式像瀑布一样层层下落,最终最具体的规则生效。

总结

本节课中我们一起学习了CSS的基础知识。我们了解到CSS负责网页的表现层,通过与HTML分离,极大地增强了设计的灵活性和可维护性。我们掌握了CSS的基本语法结构,学习了多种选择器(类型、后代、类、ID)来精确选取页面元素,并简要了解了字体、文本、颜色、盒模型等常用属性。我们还探讨了使用外部样式表的最佳实践,以及“级联”规则如何解决样式冲突。在接下来的课程中,我们将继续探索CSS的更多强大功能,如图片控制、布局技巧等,以创建更加丰富和响应式的网页。

L8.2:链接网页:制作链接 🔗

在本节课中,我们将学习网页制作的核心技术之一:创建超链接。我们将了解链接的基本原理、绝对引用与相对引用的区别,以及如何正确地在网页中实现它们。

概述

超链接是万维网(Web)的核心,它使得网页之间能够相互连接。本节课程将介绍如何在HTML中创建链接,重点讲解绝对引用和相对引用的概念、各自的优缺点以及适用场景。

链接的基本原理

上一节我们介绍了网页的基本结构,本节中我们来看看如何将不同的网页连接起来。链接是通过HTML中的 <a>(锚点)标签实现的。其基本语法是创建一个指向另一个网页的引用。

例如,一个指向 another.html 网页的链接代码如下:

<a href="another.html">访问另一个网页</a>

这段代码表示,点击“访问另一个网页”这段文本,浏览器就会请求并跳转到 another.html 这个文件。

绝对引用与相对引用

在指定链接目标时,我们有两种主要方式:绝对引用和相对引用。理解它们的区别至关重要。

绝对引用

绝对引用包含了完整的路径信息,从协议(如 http://https://)到服务器域名,再到具体的文件路径。

例如,一个指向斯坦福大学服务器上某个文件的绝对引用如下:

http://www.stanford.edu/another.html

这种引用方式明确指出了资源在互联网上的完整“地址”。

相对引用

相对引用则只指定相对于当前文件所在位置的目标文件路径。它省略了协议和服务器域名。

例如,如果 example.htmlanother.html 文件位于服务器的同一个文件夹下,链接可以简写为:

another.html

如果 another.html 位于当前目录下的一个名为 articles 的子文件夹中,引用则写为:

articles/another.html

在Web领域,无论底层操作系统如何,路径分隔符统一使用正斜杠 /

如何选择:绝对引用 vs. 相对引用

那么,在实际创建网站时,我们应该使用绝对引用还是相对引用呢?以下是它们各自的优缺点。

相对引用的优势

使用相对引用有很多优势,这是我们优先考虑使用它的主要原因。

1. 可移植性更强
相对引用的最大优点是便于移植。如果网站内所有链接都使用相对引用,那么整个网站文件夹可以轻松地移动到另一台服务器或另一个目录,而所有内部链接依然有效。

2. 代码更简洁
相对引用更短,减少了需要键入的字符数量。这不仅编写起来更方便,也使得HTML文件体积更小,理论上能略微减少带宽消耗并提升加载速度。

绝对引用的适用场景

我们使用绝对引用的主要原因,是在需要链接到另一台Web服务器上的资源时。例如,从你的个人网站链接到 nytimes.com 上的一篇文章,就必须使用绝对引用,因为相对引用只能在同一个服务器内部使用。

关于URL和默认文件的补充说明

你可能会注意到,很多URL(如 www.stanford.edu)并没有指定具体的文件名。这是因为Web服务器通常配置了一个默认文件(最常见的是 index.html)。当访问一个目录路径时,服务器会自动提供该目录下的默认文件。

此外,我们常说的URL(统一资源定位符)实际上是URI(统一资源标识符)的一个子集。除了常见的 http:https:,还有其他类型的URI,例如:

  • mailto:: 用于创建电子邮件链接。
  • ftp:: 用于文件传输协议。
  • file:: 用于指向本地计算机上的文件(注意:在网页开发中应避免使用,因为它完全不可移植)。

总结

本节课中我们一起学习了网页链接的创建。我们掌握了使用 <a> 标签制作链接的基本方法,深入理解了绝对引用相对引用的核心概念与区别。记住,在网站内部链接时,优先使用相对引用以获得更好的可移植性和简洁性;只有在链接到外部网站时,才必须使用绝对引用。同时,我们也要避免使用 file: 这类不可移植的URI。在下一个视频中,我们将学习如何设计链接的样式,例如改变它的颜色和外观。

L8.3:链接网页:格式化链接 🔗

概述

在本节课中,我们将学习如何格式化网页中的超链接。我们将重点介绍如何移除链接的下划线、如何改变链接的颜色,并引入一个强大的CSS概念——伪类,它允许我们根据用户与链接的交互状态(如是否访问过、是否正在点击)来应用不同的样式。


链接的默认样式与下划线

在上一节中,我们学习了如何使用 <a> 标签创建链接。本节中,我们来看看如何改变链接的默认外观。

默认情况下,链接通常带有蓝色和下划线。下划线由CSS的 text-decoration 属性控制。

如果你想移除链接的下划线,可以通过将 text-decoration 属性设置为 none 来实现。

代码示例:

a {
  text-decoration: none;
}

这段CSS规则表示:所有的锚点元素(即链接)都不显示下划线。

但需要注意,下划线是向用户表明“这是一个可点击链接”的重要视觉线索。移除下划线可能会降低链接的可供性,即用户可能无法立即识别出哪些文本是链接。因此,如果移除了下划线,应考虑用其他方式(如颜色、背景)来突出显示链接。

你也可以反其道而行之,为其他非链接文本添加下划线,但这可能会造成混淆,因为用户习惯性地认为带下划线的文本就是链接。


链接的颜色状态

链接的颜色通常会根据其状态发生变化。标准链接有三种主要状态颜色:

  1. 未访问链接:通常是蓝色。
  2. 已访问链接:通常是紫色。
  3. 活动链接:当鼠标按下(或手指触摸)但尚未释放时链接显示的颜色。

你可能会想为不同状态的链接设置不同的类,例如 .link.visited。但这种方法行不通,因为网页服务器无法为每个用户动态生成不同的HTML(它不知道某个用户是否访问过某个链接)。

解决方案是使用CSS伪类。伪类由浏览器动态应用,它根据链接的当前状态自动为元素添加一个虚拟的“类”。

以下是链接的三种核心伪类:

  • a:link:用于选择所有未被访问过的链接。
  • a:visited:用于选择所有已被访问过的链接。
  • a:active:用于选择正在被激活(例如鼠标按下时)的链接。

代码示例:

a:link {
  color: red; /* 未访问的链接显示为红色 */
}
a:visited {
  color: green; /* 已访问的链接显示为绿色 */
}
a:active {
  color: blue; /* 点击瞬间的链接显示为蓝色 */
}

请注意,伪类选择器前使用的是冒号 :,而不是类选择器使用的点 .。将 a 放在伪类前面是为了明确指出这些样式只应用于 <a> 标签,因为有些伪类(如 :active)也可能适用于按钮等其他元素。


其他有用的伪类

伪类的概念不仅限于链接,它还可以用于许多其他格式化场景,帮助我们选择特定条件下的元素。

以下是一些常用的伪类示例:

  • :first-child:选择父元素下的第一个子元素。
  • :last-child:选择父元素下的最后一个子元素。
  • :nth-child(n):选择父元素下的第n个子元素。

例如,你可以使用 :nth-child(odd):nth-child(even) 来为表格创建斑马条纹效果,使奇数行和偶数行具有不同的背景色,从而提高可读性。

代码示例:

tr:nth-child(odd) {
  background-color: lightgray;
}
tr:nth-child(even) {
  background-color: darkgray;
}

总结

本节课中,我们一起学习了如何格式化网页链接。我们掌握了如何通过 text-decoration 属性控制下划线,并理解了保持良好可供性的重要性。更重要的是,我们引入了伪类的概念,学会了使用 :link:visited:active 来根据链接状态设置不同颜色。最后,我们还了解到伪类可以广泛应用于其他元素,如 :nth-child 用于创建列表或表格的交替样式。这些工具能帮助我们创建更美观、交互更清晰的网页。

L9.1:创建网页:图像 🖼️

在本节课中,我们将学习如何向网页中添加图像。图像是网页设计的重要组成部分,它们能让网页内容更加生动和吸引人。我们将从最基本的图像标签开始,逐步探讨如何控制图像在页面上的位置、大小以及与其他元素的交互方式。

概述

在之前的课程中,我们学习了如何创建网页和添加超链接。虽然链接很重要,但如果网页只有文本会显得单调。因此,本节课程将重点介绍如何向网页中添加图像。我们将学习使用 <img> 标签,并探讨如何通过CSS样式来控制图像的布局和外观。

添加图像的基本方法

向网页中添加图像需要使用 <img> 标签。这是一个独立的标签,没有对应的结束标签。

以下是添加图像的基本语法:

<img src="venice.jpg" alt="威尼斯风景">
  • src 属性:这是“source”(源)的缩写,用于指定图像文件的路径。路径可以是相对路径(如 venice.jpg),也可以是绝对URL。
  • alt 属性:这是“alternative text”(替代文本)的缩写。它有两个主要作用:一是为使用屏幕阅读器的视障用户描述图像内容,提升网页可访问性;二是当图像无法加载时,会显示这段文本。为图像提供 alt 属性是编写合法HTML的要求。

图像与文本的默认布局

当我们简单地将 <img> 标签放入一段文本中时,图像会像一个大号的字符一样嵌入在行内。

例如,以下代码:

<p>从前<img src="big-o.png" alt="大字母O">有一个...</p>

在浏览器中,图像会与文本的基线对齐,并占据一行中的空间,就像文本的一部分。这通常不是我们想要的布局效果。

使用浮动控制图像位置

为了使文本能够环绕在图像周围,我们可以使用CSS的 float 属性。

以下是实现文本环绕图像的关键步骤:

  1. 为图像标签添加一个 idclass,以便通过CSS选择它。
  2. 在CSS中为该选择器设置 float: left;float: right; 属性。

例如,要让一张威尼斯图片浮动在左侧,CSS规则如下:

#venice {
    float: left;
}

重要提示:在HTML源代码中,<img> 标签必须放在你希望环绕它的文本内容之前。浏览器会按照源代码顺序处理元素,浮动元素之后的文本才会环绕它。

使用边距、边框和内边距

为了让图像与周围文本之间有适当的空间,或者为图像添加装饰,我们可以使用CSS的盒模型属性。

盒模型主要包括三个属性,它们围绕在元素内容周围:

  • margin (外边距):元素边框外部的透明区域,用于控制与其他元素的距离。
  • border (边框):围绕元素内容和内边距的线。
  • padding (内边距):元素内容与边框之间的透明区域。

我们可以为图像统一设置这些属性,也可以分别控制四个方向(上、右、下、左)。

例如,为图像添加一个蓝色边框和周围的空间:

#venice {
    float: left;
    margin: 15px; /* 四周的外边距均为15像素 */
    border: 10px solid blue; /* 10像素宽的实心蓝色边框 */
    padding: 10px; /* 边框与图片内容之间的内边距为10像素 */
}

关于边框的注意事项:设置边框时,必须指定 border-style(边框样式,如 soliddashed)。如果只设置了 border-widthborder-color 而没有设置 border-style,边框将不会显示,因为默认样式是 none

将图像作为独立块级元素显示

有时,你可能不希望文本环绕图像,而是希望图像单独占据一行,显示在段落之间。默认情况下,单独的 <img> 标签是行内元素,这可能会带来一些布局限制。

一个更好的方法是将图像包裹在一个 <div> 块级元素中,然后对这个 <div> 进行样式控制。

例如,要使图像在页面中居中显示:

<div id="venice-container">
    <img src="venice.jpg" alt="威尼斯风景">
</div>
#venice-container {
    text-align: center; /* 使div内的行内内容(如图像)居中 */
}

直接对 <img> 标签使用 text-align: center; 是无效的,因为该属性通常只对块级容器内的内容起作用。

图像的垂直对齐

当图像作为行内元素与文本混排时,我们可以使用 vertical-align 属性来调整图像相对于文本行的垂直位置。

常见的值包括:

  • baseline:默认值,与文本基线对齐。
  • middle:与文本的中线对齐。
  • top:与行中最高元素的顶部对齐。
  • bottom:与行中最低元素的底部对齐。

例如,将图像与文本垂直居中对齐:

#big-o {
    vertical-align: middle;
}

总结

在本节课中,我们一起学习了如何向网页中添加和美化图像。我们从最基本的 <img> 标签及其必要属性 (src, alt) 开始。接着,我们探讨了如何使用 float 属性实现文本环绕图像的经典布局,并学习了如何用 marginborderpadding 来控制图像周围的间距和外观。我们还了解了将图像放入 <div> 中以实现更灵活布局(如居中)的方法,以及使用 vertical-align 微调图像与文本的垂直对齐方式。掌握这些技巧,你就能创建出图文并茂、布局美观的网页了。

L9.2:创建网页:指定颜色 🎨

在本节课中,我们将学习如何在网页上指定颜色。我们将回顾颜色的基本原理,了解不同的颜色表示方法,并学习如何在HTML和CSS中实际应用它们。

概述

在网页设计中,颜色是至关重要的元素。本节课程将介绍如何在网页上指定颜色,包括使用十六进制、RGB、命名颜色以及透明度(Alpha)等不同方式。理解这些概念将帮助你创建更美观、更具吸引力的网页。

颜色的历史与基础

上一节我们介绍了十六进制数字,这对于在网页上使用颜色至关重要。计算机通过混合红、绿、蓝(RGB)三种颜色的光来生成屏幕上每个像素的颜色。当我们指定颜色时,实际上是在指定红、绿、蓝三种颜色的强度。

早期,网页设计面临颜色显示不一致的问题。当时,计算机显示器可能只支持256种颜色,而不同制造商无法就这256种颜色达成一致。最终,所有制造商都同意的只有211种颜色,这些颜色被称为“网络安全色”。不过,对于现代计算机显示器或移动设备而言,这已不再是问题,因为它们通常能显示1670万种或更多颜色。

命名颜色与CSS3

目前,我们有148种预定义的命名颜色。这些颜色有时被称为CSS3颜色,因为它们是级联样式表3(CSS3)标准的一部分。CSS3是CSS的一个扩展版本,包含了一套扩展的颜色关键字。此外,还有基于标量矢量图形(SVG)的颜色标准。

虽然命名颜色使用方便,但它们只提供了148种选择,远不及完整的1670万色调色板。因此,它们更适合快速原型设计或当需要特定知名颜色时使用。

十六进制颜色表示法

在网页上,颜色传统上使用十六进制数表示。回忆我们之前关于计算机使用24位或32位颜色表示法的讨论:颜色通过混合红、绿、蓝光来表示,每种颜色对应一个字节(8位),其十进制值范围是0到255,十六进制值范围是00到FF。

在网页代码中,颜色通常表示为以#开头的六位十六进制数。例如,深红色可以表示为 #DC143C。这六位数字中,前两位代表红色分量,中间两位代表绿色分量,最后两位代表蓝色分量。

理解十六进制表示法的关键在于能够调整这些数字。例如,如果你觉得 #DC143C 中的红色太多,你可以减少红色分量的值,比如改为 #AC143C,这样就会得到一个更暗的红色。同样,你可以调整绿色或蓝色的值来微调颜色。

透明度(Alpha通道)

除了红、绿、蓝,我们还可以指定第四个值来控制透明度,在计算机科学中通常称为Alpha通道。它表示不透明度(阻挡光线的程度)或透明度(允许光线通过的程度)。

在八位十六进制颜色表示中(如 #8C1515FF),最后两位 FF 代表Alpha值。FF 表示完全不透明,而 00 表示完全透明。通过调整这个值,例如从 FF 降到 80(大约50%不透明度),你可以让背景元素透过颜色显示出来,从而实现叠加效果。

简写的十六进制表示

为了书写简便,CSS也支持三位或四位十六进制颜色表示法。

  • 三位表示法:当六位十六进制数的每两位都相同时,可以简写为三位。例如,#FFCC00 可以简写为 #FC0。但请注意,这会将颜色精度从24位(1670万色)降低到12位(4096色)。
  • 四位表示法:在三位简写的基础上加上一位Alpha值。例如,#FFCC0080(50%不透明的橙色)可以简写为 #FC08

在网页中使用颜色

现在,我们来看看如何在网页中实际应用这些颜色规范。以下是在CSS样式表中指定颜色的几种常见方式。

以下是几种在CSS中指定颜色的方法示例:

  1. 使用命名颜色:直接使用预定义的颜色名称,如 wheat
  2. 使用十六进制:最传统的方式,如 #8c1515
  3. 使用RGB函数:可以避免直接使用十六进制。
    • 使用百分比:rgb(50%, 20%, 80%)
    • 使用0-255的十进制数:rgb(128, 0, 255)
  4. 使用RGBA函数(包含Alpha):在RGB基础上增加透明度,如 rgba(128, 0, 255, 0.5) 表示50%不透明度。
  5. 使用HSL/HSLA函数:使用色相、饱和度、亮度来定义颜色,这在选择配色方案时非常有用,我们将在后续课程中详细讨论。

总结

本节课中,我们一起学习了在网页上指定颜色的多种方法。我们从颜色的历史与计算机显示原理出发,了解了命名颜色、十六进制表示法、RGB表示法以及透明度(Alpha通道)的应用。掌握这些知识,特别是理解如何通过调整十六进制值来微调颜色,将使你能够更自信地在网页设计中运用色彩,创造出更丰富、更专业的视觉效果。

L9.3:十六进制 🔢

在本节课中,我们将要学习十六进制数字系统。我们将了解为什么计算机科学中广泛使用十六进制,它与二进制和十进制的关系,以及如何在不同进制之间进行转换。

概述

计算机内部的一切操作都是使用二进制完成的。然而,二进制表示法有时显得冗长且容易出错。因此,除了二进制和十进制,计算机还经常使用十六进制数字系统。在实际使用计算机时,你更可能看到十六进制而不是二进制,因为二进制真的很难直接阅读和使用。

什么是十六进制?

上一节我们介绍了二进制和十进制,本节中我们来看看十六进制。十六进制是基数为16的数字系统。我们之前已经看到,二进制是基数2,因此只有两个数字:0和1。十进制是基数10,所以有0到9这十个数字。十六进制则需要十六个数字,但我们没有现成的十六个独立数字符号。

为了解决这个问题,我们借用了字母A到F。

以下是十六进制中使用的所有数字:

  • 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
  • A (代表十进制10)
  • B (代表十进制11)
  • C (代表十进制12)
  • D (代表十进制13)
  • E (代表十进制14)
  • F (代表十进制15)

为什么使用十六进制?

考虑一个二进制数字序列,例如MIPS CPU的一条指令:00001000110011001100110011001100。将其视为二进制存在一些问题:它很长、笨拙,并且很容易在阅读或抄写时出错,例如意外地混淆0和1,或者漏掉某一位。

我们想要一种更紧凑的表示方式。当然,我们可以将其转换为十进制,这个二进制数等价于十进制数 21,710,880。这相当紧凑,但从二进制转换到十进制很麻烦,从十进制转换回二进制更是如此。

有许多与二进制相关的数字系统比十进制工作得更好,例如八进制和十六进制。它们之所以更好,是因为它们的基数是2的幂次方。

  • 八进制的基数是8,而 2³ = 8
  • 十六进制的基数是16,而 2⁴ = 16

基数8、16与基数2之间存在自然的数学关系,而基数10和2之间则没有。因为16是2的四次方,所以每4位二进制数恰好对应一个十六进制数字。这提供了一个自然的转换断点。相比之下,我们无法用十进制做到这一点,因为10不是2的整数次幂。

二进制到十六进制的转换

现在,让我们看看如何将二进制数转换为十六进制。这个过程比转换为十进制要简单快捷得多。

以下是将二进制数分解为四位一组并转换为十六进制数字的方法:

  1. 从右向左,将二进制数每四位分成一组。如果最左边一组不足四位,可以在前面补零。
  2. 参照转换表(或心算),将每一组四位二进制数转换为对应的十六进制数字。
  3. 将这些十六进制数字组合起来,就得到了最终的十六进制数。

为了方便转换,你可以使用下面这个表格:

四位二进制 十进制值 十六进制数字
0000 0 0
0001 1 1
0010 2 2
0011 3 3
0100 4 4
0101 5 5
0110 6 6
0111 7 7
1000 8 8
1001 9 9
1010 10 A
1011 11 B
1100 12 C
1101 13 D
1110 14 E
1111 15 F

实践示例:颜色代码

让我们用一个24位颜色值的例子来实践一下。斯坦福大学官方的主色调红色(Cardinal Red)的二进制表示如下:

10001100 00010101 00010101
(红色分量) (绿色分量) (蓝色分量)

我们将每个字节(8位)拆分成两个四位组:

  1. 红色分量 10001100
    • 第一组 1000 -> 查表为 8
    • 第二组 1100 -> 查表为 C
    • 所以红色分量的十六进制是 8C

  1. 绿色分量 00010101

    • 第一组 0001 -> 查表为 1
    • 第二组 0101 -> 查表为 5
    • 所以绿色分量的十六进制是 15
  2. 蓝色分量 00010101

    • 与绿色分量相同,所以是 15

因此,斯坦福红的十六进制颜色代码是 #8C1515。这正是你在网页设计或Photoshop等工具中会看到的表示方式。每个字节(8位)对应一对十六进制数字,这使得表示非常紧凑和规整。

十六进制的应用与识别

十六进制在计算机领域应用广泛。除了颜色代码,你还会在以下场景中遇到它:

  • 网络物理地址(MAC地址):形如 00-1A-2B-3C-4D-5E,用于唯一标识网络设备。
  • 内存地址:在调试或系统错误信息中,内存地址常以十六进制显示。
  • 机器码与调试信息:程序崩溃时产生的错误代码或内存转储经常包含十六进制数字。

如何识别一个数字是十六进制呢?有两个常见线索:

  1. 数字中包含了字母 A到F(大小写均可)。例如:1F3Ac0ffee
  2. 在编程和系统表示中,十六进制数常以前缀 0x 开头。例如:0x1F3A0xC0FFEE

需要注意的是,有时以单个 0 开头的数字表示八进制数(基数为8)。例如,012在八进制中代表十进制的10。

总结

本节课中我们一起学习了十六进制数字系统。我们了解到,由于二进制表示冗长易错,而十进制与二进制转换不便,因此计算机科学中引入了十六进制。十六进制基数为16,使用数字0-9和字母A-F表示。它的核心优势在于与二进制的天然对应关系:每四位二进制数可以直接转换为一位十六进制数。我们学习了通过“四位一组”查表法进行二进制到十六进制的转换,并探讨了十六进制在颜色代码、网络地址、内存寻址等领域的实际应用,以及如何通过包含A-F字母或0x前缀来识别十六进制数。掌握十六进制将帮助你更轻松地理解计算机底层的数据表示。

L9.4:网页示例教程

概述

在本节课中,我们将通过一个综合性的网页示例,将之前学到的HTML与CSS知识整合在一起。我们将探讨如何选择合适的选择器、决定样式表的位置、使用<div>进行布局,并学习如何让网页在浏览器中居中显示。


核心概念与样式设置

我们将首先介绍本示例涉及的关键主题。您可能会疑惑如何决定使用类型选择器、类选择器还是ID选择器。我们将在实际操作中看到它们的应用。

您可能还想知道应该使用外部样式表还是内部样式表。我们将看到两者的示例。

在实际操作中,我们还将了解如何以各种不同的方式使用<div>元素。

您会注意到此特定网页位于Web浏览器的中心,我们将向您展示如何实现这一点。

设置标题字体

我们要做的第一件事是将所有标题的字体设置为无衬线体(sans-serif)。

我们将在本季度晚些时候讨论网页设计时详细讨论无衬线体,但目前您可以看到顶部有衬线的标准外观和底部无衬线外观之间的区别。底部看起来更干净一些。

我们想要应用到所有标题的设计选择,这意味着我们需要使用类型选择器。

以下是我们使用的类型选择器代码,它将字体系列设置为无衬线体:

h1, h2, h3 {
  font-family: sans-serif;
}

内部与外部样式表的选择

接下来的问题是,这个样式应该放在内部还是外部样式表中?

考虑内部与外部的方式是:这是一种您可能希望在整个网站中为所有网页使用的特定样式,还是仅针对当前这个网页的特定样式?

现在,我们可能希望网站上的所有标题看起来都一样,所以这应该放在一个外部样式表中。


具体元素样式设计

主标题(h1)样式

如果您看那个<h1>,您会注意到<h1>是页面顶部的最大尺寸标题,通常位于页面顶部。您会看到它居中,我已经更改了颜色。它原本是灰色的,但很单调。我已经更改了颜色,它们现在都是灰色的。

我是这样做的:我将背景颜色设置为灰色,将前景(文字)颜色设置为白色。我还将文本对齐设置为居中。这就是居中的方式。我已将字体大小设置为我选择的特定值,而不是使用Web浏览器的默认设置。我还将内边距(padding)设置为10像素。

让我们仔细看看此处的10像素。将padding设置为10像素与将margin设置为10像素有什么区别?

这里我们只关注该标题的中心,我们可以看到差异。请注意顶部,我们实际注意到文字上方和下方有很多灰色。而底部设置只是将margin设置为10像素,而不是10像素的padding,结果是一个更薄的灰色边框。

这里发生的事情是:背景颜色基于padding。所以当我们将padding设置为10像素时,背景颜色包含并覆盖了那10个像素。如果我们将margin设置为10像素,这是边框外的内容,我们实际上并没有在这里绘制边框,但基本上背景颜色不会覆盖边框外的任何内容,包括margin。这就是为什么它看起来不同。

假设我们希望这个<h1>样式应用于我们网站上的所有网页,我们可能会这样做,将它放在一个外部样式表中。

其他标题样式

我们已经完成了这里的标题设置。<h2>在它们的上方和下方都有线条,所以我们用text-decoration来设置它。我们已经将<h3>设置为斜体。我们再次希望在我们所有的网页上都这样,所以使用外部样式表。

署名部分样式

如果您看左下角,您会看到一个很小的文本,在实际的网页上有点难以阅读。它仍然很小,但如果我们放大它是可读的。

这归功于我实际获取文本的地方。照片是我的,但该文本是从维基百科中获取的。

首先有几个问题要问:我应该为此使用<div>,还是应该使用一个段落(<p>),还是应该使用其他东西?我应该为此使用一个类还是一个ID?

在这种特殊情况下,我认为没有正确的答案。我使用了一个段落,但是如果您想使用一个<div>,也可以。

我给它一个ID,因为我假设网页上只有一个署名。但如果您认为可能想在其他地方也使用这个署名样式设置,也许您想在照片上加上署名,那么设置这个为一个类可能是有意义的。

您可以看到,对于这个特定的ID,我将字体大小设置为超小,并且我还将字体系列设置为无衬线体。

因此,正文的文本除了标题和其他一些地方是衬线体,但特别是标题和此署名部分是无衬线体。


图像与标题的组合处理

让我们回到这里的大局。正如我提到的,主要内容我们将要讨论的主要主题是这些图像。所以让我们仔细看看在这些图像中。

如果您查看图像,您会注意到实际上有图像,并且在其下方有一个标题。图像和标题周围有一个边框。标题居中且为斜体。所以我们想要弄清楚如何实现所有这些。

创建图像与标题组合

我要做的第一件事是,我想将图像和标题视为一个组。所以我将继续创建一个<div>,并将<img>标签和该标题放在那个<div>中。

我希望这些样式元素对于所有图像都是相同的,所以我将继续创建一个类。如果我们只希望样式应用于单个图像,我们可以给它一个ID。但是如果我们认为这将是我们想要应用于多张图像的东西,请继续并给它一个类。

这是我在这里的初步方案。您可以看到我已经创建了一个<div>,我已经给了一个类photo。然后我也想在标题上做一些额外的样式,所以我给了一个类caption。我想对所有不同的标题使用相同的样式信息,所以我想给它一个类。我也在每个图像的<img>标签中设置了高度和宽度。

实际上是这样:当网页加载时,这里将要发生的是,HTML文件将首先被接收。HTML文件可能会很好地引用外部样式表,它也会引用一堆图像。在Web浏览器等待样式表下载时,特别是在等待这些图像下载时,它不一定知道图像有多大。因此会发生的情况是,您会得到一个邮票大小的图标,显示图像应该出现的位置,然后当它弄清楚这些图像实际上有多大时,它会调整网页的大小。

通过在<img>标签上放置一个特定的宽度,我告诉网络浏览器继续并为该图像保留空间。所以我们会提前为图像创建一个与我指定的空间一样大的空白区域。当图像最终下载时,它将继续并将其填充到该空间中。

您不必在<img>上指定,您可以在样式表上指定它。即在下载级联样式表之前,无论如何它不会知道如何布置网页。我们将在讲座中广泛讨论如何布置网页。

图像样式规则

以下是我将用于照片和标题的规则。您可以看到我在照片周围放置了边框并添加了一些内边距(padding)。然后我告诉标题它应该文本居中(text-align: center)并且字体样式应该是斜体(font-style: italic)。

这适用于我们这里的小图像。这是我的老狗Karen Terry Molly,这是在宿舍员工会议的中间,没人注意她正试图偷偷摸摸在我们都在说话时把零食放在桌子上。所以这对那个完全没问题。

处理标题宽度问题

但是在另一个图像上,这是我们希望另一个图像显示的样子,但事实证明它实际上不起作用。这就是发生的事情。所以,发生在这里为什么它在一个图像上起作用而在另一个上不起作用?

这里发生的是图像具有特定的宽度,但文本没有。发生的事情是,因为文本没有自然宽度,除了您知道的句子或段落长度,它将扩大以占据尽可能多的可用空间。所以“两个月的凯伦特里小狗从刚刚展开的外壳中逃脱”这段文字说,我将占用尽可能多的空间,直到我可以出现在一行上,或直到我填满整个网络浏览器窗口。这是一个问题。

所以我们的解决方案是我们将在该标题上提供特定的宽度。

类、ID与样式表位置的选择

所以让我们回到我们的问题:这里是否应该将其指定为类或ID,以及这应该是内部还是外部样式表?

我认为这里的每个图像可能有不同的宽度,所以这不是我所有图像的共同点。现在您可能正在运行某种报纸,并且您只有几个标准宽度的图像,所以在这种情况下,您可能会使用一个类。但在这种特殊情况下,我认为我的所有图像可能是一个随机宽度。

所以,我将继续为这个图像创建一个规则,所以这意味着我给它一个ID。然后就内部与外部而言,如果这是多个网页中的共同点,正如我所说的,我假设我的宽度将从一个图像到另一个不同,并且仅针对此图像,因此没有理由将其放在外部样式表中,该样式表将被我网站上的所有其他网页下载。这是特定于该图像的。

所以,我要继续并提供一个宽度。我将把它放在内部样式表中。所以您知道有我的样式开始/停止标签,这是在内部样式表中。

您可以看到我实际上将把宽度放在标题上,但我将把ID放在周围的<div>上。这里的想法是,您知道可能会有其他一些特定于这个、这个特定的图像标题的设置,所以通过在整个<div>上放置ID,我获得更多的灵活性。

这个特殊的规则结合了:它需要在转义照片中的ID,然后是空格,然后是.caption。您记得这实际上是一个后代选择器,它说:这将适用于任何包含在带有ID escape-photo的元素中,并且在该escape-photo <div>中的某个位置,如果该escape-photo <div>中还有其他一些元素,该元素具有类caption,这将适用于它。

所以我继续说,适合它的标题,或者如果有任何其他符合此特定规则的标题,在这种情况下没有。宽度为200像素。

然后我想您知道我们可能想在某个时候更改另一张照片上的标题,所以我也可以继续并为此指定宽度。这是很常见的做法,因为您是网站设计师,您是网页设计师,其他人可能会更改网页的内容,在另一个时间。所以您现在应该考虑更长远的想法。

现在下面的图像,另一张照片下面的标题不够长,不足以引起问题,但稍后可以由文本编辑器更改,他们可能不明白如何修复HTML/CSS问题,以便更好地继续并立即指定两个。


页面布局与浮动

我们在左侧和右侧都有照片。这似乎是我们希望在所有网页上都有的东西,因此建议它应该是一个类。

所以我已经继续并在我的<div>上指定它。在这里我有一张right-photo和一张left-photo类。注意该类实际上指定它既是我们之前看到的规则的photo,也是right-photoleft-photo

我之前并没有真正提到这一点,但是您可以在标签中的class属性值对中列出多个类。所以我认为这有点奇怪,我认为它们应该用逗号分隔,但是您没有列出您想要的尽可能多的类,只是用空格分隔它们。

以下是left-photoright-photo的规则:

.left-photo {
  float: left;
  margin-right: 10px;
}

.right-photo {
  float: right;
  margin-left: 10px;
}

基本上我向左浮动或向右浮动,然后我专门设置左边或右边的边距。基本上边距位于它浮动位置的另一侧。

所以这里的想法是:如果向左浮动,它会向左对齐,然后文本在右侧流动。我想要一点照片和文字之间的边距。或者,如果照片向右浮动,我希望它在网页的右侧对齐,在左侧文字与文字并排流动的地方之间有一点边距。

使网页居中

您还会注意到网页在网络浏览器中居中,这是很常见的。通常您要做的是添加一个额外的<div>,将绝对包围正文中的所有内容。

所以您可以看到我有外部<body>标签,然后在<body>标签里面我有一个<div>,我已经给了它ID。网页中的所有内容都在那个内部<div>中。

然后我要做的是,我要为那个内部<div>写一个规则。这里是我说,哦,这是所有人,我给了它一个特定的宽度。我说整个内容应该填充一千像素,然后我将边距设置为自动(margin: auto)。

margin: auto允许网络浏览器选择边距。就其本身而言,通常对左边距和右边距所做的,就是将左右边距平均化,并将项目放在中心。这样就可以给我们这个很好的效果。

另一个有时用于这些外部<div>的东西是,如果您想在整个网页周围放一个框,您可以继续并在它周围放一个边框,您会得到一个漂亮的小框。


HTML5语义元素简介

这就是最后一个我想提的是,这种<div>的使用是超级灵活的。我们将在下一节课再看一遍,您可以在下一个家庭作业中玩一点儿。

但我确实想为更高级的学生提一下,那些计划真正开始创建真实的学生网站,特别是如果您打算在专业环境中执行此操作,您应该知道HTML5添加了一些新标签,可以用来代替这些<div>。这些几乎与我在这里创建的<div>做同样的事情。

<div>用于标题,用于图像和标题的<div>,它们本身通常没有任何样式信息,但它们用于告诉Web浏览器有关此标签应该是什么的语义信息。这些对于支持有可访问性问题的观众非常有帮助。

特别是如果有人看不到,他们正在使用的称为屏幕阅读器的工具,网络浏览器正在阅读内容。这些语义标签对于让那些读者知道发生了什么非常有帮助。

所以在这种特殊情况下,有一个<figure>和一个<figcaption>标签。所以这些可以替换我的两个<div>:标题的<div>(显然我说class="caption")可以用<figcaption>替换,然后用图片替换外面的照片<div>可以用<figure>替换。

然后这里是其他一些语义元素的列表,我之前提到过一些。我认为所以有一篇文章(<article>)、一个方面(<aside>)有点像,如果您看过那些教科书,那里有一种蓝色盒子。您可以在CS105课程阅读器的一些章节中看到我的蹩脚版本,我只是标记“嘿,这是应该去网站”,但我实际上并没有将它放在外部框。

<details><summary><time>。然后这些实际上可能会为网络浏览器做一些事情,比如这里的导航(<nav>)底部。这对那些无法直接阅读您的网页,或让网页阅读给他们的人非常有帮助。

因此,<nav>指示一个部分实际上是导航部分。因此,如果您了解那些我们稍后会讨论的通用设计,在您有侧边栏或顶部导航栏的地方,您可以说:“嘿,这里有一些其他元素,这里是我网站上的其他一些网页,您可以通过将导航放在那里点击。”您可以让网络浏览器知道:“嘿,在访问我的网页的人之前,不要向访问我的网页的人阅读此部分,直到他们明确说‘好的,我已经听完了网页的内容是什么,我的导航选项’。”然后网络浏览器可以像“哦,我看到你有那个导航部分,现在是我要继续向我的观众阅读导航部分的地方。”

然后有一个页眉(<header>)和一个页脚(<footer>)。由于顶部和底部的某些特殊内容,我可能不会使用这些页眉和页脚,认为它实际上用于任何事情。现在,它只是一个您可以使用的另一个标签,您可以用同样的方式给样式规则,我给我的<div>加上class="photo"和我的<div>加上caption

所以这些都是不同的选项。再次通过提供多一点语义信息,您会让网络浏览器更容易告诉您您想要做什么。您也可能让人们更容易理解,就像您创建一个网页设计。让我们说您,这实际上发生在我的一些学生,您去一些公司,让我们看看您和一群朋友开始一个新的非营利组织,没有人知道如何制作网页,您就是那个会做的人。所以您去制作网页,但您可能希望其他人能够修改该网页。因此通过使用这些特殊标签,您可以让人们知道您的HTML文件的不同部分将用于什么,为您为他们提供的更多内容提供指导,而不仅仅是有一堆<div>


总结

在本节课中,我们一起学习了一个综合性网页示例的实现。我们探讨了选择器的应用(类型、类、ID)、内部与外部样式表的决策、使用<div>进行分组和布局的技巧,以及如何使网页内容在浏览器中居中。我们还简要介绍了HTML5的语义化标签及其在可访问性和代码可维护性方面的优势。这些知识将帮助我们创建结构更清晰、更易于维护的网页。

L10.1:创建网页:添加表格 📊

在本节课中,我们将学习如何在网页中添加表格。表格是组织和展示数据(如时间表、目录或得分表)的强大工具。我们将从基础的HTML表格标签开始,逐步学习如何通过CSS样式美化表格,并掌握一些高级技巧,如合并单元格。

概述 📋

表格在网页设计中应用广泛,例如展示表演时间表、志愿者目录或比赛得分。HTML提供了专门的标签来创建表格结构,而CSS则用于控制其外观。本节将详细介绍如何使用<table><tr><td><th>标签构建表格,并通过CSS添加边框、调整对齐方式和实现斑马条纹等效果。

创建基础表格结构

上一节我们介绍了网页的基本结构,本节中我们来看看如何创建表格。HTML使用特定的标签来定义表格,其核心思想是按行输入数据。

以下是创建表格所需的基本HTML标签:

  • <table>:定义整个表格的容器。
  • <tr>:定义表格中的一行。
  • <td>:定义表格中的一个标准单元格,用于存放数据。
  • <th>:定义表格中的一个标题单元格,通常用于列或行的标题。浏览器默认会将其内容加粗并居中显示。

例如,一个简单的比赛得分表格的HTML结构如下:

<table>
  <tr>
    <th>Stanford</th>
    <th>California</th>
  </tr>
  <tr>
    <td>21</td>
    <td>3</td>
  </tr>
</table>

使用CSS为表格添加样式

仅使用HTML创建的表格缺乏视觉区分,看起来并不美观。因此,我们需要使用CSS来为其添加样式,例如边框和间距。

以下是常用的表格样式属性:

  • border:为单元格添加边框。需要同时指定宽度、样式和颜色,例如 border: 1px solid black;
  • padding:在单元格内容与边框之间添加内边距,使内容更易读,例如 padding: 5px;
  • border-collapse:这是一个应用于<table>元素的属性。将其值设置为collapse可以合并相邻单元格的边框,使表格看起来更整洁。

为之前的基础表格添加样式的CSS规则如下:

table {
  border-collapse: collapse;
}
td, th {
  border: 1px solid black;
  padding: 5px;
}

调整单元格对齐与尺寸

为了使表格布局更符合需求,我们经常需要调整单元格内文本的对齐方式以及单元格本身的宽度和高度。

以下是控制单元格外观的CSS属性:

  • text-align:控制单元格内文本的水平对齐方式,可选值有 leftcenterright
  • vertical-align:控制单元格内文本的垂直对齐方式,可选值有 topmiddlebottom
  • widthheight:直接设置单元格的宽度和高度。

例如,我们可以让“Stanford”标题左对齐,并设置特定的列宽:

th.stanford {
  text-align: left;
  width: 100px;
}

合并单元格:colspanrowspan

有时,一个单元格需要横跨多列或多行,这时就需要使用colspanrowspan属性。这两个是HTML属性,直接写在<td><th>标签内。

以下是合并单元格的方法:

  • colspan=”数值”:让一个单元格跨越指定的列数。
  • rowspan=”数值”:让一个单元格跨越指定的行数。

例如,一个标题需要横跨三列:

<th colspan=”3”>地理起源</th>

再例如,一个单元格需要纵跨两行,以消除多余的边框线:

<th rowspan=”2”>类别</th>
<!-- 注意:下一行的对应位置不需要再写单元格 -->

高级美化技巧:斑马条纹

为了让长表格更易阅读,一种常见的技巧是交替改变行的背景色,即“斑马条纹”效果。这可以通过CSS的伪类选择器 :nth-child() 轻松实现。

以下是实现斑马条纹的CSS代码:

tr:nth-child(even) {
  background-color: #f2f2f2;
}

这段代码会为所有偶数行(even)添加一个浅灰色的背景。你也可以使用 odd 来选中奇数行。

总结 🎯

本节课中我们一起学习了如何在网页中创建和美化表格。我们首先使用<table><tr><td><th>标签构建了表格的基本骨架。接着,我们通过CSS添加边框、内边距并合并边框,使表格结构清晰。然后,我们学习了如何调整文本对齐和单元格尺寸以优化布局,并利用colspanrowspan属性合并单元格。最后,我们还介绍了使用:nth-child()伪类创建斑马条纹的高级技巧。掌握这些知识,你将能够创建出功能清晰、视觉美观的网页表格。

L10.2- 网页示例:博客 📝

概述

在本节课中,我们将学习如何构建一个博客网页。我们将通过一个具体的示例,了解如何使用HTML和CSS来结构化内容、应用样式,并探索“类”在网页设计中的强大作用。我们还将简要介绍“微格式”的概念,看看如何通过添加特定的类来为网页内容赋予额外的语义信息。


历史博客示例

在上一节中,我们看到了“类”在多个地方的使用。我们创建了用于标题的类、用于照片的类,以及一个单独的用于照片描述的类。这种为div或其他元素提供“类”的想法非常强大,并且经常出现在博客设计中。

以下是我构建的一个关于斯坦福历史的博客示例。我已经对其进行了初步的格式化。实际上,在接下来的作业中,你将有机会对这个文件进行一些格式化练习。

我们将为你提供此文件的HTML代码,但不提供CSS。我们希望你能从头开始创建CSS。现在,让我们回顾一下我使用的CSS,以便获得当前的外观。

博客的结构

在博客上,通常有一系列条目。每个条目都包含以下部分:

  • 一个带有标题的标题区域。
  • 一个日期。
  • 内容主体(我这里没有显示页脚,但我们会在源代码中看到实际上有一个页脚)。
  • 可以为每个博客条目显示的一系列标签。

这些都是博客中常见的不同元素。如果你经常查看博客的源代码,你会发现他们完成了一些类似的项目:其中一些可能是<p>段落,一些可能是<div>,一些实际上可能是标题<h2>。他们通常会为这些元素添加“类”。

源代码结构

我的博客源代码看起来是这样的:每个条目都有一个类为entrydiv。在这个div内部,有:

  • 一个类为entry-title的标题(包含标题和日期)。
  • 一个类为content的内容区域。
  • 一个类为entry-footer的页脚。
  • 一个类为tags的标签区域。

在标题示例的结尾,我们提到了一些新的HTML5元素(如<article><figure><figcaption>)。这些元素本身不提供任何格式,但可用于提供语义信息。例如,你可以用<article>这个HTML5元素替换这里的class="entry"

然而,当我查看一些实际在线的博客时,并没有看到它们使用这些新技术。可能是因为这些是较旧的博客,或者现代博客软件尚未更新以使用这些元素。但无论如何,我查看的博客看起来都非常接近我们在这里看到的内容。因此,我们将继续使用这种基于“类”的结构。


如何格式化博客

我认为需要记住的核心思想是:我们有一堆<div>元素(在一种情况下我们实际上有一个<h2>),我们通过为它们添加class属性来对它们进行分类。正如我所说,这种做法一直出现。接下来,在我们完成对每个元素的格式设置时,我会指出各种不同的格式功能,这些可能对你自己工作有用。

格式化条目标题

让我们从条目标题开始。你可以看到标题是“Leland Stanford Purchases Land”或“University Founded”等。这些是我的<h2>元素,但我同时也给了它们class="entry-title"

我选择为.entry-title类编写CSS规则,而不是直接为h2选择器编写。这是因为网页上可能还有其他h2元素(例如侧边栏或广告中的h2),我不希望样式影响到它们。

以下是我应用的CSS规则:

.entry-title {
    font-size: 24px;
    font-weight: bold;
    color: #333;
    font-family: "Times New Roman", Times, serif;
}

这里有一个我们之前没详细讨论的有趣属性:font-family。注意我实际上列出了三个字体系列。当你列出多个字体时,浏览器会按顺序查找每个字体,一旦找到用户计算机上安装的字体,就会使用它。因此,你可以从你最希望使用的字体开始列出,最后以通用字体族(如serifsans-serif)结束,以确保总有可用的字体。

格式化条目日期

如果你查看右侧的图像,可以看到日期显示在最右侧,紧挨着实际的博客条目。我是通过使用float属性来实现这一点的。

.entry-date {
    float: right;
}

在标题示例中,我提到过在浮动文本时应小心设置宽度。但在这个例子中,由于日期实际上是年份,永远不会超过四个字符,所以这不是问题。如果浮动的元素是较长的文本,你通常需要为其设置一个宽度,以防止它扩展到整个网页的宽度。

添加入口间的分隔线

你可以看到每个条目的上方和下方都有红线穿过。我是如何做到的呢?

  1. 我为每个.entry元素添加了一个顶部边框:border-top: 2px solid red;
  2. 我在每个元素上方添加了一点额外的填充和边距。注意,paddingmargin可以接受多个值。例如,margin: 10px 5px;表示上下边距为10px,左右边距为5px。
  3. 我只设置了顶部边框,那么如何获得底部边框呢?有几种方法可以做到这一点。最常见的技术是:为最后一个项目添加一个类(例如last),然后编写一个仅适用于具有last类的项目的规则,为其添加底部边框。
.last {
    border-bottom: 2px solid red;
}

这样,所有条目都有顶部边框,只有最后一个条目有底部边框,从而得到了我们看到的四行效果(条目之间的三行,以及最下面的第四行)。

还有其他方法可以实现,例如使用伪类。我们在谈论伪类时简要提到过,比如:last-child:last-of-type。例如,我可以写一个规则.entry:last-of-type { border-bottom: ... }来达到类似效果。

处理页脚和标签

最初我没有页脚,但当我决定把它变成作业时,我想添加一些更有趣的东西供你们练习。所以我添加了一个页脚,但我不太确定如何处理它,所以我“作弊”了。我想向你展示这个使元素“消失”的小技巧。

有两种不同的技术可以使项目消失:

  1. display: none;:这会完全从文档流中移除该元素,就像它不存在一样。
    .entry-footer {
        display: none;
    }
    
  2. visibility: hidden;:这做了一些不同的事情。元素所在的空间仍然保留,但元素本身不会显示。

在这个例子中,我使用了display: none;来隐藏页脚,这样你们在作业中就可以自由发挥。如果使用visibility: hidden;,你会在每个条目的底部看到为这些entry-footer div保留的空白区域。

居中整个内容区域

我用一个<div>包围了整个博客内容,并给了它一个id(例如#main)。然后我使用margin: 0 auto;来使其水平居中。

#main {
    margin: 0 auto;
    width: 800px; /* 通常需要指定一个宽度 */
}

这个想法是:将一堆元素添加到一个容器中,然后设置容器的左右外边距为auto,这被证明是实现居中的一种非常强大的方法。


微格式简介

这里的核心思想是:HTML不一定有我们认为可能有趣的所有东西的标签。如果没有可用的特定标签,我们可以继续使用<div>或其他标签之一,并给它一个特定的“类”,从而添加我们自己的语义标签。有一群人推广并运行这种想法,那就是“微格式”。

我们要看两种微格式。

1. hReview(评论微格式)

假设我想去看电影(遗憾的是现在不能),我听说《复仇者联盟:终局之战》上映了。我在谷歌搜索“Avengers Endgame review”,可以看到一些评论在谷歌结果中显示了星级评分。谷歌是怎么知道这些是评论并且能显示星级的呢?

谷歌支持一种叫做 hReview 的微格式。网站可以在其HTML中使用特定的类(如class="hreview"class="rating")来标记评论内容和评分。谷歌等工具可以识别这些类,从而提取信息并在搜索结果中显示星级。

2. hCard(电子名片微格式)

另一种经常出现的微格式是 vCardhCard 格式,用于虚拟名片。人们经常有名片,而hCard背后的想法是,你可以将此类联系信息放在你的网站上。

但是,如果你希望人们能够提取该信息并将其存储在单独的程序(如通讯录)中,仅仅将信息放在网站上是不够的。如果你在网站上使用这种特定技术(即添加特定的类,如class="street-address"class="tel"),那么旨在读取电子名片的工具就可以通过搜索HTML寻找这些特定类,来提取信息并填入数据库。

例如,虽然没有<street-address>这个HTML标签,但通过在一个<span>上放置class="street-address",我就让任何使用电子名片的工具知道:“这是我的街道地址”,它可以继续从那个标签中提取信息“169 University Ave”。

类似地,可以有class="postal-code"(邮政编码)、class="country-name"(国家名称)、class="tel"(电话)、class="email"(电子邮件)。这些都是没有直接关联的HTML标签的信息,但通过在上面放置类,我可以提供分类信息。一个旨在阅读网页的程序可以继续选择该信息并处理它。


总结

本节课中,我们一起学习了如何构建和格式化一个博客网页。我们深入探讨了使用“类”来组织和样式化HTML元素,包括设置标题、日期、边框以及实现布局技巧如浮动和居中。我们还初步了解了“微格式”的概念,看到了如何通过添加特定的类(如hreviewhcard相关的类)来为网页内容赋予额外的语义,从而使机器能够更好地理解和处理信息。这些技术是构建现代、结构化、语义化网页的基础。

L10.3:创建网页:使用 Web 服务器 🚀

在本节课中,我们将学习如何将本地创建的网页文件部署到真正的 Web 服务器上,使其能够被互联网上的其他人访问。我们将介绍从选择托管服务到文件传输、测试及问题排查的完整流程。


概述

到目前为止,我们已经讨论了如何编写 HTML 和 CSS 文件。然而,我们尚未讨论如何将这些文件发布到 Web 上。虽然本课程作业通常不需要此步骤,但了解如何创建真正的网页至关重要。


本地开发与 Web 服务器的区别

上一节我们介绍了如何编写和测试网页文件。本节中我们来看看如何将它们发布到互联网上。

在课程作业中,我们一直在本地笔记本电脑上创建文件,并直接在本地浏览器中打开它们。这利用了 Web 浏览器的渲染和显示功能,但跳过了从服务器请求文件的过程。

为了让其他人能够访问我们的网页,我们需要将文件放置在 Web 服务器上。服务器可以将这些文件发送给许多不同的访问者。

因此,我们需要逆转之前的过程:将 HTML、CSS、图像等所有与网页相关的文件,从本地开发环境转移到 Web 服务器上。


部署网页的核心步骤

以下是部署网页到 Web 服务器需要完成的主要步骤:

  1. 获取网络托管服务:需要一个实际的服务器来存放文件。
  2. 在本地创建和测试文件:确保所有文件在本地运行正常。
  3. 选择文件传输软件:使用工具将文件上传到服务器。
  4. 确定服务器上的文件位置:知道文件应该放在服务器的哪个目录。
  5. 传输文件:执行上传操作。
  6. 在线验证:确保所有文件在线上正常工作。

步骤一:选择网络托管服务

首先,你需要一个网络托管服务。例如:

  • 斯坦福大学为学生提供托管服务,每个学生都有一个可通过 web.stanford.edu 访问的目录。
  • 许多互联网服务提供商也提供基本的网络托管。
  • 此外,还有众多公司提供网络托管服务,以及像亚马逊 AWS 这样的专业级服务。

步骤二:本地测试文件

在将文件上传到公共服务器之前,务必在本地进行充分测试。直接在线上服务器测试可能导致访问者看到错误的内容。

更专业的做法是使用单独的测试服务器或服务器上的测试目录进行预发布检查。


步骤三:选择文件传输软件

通常,你需要一个支持 SFTP 的文件传输程序。FTP 是其早期版本,但建议使用更安全的 SFTP。

以下是几个 SFTP 客户端示例:

  • SecureFX(适用于 Windows)
  • Fetch(适用于 Mac)

斯坦福大学的学生和教职工可以免费获取这些软件。你也可以使用 Web 界面(如 afs.stanford.edu)上传文件,这对于简单任务可能更方便。


步骤四:确定服务器文件位置

你需要知道将文件上传到服务器的哪个目录。

以斯坦福的托管服务为例:

  • 每个学生都有一个名为 www 的目录。
  • 放在 www 目录中的文件是公开可访问的。
  • 访问网址格式为:http://web.stanford.edu/~你的SUNetID/
  • 默认会加载 index.html 文件。

如果你使用其他托管服务,服务提供商会告知你应使用的目录。


步骤五:传输文件

使用 SFTP 客户端连接服务器后,界面通常会分为左右两栏,分别显示本地和服务器端的文件。

传输文件的方法通常很简单:

  • 将文件或文件夹从本地(左侧)拖拽到服务器目录(右侧)。
  • 你可以传输单个文件,也可以传输整个包含嵌套结构的文件夹。

步骤六:在线检查与问题排查

文件传输完成后,必须仔细检查网站在线状态是否正常。以下是一些常见问题及解决方法:

常见 HTTP 错误

  • 404 错误:文件未找到。可能原因是文件路径错误或文件名拼写错误。
  • 403 错误:权限禁止。通常是因为目录权限设置不正确。

文件引用问题

  • 避免使用绝对路径:在链接网站内部的页面或资源(如图片)时,应使用相对路径,而非指向本地硬盘的绝对路径(如 C:\Users\...)。
  • 正确使用相对路径:确保站内链接使用相对于当前文件的路径。

文件命名规范

为避免大小写和字符引发的问题,请遵循以下命名约定:

  • 使用全小写字母。
  • 可以使用数字 0-9
  • 可以使用连字符 -
  • 扩展名使用 .html.css
  • 避免使用空格和特殊字符。空格在 URL 中会被转换为 %20,容易导致错误。

图像格式兼容性

为确保图像在所有浏览器中正常显示,请使用以下广泛支持的格式:

  • JPEG
  • PNG
  • GIF
  • SVG(用于矢量图形)

避免使用以下可能不兼容的格式:

  • HEIC(苹果设备格式):需要转换为 JPEG 或 PNG。
  • WebP/WebM:部分浏览器(如 Safari)尚未完全支持。

注意:转换图像格式需要使用图像编辑软件(如 Photoshop),仅重命名文件扩展名是无效的。

隐私与安全提醒

发布到网络上的信息是公开的,请谨慎处理个人敏感信息:

  • 慎重考虑是否发布个人姓名、电话号码、家庭地址。
  • 电子邮件地址容易被自动化程序收集,用于发送垃圾邮件。

总结

本节课我们一起学习了将网页部署到 Web 服务器的完整流程。我们了解了从选择托管服务、本地测试、使用 SFTP 传输文件,到在线验证和排查常见问题(如 404/403 错误、路径问题、命名规范和图像格式)的每一步。记住在发布前充分测试,并始终注意线上内容的隐私与安全。现在,你已经掌握了让网页在互联网上“活”起来的关键技能。

L11.1:网页布局技术概述 🧭

在本节课中,我们将学习四种不同的网页布局技术。我们将了解每种技术的基本概念、优缺点,并明确推荐当前及未来最值得使用的技术。通过对比,你将理解为何应将内容(HTML)与表现(CSS)分离,以及现代布局技术如何实现这一目标。

概述

网页布局技术决定了网页上各元素的排列方式。本节将概述四种主要技术:基于表格的布局、基于浮动的布局、Flexbox布局和基于网格的布局。我们将通过三个示例布局来探讨每种技术的实现思路,并重点分析为何基于网格的布局是未来的发展方向。

四种布局技术简介

以下是四种我们将要探讨的网页布局方式。

  1. 基于表格的布局:这种方法已不再使用,但通过与网格布局对比,能帮助我们理解CSS的优势。
  2. 基于浮动的布局:这是多年来使用最广泛的技术。
  3. Flexbox布局:在过去大约三年里逐渐流行,但它最初并非为整体网页布局而设计。
  4. 基于网格的布局:这是网页布局的未来,提供了最大的灵活性,但目前可能不如前两种技术使用广泛。

除非有充分理由,否则建议使用第四种技术。接下来,让我们逐一审视这些技术。

示例布局

在深入每种技术之前,我们先定义三个将用于演示的示例布局。

  1. 简单的三列网页:一个包含三列的基本布局。
  2. 混合列格式网页:顶部有三列,底部有两列的布局。
  3. 复杂混合布局:一种更不规则、包含嵌套结构的布局。

我们将看到每种布局技术如何处理这三种情况。

1. 基于表格的布局 📊

基于表格的布局利用HTML表格的概念来排列元素。HTML早期就支持表格,因此曾有一段时间人们普遍使用表格来布局网页。

实现思路

对于简单的三列布局,可以创建一个包含一行三列的表格。

对于顶部三列、底部两列的布局,有两种方法:

  • 嵌套表格:创建一个两行一列的外层表格,其第一行单元格内嵌套一个三列表格,第二行单元格内嵌套一个两列表格。
  • 使用colspan属性:创建一个两行四列的表格,并通过colspan让某些单元格跨越多列。

对于复杂混合布局,通常需要多层表格嵌套。例如,可以先创建一个两列表格作为主框架,然后在右侧列内再嵌套其他表格来实现更复杂的结构。

存在的问题

基于表格的布局存在几个主要问题:

  • 布局与HTML紧耦合:布局信息直接写在HTML的<table>标签中。若想更改整个网站的布局,必须修改每一个HTML文件,无法通过单独修改CSS来实现。
  • 计算成本高:当表格嵌套层级过深(如四层、五层或六层)时,浏览器渲染会变慢,代码也变得混乱。

因此,我们不再使用基于表格的布局。

2. 基于浮动的布局 🎈

随着CSS的普及,基于浮动的布局技术出现了,并且至今仍被广泛使用。

实现思路

浮动(float)属性原本用于让文本环绕图片。实际上,任何块级元素都可以被浮动。如果将多个<div>元素都设置为float: left,它们就会水平排列,形成列的效果。

对于简单的三列布局,创建三个<div>并全部设置为float: left即可。

对于顶部三列、底部两列的布局

  1. 创建两个主要的<div>,它们会默认上下堆叠。
  2. 在第一个<div>内放置三个子<div>,并全部设置为float: left
  3. 在第二个<div>内放置两个子<div>,也全部设置为float: left

对于复杂混合布局,同样采用嵌套思路:

  1. 创建两个浮动到左侧的<div>,形成左右两列。
  2. 在右侧列的<div>内,再创建两个子<div>,它们默认上下堆叠。
  3. 在下方子<div>内,再创建两个孙<div>并设置为float: left

优点与缺点

优点:兼容性极佳,几乎支持所有浏览器。

缺点

  • 布局仍与HTML结构紧密相关:为了实现浮动效果,必须创建特定结构的<div>,HTML的编写方式在很大程度上决定了最终的布局。仅通过修改CSS来实现完全不同的布局仍然很困难。
  • 需要处理“清除浮动”等额外问题:为了确保布局稳定,通常需要额外的CSS技巧(本讨论中未深入),这使得代码不够简洁。

3. Flexbox布局 🧩

Flexbox(弹性盒子)布局旨在为容器内的子元素提供更高效的对齐与空间分配方式。

核心概念与设计初衷

Flexbox的核心是控制一维(行或列)上子元素的排列。通过设置容器的display: flex,可以轻松改变子元素的排列方向(flex-direction)、对齐方式(justify-content, align-items)和伸缩比例(flex-grow)。

重要提示:Flexbox最初并非为整个网页的宏观布局而设计。它与网格布局几乎是同时被定义的,其设想的分工是:

  • 网格布局:用于规划网页的整体宏观结构(主要组件的位置)。
  • Flexbox布局:用于控制微观、局部容器内子元素的排列(例如导航栏、卡片列表)。

然而,由于人们厌倦了浮动布局的繁琐,Flexbox也被“借用”来进行整体网页布局。

实现思路(用于整体布局时)

对于简单的三列布局

  1. 创建一个父<div>作为Flex容器(display: flex)。
  2. 在容器内放置三个子<div>
  3. 设置flex-direction: row(默认值),使子元素排成一行。

对于顶部三列、底部两列的布局

  1. 创建一个外层Flex容器,设置flex-direction: column,使其子元素垂直堆叠。
  2. 在这个容器内有两个子<div>
  3. 第一个子<div>本身也是一个Flex容器(display: flex; flex-direction: row),里面包含三个孙<div>
  4. 第二个子<div>同样是Flex容器(display: flex; flex-direction: row),里面包含两个孙<div>

对于复杂混合布局,嵌套逻辑类似:

  1. 外层Flex容器方向为行(row),包含左右两列。
  2. 右列本身是一个Flex容器,方向为列(column),包含上下两部分。
  3. 下部分又是一个Flex容器,方向为行(row),包含两个元素。

优点与缺点

优点:非常适合组件内部的微观布局,可以轻松实现元素顺序重排、对齐和空间分配。

缺点(用于整体网页布局时):

  • 仍受HTML结构限制:布局能力很大程度上取决于HTML中元素的嵌套关系。虽然可以通过CSS改变排列方向(例如为移动端将行改为列),但整体结构变换的灵活性有限。

4. 基于网格的布局 🌐

基于网格的布局将网页视为一个二维网格系统,允许将元素精确放置在任何网格区域中。

核心概念

你可以将网页划分为任意行和列的网格,然后通过CSS直接将元素放置到特定的网格单元格中。元素甚至可以跨越多行或多列(使用grid-rowgrid-column属性)。

实现思路

对于简单的三列布局:定义一个一行三列的网格。

对于顶部三列、底部两列的布局

  • 方法一:定义一个两行一列的网格,然后在两个网格单元格内分别嵌套一个三列网格和一个两列网格。
  • 方法二:直接定义一个两行四列的网格,并使用grid-column属性让元素跨列。

对于复杂混合布局:同样可以采用嵌套网格,或者直接定义一个三行两列的外层网格,并通过指定每个元素占据的网格区域来实现布局。

革命性优势

网格布局的图示与表格布局非常相似,但其根本区别在于实现位置

  • 表格/浮动/Flexbox:布局逻辑与HTML结构深度绑定。改变布局通常需要改动HTML。
  • 网格布局布局定义完全在CSS中。只要HTML中的元素有ID或类名,你就可以在CSS的网格定义中,自由决定将它们放置在任何位置。

这意味着你可以为桌面(大屏幕)、平板(中屏幕)和手机(小屏幕)在CSS中定义完全不同的网格布局,而无需修改一行HTML代码。这真正实现了内容与表现的彻底分离,提供了无与伦比的灵活性。

现状与推荐

优点

  • 完全灵活:CSS完全控制布局,与HTML解耦。
  • 易于理解:二维网格模型比浮动的“hack”方式更直观,也比用Flexbox做宏观布局更清晰。
  • 强大能力:轻松实现复杂、响应式的布局。

唯一的缺点(正在减弱):浏览器兼容性。目前约95%的浏览器支持网格布局(Flexbox约97%,浮动接近100%)。对于绝大多数现代网站受众而言,这已不是问题。

强烈推荐:除非你的目标用户群体大量使用非常陈旧的浏览器(如某些特定环境下的旧版IE),否则基于网格的布局绝对是应该采用的技术。它代表了网页布局的未来。

总结

本节课我们一起学习了四种网页布局技术:

  1. 基于表格的布局:已过时,将布局与内容混合在HTML中,难以维护。
  2. 基于浮动的布局:曾是最主流的技术,兼容性好,但布局仍与HTML结构紧密相关,代码较混乱。
  3. Flexbox布局:擅长处理一维空间内子元素的排列,是微观布局的利器,但并非为整体网页布局设计。
  4. 基于网格的布局:现代、强大的二维布局系统,将布局定义完全置于CSS中,实现了内容与表现的完美分离,是当前和未来构建复杂、响应式网页的首选技术。

在下一讲中,我们将具体学习如何使用CSS Grid语法来创建网格并放置元素。

L11.2:网页布局:基于网格的布局 📐

在本节课中,我们将要学习网页布局的核心技术之一:基于网格的布局。我们将了解如何定义网格、放置元素以及处理不同尺寸的内容,从而创建结构清晰、响应式的网页设计。

概述

基于网格的布局是一种强大的CSS技术,它允许开发者将网页内容组织在由行和列构成的网格中。与之前介绍的其他布局技术相比,网格布局提供了更直观和灵活的方式来控制元素在页面上的位置。

定义网格

要使用基于网格的布局,首先需要定义一个网格容器。这可以通过将父级元素的 display 属性设置为 grid 来实现。

body {
  display: grid;
}

在上面的代码中,我们将整个网页的 body 元素定义为一个网格容器。如果你需要更复杂的嵌套结构,也可以将网格应用在任何其他父级元素上。

设置网格的行与列

定义了网格容器后,我们需要指定网格的行和列。这可以通过 grid-template-columnsgrid-template-rows 属性来完成。

以下是一个创建2x2网格的示例:

body {
  display: grid;
  grid-template-columns: 100px 100px; /* 两列,每列100像素 */
  grid-template-rows: 100px 100px;    /* 两行,每行100像素 */
}

这段代码创建了一个简单的正方形网格。每个网格单元格的尺寸是固定的。

在网格中放置元素

将网格容器设置好后,就可以将子元素放置到网格的特定位置上了。我们可以使用 grid-column-startgrid-row-start 属性来指定元素从哪一列、哪一行开始放置。

以下是将四个元素分别放置到2x2网格四个角上的示例:

#a {
  grid-column-start: 1;
  grid-row-start: 1;
}
#b {
  grid-column-start: 2;
  grid-row-start: 1;
}
#c {
  grid-column-start: 1;
  grid-row-start: 2;
}
#d {
  grid-column-start: 2;
  grid-row-start: 2;
}

基于网格布局的一大优势是,元素的视觉位置可以完全独立于其在HTML文档中的顺序。这为我们提供了极大的布局灵活性。

网格的尺寸单位

在定义网格时,我们可以使用多种不同的单位来指定行和列的尺寸,这为创建响应式设计提供了便利。

以下是几种常用的单位:

  • 固定单位:如 px(像素)、in(英寸)、cm(厘米)。
  • 百分比:如 50%,表示占据容器宽度的50%。
  • 分数单位 fr:这是一个非常实用的单位,它表示在分配完所有固定尺寸后,剩余空间所占的份额。

fr 单位的使用示例如下:

grid-template-columns: 75px 1fr 150px;

在这个例子中,第一列固定为75像素,第三列固定为150像素。中间的第二列则使用 1fr,这意味着它将占据扣除75px和150px后剩下的所有可用空间。

如果有多列使用 fr 单位,它们将按比例分配剩余空间。例如:

grid-template-columns: 75px 1fr 2fr;

这里,剩余空间将被分成3份(1+2),第二列获得1/3,第三列获得2/3。

创建跨行或跨列的元素

有时我们需要让一个元素占据多个网格单元格。这可以通过同时指定 startend 属性来实现。

需要注意的是,end 的值指的是网格线编号,并且是“独占”的。例如,grid-row-end: 3; 意味着元素从开始行一直延伸到第3条网格线之前,即实际覆盖了第1行和第2行。

以下是一个元素跨两行、另一元素跨两列的示例:

/* 元素A跨越第1行和第2行 */
#a {
  grid-column-start: 1;
  grid-row-start: 1;
  grid-row-end: 3; /* 结束于第3条行网格线前 */
}

/* 元素B跨越第2列和第3列 */
#b {
  grid-column-start: 2;
  grid-column-end: 4; /* 结束于第4条列网格线前 */
  grid-row-start: 1;
}

处理内容尺寸与自动行高

在实际应用中,网格单元格内的内容高度往往是未知的。我们可以通过将行高设置为 auto 来让行高根据内容自动调整。

body {
  display: grid;
  grid-template-columns: 100px 100px;
  grid-template-rows: auto 100px; /* 第一行自动调整,第二行固定100像素 */
}

这样,第一行的高度会扩展到足以容纳该行中最高的元素,而第二行则保持固定高度。

如果内容超出了固定高度的单元格,默认行为是内容溢出。我们可以使用 overflow 属性来控制这一行为。

.grid-item {
  overflow: hidden; /* 超出部分将被隐藏 */
}

实战示例:新闻布局

让我们看一个更接近实际网站的例子:一个简单的新闻主页布局。

HTML结构:

<div id="main">
  <div id="story1">...</div>
  <div id="story2">...</div>
  <div id="story3">...</div>
  <div id="story4">...</div>
</div>

CSS网格布局:

#main {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr; /* 三列等宽 */
  grid-template-rows: auto; /* 行高自动 */
  gap: 10px; /* 网格间隙 */
}

#story1 {
  grid-column-start: 1;
  grid-column-end: 4; /* 横跨三列 */
  grid-row-start: 1;
}
#story2 {
  grid-column-start: 1;
  grid-row-start: 2;
}
/* ... 其他故事元素的定位 */

在这个布局中,头条新闻(#story1)横跨所有三列,而其他新闻则排列在下方的网格中。通过只修改CSS,我们就可以轻松调整各个新闻块的位置和大小,而无需改动HTML结构,这充分体现了网格布局的灵活性。

总结

本节课中我们一起学习了基于网格的CSS布局。我们从如何定义一个网格开始,学习了设置行与列、使用不同的尺寸单位(特别是灵活的 fr 单位),以及如何将元素精确地放置到网格中,包括创建跨行跨列的元素。我们还探讨了如何处理动态内容的高度和内容溢出问题。最后,通过一个新闻布局的实战示例,我们看到了网格布局在创建复杂、灵活且结构清晰的网页设计中的强大能力。掌握网格布局是迈向专业前端开发的关键一步。

L12:网页重现:《纽约时报》 🗞️

在本节课中,我们将学习如何使用基于网格的CSS布局,从头开始复制《纽约时报》首页。我们将看到网格布局如何简化复杂网页的构建过程,并探索处理页面中特殊区域的技巧。

概述

我们将使用基于网格的布局来重现《纽约时报》首页。网格布局允许我们轻松创建复杂的多列设计,而无需依赖浮动或Flexbox等传统方法。本节将展示网格布局的强大灵活性,并逐步构建页面。

页面结构分析

首先,我们来分析《纽约时报》首页的结构。页面主要分为三列,但某些区域并不严格遵循这个规则。

以下是需要注意的几个关键点:

  • 页面主体由三列构成。
  • 底部有一个区域横跨了两列,这使用了列跨度功能。
  • 顶部的照片也横跨了多列。
  • 中间有一行内容的结构比较特殊,我们稍后会处理它。

目前,我们暂时忽略中间的特殊行,假设所有内容要么遵循主要的三列布局,要么是在这三列上的一个简单跨度。

初始布局与网格设置

现在,我们开始构建页面。左边是我们将要创建的副本,右边是《纽约时报》的实际截图作为参考。

我们使用“lorem ipsum”拉丁文假文本作为文章内容。首先,查看基础的HTML结构。

<body>
  <div id="a1">...</div>
  <div id="a2">...</div>
  <div id="b1">...</div>
  <div id="b2">...</div>
  <div id="b3">...</div>
  <div id="c1">...</div>
  <div id="c2">...</div>
</body>

HTML中创建了许多<div>,并按照布局的行(A、B、C)进行标记。网格布局的优势之一是,我们不受HTML元素顺序的限制,可以在CSS中任意放置它们。这种灵活性是浮动或Flexbox布局难以实现的。

接下来,我们将网格应用到<body>上。

body {
  display: grid;
  grid-template-columns: 400px 360px 240px;
  grid-template-rows: auto;
}

这里将列宽设置为400像素、360像素和240像素,行高设置为auto,意味着行高会根据内容自动调整。

创建分隔线

观察原版页面,各版块之间有清晰的分隔线。

有多种方法可以创建这些线。最初尝试在每个内容元素上添加border-top,但元素之间的margingap会导致线条出现中断。

因此,我们选择创建独立的<div>来专门绘制这些线条。由于顶部的线更粗,我们为它们设置不同的类名。

<body>
  <div class="line-top"></div>
  <div id="a1">...</div>
  ...
  <div class="line-middle"></div>
  ...
  <div class="line-bottom"></div>
</body>

这些线条<div><body>的直接子元素,因此可以被放置在我们为<body>设置的网格中的任何位置。我们通过CSS为它们添加边框。

.line-top, .line-middle, .line-bottom {
  border-top: 1px solid #ccc;
  margin: 5px 0;
}
.line-top {
  border-top-width: 2px; /* 顶部线条更粗 */
}

这些线条<div>将横跨所有三列(从第1列到第4列),因此不会因为列间隙而中断,完美地形成了贯穿页面的水平线。

放置内容元素

设置好网格和线条后,我们开始放置具体的内容元素。

第一个元素(A1)是左上角的文章。我们将其放置在网格的第2行、第1列。

#a1 {
  grid-row: 2;
  grid-column: 1;
  margin-right: 8px;
}

div中可以包含任何可渲染的元素,如标题、列表、图片等。

接下来是威尼斯图片(A2),它需要横跨两列。

#a2 {
  grid-row: 2;
  grid-column: 2 / 4; /* 从第2列开始,到第4列结束(即跨越第2、3列) */
  width: 600px;
}

图片宽度的计算很重要:由于它横跨了第2列(360px)和第3列(240px),所以总宽度设置为600px。如果图片位置改变,宽度也需要相应调整。

然后我们放置B部分的内容。B2区域包含一张图片及其下方的标题。

<div id="b2">
  <img src="..." alt="...">
  <p>Image caption here...</p>
</div>
#b2 {
  grid-row: 4;
  grid-column: 2;
  width: 360px; /* 与列宽一致 */
}
#b2 p {
  font-size: 0.9em;
  color: #555;
}

B3区域是右侧的新闻文章,我们为其添加一个浅灰色的左边框,以模拟原版页面中的垂直分隔线。

#b3 {
  grid-row: 4;
  grid-column: 3;
  border-left: 1px solid #eee;
  padding-left: 10px;
  margin-left: 10px;
}

最后是C部分底部的元素,它也是一个列跨度元素。

#c2 {
  grid-row: 6;
  grid-column: 2 / 4; /* 横跨第2、3列 */
}

实现页面居中

目前,我们的页面紧贴浏览器窗口左上角。当窗口变宽时,右侧会出现大片空白。

我们希望页面能像原版一样始终居中。解决方法是:将所有内容包裹在一个<div id="main">中,然后让这个容器在页面中居中。

<body>
  <div id="main">
    <!-- 所有之前的内容都放在这里 -->
  </div>
</body>

我们修改<body>的网格,创建三个新列:左右两边是弹性空间(1fr),中间是固定宽度(1000px)的内容区。

body {
  display: grid;
  grid-template-columns: 1fr 1000px 1fr;
}
#main {
  grid-row: 1;
  grid-column: 2; /* 将主要内容放在中间列 */
}

然后,在#main内部,我们重新定义之前用于内容布局的三列网格。

#main {
  display: grid;
  grid-template-columns: 400px 360px 240px;
  grid-template-rows: auto;
}
/* 之前所有内容元素(#a1, #a2, .line-top等)现在都是 #main 的子元素,其网格位置规则不变 */

这样,#main本身在页面中居中,其内部再使用原来的网格进行精细布局。

处理特殊布局区域

现在,我们来处理之前忽略的那个特殊区域。这个区域没有遵循通用的三列规则。

这个区域包含多个元素,横跨了所有三列,并且内部还有自己的结构和边框。

一种方法是为主网格添加第四列,但这会迫使其他所有元素都需要调整列跨度。我们选择另一种方法:创建一个横跨两列的容器<div>,然后在这个容器内部再建立一个独立的网格

HTML结构如下:

<div id="x1">
  <div id="xy1">Story 1</div>
  <div id="xy2">Story 2</div>
</div>
<div id="x2">Right-side caption</div>

CSS布局如下:

/* 主网格中放置这个特殊容器 */
#x1 {
  grid-row: 3; /* 假设放在第3行 */
  grid-column: 1 / 3; /* 横跨第1、2列 */
  border-top: 1px solid #ccc;
  padding-top: 10px;
  display: grid; /* 内部再建一个网格 */
  grid-template-columns: 460px 300px;
  gap: 15px; /* 内部两个故事之间的间隙 */
}
#xy1 { grid-row: 1; grid-column: 1; }
#xy2 { grid-row: 1; grid-column: 2; }

/* 右侧的标题单独放置 */
#x2 {
  grid-row: 3;
  grid-column: 3;
}

通过这种方式,我们成功地在主网格中嵌入了一个具有独立结构的复杂区域。

最终成果与总结

完成所有步骤后,我们的复制品与原版《纽约时报》首页已经非常接近。

本节课中,我们一起学习了如何使用CSS网格布局来重现一个复杂的网页。我们掌握了以下核心技能:

  1. 分析页面结构并将其分解为网格。
  2. 定义网格容器display: grid)并设置列与行(grid-template-columns/rows)。
  3. 使用列跨度grid-column: start / end)来创建横跨多列的区域。
  4. 实现页面居中,通过在外层创建带有弹性空间的网格。
  5. 处理嵌套布局,在网格项目内部创建新的网格来处理特殊结构。
  6. 创建视觉分隔线,使用独立的网格项目或边框。

网格布局提供了强大的灵活性和控制力,使得创建如《纽约时报》这样复杂的版面变得清晰且易于管理。希望本教程能帮助你理解并开始运用这一强大的CSS工具。

L13.1:创建网页:输入表单 📝

在本节课中,我们将要学习如何创建网页表单。表单是网页与用户进行交互的核心工具,它允许用户输入信息并提交给服务器。我们将从基础开始,了解各种表单元素及其用途。

到目前为止,我们一直在研究如何在网页上呈现信息。但网站所做的不仅仅是允许呈现信息,它们实际上允许查看者和网站之间进行交互。因此,为了做到这一点,我们需要使用表单。在本视频中,我们将向您展示如何创建表单的基础知识。

表单概述

以下是表单在网页上的常见应用示例:

  • 亚马逊搜索:允许用户输入搜索关键词。
  • 纽约时报搜索文章:提供搜索框以查找新闻。
  • 天气频道:让用户输入地点以查询天气。
  • 谷歌地图:提供复杂的表单,包含下拉菜单等元素,用于筛选餐厅等。

表单可以变得非常复杂。例如,斯坦福图书馆的高级搜索表单包含多种可勾选的选项;HBO的账户创建页面也是一个典型的表单应用。

在本课程中,我们将重点关注如何实际创建网页以提交信息,而不是服务器本身将如何响应该信息。CS106E的学生将会更深入地学习服务器端如何处理提交的信息。

表单与表单元素

现在,让我们来看看我们的示例表单。这个表单允许滑雪俱乐部的成员提交旅行信息。您可以看到有各种不同的元素允许用户输入信息。

在HTML术语中,允许用户提交信息的整个项目被称为 <form>。而用户在其中输入信息的具体元素(如文本框、按钮)被称为 表单元素。在计算机科学中,这些类型的项目通常被称为“控件”或“小部件”。所以,一个HTML表单元素本质上是一个出现在网页上的控件。

我们将把我们所有的控件或我们的表单元素放在一个<form>标签中。现在,我只想放一个id以便我们可以识别它,方便后续添加样式或以某种方式使用它。id实际上并不是必需的,这里可能会出现一堆其他属性。例如,如果用户要将表单信息提交给Web服务器,服务器需要知道处理这些信息的程序在哪里,因此还会有其他属性。但现在,我们将专注于表单标签本身和出现在其中的元素。

输入元素:文本框与密码框

使用<input>标签可以创建多种表单元素。<input>标签是一个多用途标签,它创建了一堆我们在这个网页上看到的不同元素,以及其他一些元素。输入元素的确切用途由它的type属性来确定。

以下是常见的输入元素类型:

文本输入框

type属性设置为text时,它创建一个单行文本字段。

<input type="text" name="creditcard" value="1234-1234-1234-1234" placeholder="输入信用卡号">
  • name:当信息被发送到网络服务器时,name决定了如何识别该字段。服务器将收到“名称-值”对。
  • value:这是将提交给Web服务器的内容。如果用户没有在该字段中输入任何内容,则提交这个初始值。用户可以编辑它。
  • placeholder:给出预期内容的提示(浅灰色显示)。它不是实际值,用户点击时它会消失。

密码输入框

type属性为password时,其工作方式与文本类型非常相似,不同之处在于用户输入的内容会显示为圆点或星号。

<input type="password" name="userpass">

请注意:此密码字段仅能防止旁人偷看屏幕,并不能保护信息在互联网上传输的安全。要实现真正的密码保护,需要使用HTTPS协议。

输入元素:复选框与单选按钮

上一节我们介绍了用于文本输入的控件,本节中我们来看看用于进行选择的控件。

复选框

type属性为checkbox时,创建复选框,允许用户选择多个独立选项。

<input type="checkbox" name="friday_dinner" checked> 周五晚餐
  • checked:此属性使复选框初始状态为选中。
  • 与输入元素关联的标签需要手动添加,放在<input>标签之外。

单选按钮

type属性为radio时,创建单选按钮,用于在一组互斥的选项中进行选择。

<input type="radio" name="travel" value="bus" checked> 巴士
<input type="radio" name="travel" value="plane"> 飞机
<input type="radio" name="travel" value="car"> 自驾
  • 互斥性:同一name属性下的所有单选按钮构成一组,只能选择其中一个。
  • value:指定当该选项被选中时,提交给服务器的值。
  • checked:指定组中的默认选中项。

重要区别:复选框用于可以多选或全不选的场景;单选按钮用于必须且只能从一组中选择其一的场景。请根据目的正确使用。

按钮元素

表单中通常需要按钮来触发提交或重置操作。

提交与重置按钮

使用<input>标签,type属性设置为submitreset

<input type="submit" value="提交表单">
<input type="reset" value="重置信息">
  • type=”submit”:单击后,收集表单中所有信息(名称-值对)并发送到Web服务器。
  • type=”reset”:单击后,丢弃用户输入的所有信息,所有表单元素恢复其初始值。
  • value:此属性定义了显示在按钮上的文本。不同浏览器可能有不同的默认文本,因此建议明确设置。

通用按钮

type属性为button时,创建一个通用按钮,通常用于客户端脚本处理(如JavaScript),而不是提交信息到服务器。

<input type="button" value="点击我">

下拉选择与文本区域

除了基本的输入框和按钮,表单还提供了更复杂的选择和多行文本输入方式。

下拉选择框

使用<select><option>标签创建下拉菜单。

<select name="rental_package">
  <option value="none">无租赁套餐</option>
  <option value="ski" selected>滑雪套餐 $25</option>
  <option value="board">滑雪板套餐 $30</option>
</select>
  • <select>:定义整个选择框,name属性用于服务器识别。
  • <option>:定义每个选项。
  • value:指定提交给服务器的值,可以与显示文本不同,这能避免特殊字符(如$)传输问题。
  • selected:指定默认选中的选项。
  • size:设置此属性(如size=”3″)可将下拉菜单变为固定高度的列表框。

文本区域

使用<textarea>标签创建多行文本输入框,与单行文本字段(<input type=”text”>)相对。

<textarea name="comments" rows="4" cols="50">请在此输入您的评论...</textarea>
  • 它是一个双标签,必须有结束标签</textarea>
  • 初始文本必须放在开始标签和结束标签之间,而不是使用value属性。
  • 可以使用rowscols属性设置初始大小,但更推荐使用CSS进行样式控制。

其他输入类型

HTML5还引入了一些更专门的输入类型,用于特定类型的数据:

  • 数字<input type=”number”> 显示带增减箭头的数字输入框。
  • 范围滑块<input type=”range”> 创建一个滑块。
  • 日期<input type=”date”> 提供日期选择器。
  • 时间<input type=”time”> 提供时间选择器。
  • 颜色<input type=”color”> 打开颜色选择对话框。

这些输入类型在现代浏览器中能提供更好的用户体验和验证。

总结

本节课中我们一起学习了如何创建网页表单以实现用户交互。我们介绍了表单的基本结构<form>,并详细探讨了多种表单元素:用于文本输入的textpassword;用于选择的checkboxradio;用于提交的submitresetbutton;以及用于多选和长文本的selecttextarea。我们还简要了解了HTML5新增的一些输入类型。理解这些元素及其属性是构建交互式网页的基础。

L13.2:网页重现:华盛顿邮报 🗞️

在本节课中,我们将学习如何利用HTML和CSS技术,复制《华盛顿邮报》网站底部的一个专业版块。我们将重点关注网格布局、边框样式、间距控制以及如何通过CSS精确地控制视觉呈现。

概述

我们将分析目标网页的结构,并使用HTML进行语义化标记,然后通过CSS(特别是网格布局)来精确控制元素的排列和样式。核心在于理解如何将设计稿转化为代码,并掌握CSS中边框、边距、填充等属性的细微调整。


网页结构与目标

在左侧是整个《华盛顿邮报》网页,它非常长。我们本次要复制的部分是页面底部用红色框标出的区域。虽然原版页面可能已经更新,但这个练习能帮助我们学习处理边框和一些微妙的细节,使元素看起来更专业。

这个练习也强调了我们在整个课程中一直贯彻的理念:使用级联样式表(CSS)来控制演示文稿。特别是在基于网格的布局中,将表现信息放在CSS中,而将语义信息(如标题、各部分名称)留在HTML里,能带来极大的灵活性。


整体布局与网格设置

首先,我们可能会注意到网页顶部有一个横穿的黑条。在我们的复制练习中,我们将专注于底部区域,而不会讨论这个固定导航栏的实现。

我们在左边是我们要复制的部分,右边是实际的网页截图。我使用了之前讲座中见过的“Lorem Ipsum”文本,并没有使用《华盛顿邮报》的实际标题和图片。

观察HTML结构,我把内容分成了A、B、C、D、E、F几个部分,它们与网页上显示的不同区域相对应。无论它们在HTML中出现的顺序如何,也无论我给它们起什么名字,我都可以通过CSS网格布局完全打乱它们的显示顺序。这体现了CSS布局的强大之处。

通过使用网格,我们有很大的灵活性。例如,如果在手机上显示,我们可以轻松地将布局从三列两行改为一列。


CSS基础样式设置

现在,让我们来看看实际的CSS代码。

首先,观察右侧截图,可以看到实际的内容区块有白色背景,而它们周围的区域是浅灰色背景。因此,我将body的背景色设置为#f7f7f7。这是一个十六进制颜色值,其中红、绿、蓝三个通道的值相同(f7),因此它呈现为一种灰色阴影。《华盛顿邮报》的整体设计就围绕不同深浅的灰色展开。

此外,我将字体系列设置为sans-serif。我没有深究他们使用的具体字体,只是知道他们使用的是无衬线字体。

我们复制的所有内容都包含在一个id为lower-sectiondiv中,它代表《华盛顿邮报》的底部区域。我将其设置为网格布局:

display: grid;
grid-template-columns: 1fr 1fr 1fr;

fr代表分数单位,1fr 1fr 1fr意味着将可用空间平均分为三列。行高设置为auto,意味着行的高度会根据其中的内容自动调整。


内容区块的样式

接下来,我们要查看每个独立部分(A到F部分)的样式。

我为每个部分设置了padding(内边距)和margin(外边距)。记住,padding是元素内容与边框之间的空间,而margin是边框之外的空间。如果不设置padding,文本就会紧贴边框;如果不控制margin,所有元素就会紧贴在一起。

每个部分有白色背景,并且我使用了从原网页通过取色工具获取的精确颜色来设置边框,以还原其微妙的3D外观。如果你仔细观察,会发现每个区块的左上角和右上角边框颜色与底部边框略有不同,且底部边框更粗一些。

以下是我为部分区块设置的边框样式示例:

border-width: 1px 1px 2px 1px; /* 上、右、下、左 */
border-style: solid;
border-color: #eaeaea #eaeaea #d5d5d5 #eaeaea;

这里,底部边框是2像素,颜色#d5d5d5也比其他边略深。同时,我使用border-radius为某些角落添加了圆角效果。


区块内部元素详解

现在,我们深入看看每个部分内部的构成。

首先,有一个<h3>标签作为部分的标题。我假设整个网页顶部的主标题会用<h1>,其他重要标题会用<h2>,因此这里使用<h3>是合适的。我并没有为<h3>.section-title类添加额外样式,保留了其默认的大小、粗细和边距。

接着是图片。我给图片添加了.section-image类,并将其宽度设置为100%

width: 100%;

这样做的原因是,我们的列宽是使用fr单位定义的,会随着浏览器窗口变宽而增加。将图片宽度设为100%,可以确保图片随着所在列的宽度同步缩放,高度则会按比例自动调整。

然后,有一些子部分(div),它们可能包含多个元素。例如,第一个子部分包含了一个标有“分析”的小标签和实际的标题。我将这些组合在一个div中,并为其设置marginpadding,以控制间距。

为了在每个文章条目之间添加分隔线,我为这些子部分设置了底部边框:

border-bottom: 1px solid #d5d5d5;

但我不希望最后一个条目下面也有这条线。因此,我使用:last-child这个伪类选择器来覆盖最后一个子元素的样式:

.last-child {
    border-bottom: 0;
}

字体与细节调整

最后,我们来设置字体大小和粗细等细节。

对于每个子部分的主要文本(<h4>),我将其样式设置为:

margin: 0;
padding: 0;
font-size: 12pt;

网页中的许多元素(特别是标题)都有预设的上下边距。通过明确将marginpadding设为0,我们可以精确控制间距,让“分析”这个词紧贴其下方的标题,还原原设计。

<h4>的默认样式已经是粗体,这正好符合我们的需求。

而对于“分析”、“评论”或“视角”这类小标签(只是一个div),我需要手动为其提供样式:

font-size: 9pt;
font-weight: bold;

总结

本节课中,我们一起学习了如何复制《华盛顿邮报》网页的一个专业版块。主要收获包括:

  1. 使用CSS网格布局display: grid)创建灵活的多列结构,并能轻松调整元素顺序和响应式布局。
  2. 精确控制样式细节:通过设置paddingmarginborder(包括宽度、样式、颜色和圆角)以及背景色,来还原设计的视觉层次和空间感。
  3. 利用选择器:使用类(.class)为多个相似元素定义通用样式,使用ID(#id)为特定定位元素定义样式,并使用伪类(如:last-child)进行特殊处理。
  4. 控制字体呈现:通过font-sizefont-weight等属性,并重置元素的默认边距,来实现精确的排版。

希望这个练习带给你的主要启示是:利用我们所教授的技术,你完全可以制作出与专业网站相媲美的网页。这需要一些时间进行分析和微调,以使尺寸和间距都恰到好处。如果你具备一定的艺术和图形设计技能,你完全可以先构思出自己的设计,然后运用层叠样式表将你的愿景变为现实。

L14.1:高级图像技术 🖼️

在本节课中,我们将要学习一些关于图像的更高级的网页技术。我们将探讨如何让一张图片的不同部分链接到不同的网页,以及如何通过优化图像加载方式来提升网页性能。

图像映射

上一节我们介绍了图像的基本使用,本节中我们来看看如何让一张图片的特定区域变得可交互。图像映射背后的基本思想是,将图像的不同部分链接到不同的网页。

例如,假设我们有一张包含Maddie和Roomba的图片。我们想要实现的效果是:如果用户点击图片中Maddie的部分,就跳转到 maddie.html 页面;如果点击Roomba的部分,就跳转到 roomba.html 页面。这种技术在很多场景下都很有用,比如在地图上点击不同区域跳转到对应介绍,或者在合影中点击不同人物跳转到其个人主页。

如何创建图像映射

以下是创建一个图像映射的步骤:

  1. 定义地图区域:使用 <map> 标签定义一个地图,并为其指定一个唯一的名称。
  2. 定义可点击区域:在 <map> 标签内部,使用 <area> 标签定义图像上的可点击区域。每个区域需要指定形状(如矩形 rect)、坐标和链接目标。
  3. 关联图像与地图:在 <img> 标签中使用 usemap 属性,将其值设置为 # 加上地图的名称,从而将图像与定义好的地图关联起来。

以下是一个具体的代码示例,展示了如何为一张电脑图片的显示器和扬声器部分分别设置链接:

<!-- 1. 定义地图,名称为“computermap” -->
<map name="computermap">
    <!-- 2. 定义一个矩形区域,对应显示器部分 -->
    <area shape="rect" coords="50,20,215,157" href="monitor.html" alt="Computer Monitor">
    <!-- 定义另一个矩形区域,对应扬声器部分 -->
    <area shape="rect" coords="其他坐标" href="speaker.html" alt="Computer Speaker">
</map>

<!-- 3. 关联图像与地图 -->
<img src="computer.jpg" alt="A computer" usemap="#computermap">

坐标说明coords="50,20,215,157" 定义了矩形的左上角 (50, 20) 和右下角 (215, 157) 坐标。你可以使用像 Adobe Photoshop 或 Windows 画图这样的图像处理软件来获取特定区域的精确坐标。

支持多种形状

图像映射不仅支持矩形区域,还支持圆形和多边形区域,以适应更复杂的形状。

  • 圆形:使用 shape="circle",坐标格式为 coords="x, y, r",其中 (x, y) 是圆心,r 是半径。
  • 多边形:使用 shape="poly",坐标格式为 coords="x1,y1,x2,y2,x3,y3,...",按顺序列出多边形的各个顶点坐标。

CSS Sprites(CSS精灵)

上一节我们学习了如何让图像的不同区域响应点击,本节中我们来看看如何优化网页上多张图像的加载速度。如果你的网页包含很多小图像(比如图标),每个图像都需要向服务器发起一次独立的下载请求,这可能会拖慢页面加载速度。

CSS Sprites 技术通过将多个小图像合并到一张大图中来解决这个问题。网页加载时,只需下载这一张大图,然后通过 CSS 背景定位技术,在每个需要显示图标的地方,只显示这张大图中对应的部分。

传统方式 vs. Sprites方式

传统方式(多个图像文件)
网页包含多个 <img> 标签,每个标签指向一个独立的小图像文件。这会导致多次 HTTP 请求。

Sprites方式(单个图像文件)

  1. 将多个小图标合并成一张大图(例如 combined.gif)。
  2. 在 HTML 中,使用 <div> 等元素来预留图标的位置。
  3. 在 CSS 中,为每个 <div> 设置相同的背景图像(即那张大图),但通过 background-position 属性调整背景图的位置,使得每个 <div> 只显示大图中自己对应的那个图标。

以下是使用 CSS Sprites 的代码示例:

<style>
    .sprite {
        width: 50px; /* 图标容器的宽度 */
        height: 50px; /* 图标容器的高度 */
        background-image: url('combined.gif'); /* 引用合并后的大图 */
        background-repeat: no-repeat; /* 防止背景图重复 */
    }
    #traffic-light {
        background-position: 0 0; /* 显示大图中第一个图标(红绿灯) */
    }
    #stop-sign {
        background-position: -50px 0; /* 将背景图左移50px,显示第二个图标(停止标志) */
    }
</style>

<div class="sprite" id="traffic-light"></div>
<div class="sprite" id="stop-sign"></div>

优势:这种方法显著减少了 HTTP 请求次数,对于图标众多的网页(如亚马逊、谷歌等大型网站)能有效提升加载性能。

Base64 内联图像

我们刚刚介绍了通过合并图像来减少请求次数的方法。还有一种更彻底的技术,可以完全消除对图像文件的独立请求,那就是 Base64 编码。

Base64 是一种将二进制数据(如图像文件)编码成 ASCII 字符串的方法。你可以将图像转换成一段很长的 Base64 字符串,然后直接将其嵌入到 HTML 或 CSS 文件中。

如何使用 Base64 图像

<img> 标签的 src 属性中,你可以不使用文件路径,而使用以 data:image/[格式];base64, 开头的字符串,后面跟上图像的 Base64 编码数据。

<img src="data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7" alt="Embedded Image">

如何获取 Base64 字符串:你可以使用在线的 “Base64 图像编码器” 工具,将你的图像文件上传,工具会自动生成对应的 Base64 编码字符串,然后你将其复制粘贴到代码中即可。

优缺点

  • 优点:完全消除了额外的图像文件请求,对于极小的图像(如1x1像素的跟踪像素)或必须确保与页面同时加载的图标非常有用。
  • 缺点:Base64 字符串会使 HTML 或 CSS 文件体积显著增大(通常比原图像文件大33%左右),且不利于浏览器缓存。因此,它更适合用于非常小的、不常变化的图像。

总结

本节课中我们一起学习了三种高级图像处理技术:

  1. 图像映射:允许我们将一张图片的不同区域定义为可点击的热点,并链接到不同的目标。核心是使用 <map><area> 标签。
  2. CSS Sprites:通过将多个小图标合并到一张大图中,并使用 CSS 背景定位来显示特定部分,从而减少 HTTP 请求数量,提升网页加载性能。
  3. Base64 内联图像:将图像数据直接编码为文本字符串并嵌入到 HTML 或 CSS 中,彻底消除了对图像文件的独立请求,适用于微小且需确保加载的图像。

掌握这些技术,能让你在网页设计中更灵活地使用图像,并优化用户体验。

L14.2:表单:GET与POST 📝

在本节课中,我们将学习网页表单中两种提交数据的方法:GETPOST。我们将探讨它们的工作原理、区别以及在实际应用中的选择依据。

概述

表单是网页与用户交互的重要工具,它允许用户输入信息并提交到服务器。表单的 method 属性决定了数据如何被发送到服务器,主要分为 GETPOST 两种方式。理解它们的差异对于构建功能正确、用户体验良好的网页至关重要。

表单基础回顾

上一节我们介绍了如何在网页上创建表单。表单允许用户输入信息,并将其提交到网络服务器。表单标签中有许多属性,其中 action 属性决定了服务器上处理表单数据的程序位置。

例如,action="example.php" 意味着服务器上有一个名为 example.php 的程序来处理表单数据。如果开发者没有使用服务器端程序,也可以使用 mailto: 链接,将表单数据通过电子邮件发送。

方法(Method)属性

除了 action,表单的 method 属性也至关重要。它控制着信息如何被发送到网络服务器,并对用户体验和服务器状态有深远影响。

以下是 method 属性的两种主要值:

  • GET:数据作为URL的一部分发送。
  • POST:数据在HTTP请求主体中发送,不显示在URL中。

GET方法详解

当表单使用 method="get" 时,用户输入的信息会被编码并附加到 action 指定的URL之后。

例如,在亚马逊的搜索表单中,如果你在“电子产品”分类下搜索“iPad”,提交后生成的URL可能类似于:
https://www.amazon.com/s?url=search-alias%3Delectronics&field-keywords=iPad

在这个URL中:

  • url=search-alias%3Delectronics 对应下拉菜单的选择。
  • field-keywords=iPad 对应文本输入框的内容。
  • %3D 是等号 = 的URL编码形式。

GET请求的核心特点是:数据直接暴露在URL中。

GET方法的影响

由于数据包含在URL里,GET请求会带来以下影响:

  • 可书签化:用户可以为此结果页面添加书签,书签包含了所有搜索条件,再次打开时会得到相同的结果。
  • 可分享:用户可以将这个完整的URL通过邮件发送给朋友,朋友点击链接会看到完全相同的搜索结果。
  • 幂等性:GET请求被认为是“幂等”的,意味着多次执行相同的GET请求不会改变服务器状态,应该返回相同的结果。因此,浏览器可以放心地缓存GET请求的结果。

POST方法详解

当表单使用 method="post" 时,用户输入的信息会被放在HTTP请求的内部(请求体)中发送,而不会显示在浏览器的地址栏里。

如果我们将亚马逊的表单方法改为POST,那么提交后,浏览器的地址栏只会显示:
https://www.amazon.com/s/
而不会包含 url=...field-keywords=... 这些参数。

POST请求的核心特点是:数据在后台发送,对用户不可见。

POST方法的影响

由于数据不包含在URL里,POST请求会带来以下影响:

  • 不可书签化:用户为结果页面添加书签时,只会保存基础的URL(如 https://www.amazon.com/s/),丢失了具体的搜索条件。再次打开书签时,无法重现之前的搜索结果。
  • 不可直接分享:通过邮件发送URL给他人,他人无法看到你之前提交的表单数据所产生的结果。
  • 非幂等性:POST请求通常用于改变服务器状态(如提交订单、发布帖子)。因此,它不是幂等的,重复提交可能会导致重复操作(例如重复下单)。浏览器在刷新一个由POST请求生成的页面时,通常会弹出对话框询问“是否要重新提交表单信息”。

如何选择GET与POST

选择哪种方法取决于表单的用途。以下是一些指导原则:

  • 使用GET请求的场景:适用于获取信息且不改变服务器状态的查询操作。例如:

    • 搜索引擎查询
    • 字典或维基百科的词条查找
    • 任何希望结果可以被收藏或分享的查询页面
  • 使用POST请求的场景:适用于改变服务器状态或包含敏感信息的操作。例如:

    • 用户登录(包含密码)
    • 在线购物下单
    • 在论坛或社交媒体发布新内容
    • 修改服务器上的用户资料或数据

核心原则:如果表单提交会改变服务器上的某些东西(数据库、文件、状态),请务必使用POST。如果只是查询并展示信息,使用GET可以为用户提供更好的便利性(书签、分享),但需注意URL长度限制和隐私信息泄露问题。

总结

本节课中我们一起学习了网页表单中GET与POST两种提交方法的奥秘。

  • GET 方法将数据编码在URL中,适合用于不改变服务器状态的查询,其结果可被收藏和分享。
  • POST 方法将数据隐藏在HTTP请求体内,适合用于会改变服务器状态的操作(如提交订单、发布信息),能更好地保证操作的安全性和准确性。

正确选择 method 属性,是构建一个既符合HTTP规范,又拥有良好用户体验的网页表单的关键。

L14.3:响应式网页设计 🖥️📱

在本节课中,我们将要学习响应式网页设计。这是一种创建网页的技术,旨在让网页能够根据用户所使用的设备(如手机、平板电脑或桌面电脑)自动调整其布局和样式,以提供最佳的浏览体验。

网页与印刷页面的关键区别

上一节我们介绍了网页设计的基本概念,本节中我们来看看网页设计与传统印刷设计的一个核心区别。

使用网页与使用印刷页面的主要区别之一在于,设计者无法控制输出设备。当我们使用 Microsoft Word 时,我们是在为打印的页面设计。我们可以指定纸张尺寸,例如 8.5 x 11 英寸或 17 x 11 英寸。

然而,在使用网络浏览器时,我们没有这种控制权。我们的网站可能在一个小手机上显示,也可能在一个大平板电脑、巨大的桌面计算机甚至某人的高清电视上显示。我们无法控制输出设备,因此需要创建适用于所有这些不同设备的网页。

响应式设计的必要性

这是一个问题,因为网页设计的吸引力和实用性会因设备尺寸而异。在手机上看起来很棒的布局,在高清电视上可能看起来很糟糕,并且无法很好地工作。

此外,各种用户界面元素在某些设备上运行良好,但在其他设备上可能不行。以下是几个例子:

  • 工具提示:许多网站使用工具提示。你将鼠标移到某个元素上,停留一会儿,就会得到关于该元素的信息。但手机无法使用这个功能,因为你不能将手指悬停在特定元素上以调出工具提示。
  • 操作精度:在手机上工作时,你的手指与鼠标指针相比非常大。使用鼠标或触控笔,你可以获得精细的控制,可以小心地点击屏幕上的小元素。但在手机上,要达到同样的精度则困难得多。

因此,取决于我们正在使用的实际设备,有些设计会很好地工作,而有些设计会很糟糕。所以问题是我们将如何应对。

早期的解决方案及其局限性

在响应式设计出现之前,主要有两种方法,但它们都有局限性。

第一种方法是设定最小标准尺寸。多年来,网站设计者会设定一个目标尺寸。例如,网站最初为640像素宽的设备设计,然后提升到800像素,再到1024像素。网站每隔几年就会为此重新设计。如果你用一个较小的显示器访问该网站,就会出现水平滚动条,因为网站内容不适合你拥有的空间。如果你用一个更大的显示器访问,网站仍然卡在特定的尺寸上,无法充分利用屏幕空间。

第二种方法是为不同设备创建不同的网站。最显著的是通常有桌面版本和移动版本。其原理是,当你的浏览器向网站发出HTTP请求时,会发送一个“用户代理”信息。网络服务器可以查看该用户代理,并决定将用户引导到桌面版本或移动版本。

这种方法效果也不佳。你必须维护所有不同用户代理的更新列表以及它们应被引导到的位置。这意味着你需要维护多个不同的网站(例如一个用于桌面,一个用于移动),这增加了开发和维护成本。此外,还存在设备识别不准确的问题。例如,iPad经常被识别为移动设备,从而被引导到为小手机设计的移动版网站,无法充分利用其大屏幕的优势。

响应式网页设计方法

所以,实际上有一个更好的方法,即响应式网页设计

响应式网页设计要做的是:获取有关用户正在使用的实际设备的信息,询问该设备的特征是什么,然后在此基础上应用不同的样式规则。

这与级联样式表协同工作,以便为不同的设备使用不同的CSS规则。正如我之前提到的,这对于基于网格的布局尤其好用。

响应式设计示例

让我们看看这可能如何工作。我将首先给出一个概述示例。

假设我们有一个网页,左侧是导航栏(例如斯坦福宿舍列表),中间是新闻文章,右侧是一个日历。在宽屏桌面设备上,三者并排显示。

但是,当网页在某些时候变得越来越窄时,将日历放在右侧实际上没有意义。我们可以这样做,但新闻文章将变得更小。所以我们要做的是将日历移动到新闻文章下方。如果屏幕变得更窄,我们不再将图像浮动在文章一侧,而是让图像占据网络浏览器窗口宽度的100%,然后把文章的文本放在它下面,最后把日历放在最下面。

这是一个三阶段的设计。你可以有任意多的阶段。

实现技术:媒体查询

现在,让我们仔细研究一个更具体的示例。在左边,我有一个所有斯坦福宿舍的列表。我们要做的是,随着网络浏览器窗口变得更窄,我们将完全移除宿舍列表。

那么我们将如何做到这一点?实际上非常简单,基本上我将介绍媒体查询这个概念。

左边的宿舍列表位于 <nav> 导航元素内。通常情况下,导航元素浮动到左侧。但我所做的是添加一个媒体查询:

@media (max-width: 480px) {
  nav {
    display: none;
  }
}

换句话说,如果屏幕宽度是480像素或更少,那么只要去掉那个导航元素,就根本不显示它。

但在大多数情况下,你并不想完全摆脱导航元素,而是想移动它们。这就是我们下一个例子要做的。

我实际上要把它们放在屏幕顶部,当窗口变得非常狭窄时。这就是我再次这样做的方式:

@media (max-width: 600px) {
  nav {
    float: none; /* 移除浮动 */
  }
  nav li {
    display: inline; /* 将列表项改为行内显示 */
  }
}

在这里,如果最大宽度为600像素或更小,我应用两个不同的规则:

  1. 移除导航元素上的浮动。
  2. 将所有列表项从它们的标准“块”显示转为“内联”显示。

列表项通常是块级元素,它们会创建文本块。通过将其改为 display: inline,它们的行为就像 <span> 标签一样,使列表从左到右水平排列,而不是从上到下垂直排列。

媒体查询的多种用法

你可以通过将媒体查询直接放入样式表来实现,如上例所示。实际上,你还可以做一些类似的事情,例如使用 @import 规则,或者为不同的级联样式表文件创建多个 <link> 标签。

<!-- 为不同屏幕宽度链接不同的CSS文件 -->
<link rel="stylesheet" media="(max-width: 600px)" href="small.css">
<link rel="stylesheet" media="(min-width: 601px)" href="large.css">

可查询的设备特征

最后,我想让你了解一下我们可以询问哪些设备特征,以便确定使用哪组样式表或样式规则。

以下是你可以查询的一些重要特征:

  • 宽度和高度width, height, min-width, max-width, min-height, max-height。你可以确定用户设备的宽度和高度,或者他们浏览器窗口的尺寸。
  • 方向orientation。你可以确定他们的设备是处于横向模式还是纵向模式。
  • 纵横比aspect-ratio。屏幕的宽高比。
  • 颜色color, monochrome。有关设备能够显示多少种颜色的信息。
  • 分辨率resolution。屏幕的像素密度,即每英寸有多少点。

所有这些都将帮助你确定你的网页应该具有什么样的外观。

还有一些我认为真的很有趣的查询,例如:

  • 指针精度pointer。取决于用户是使用手指(粗指针)选择项目,还是使用更细粒度的指针,如鼠标或触控笔。你可以据此调整点击元素的大小。
  • 悬停能力hover。你可以查询用户是否有能力进行悬停操作(例如使用鼠标)。如果他们使用手机手指,则答案是否定的,因此你可以移除或改变依赖悬停的功能(如工具提示)。

因此,整个想法是响应有关用户设备的信息,并使用你的级联样式表来更改网页的显示方式。

总结

本节课中我们一起学习了响应式网页设计。我们了解了为何需要响应式设计(因为设备尺寸和交互方式的多样性),回顾了早期解决方案(固定尺寸和独立移动站)的局限性,并深入探讨了现代响应式设计的核心——媒体查询。我们学习了如何通过查询设备宽度、方向、指针类型等特征,来动态应用不同的CSS规则,从而创建出能自动适应手机、平板和桌面等各种设备的灵活网页布局。如果你打算进行专业的网页设计,掌握响应式设计是非常重要的。

L15.1:人机交互 👨‍💻

在本节课中,我们将要学习计算机科学中的一个重要研究领域——人机交互。我们将了解它的定义、重要性、相关学科以及设计用户界面的基本流程。

人机交互,或 HCI,是计算机科学中的一个重要研究领域。它专注于如何改善人与计算机之间的交互。您今天正在使用的计算机是一个好兆头。HCI的成功是因为HCI负责诸如我们都在使用的 Windows图标、鼠标和指针(或 WIMP 界面)之类的东西。以及事实上你们都在使用图形用户界面,而不是命令行界面(我们在其中键入计算机命令)。这显示HCI研究人员对我们的世界产生了巨大影响。

目前HCI的热门话题包括虚拟和增强现实、语音命令、手势(您可以用手与计算机进行交互)。HCI研究人员甚至正在研究脑机接口。我一直喜欢指出的一件事是,当我教我的课时(因为我教了很多非计算机科学的学生),是有很多不同的领域实际上对HCI工作非常重要。你们中的一些人现在可能正在研究这些领域。

与HCI相关的领域 🧠

上一节我们介绍了HCI的核心概念,本节中我们来看看哪些其他学科对HCI至关重要。

以下是几个对HCI工作非常重要的领域:

  • 心理学:心理学在许多不同的方面与HCI直接相关。更好地理解人类认知的一件事可以带来更好的应用。因此,诸如人类如何感知颜色,或人类记忆的局限性,应该推动我们的应用。此外,心理学研究与计算机科学用户界面研究有很多相似之处。所以在心理学大楼的地下室里,有一堆房间,我们在那里介绍心理学学生,要求他们执行任务。在单向玻璃镜子的另一边,我们有心理学研究生正在观察他们如何执行这些任务。HCI研究人员在大型计算机科学公司做完全相同的事情。所以我们有相同的房间,带有单向玻璃,我们在那里安装了一台计算机。我们带来了一些测试对象,我们要求他们在我们的计算机上执行任务。我们看看他们实际上如何与计算机交互,他们是否能够以我们期望的方式使用计算机。通常对于大公司,我们实际上会有心理学家,他们了解如何正确运行这些实验。
  • 平面设计:当我在工业界工作时,我们有一群来自艺术学校的人,专门为HCI工作而工作。
  • 人体工程学:显然人体工程学非常重要。
  • 社会学和民族志:社会学和民族志以及其他相关领域对HCI也非常重要。我认为,特别是现在,有这么多的应用程序是为多人使用而设计的。如何帮助群体互动,社会学家的重要性当然是显而易见的。但另一种方式社会学家和民族志学者真的很重要是,如果我们被要求进入并帮助改善特定的工作场所,我们需要社会学家和民族志学者进入并研究该工作场所,并在我们可以帮助自动化并将工作场所计算机化之前,很好地了解该工作场所发生的事情。

用户界面设计流程 🔄

了解了HCI的跨学科特性后,我们来看看专家在尝试设计新用户界面时通常会遵循的流程。

让我们快速浏览一下用户界面专家在尝试设计新用户界面时所做的一些事情。您应该做的第一件事是找到一些需求。我们要确定用户的实际需求是什么。所以我们将花一些时间观察工作场所、研究当前流程、并采访潜在用户。然后我们将确定该工作场所中的不同角色以及该工作场所中定期执行的不同任务。然后我们将进行一些原型设计。我们将对这些原型进行一些测试。然后我们将迭代。这最后一部分(迭代)是非常重要的,因为事实证明,我们并不是特别擅长提出对工作场所真正有帮助的计划。因此非常重要的是,我们不仅要采访人们,并根据他们的反馈提出想法,还要进行测试和迭代。在采访中给我们的只是初步想法。我们测试它们并再次尝试它们。实际上并不少见用户认为他们想要一个程序,然后当他们实际尝试该程序时意识到,“哦,这并不像我想象的那么有用。我想也许我们真正想要的是另一件事。”所以,你与潜在用户一起尝试不同的东西,并最终真正确定他们真正需要什么,而不是他们最初感知的担忧,是超级重要的迭代过程。

原型设计:从低保真到高保真 🎨

迭代过程的核心是原型设计。本节中我们来看看不同保真度的原型及其作用。

您可以构建的原型质量范围从低保真到高保真,或者有时会提到低保真到高保真。

以下是几种不同保真度原型的示例:

  • 低保真原型:示例包括一张非常低保真度的纸草图。您现在只需在一张纸上绘制图表。这些草图仍可用于用户界面实验。因此您可以与人们讨论他们从一个显示特定窗口的图表开始的过程。而你知道,“嘿,你会在哪里点击这个窗口?你认为你应该使用哪些按钮?”
  • 中保真原型:例如线框图。这是在计算机上完成的,但我们不关心实际应用程序中的细节,例如颜色和字体。但线框仍然让我们很好地了解该屏幕上可能有什么样的信息。我们可以制作一个更高质量的模型,其中包含字体和颜色以及其他实际有效的东西。
  • 高保真原型:然后我们可以拥有更高保真度的原型,用户可以在其中实际与它们交互,并按下某些按钮。因此,这可能起作用的方式是假设我们在网上书店工作(一个例子,我们将在一分钟内更详细地使用)。我们可以告诉某人,“嘿,继续在这里与我们的高保真模型交互,并查找一本关于南美历史的书。”因此,用户希望找到正确的文本字段以输入他们的搜索词组,并输入“南美洲历史”,然后他们会按回车键。我们将移至另一个屏幕,显示查找“南美洲历史”的结果。但这里发生的关键是,您输入的内容并不重要。当您单击搜索按钮时,文本字段只会转到“南美历史”的结果,因为这是我们唯一将其硬连线的内容。因此在这里您有一个原型,它看起来真实并且有一些交互,但并不相同于一个真正的程序,它要简化得多。

所以这些更高保真度的原型可以提供更好的结果,但它们有些问题。因为事实证明,团队投入的时间和精力越多去创建这些原型,他们越难接受结果表明他们花费了所有时间的高保真原型实际上是错误的方法。如果你唯一做过的事情是在一张纸上画了一些图表,然后你开始与用户互动,然后发现你的想法是错误的,放弃它要容易得多。制作纸质图表的成本花费了很少时间。然而,拿出这些图表花费的时间要少得多,无论它们是在纸上完成的,或者在计算机上实际拥有交互式原型。

案例研究:在线书店 📚

我提到过我们将讨论一下书店。所以我想做一个小案例研究来帮助您了解,如果我们正在设计一个新程序,我们应该考虑一些事情。

所以在这种特殊情况下,我想我和助教们聚在一起,我们决定要做一个新的创业公司。我们认为最好的主意是创建一个在线书店(看起来是个好主意)。我认为没有人做对了,所以让我们看看我们如何完成创建这个在线书店的过程。

所以我们需要考虑我们的用户是谁。记住我之前说过的话,我们想考虑角色和任务。对于这个特定的视频,我将专注于我们与客户的互动。但如果我们真的要这样做,就会有与书店员工有关的另一组任务。但我们将只关注客户。

识别用户与任务

因此我们想考虑我们的网站访问者是谁,为什么他们访问该网站,他们的技术水平如何,他们拥有什么样的设备,以及他们的互联网连接看起来有多快、多可靠,像这样的事情。

然后我们想考虑他们可能想要与我们的网站进行交互的不同任务:

  • 寻找特定书籍:显而易见的是,我正在寻找一本特定的书。
  • 探索特定主题:但这并不是某人访问网站的唯一原因。他们可能对特定主题感兴趣,但对特定书籍不感兴趣,或对与该主题相关的特定书籍没有概念。这确实是一个关键区别。我认为斯坦福图书馆在这方面做得更好。但是很长一段时间内,当您寻找特定书籍时,他们有一个很棒的界面,它会告诉您这本书在哪个图书馆、我们有多少副本、该副本签出的时间、以及它接下来将可用的内容。所以这是超级有用的。但是当谈到我有一个我感兴趣的主题但我不知道该主题的特定书籍时,这是一个可怕的界面。因此您需要仔细考虑所有原因,为什么有人可能会来我们这里的小书店。你想仔细考虑如何满足这些需求。
  • 浏览与发现:然后这是底部的第三个需求。你知道他们可能坐在课堂上,他们可能很无聊。他们每次参加探索性计算机课程时都想访问我们的网站。我们很乐意让他们这样做。我们希望找到新的和有趣的内容来向他们展示,他们每天对他们感兴趣的事物类型有所了解。你知道找到向他们展示这些书的漂亮的有光泽的方式,这样他们就会经常来,并希望对`我们展示给他们的东西感到兴奋,并最终购买它们。

所以这些是有人访问我们网站的三个非常不同的原因。我们可能需要在我们网站上做的事情来满足这三个不同的任务中的每一个,实际上是完全不同的。所以你需要仔细思考为什么人们在这里,而不仅仅是你知道什么是最低限度的事情有人可能需要来访问我们的网站,但尝试尽可能广泛,这将为您提供更广泛的受众范围,这将为您提供一个整体更好的网站。

创建用户角色

然后我们可能想要做的就是来获取关于潜在客户的信息,并记住有很多不同的人访问我们的网站,他们都有不同的特征。

所以,经常使用的一种策略实际上是针对用户角色的。这里有一些潜在的客户角色:

  • Nikki(年轻专业人士):她用手机访问网站。她的手机网速适中,但她经常在回家的路上访问,在我们乘坐巴士回家时,工作和互联网连接通常有点不稳定。
  • Patrick(老年人):他的技术专长较低,并且使用 Windows 95 笔记本电脑访问网站。所以您知道我们是否愿意为他提供支持,他的网络浏览器可能是很老了,他可能有一些安全问题,因为微软不再支持 Windows 95。这实际上是一个好点。所以你需要考虑你要支持的人范围。因为你支持的人越多,你在他们的网络浏览器中遇到问题的可能性越大,他们的网络浏览器的兼容性就越低,您将能够使用的酷CSS内容就越少。所以他有一台非常旧的笔记本电脑,但他确实有良好的互联网连接。
  • Maddie(初中生):她有中等技术水平,她有一台低端平板电脑,但她确实有良好的互联网连接。

所以这里的想法是我们可以考虑我们确定客户可能在网站上做的不同任务。然后在我们讨论设计时,我们实际上可以提出这些角色。所以我们可以说,“好吧,这似乎是一个有趣的设计。我可以看到Nikki将如何弄清楚如何使用它。但是Patrick如何才能弄清楚这一点?”或者你知道,如果我们正在考虑我们网站上显示的各种信息,您可能会问,“Nikki是否能够在她上下班途中很好地看到网站的这一部分?如果我们占用了太多带宽,她无法这样做,我们将如何为她提供替代方案?”因此创建这些不同的角色并为他们命名,让我们可以在开会时谈论这些类型的用户。

用户测试与迭代

然后我们想要进行用户测试,就像我们之前讨论过的那样。您将拥有不同质量的不同原型,并且您可能想要从低保真原型开始,因为高保真原型需要花费大量时间和精力进入。所以你知道花一些时间在低保真原型上,如果看起来你在正确的方向上,你可以继续改进它们并提出越来越多的更高质量的原型。

你需要得到一群没有直接参与项目的用户。事实上你可能想尝试获得与你的角色匹配的用户。这样你就知道,我们是否认为我们会让使用 Windows 95 笔记本电脑的老年人访问我们的网站?找一些可能没有很多技术专长的老用户,把他们带到你的原型前,无论你在做什么级别的原型制作。然后问他们如何执行特定的任务。假设你说,“好,假设这里有一个屏幕,试图找到Jane Cardinal的新畅销书,你认为你会如何用这个屏幕来做这件事?”所以你知道,他们是否看到了纸质原型?希望如果它是一幅好画,他们可以看看它,说,“哦,我看到这将如何转化为一个真正的计算机程序。我想他们可以点击这张纸,然后说,‘哦,我想我会在这个文本字段中输入一些东西。’”或者如果它是一个高保真原型,他们实际上可以与它互动。他们看到那里的文本字段,他们可以在上面单击鼠标,然后他们可以继续在那里输入信息。我们可以问他们,“假设你正在寻找一本关于南美历史的书,你会怎么做?”他们可以看看你的原型,并确切地弄清楚他们将如何与它交互。

总结 📝

本节课中我们一起学习了人机交互。HCI就像我说的那样,是计算机科学中一个非常重要的领域。它专注于改善人与计算机之间的交互,涉及心理学、设计、社会学等多个学科。设计一个好的用户界面需要理解用户需求、创建用户角色、进行从低保真到高保真的原型设计,并通过用户测试不断迭代。对于可能没有很强计算机科学背景的人来说,这是一个很好的参与并帮助您开发技能的领域。

L15.2:网站设计 🎨

在本节课中,我们将要学习网站设计,特别是如何通过品牌塑造使你的网站具有独特性和可识别性。我们将探讨品牌的核心要素,包括徽标、字体、配色方案以及布局与导航,帮助你打造一个既专业又吸引人的网站。


品牌塑造的核心要素

上一节我们介绍了网站设计的目标是建立独特的品牌。本节中,我们来看看构成品牌的四个关键要素。

以下是品牌塑造的四个要素:

  1. 徽标:确保徽标清晰可辨,并出现在所有网页或至少某些版本上。你可以在内页使用简化版本,在主页面上使用精美版本。
  2. 排版:为网站选择字体,主要考虑无衬线字体、衬线字体或等宽字体。
  3. 配色方案:选择一套协调的颜色来定义网站的整体视觉风格。
  4. 布局与导航:设计清晰的页面结构和导航路径,确保用户能轻松找到所需内容。

徽标设计

关于徽标,你需要确保它清晰可辨,并出现在你的所有网页或至少某些版本上。你可以选择在内页上使用简化版本,然后在主页面上使用精美版本。

这里要记住的是,如果你的网站很成功,人们会进行“深度链接”。这意味着人们会链接到你网站深处的单个页面。许多业余爱好者常犯的一个错误是只专注于主页上的品牌,因为他们认为每个人都会通过主页访问。但考虑到人们经常通过邮件或社交媒体分享特定页面的链接,你就知道这不是真的。用户可能直接访问一个产品页面或一篇电影评论,这些都是网站的内页。所以,请始终牢记深度链接的存在。


字体选择

为网站做出的关键设计选择之一是字体的使用。我们将讨论三种基本字体选择。

以下是三种基本字体类型:

  • 无衬线字体:例如 ArialHelvetica。这种字体通常被认为是现代和乐观的。
  • 衬线字体:例如 Times New RomanGeorgia。这种字体通常被认为是正式和权威的,有时也被视为老式。
  • 等宽字体:例如 Courier New。这种字体中所有字符的宽度都相同。

真实网页经常混合使用这些字体,但大多数网站主要使用无衬线或衬线字体,等宽字体则用于特殊目的。

衬线字体 vs. 无衬线字体

让我们确保每个人都了解衬线是什么以及无衬线是什么。在左边我有一个无衬线字体,在右边我有一个衬线字体。衬线是字母末端的小装饰线。

设计人员会告诉你,衬线字体是正式和权威的,尽管这种观点也认为衬线字体是老式的。而无衬线字体是乐观和现代的。你需要考虑你网站的目的是什么,以及在选择字体时你更喜欢现代感还是权威感。

实际案例分析

让我们看看一些实际的网站,看看他们选择了哪种字体。

  • 《纽约时报》:它可能更希望展现权威形象。观察其网站,标题和新闻报道主要使用衬线字体,而侧边栏和一些导航元素则使用无衬线字体。
  • MSNBC:它试图展现更现代的形象。其网页上可能完全没有衬线字体。
  • 苹果公司:其页面完全没有衬线字体,他们想要当代、乐观的外观,对成为老派的权威正式形象不感兴趣。
  • 纽约 Mandarin Oriental 酒店:作为一个五星级酒店,它主要使用衬线字体来体现正式和奢华感,尽管顶部导航栏使用了无衬线字体。

等宽字体

我提到了等宽字体,让我们谈谈等宽字体。因此,你的主要选择是衬线或无衬线字体。还有另一种字体叫等宽字体。实际上,等宽字体可以有衬线,也可以是等宽字体中的无衬线体。其特点是所有字符的宽度都相同。

这里我有一个非等宽字体,注意字母“i”比“m”窄得多,这被称为可变宽度或比例字体。相比之下,这是一个等宽字体,在底部有“miwim”,你可以看到那里的“i”比非等宽字体要宽得多。如果我们在这里反转字母,我们肯定可以看到这一点。

等宽字体背后的基本思想是所有字符的宽度都相同。等宽字体通常用于表示程序代码或计算机输出。例如,在 Visual Studio Code 等代码编辑器中,默认设置会使用某种等宽字体,这会让所有代码排列得更整齐。


配色方案

我们的下一个主题是选择配色方案。要选择配色方案,我们实际上需要更多地了解色彩理论。

色彩模型:RGB 与 HSB

我们一直在使用红色、绿色和蓝色(RGB)模型,而 RGB 是计算机显示器实际创建颜色的方式。但这是考虑颜色的其他方法,其中之一是色相、饱和度、亮度(HSB)方案,这就是我们将要使用的方案。

  • 色相:你可以将色相视为彩虹的颜色,即色谱的颜色。
  • 饱和度:除了选择色调,我还可以改变饱和度。左边是完全饱和,最右边几乎没有饱和度。如果我把饱和度降得足够低,最终会得到白色。
  • 亮度:我还可以控制亮度。在最左边,我有最大亮度,然后光线越来越少,在最右边我没有亮度。

所以我建议,RGB 是电脑显示器实际生成颜色的方式,而 HSB 是人们经常用来确定他们的配色方案应该是什么的方案。

单色配色方案

基于我们目前所知,一种可能的配色方案是只需选择一种色调,然后通过降低饱和度或增加/降低亮度来改变颜色。这被称为单色配色方案。这当然有效,它看起来不会很糟糕,但也可能看起来不那么令人兴奋。

使用色轮

我们想了解一下如何选择色调。通常,我们会使用所谓的色轮来选择色调。让我们创建一个色轮。我将从我们的三基色开始。记住,在计算机上我们使用的是加色,所以三基色是红、绿、蓝。然后我要做的是混合颜色。我要把红色和蓝色混合得到紫色,将绿色与红色混合得到黄色,将绿色与蓝色混合得到青色。通过将每种颜色与其最近的邻居混合,我们得到了六种颜色的色轮。然后我可以再做一次,获得12色色轮。事实上,Adobe Photoshop 等绘画程序内置了色轮,通过不断混合颜色,我们可以得到连续的颜色范围。

创建配色方案

可以使用色轮创建多种不同的配色方案。

  • 互补色:最简单的配色方案选择是互补色。我们在色轮的一侧取一种颜色,然后选择在色轮正对面的颜色。例如,蓝色和黄色。许多大学(如加州大学系统)就使用蓝色和黄色的变体作为校色。
  • 分裂互补色:如果你想要两种以上的颜色,你可以使用分裂互补方案。在色轮的一侧取一种颜色,在另一侧选择两种相邻的颜色。
  • 三色系:或者你可以使用三色配色方案,其中所有三种颜色在色轮上的距离相等。
  • 类似色:或者你可以使用类似的配色方案,其中你选择的颜色在色轮上彼此接近。

布局与导航

我想提到的最后一件事是布局和导航问题。在创建网页时,尽量确保你对人们将如何浏览你的网站有一个清晰的想法。

提供清晰的导航

在任何一个页面上,提供一个关于网站上其他可用内容的清晰感。正如我之前提到的,如果你有一个成功的网站,你会让人们深度链接到你的网页。所以,你的任何一个网页都应该让访问那个特定页面的人清楚,你的网站上还有很多其他可用的东西。你应该让它非常清楚如何进入主页,并让他们了解你网站的其他部分。

保持一致性与避免死胡同

提供一致的导航方案。这样,用户从一个页面转到下一个页面时,不必每次都思考应该点击什么去下一个地方。而且,要确保没有“死胡同”页面——即用户离开该页面的唯一方法是点击浏览器的后退按钮。如果有人最终链接到该页面,他们将无法找到通往你网站其余部分的方法。在我评分过的许多业余网站中,经常看到有网页但不清楚如何访问它,或者存在无法导航出去的页面,这是非常无用的。


总结 🎯

本节课中,我们一起学习了网站设计的核心要素。我们探讨了如何通过独特的徽标、恰当的字体选择、协调的配色方案以及清晰的布局与导航来塑造网站品牌。记住,好的设计不仅能吸引用户,还能让他们轻松地在你的网站中找到所需信息,并留下深刻印象。避免创建无法访问或无法离开的页面,始终从用户的角度思考导航的便利性。

L16.1:Python 介绍:与 Python Shell 交互 🐍

在本节课中,我们将要学习 Python 编程语言的基础,特别是如何与 Python Shell 进行交互。我们将了解 Shell 是什么,如何执行基本的算术运算,以及如何理解变量和运算符优先级等核心概念。


什么是 Shell? 💻

上一节我们介绍了课程背景,本节中我们来看看什么是 Shell。

Shell 是一个程序。Shell 是用户与操作系统交互的接口。用户通过键入命令与 Shell 进行交互。在这个视频中,Shell 是一个程序,它最终实现了用户与程序的交互。


启动 Python Shell 🚀

要开始使用 Python,你需要启动 Python Shell。你可以在你的电脑上打开终端或命令提示符,然后输入 python 来启动它。


基本算术运算 ➕➖✖️➗

现在我们已经启动了 Shell,接下来看看如何在其中执行基本操作。Python Shell 可以作为一个强大的计算器使用。

以下是你可以执行的基本算术操作:

  • 加法:使用 + 符号。例如,输入 2 + 3
  • 减法:使用 - 符号。例如,输入 5 - 2
  • 乘法:使用 * 符号。例如,输入 3 * 4
  • 除法:使用 / 符号。例如,输入 9 / 3
  • 指数运算:使用 ** 符号。例如,2 ** 3 表示 2 的 3 次方。
  • 取模运算:使用 % 符号。它返回除法后的余数。例如,17 % 5 的结果是 2。

当你输入 2 + 3 时,Shell 会立即显示结果 5。这就是你如何看待基本算术。你可以通过 Shell 执行这些操作。


运算符优先级与括号 🔢

当我们进行更复杂的计算时,理解运算顺序很重要。例如,5 + 3 * 2 的结果是多少?

Python 遵循标准的数学运算符优先级:乘法和除法在加法和减法之前进行。所以,3 * 2 先计算得到 6,然后 5 + 6 得到 11

如果你想改变运算顺序,可以使用括号 ()。例如,(5 + 3) * 2 会先计算 5+3 得到 8,然后 8 * 2 得到 16


变量与赋值 📦

仅仅进行计算还不够,我们常常需要保存结果以供后续使用。这时就需要用到变量。

变量就像计算机内存中的一个存储位置,你可以给它起一个名字(标识符)并存入一个值。

以下是关于变量的要点:

  • 使用等号 = 进行赋值。例如,x = 5
  • 变量名可以包含字母、数字和下划线,但不能以数字开头。
  • 赋值后,你可以通过变量名来使用这个值。例如,输入 x * 2 会得到 10

例如,我们可以将摄氏温度转换为华氏温度。公式是:华氏度 = 摄氏度 * 9/5 + 32

在 Python Shell 中,你可以这样做:

celsius = 19
fahrenheit = celsius * 9 / 5 + 32
print(fahrenheit)

print() 函数会将变量的值显示在屏幕上。

变量 celsiusfahrenheit 就存储在计算机的内存中,程序可以随时访问它们。


数值类型:整数与浮点数 🔢

Python 主要处理两种数字类型:

  • 整数:没有小数部分的数字,如 5-30
  • 浮点数:包含小数点的数字,如 3.142.0-0.5

当你进行除法运算时,即使能整除,结果也通常是浮点数。例如,4 / 2 的结果是 2.0


字符串简介 🔤

除了数字,Python 还可以处理文本,这称为“字符串”。字符串需要用引号括起来,单引号 ‘’ 或双引号 “” 都可以。

例如:

name = “Stanford”
print(“Hello, “ + name)

这段代码会输出:Hello, Stanford+ 操作符可以连接两个字符串。


总结 📝

本节课中我们一起学习了 Python 编程的基础入门知识。我们首先了解了 Shell 是用户与计算机系统交互的界面。然后,我们学习了如何在 Python Shell 中执行基本的算术运算,并理解了运算符的优先级规则。接着,我们引入了变量的概念,它允许我们在内存中存储和操作数据。我们还简单区分了整数和浮点数这两种数值类型,并接触了用于表示文本的字符串。

通过这些练习,你已经迈出了与 Python 交互的第一步,为编写更复杂的程序打下了基础。

L16.2:Python 介绍:我们的第一个 Python程序 🐍

在本节课中,我们将学习如何创建并运行你的第一个Python程序。我们将从使用Python Shell作为计算器开始,逐步过渡到编写一个可以保存和重复使用的完整程序。课程的核心是学习如何使用print函数输出结果,以及如何通过input函数获取用户输入,从而创建一个实用的温度转换程序。


从交互式Shell到程序文件

上一节我们介绍了如何将Python Shell用作一个基本的超级计算器,并尝试了如摄氏度转华氏度(摄氏度 * 9 / 5 + 32)和公里转英里(公里 * 0.621)等公式。

然而,上次使用的方式存在一个问题:每次想要使用这些公式时,都需要重新输入。虽然Python Shell可以作为临时的计算工具,但这并没有真正创建一个可以重复使用的程序。

为了将它们转换成真正的程序,我们需要将这些代码存储到一个文件中,以便在需要时能够重用它们。


使用IDLE的文件编辑器

幸运的是,IDLE(Python的集成开发环境)内置了一个文件编辑器。如果你正在运行IDLE(关于如何运行IDLE的说明,请参考上一个视频),你可以通过“文件”菜单创建一个新文件。

当你这样做时,编辑器窗口将会启动。现在,窗口底部显示的内容就是我们的代码编辑区域。


编写并运行第一个程序

我们的第一个程序非常简单,基本上只是将两个数字相加。例如,我们输入 3 + 2 并将其放入编辑器中。

当我们尝试通过编辑器“运行”菜单中的“运行模块”来执行它时,系统会询问我们是否要保存文件。我们将文件保存为 example.py。Python文件通常以 .py 作为扩展名。

保存后,再次选择“运行模块”来执行它。但此时,程序似乎没有输出任何内容。这是因为在Python程序中,我们需要明确地告诉解释器我们想要输出内容到Shell。


使用 print 函数输出结果

我们可以使用 print 函数来实现输出。print 函数后面跟着一对括号,括号内是我们希望它打印的内容。

例如,我们输入:

print(3 + 2)

现在,当我们运行这个程序时,可以在Shell中看到它打印出了数字 5

这样,我们就成功地让程序输出了一个数学表达式的结果。print 函数不仅可以输出数字,还可以输出文本(字符串)。记得上一课我们提到,Python可以处理整数、浮点数以及字符串。

例如:

print("go stanford")

这行代码会打印出字符串 go stanford。引号表明这是一个字符串,而不是名为 gostanford 的变量。


创建温度转换程序

上一课我们手动将摄氏度转换为华氏度。现在,让我们创建一个程序来自动进行这种转换。

以下是我们的第一次尝试:

celsius = 30
fahrenheit = celsius * 9 / 5 + 32
print("Temperature in Fahrenheit is", fahrenheit)

在这段代码中:

  1. 我们创建了一个名为 celsius 的变量,并将 30 存储在其中。
  2. 我们进行转换计算 celsius * 9 / 5 + 32,并将结果存储在变量 fahrenheit 中。
  3. 我们使用 print 语句输出结果。这个 print 语句稍微高级一些:它先打印字符串 "Temperature in Fahrenheit is",然后打印变量 fahrenheit 的值。

运行这个模块,程序会输出:Temperature in Fahrenheit is 86.0

这个程序可以工作,但它有一个问题:程序将 celsius 变量固定设置为 30。如果温度总是30度,这个程序就失去了通用性。我们希望程序能处理不同的摄氏温度。


使用 input 函数获取用户输入

为了实现这个目标,我们需要从用户那里获取输入。这是编程中获取用户信息的下一步。

我们将使用 input 函数。input 函数后面也有一对括号,括号内我们可以放一个字符串,作为给用户的提示信息。

input 函数会返回一个值,我们可以将这个值存储到一个变量中。例如:

username = input("Enter your name: ")

这行代码会提示用户“Enter your name:”,用户输入的任何内容(结果)都将作为字符串存储在变量 username 中。


完善温度转换程序

现在,我们将 input 应用到温度转换程序中。我们希望用户输入摄氏温度。

你会注意到下面的代码有两行:

temp = input("Enter temperature in Celsius: ")
celsius = float(temp)

为什么有两行?原因是 input 函数返回的是字符串数据类型。如果用户输入 30,我们得到的不是数字 30,而是由字符 ‘3’‘0’ 组成的字符串。我们不能直接将字符串用于数学公式。

因此,我们需要第二行代码:float(temp)float() 函数会将其参数(括号内的内容)转换为浮点数。在这里,它将用户输入的字符串(例如 "30")转换为实际的数字 30.0,并将这个值存储到 celsius 变量中。

实际上,我们可以将这两行合并为一行:

celsius = float(input("Enter temperature in Celsius: "))

这行代码会请求用户输入,然后将返回的结果(字符串)立即传递给 float() 函数进行转换,最后将转换后的数字存储在 celsius 变量中。


完整的交互式温度转换程序

现在,我们有了完整的程序:

# 提示用户输入摄氏温度,并转换为浮点数
celsius = float(input("Enter temperature in Celsius: "))

![](https://github.com/OpenDocCN/cs-notes-pt2-zh/raw/master/docs/stf-cs105-intro/img/1e56225584b4fc49f9911359f77fde5d_34.png)

# 进行华氏度转换计算
fahrenheit = celsius * 9 / 5 + 32

# 打印结果
print("Temperature in Fahrenheit is", fahrenheit)

程序流程如下:

  1. 要求用户输入摄氏温度。
  2. 将输入转换为浮点数并存储在变量 celsius 中。
  3. 检索 celsius 的值,进行 * 9 / 5 + 32 计算。
  4. 将结果存储在变量 fahrenheit 中。
  5. 打印出华氏温度。

你可以多次运行这个程序,每次它都会要求输入一个新的摄氏温度,并给出对应的华氏温度。这比一遍又一遍地重新键入公式要方便得多。

关于代码风格的建议:虽然技术上可以将所有计算放在 print 语句的一行内完成,但将程序分解为更小的步骤(如上面的示例)会使代码更易于阅读和调试。将太多逻辑塞进一行代码,虽然紧凑,但更容易出错,并且让他人(或未来的你)难以理解。


总结

本节课中我们一起学习了如何创建你的第一个Python程序。我们从使用交互式Shell过渡到编写保存在文件中的程序,学会了使用 print 函数输出内容,以及使用 input 函数获取用户输入。通过构建一个实用的温度转换程序,你了解了变量、数据类型转换(float())和基本运算如何协同工作。这让你初步体验了编程的样子以及编程语言是如何运作的。Python因其易用性和强大的功能,在包括科学计算在内的众多领域被广泛使用,这也是它备受推崇的原因。在下一节课中,我们将扩展编程能力,学习如何做更多有趣的事情。

L17.1:关于编程 🧑‍💻

在本节课中,我们将要学习编程的基本概念。编程并非神秘莫测,它实际上与我们日常生活中的许多活动非常相似。我们将通过类比来理解编程的核心思想,并学习如何编写和调试简单的程序。

编程与日常活动的相似性

上一节我们介绍了编程并非遥不可及,本节中我们来看看编程与我们熟悉的日常活动有何相似之处。

编程的核心是给出一系列明确的指令,让执行者(无论是人还是计算机)按顺序执行,以完成特定任务。这与我们为他人指路或按照菜谱做饭的过程非常相似。

以下是两个具体的类比:

  • 指路:如果有人问“如何从斯坦福到旧金山国际机场”,我会给出如下指示:
    1. 上沙山路向东行驶。
    2. 上280号高速公路向北。
    3. 转380号高速公路向东。
    4. 转101号高速公路向南。
    5. 进入SFO车道,即可到达机场。
  • 烹饪:制作卡夫通心粉和奶酪的说明:
    1. 把水煮沸。
    2. 加入通心粉,煮7到8分钟。
    3. 沥干水分。
    4. 加入黄油、牛奶和奶酪混合物。
    5. 搅拌均匀。

对于这两项任务,我们都给出了一系列应该逐步执行的指示,并且它们将按照列出的顺序执行。这很重要,并且不要跳过任何一个实际证明的步骤也很重要。所以如果我们的指令很好,并且用户明确地遵循它们,他们应该成功地完成我们给出的指令的任务。

这里的好消息是,计算机将始终遵循指令。事实证明,人类并非总是如此。一个尴尬的例子是:我第一次尝试制作通心粉和奶酪时,跳过了“沥干水分”的步骤,最后得到了通心粉奶酪汤,这很恶心。但如果你告诉电脑“排干通心粉”,它一定会执行。

指令顺序的重要性

上一节我们了解了计算机严格遵循指令的特性,本节中我们来看看指令的排列顺序为何如此关键。

计算机执行指令会按照列出的顺序。您列出计算机指令的顺序通常非常重要。因此,如果我们在编写程序时颠倒了语句的顺序,可能会导致程序无法工作或产生错误结果。

考虑一个将摄氏温度转换为华氏温度的小程序:

celsius = input(“请输入摄氏温度:”)
fahrenheit = celsius * 9 / 5 + 32
print(fahrenheit)

程序必须按照这个顺序发生:先获取输入,再计算,最后输出。在下面的错误例子中,顺序被颠倒了:

celsius = input(“请输入摄氏温度:”)
print(fahrenheit) # 此时fahrenheit还未计算,没有意义
fahrenheit = celsius * 9 / 5 + 32

如果我重新排序这些语句,它不会起作用。我经常看到学生只是随机重新排列他们的语句,因为它不起作用,他们认为如果只是重新排列它们,也许会解决问题。您需要仔细考虑顺序是什么,以及为什么语句按该顺序排列,并在逻辑上将它们按正确的顺序排列

在另一个将总分钟数转换为小时和分钟的例子中:

total_minutes = 125
hours = total_minutes // 60
minutes = total_minutes % 60
print(hours, “小时”, minutes, “分钟”)

在这种特殊情况下,前两个计算hoursminutes的语句顺序可以互换,因为它们的计算互不依赖。而最后的打印语句确实必须在前两个语句发生之后执行。

排序通常很重要,但并不总是很重要。解决这个问题的最佳方法是从逻辑上思考这些语句中的每一个所做的事情,判断这个语句和下一个语句之间的顺序,或者前一个语句和这个语句之间的顺序是否那么重要。

编辑-调试周期

在理解了指令和顺序后,我们需要一个有效的方法来构建和修正我们的程序,这就是编辑-调试周期。

正如我在上一堂课中所建议的,有一个编辑调试周期。我在其中编写或编辑我的代码,我尝试一下,看看它是否有效。如果它不起作用,我将返回编写或编辑代码,然后我再试一次。只要有必要,我就会继续重复这个过程。

这与我们为HTML和CSS所做的过程非常相似。这是我们将使用Python或任何其他编程语言的过程。

我认为不是这个过程分崩离析的一个领域,它仍然是相同的过程。但编程过程比使用HTML和CSS的过程更难的一个领域是:在HTML和CSS中,结果总是可见的。这就是为什么我喜欢教HTML和CSS首先,你在网页上加载它,你可以看到出了什么问题。例如,“哦,我以为我只是让标题加粗,但我看到了整个网页都是粗体”,然后我开始考虑什么可能使整个网页变粗,也许我忘了结束我的粗体标签。

在编程中,有时结果是不可见的,尤其是中间结果通常是不可见的。因为我们有这些存储位置(变量),我们将中间结果存储在这些变量中,我们不知道变量被设置为什么。

使用打印语句进行调试

既然中间结果不可见会导致调试困难,那么本节我们将学习一个让这些结果“可见”的强大工具——打印语句。

为了弄清楚程序是否运行良好,或者为什么它不起作用,很多时候它不起作用的原因是我们的中间结果是错误的。所以我将如何弄清楚中间结果是什么?因为它们作为专业程序员是不可见的。我们可以应用各种不同的工具使不可见的结果可见,但对于刚开始的人来说,最简单的事情就是使用打印语句

打印语句会将打印语句中的任何内容的值打印到Python解释器或Python Shell。假设我有一个变量x,我不知道x在做什么,它似乎不起作用。我猜x是错误的,但我不确定,因为我实际上看不到它,它是不可见的。我所做的是将这个print(x)语句添加到我的代码中。

然后您再次考虑将代码放在哪里,这取决于您关注代码的哪一部分。但您知道,如果我放在print(x)中,会发生什么是Python Shell将在那里打印x的值。我要么就像“哦,它实际上看起来像我期望的那样,所以也许问题出在另一个变量”,或者它打印出x的值,我觉得这是不对的,我认为它应该是另一个值,然后我开始深入研究代码,试图找出为什么x的值是Python解释器所认为的,与我认为应该是的相反。

实际上有一个更高级的版本:print(“x:”, x)。这要做的是:第一个“x:”用引号括起来,它是一个字符串,所以计算机会按原样打印“x:”。然后逗号后的第二个x是一个变量名,所以它会检索x的值并打印出来。假设x的值当前是3,这个打印语句将打印出x: 3。所以,如果你打印出一堆不同变量的值,它会帮助你识别。例如,在另一个地方我有print(“y:”, y),我会在一个地方看到x是12,y是15,所以它很有用。继续并添加额外的标签,当我试图弄清楚不同的变量被设置为什么时,将打印出来。

语法错误与语义错误

在编程过程中,我们不可避免地会遇到错误。了解错误的类型是有效调试的第一步。实际上有几种不同类型的错误可以当我们在编程时会发生:语法错误语义错误

当语法中有错误时会发生语法错误。例如:

celsius = input(“请输入温度:”)
fahrenheit = celsius * 9 / 5 + 32 # 这里缺少乘号,语法错误

这相当于试图用一种人类语言的语言而不是遵循正确的语法规则来理解一个句子。人类有时可以弄清楚你想说什么,但是如果你不遵守语法规则,计算机就会像“我不知道你想告诉我什么,所以我会忽略它”。所以这是第一个错误来源。通常,当你在Python编辑器中编辑你的程序时,如果你有语法错误,你告诉它运行,它会立即告诉你:“嘿,我浏览了这个程序,你告诉我运行,但在Python中有一堆完全非法的东西,这些是不允许的,这些是你的语法错误。”

另一个错误来源是语义错误。当你有一个完全合法的程序,但它实际上不是正确的时,会发生语义错误。在这种情况下,我将摄氏度转换为华氏度,我乘以9,然后除以5,结果我也应该加上32,但我没有。所以这个程序会运行,它是完全合法的、语法正确的Python,但它会给我错误的答案。

因此,与Python将发现的语法错误形成对比,一旦我告诉它运行,它会列出“嘿,这里是你做的一堆错误,这些是非法的”。语义上不正确的程序是合法的,它会运行,它只是不会给你正确的结果。所以当发生这种情况时,如果它是一个短程序,你只需要查看程序并思考“为什么我会得到这个数字,而我认为我应该得到另一个数字”。而当你有一个稍长的程序时,你必须回到我几分钟前所说的关于试图计算的内容,找出那些不可见的中间结果是什么,以及这些结果出了什么问题。所以,添加一些打印语句来弄清楚出了什么问题。

关于“错误”和编程心态

我想告诉你的最后一件事是,编程可能非常令人沮丧。而且很多时候,你刚开始是一名程序员,你认为“我的程序中有所有这些错误,这一定是我的错,我一定不是很擅长这个”。这实际上不是真的。

所以结果证明,如果你的程序中有错误代码,你的代码中有错误或漏洞,实际上有一个关于为什么将它们称为“bug”(虫子)的故事。这是杜撰的,虽然显然这个故事确实发生过,但尚不清楚这是为什么它们被称为bug。故事是:当我们有机电机器时,程序无法运行。当他们试图找出程序无法运行的原因时,有一只飞蛾卡在其中一个电磁开关中。因此,他们说“这是一个bug”。所以这个故事的一部分实际上是真的,发现这个错误的人是格蕾丝·霍珀海军少将。她创造了一个当时使用最广泛的编程语言之一,叫做COBOL。海军实际上有一张bug的照片。不太清楚的是,这实际上是名称“bug”的来源,但这是一个有趣的故事。

所以,你的代码中有错误,你的代码中有漏洞,因为你是一个人,而人类通常不会在第一次就正确地编写代码。正常情况下,这意味着您不是某种机器人。所以这主要是为了让您了解,助教们和我的代码中也有错误。所以如果您的代码中有错误,恭喜,我们在我们的代码中也有很多次错误。很多次我和我的电脑坐在那里,想把电脑扔出窗外,因为我非常确定我写的代码是正确的,但它仍然是错误的。你知道试图找出答案是非常令人沮丧的,你真的确定你搞砸了。

事实上,发生的非常罕见的情况之一是:大约五六年前,我刚刚接管了CS108,每年我都会在学生做之前自己重写家庭作业,以提醒自己学生将要努力解决的问题。我记得我写的一个程序,它实际上是第一次运行,令人震惊,这是有史以来最好的感觉。我提到了这一点,因为那是非常不寻常的,从未发生过。你不擅长编程,这是因为它(调试)是编程经验的一部分

事实上,专业人士的代码中有错误,而且他们的代码中经常有很多错误。这就是程序不断更新的原因之一。所以你知道,如果你看到操作系统正在更新,就像Windows一样不断更新,你的应用程序也在不断更新,有时它会添加新功能,但通常是因为他们发现了代码中的错误,或者他们的代码中可能存在安全漏洞,因为他们没有仔细考虑事情。

所以,在你的代码中有错误,是体验中完全正常的一部分。我认为对我们这些程序员来说,这只是实际创建一个正在运行的程序,并看到我们在脑海中想到的东西的乐趣,我们实际上已经创建了它并且它正在运行。最终让它全部运行的乐趣是值得的,尽管在调试过程中会遇到挫折。但不要觉得有什么问题,因为您的代码中有错误,这再次是成为人类的一部分。这是每个程序员都会经历的事情,包括助教们和我自己,以及所有专业程序员。

总结与预告

本节课中我们一起学习了编程的基本思想。我们了解到编程就像给出清晰的指令,关键在于指令的顺序必须合乎逻辑。我们介绍了编辑-调试周期,并学习了使用print()语句来查看不可见的中间变量值,这是调试的重要工具。我们还区分了语法错误(代码格式非法)和语义错误(代码合法但逻辑错误)。最后,我们建立了正确的心态:代码中出现错误是编程过程中完全正常且普遍的一部分,无需因此气馁。

在下一个视频中,我们将通过添加一些新结构来扩展您的编程能力,这些结构实际上将大大增强您使用程序的能力。

L17.2:控制结构:条件流程

在本节课中,我们将要学习程序中的控制结构,特别是条件控制结构。这些结构允许程序根据特定条件决定执行哪些语句,从而让程序的行为更加灵活和智能。

程序执行的顺序

在上一课中,我们讨论了程序通常按照语句编写的顺序依次执行。这就是我们希望在简单程序中发生的事情。

但有时,我们希望更精确地控制哪些语句被执行。我们可以使用称为“控制结构”的工具来实现这一点。控制结构决定了程序语句的执行顺序。

控制结构的类型

根据控制结构的特定类型,它可能会:

  • 在某些情况下执行某些语句,而在其他情况下不执行。
  • 导致计算机重复执行给定的语句,直到特定条件发生。

我们将研究几种不同类型的控制结构。今天要讨论的控制结构属于一类称为“条件控制结构”的类别。

条件控制结构简介

最简单的条件控制结构是:如果特定条件为真,则执行一段代码;否则,不执行任何操作。

更高级的版本,我们今天也将讨论,包括:

  • 如果条件为真,则执行一段代码;如果条件为假,则执行另一段不同的代码。
  • 根据特定变量的值,执行不同的代码段。例如,如果变量值为1,执行代码A;如果值为2,执行代码B;如果值为3,执行代码C。

实际上,最后一组功能可以通过组合前两组控制结构来实现。这在计算机科学中经常发生:更基础的机制可以用来实现更复杂的机制。但我们通常直接使用更复杂的机制,因为它更容易让人理解和阅读代码。

If 语句

让我们来看第一个控制结构:if 语句

以下是一个使用 if 语句的代码示例,我们将以温度为例:

temperature = float(input("输入温度:"))
print("计算机对热敏感。")
if temperature > 90:
    print("太热了!")
    print("您的计算机不会感到高兴。")
print("对您的计算机很好。")

这段代码的作用是:

  1. 使用 input 语句从用户那里获取温度。
  2. 将输入的字符串转换为浮点数,因为我们需要将其视为数字。
  3. 比较存储在变量 temperature 中的数字是否大于 90。
  4. 如果该温度大于 90 度,我们将打印“太热了!”。

让我们看看这个 if 语句的语法:

  • if 是一个关键字(或保留字),在 Python 中具有特定含义。
  • if 之后,我们有一个测试条件
  • 测试条件后面跟着一个冒号 :,这个冒号非常重要,不能省略。
  • 冒号之后,是一个或多个缩进的语句

这种工作方式是:

  1. 评估测试条件,它会给出一个真(True)或假(False)的值。
  2. 如果该测试条件为真(例如,温度大于 90),则执行下面缩进的语句。
  3. 如果测试条件为假,则跳过缩进的语句。
  4. 无论测试条件如何,后面未缩进的语句(如最后的 print)总是会被执行。

在 Python 中,语句的缩进决定了哪些语句从属于其他语句。在上面的代码中:

  • 前两行和最后一行总是会执行。
  • 中间两个缩进的 print 语句只有在 if 条件为真时才会执行。

If-Else 语句

正如之前提到的,有时我们希望在条件为真时执行一组语句,在条件为假时执行另一组语句。这就是 if-else 语句 变体提供的功能。

请看以下代码:

temperature = float(input("输入温度:"))
if temperature > 90:
    print("太热了!")
else:
    print("没关系。")

这段代码的作用是:

  • 如果温度大于 90 度,打印“太热了!”。
  • 否则(即温度小于或等于 90 度),打印“没关系。”。

这里需要注意,当进行比较时,我们需要考虑边界情况。在这个例子中,如果温度正好是 90 度,它属于“小于或等于 90”的范畴,因此会执行 else 分支,打印“没关系。”。

以下是 if-else 语句的语法:

  1. if 后跟测试条件和冒号。
  2. 缩进一个或多个语句(条件为真时执行)。
  3. else 后跟冒号。
  4. 缩进一个或多个语句(条件为假时执行)。

如果 if-else 结构后面还有其他未缩进的语句,它们总是会被执行。

链式 If-Elif-Else 语句

我们也可以将多个 if 语句链接在一起,处理多种情况。

temperature = float(input("输入温度:"))
print("计算机对热敏感。")
if temperature > 90:
    print("太热了!您的计算机不会感到高兴。")
elif temperature > 80:
    print("很温暖。")
elif temperature > 65:
    print("很好,您的电脑感觉不错。")
else:
    print("很冷,电脑喜欢又冷又好。")
print("对您的计算机很好。")

这里的执行逻辑是:

  1. 首先检查温度是否大于 90。如果是,执行其下的打印语句,然后跳过所有后续的 elifelse,直接执行最后的 print
  2. 如果温度不大于 90,则检查第一个 elif 的条件:温度是否大于 80。如果是,执行对应语句,然后跳过剩余部分。
  3. 如果温度也不大于 80,则检查下一个 elif 的条件:温度是否大于 65。如果是,执行对应语句。
  4. 如果以上所有条件都不满足,则执行 else 分支的语句。

链式结构非常方便。如果不使用它,代码会变得更加混乱,因为我们需要同时检查多个条件(例如,检查温度是否大于80 并且 小于等于90),以避免重复打印。

链式结构的一般语法是:

  • if [测试条件1]: [语句块1]
  • elif [测试条件2]: [语句块2]
  • elif [测试条件3]: [语句块3]
  • ...
  • else: [语句块N] (可选)

程序会从上到下检查条件,执行第一个为真的条件对应的语句块,然后跳出整个结构。如果所有条件都为假,则执行 else 块(如果存在)。

测试条件与布尔值

测试条件需要返回一个真(True)或假(False)的值。例如 temperature > 90 就是一个比较运算,它会产生一个布尔值。

在计算机科学中,真/假值通常被称为布尔值,以数学家乔治·布尔的名字命名。他提出了一套基于真假的数学体系,这后来成为了计算的基础。

以下是可以使用的比较运算符:

  • > :大于
  • >= :大于或等于(因为键盘上无法直接输入数学符号 ≥)
  • < :小于
  • <= :小于或等于
  • == :等于(注意:双等号用于比较,单等号 = 用于变量赋值)
  • != :不等于

示例:检查投票年龄

age = int(input("请输入你的年龄:"))
if age >= 18:
    print("你可以投票。")
else:
    print("你太年轻,不能投票。")

组合布尔条件

我们可以使用逻辑运算符将多个比较组合在一起,构成更复杂的条件。

1. 与运算符 and
and 运算符要求两边的条件同时为真,整个表达式才为真。

if temperature >= 70 and temperature <= 85:
    print("我喜欢这种天气。")

只有当温度大于等于70 并且 小于等于85时,才会打印。

2. 或运算符 or
or 运算符要求两边的条件至少有一个为真,整个表达式就为真。

if temperature < 50 or temperature > 95:
    print("太极端了!")

如果温度小于50度 或者 大于95度,都会打印“太极端了!”。

3. 非运算符 not
not 运算符将其后的布尔值反转。

if not (age >= 18):
    print("三岁就可以投票!") # 注意:这是一个逻辑反转的示例,并非实际法律

not (age >= 18) 等价于 age < 18。这个例子展示了逻辑反转,但实际代码中直接写 age < 18 会更清晰。

布尔值本身

在 Python 中,布尔值可以直接写成 TrueFalse(首字母大写)。

if True:
    print("我喜欢这种天气。") # 这行总是会打印

虽然你不会在简单的 if 语句中直接使用 True,但布尔值本身是一种数据类型,可以存储在变量中,并用于其他控制结构。

is_sunny = True
is_warm = (temperature > 70)

就像整数、浮点数和字符串一样,布尔值是我们可以存储在变量中的另一种值,并且有运算符(and, or, not)可以对它们进行操作。

总结

在本节课中,我们一起学习了条件控制结构,它们是编程中实现决策逻辑的核心工具。

  • 我们首先了解了 if 语句,它允许在条件为真时执行代码块。
  • 接着学习了 if-else 语句,它提供了条件为真和为假时的两条执行路径。
  • 然后探讨了 链式 if-elif-else 语句,用于处理多个互斥的条件分支。
  • 我们详细介绍了构成条件的比较运算符>, <, ==, !=, >=, <=)和逻辑运算符and, or, not)。
  • 最后,我们认识了布尔值True, False)作为一种基本数据类型。

掌握这些条件流程控制,是让程序变得“智能”和适应不同情况的第一步。在下一节课中,我们将继续探索其他类型的控制结构。

L18.1:Python语言附加功能 🐍

在本节课中,我们将学习Python语言的一些附加功能。这些功能在某些情况下非常方便,能帮助我们编写更清晰、更易读的代码。我们将探讨如何处理长语句、连接字符串以及如何编写注释。

多行语句的编写 ✍️

有时,一行代码太长,无法舒适地放在一行中。在某些语言中,你可以直接跨行拆分代码。但在Python中,直接换行会导致错误。

以下是几种解决方案:

  1. 使用反斜杠(\):在未完成行的末尾加上一个反斜杠,告诉Python这一行尚未结束,下一行是当前行的延续。

    name = \
    input("Enter your name: ")
    
  2. 使用括号(()):如果语句中已有括号(例如函数调用或数学表达式),Python会自动将跨行的内容视为同一语句的一部分,直到遇到右括号。

    total = (1 + 2 + 3 +
             4 + 5 + 6)
    
  3. 使用方括号([]):对于列表等数据结构,同样可以使用方括号来实现多行编写。

    my_list = [
        "item1",
        "item2",
        "item3"
    ]
    

字符串的连接 🔗

在计算机科学中,连接字符串意味着将两个字符串组合成一个。Python提供了简单的方法来实现这一点。

在Python中,只需用空格分隔字符串,它们就会自动连接在一起。

greeting = "Hello" "World"  # 结果为 "HelloWorld"

请注意,这种方法不会自动添加空格。如果需要空格,必须在字符串中包含它。

greeting = "Hello " "World"  # 结果为 "Hello World"
# 或者
greeting = "Hello" + " " + "World"  # 使用加号连接

当处理很长的字符串时,可以将其分解为多个较短的字符串,然后让Python自动连接它们。这在多行编写时特别有用。

motto = ("Die Luft der Freiheit weht "  # 注意每个字符串末尾的空格
         "- The wind of freedom blows")

此外,还可以使用三引号('''""")来创建跨越多行的字符串,无需在每行都加引号。

long_text = """This is a very long string
that spans multiple lines
without needing backslashes or plus signs."""

如何编写注释 💬

注释用于向阅读代码的人传达信息,但会被计算机忽略。在Python中,使用井号(#)来编写注释。

当Python看到井号时,它会忽略该行中井号之后的所有内容。

# 这是一个完整的注释行,计算机将完全忽略它
total = 0  # 学生总数初始化为零

在上面的例子中,第一行是完整的注释。第二行中,total = 0 会被执行,但 # 学生总数初始化为零 部分会被忽略。


本节课中我们一起学习了Python的几个附加功能:如何编写多行语句、如何连接字符串以及如何编写注释。掌握这些技巧能让你的代码更整洁、更易维护。

L18.2:列表和循环 🧮

在本节课中,我们将要学习 Python 中两个极其重要的概念:列表循环。列表是存储一系列数据的结构,而循环则允许我们高效地处理列表中的每一个元素。掌握这两者,你将能编写出处理大量数据的程序。

列表基础 📝

上一节我们介绍了本节课的主题。本节中,我们来看看什么是列表以及如何创建它。

计算机真正擅长的一件事是处理信息列表。我们并不喜欢手动处理大量信息。例如,对单个学生进行毕业检查很简单,但对成千上万名学生进行同样的检查就非常繁琐。计算机则不在乎项目的数量,它擅长对列表中的每个项目执行完全相同的计算。

在 Python 中,列表用方括号 [] 表示,其中的元素用逗号分隔。

ages = [19, 21, 20]
team_members = ["KC", "Hank", "Tammy"]

以下是创建列表时的几个要点:

  • 列表可以包含数字、字符串等多种类型的数据。
  • 列表可以存储在变量中,便于后续使用。
  • 可以创建一个空列表,即不包含任何元素的列表。
courses = []  # 这是一个空列表

访问和修改列表元素 🔍

创建列表后,我们经常需要访问或修改其中的元素。这通过索引来实现。

Python 使用方括号 [] 和索引号来访问列表中的特定元素。需要注意的是,Python 中的索引从 0 开始计数。

print(team_members[2])  # 输出:Tammy

我们也可以使用同样的方式修改列表中某个位置的值。

team_members[1] = "Joe"  # 将列表中的第二个元素(索引1)从"Hank"改为"Joe"

向列表添加元素 ➕

除了直接修改,我们还可以向列表末尾添加新元素,这需要使用 append() 方法。

append() 的语法是:列表变量.append(要添加的元素)。它会修改原列表,在其末尾添加新元素。

courses.append("CS105")
courses.append("IHUM 6A")
courses.append("ECON 1A")
# 现在 courses 列表是 ['CS105', 'IHUM 6A', 'ECON 1A']

有时你想知道列表当前有多长,可以使用 len() 函数。

num_of_courses = len(courses)  # 结果为 3

For 循环:遍历列表 🔄

我们想要对列表做的最常见的事情之一,就是处理其中的每一个项目。为此,我们将使用 for 循环

循环是一种控制结构,允许我们控制一段代码执行的次数。for 循环特别适合对列表中的每个项目执行相同的操作。

其基本语法是:for 循环变量 in 列表变量:。接下来所有缩进的代码将针对列表中的每个元素重复执行。

for course in courses:
    print("I am taking", course)

以下是 for 循环的执行过程:

  1. 第一次迭代:course 变量被设置为列表中的第一个值 "CS105",然后执行缩进块的代码,打印 “I am taking CS105”。
  2. 第二次迭代:course 变量被设置为 "IHUM 6A",再次执行打印语句。
  3. 第三次迭代:course 变量被设置为 "ECON 1A",执行打印语句。

循环体(即缩进的代码块)可以包含多条语句,它们都会针对每个元素执行。循环外部(未缩进)的代码则只在循环开始前或结束后执行一次。

循环应用示例:求和与平均值 📊

为了加深理解,让我们看两个使用循环处理数字列表的实用例子。

第一个例子是计算列表中所有数字的总和。思路是创建一个变量(如 total)初始为0,然后在循环中把每个数字依次加到这个变量上。

numbers = [100, 200, 150, 400]
total = 0
for number in numbers:
    total = total + number  # 将当前数字加到总和上
print(total)  # 输出:850

这里的 total = total + number 需要正确理解:它先计算等号右边 total + number 的值,然后将这个结果赋值给左边的 total 变量,更新其值。

基于求和的例子,我们可以进一步计算这些数字的平均值。平均值等于总和除以元素个数。

average = total / len(numbers)
print(average)  # 输出:212.5

循环应用示例:使用布尔标志 🚩

有时,我们需要在遍历列表时记录是否找到了某个特定条件。这时,布尔变量TrueFalse)就非常有用,它像一个标志。

假设我们有一个提供服务的邮编列表,需要检查用户输入的邮编是否在列表中。如果在,就打印提示;如果遍历完整个列表都没找到,则需要告知用户无法服务。

service_zips = [94301, 94304, 94305]
user_zip = int(input("What is your zip code? "))

zip_found = False  # 先设置一个“未找到”的标志

![](https://github.com/OpenDocCN/cs-notes-pt2-zh/raw/master/docs/stf-cs105-intro/img/4ceecc23b682fcb799e9184940d1c20e_36.png)

for zip_code in service_zips:
    if user_zip == zip_code:
        print("Delivery is available to you!")
        zip_found = True  # 找到了,把标志设为 True

![](https://github.com/OpenDocCN/cs-notes-pt2-zh/raw/master/docs/stf-cs105-intro/img/4ceecc23b682fcb799e9184940d1c20e_38.png)

# 循环结束后,检查标志
if not zip_found:  # 如果 zip_found 为 False
    print("Sorry, no delivery available.")

在这个模式中,我们首先假设“没找到”(zip_found = False)。在循环中,一旦找到匹配项,除了执行相应操作(打印信息),还将标志置为 True。循环结束后,通过检查这个布尔标志,就能知道是否发生过匹配,从而执行不同的后续逻辑。

总结 🎯

本节课中我们一起学习了 Python 的列表for循环。我们了解了如何创建列表、访问和修改元素、以及使用 append() 添加元素。更重要的是,我们掌握了如何使用 for 循环来遍历列表,并对每个元素执行操作,这包括计算总和、平均值以及利用布尔变量进行条件追踪。列表和循环的结合是自动化处理数据的基石,希望你通过本课的学习,能够开始编写更强大、更高效的程序。

L19.1:字符串 🧵

在本节课中,我们将要学习Python中字符串(String)的基本概念和操作方法。字符串是编程中用于表示文本数据的重要数据类型。我们将了解如何创建字符串、使用转义序列、访问字符串中的字符,以及一些常用的字符串方法。


字符串与转义序列

上一节我们介绍了字符串的基本概念,本节中我们来看看如何表示字符串中的特殊字符。

在Python中,字符串由引号(单引号或双引号)包围。有时我们需要在字符串中包含一些特殊字符,例如换行符或引号本身。这时,我们就需要使用转义序列

转义序列以反斜杠 \ 开头,它告诉Python接下来的字符具有特殊含义,而不是其字面意义。

以下是几个常见的转义序列:

  • \n:代表一个换行符
  • \t:代表一个制表符
  • \\:代表一个反斜杠本身。
  • \"\':代表字符串中的引号

例如,字符串 "Stanford\n\tUniversity" 在输出时会显示为:

Stanford
    University

其中 \n 让“University”换到新的一行,\t 让它前面有一个制表符的缩进。

如果你想在字符串中包含双引号,但不能直接用 "(因为它会与表示字符串开始和结束的引号混淆),就需要使用转义序列 \"

示例代码:

# 错误的写法:引号会提前结束字符串
# my_string = "Go "Stanford"!"

# 正确的写法:使用转义序列
my_string = "Go \"Stanford\"!"
print(my_string)  # 输出:Go "Stanford"!

字符串的序列操作

字符串和列表(List)在Python中都属于序列类型。这意味着它们共享一些相似的操作。

就像列表一样,我们可以使用方括号 [] 和索引来访问字符串中的单个字符。索引从0开始。

示例代码:

name = "Stanford"
print(name[0])  # 输出:S
print(name[1])  # 输出:t
print(name[4])  # 输出:f

我们也可以使用 len() 函数来获取字符串的长度(即包含的字符数)。

示例代码:

name = "Stanford"
print(len(name))  # 输出:8

重要区别:虽然可以访问字符串中的字符,但不能直接修改它们。字符串在Python中是不可变的(Immutable)。这与列表不同,列表中的元素是可以被修改的。


函数与方法

在操作字符串时,你会遇到两种形式的调用:函数方法。理解它们的区别很重要。

  • 函数:像一个独立的工具。你直接使用它的名字,后面跟上括号和参数。
    • 例如:print("Hello")len(my_string)
  • 方法:像一个“属于”某个特定数据的工具。你首先指定这个数据(变量),然后是一个点 .,接着是方法名和括号。
    • 例如:my_string.strip()my_list.append(item)

简单来说,方法是附加在特定数据类型(如字符串、列表)上的函数。接下来我们将看到一些常用的字符串方法。


常用的字符串方法

以下是处理字符串时非常有用的几个方法。

1. 检查字符串内容:.isdigit().isalpha()

  • 字符串.isdigit():如果字符串只包含数字,则返回 True,否则返回 False
  • 字符串.isalpha():如果字符串只包含字母,则返回 True,否则返回 False

示例代码:

data1 = "1234"
data2 = "z24"
data3 = "Hello"

print(data1.isdigit())  # 输出:True
print(data2.isalpha())  # 输出:False (因为包含数字)
print(data3.isalpha())  # 输出:True

2. 去除空白字符:.strip(), .lstrip(), .rstrip()

空白字符包括空格、制表符 \t、换行符 \n 等。

  • 字符串.strip()去除字符串两侧的所有空白字符。
  • 字符串.lstrip()仅去除字符串左侧(开头) 的空白字符。
  • 字符串.rstrip()仅去除字符串右侧(末尾) 的空白字符。

这在读取文件时特别有用,因为每行末尾通常带有换行符 \n

示例代码:

original = "  Stanford\n\t"
print(f"原始字符串: '{original}'")

data_strip = original.strip()
print(f"使用.strip(): '{data_strip}'")  # 输出:'Stanford'

data_rstrip = original.rstrip()
print(f"使用.rstrip(): '{data_rstrip}'") # 输出:'  Stanford'

实际应用场景:从文件读取多行内容时,通常会用 .rstrip() 去掉每行末尾的换行符,然后再进行处理。

示例代码:

names_list = []
# 假设 data_file 是一个已打开的文件对象
for line in data_file:
    clean_name = line.rstrip()  # 去掉行末的换行符
    names_list.append(clean_name)
# 现在 names_list 里是不带换行符的干净名字

本节课中我们一起学习了Python字符串的核心知识。我们了解了如何用转义序列表示特殊字符,字符串作为序列支持索引和长度查询,以及函数与方法的区别。最后,我们掌握了几种实用的字符串方法,包括检查内容(isdigit, isalpha)和清理数据(strip系列)。这些是处理文本信息的基础,在文件操作和数据清洗中会经常用到。

L19.2:Python文件操作 📂

在本节课中,我们将学习如何使用Python进行文件操作。文件操作是编程中的一项核心技能,它允许程序从外部文件读取数据,或将计算结果保存到文件中,从而极大地扩展了程序的功能和实用性。

打开与关闭文件

上一节我们介绍了文件操作的重要性,本节中我们来看看如何打开和关闭文件。这是与文件交互的第一步。

在Python中,使用 open() 函数来打开一个文件。这个函数需要两个主要参数:文件名和打开模式。

代码示例:打开文件

file = open("data.txt", "r")
  • 文件名:一个字符串,指定要打开的文件路径。如果文件与你的Python脚本在同一目录下,只需提供文件名即可。
  • 打开模式:也是一个字符串,告诉Python你打算对文件做什么。
    • "r":表示“读取”。如果文件不存在,Python会报错。
    • "w":表示“写入”。如果文件不存在,Python会创建它;如果文件已存在,Python会覆盖其原有内容。
    • "a":表示“追加”。如果文件不存在,Python会创建它;如果文件已存在,新内容会添加到文件末尾,不会覆盖原有内容。

完成文件操作后,必须使用 .close() 方法关闭文件。这是一个好习惯,可以确保所有数据都被正确写入并释放系统资源。

代码示例:关闭文件

file.close()

从文件中读取数据

学会了打开和关闭文件后,接下来我们学习如何从文件中读取内容。从文件中读取数据的最简单方法是使用 for 循环。

以下是一个示例程序,它读取一个包含数字的文件,并计算这些数字的总和。

代码示例:读取文件并求和

# 1. 打开文件
file = open("data.txt", "r")
total = 0

# 2. 逐行读取并处理
for line in file:
    number = int(line)  # 将从文件读取的字符串转换为整数
    total = total + number

![](https://github.com/OpenDocCN/cs-notes-pt2-zh/raw/master/docs/stf-cs105-intro/img/cade2a8903ac1e0c5226c7608f03a281_12.png)

# 3. 关闭文件
file.close()

print(total)

核心概念解释

  • for line in file: 这个循环会遍历文件中的每一行。变量 line 在每次迭代中自动被赋值为文件中的下一行内容。
  • int(line):从文件中读取的每一行内容,Python都将其视为字符串。如果我们需要将其作为数字进行计算,必须使用 int()float() 函数进行类型转换。

除了使用循环,也可以使用 .readline() 方法逐行读取。

代码示例:使用readline逐行读取

file = open(“data.txt”, “r”)
first_line = file.readline() # 读取第一行
second_line = file.readline() # 读取第二行
file.close()

当使用 .readline() 读取到文件末尾后,它会返回一个空字符串“”),这可以作为读取结束的标志。

向文件中写入数据

上一节我们学习了如何读取文件,本节我们来看看如何向文件中写入数据。写入文件同样简单。

使用 open() 函数以写入(“w”)或追加(“a”)模式打开文件后,就可以使用 .write() 方法写入内容。

代码示例:写入文件

file = open(“output.txt”, “w”)
file.write(“Hello, World!\n”) # 写入一行文本,\n表示换行
file.write(“This is a second line.”)
file.close()

重要提示

  • .write() 方法只接受字符串作为参数。如果要写入数字,需要先用 str() 函数将其转换为字符串。
  • 默认情况下,连续的 .write() 调用不会自动换行。如果需要换行,必须在字符串末尾手动添加换行符 \n
  • 务必在操作完成后调用 .close() 方法,否则数据可能不会真正保存到磁盘。

综合应用示例

掌握了基本的读写操作后,让我们通过两个更复杂的例子来巩固所学知识。

示例一:计算文件中数字的平均值

这个例子演示了如何在读取文件时,同时统计行数。

代码示例:计算平均值

filename = input(“请输入文件名:”)
file = open(filename, “r”)

total = 0
count = 0 # 新增一个变量来计数

for line in file:
    number = float(line)
    total = total + number
    count = count + 1 # 每读一行,计数加1

file.close()

if count > 0:
    average = total / count # 计算平均值
    print(“平均值是:”, average)
else:
    print(“文件为空。”)

示例二:统计成年人与儿童的数量

这个例子展示了如何根据文件内容(如年龄)进行分类统计。

代码示例:年龄统计

filename = input(“请输入包含年龄数据的文件名:”)
file = open(filename, “r”)

adults = 0
children = 0

for line in file:
    age = int(line)
    if age >= 18:
        adults = adults + 1
    else:
        children = children + 1

file.close()

print(“成年人数量:”, adults)
print(“儿童数量:”, children)

编程模式总结
在循环处理文件数据前,先初始化用于存储结果的变量(如 total=0, count=0),这是一个非常常见且有用的模式。

总结 🎯

本节课中我们一起学习了Python文件操作的核心知识:

  1. 打开与关闭:使用 open() 函数和 .close() 方法,并理解了不同模式(”r”, ”w”, ”a”)的含义。
  2. 读取文件:主要使用 for line in file: 循环逐行读取,并注意将从文件读取的字符串转换为所需的数据类型(如 int(), float())。
  3. 写入文件:使用 .write() 方法向文件写入字符串,并记得在字符串中添加 \n 来实现换行。
  4. 综合应用:通过计算平均值和分类统计的例子,学习了在文件处理中初始化变量、计数和条件判断的综合运用。

文件操作是连接程序与外部世界的重要桥梁,熟练掌握它将使你能够构建功能更强大、更实用的应用程序。

L20.1:计算机安全介绍 🔐

在本节课中,我们将要学习计算机安全的基本概念。我们将通过几个真实世界的攻击案例,来理解安全的重要性,并初步认识几个核心的安全目标。

概述

计算机安全是一个至关重要的领域,它关乎如何保护我们的信息、系统和隐私免受恶意攻击。本节课程将首先介绍几个历史上著名的安全事件,然后引出计算机安全需要达成的几个核心目标。

真实世界攻击案例

以下是几个历史上发生的、具有代表性的计算机安全攻击事件,它们展示了安全漏洞可能带来的严重后果。

案例一:GhostNet攻击(2009年)

2009年,达丨赖喇嘛办公室的计算机上被安装了一个名为GhOst RAT的程序。RAT代表“远程访问工具”。这个工具提供了对受感染计算机的远程完全控制能力,最终导致大量文件从计算机传输到攻击者手中。大多数被感染的计算机位于大使馆和非政府组织内。

调查发现,攻击始于一封来自伪造电子邮件地址的邮件,邮件附有一份关于“行动自由”的Word文档。经过为期一年的调查,攻击源头被追踪至中国海南岛,那里是某情报部门的所在地。因此,这次攻击很可能具有国家背景。

案例二:纳斯达克交易所攻击(约2010年)

2010年,FBI在检测纳斯达克计算机系统时发现了异常。系统中被植入了破坏性代码。这次攻击使用了两个“零日漏洞”。

零日漏洞指的是软件中存在的、软件供应商尚未知晓的漏洞。因此,在漏洞被公开和修复之前,攻击者可以利用它攻击任何未打补丁的系统。在这个案例中,攻击软件上的数字签名与俄罗斯联邦安全局(FSB)相符。因此,攻击者很可能是俄罗斯情报机构,或者是从FSB窃取了攻击代码的其他组织。

FBI还发现纳斯达克系统上存在其他恶意软件,包括用于金融犯罪的工具包。攻击者甚至入侵了负责纳斯达克网站管理的服务器。

案例三:ILOVEYOU病毒(2000年)

2000年5月5日,一封主题为“ILOVEYOU”的邮件开始传播。它包含一种病毒,最终感染了约4500万台计算机。它利用了一个Visual Basic脚本漏洞,该文件会将自身复制到其他文件,并向用户Outlook通讯录中的所有地址发送邮件。病毒还会覆盖多媒体文件,造成数据破坏。肇事者最终被抓获,但由于当时缺乏计算机犯罪法,他们并未受到严厉惩罚。

案例四:约翰·波德斯塔邮件泄露(2016年)

2016年,希拉里·克林顿竞选团队主席约翰·波德斯塔收到了一封伪装成谷歌安全警报的钓鱼邮件。他点击了链接,导致其邮箱凭证泄露。随后,维基解密公布了他的邮件内容。安全专家分析认为,这次攻击是由名为“Fancy Bear”的黑客组织完成的,该组织与俄罗斯情报机构有关联。

案例五:震网病毒(Stuxnet)

震网病毒是一个旨在攻击工业控制系统的复杂程序。它专门针对伊朗核计划中使用的西门子设备,尤其是用于提炼铀的离心机。震网病毒会寻找基于西门子的可编程逻辑控制器(PLC)并进行攻击。它包含了四个零日漏洞,并通过USB设备进行传播。这被认为是首个被广泛确认的、由国家力量发起的网络武器。

计算机安全的核心目标

上一节我们看到了安全攻击的多样性与危害性。本节中我们来看看,为了抵御这些攻击,计算机安全系统需要达成的几个核心目标。我们通常用Alice(发送方)、Bob(接收方)和Mallory(攻击者)这三个角色来举例说明。

以下是四个关键的安全目标:

  1. 保密性

    • 含义:确保信息只能被授权方访问。当Alice给Bob发送消息“你想见面吃午饭吗?”时,需要保证像Mallory这样的第三方无法阅读该消息。
  2. 身份验证

    • 含义:确认通信对方的身份是真实的。当Alice通过互联网与Bob通信时,她需要一种方法来证明正在与她对话的人就是Bob本人,而不是伪装成Bob的Mallory。
  3. 完整性

    • 含义:确保信息在传输过程中未被篡改。Alice发送的消息在到达Bob手中时必须与原始消息完全一致。Mallory可能无法读取消息,但他可以篡改它(例如,将见面地点从“食堂”改为“咖啡厅”),导致通信失败。
  4. 不可否认性

    • 含义:防止通信方事后否认其行为。例如,如果我发送了一条消息说“取消期末考试”,之后我不能抵赖说“那条消息不是我发的”。系统需要提供证据来证明消息确实出自我手。

这些目标相互关联,共同构成了信息安全的基础。

总结

本节课中我们一起学习了计算机安全的入门知识。我们通过GhostNet、震网病毒等多个真实案例,看到了安全漏洞可能被利用的多种方式及其严重后果。接着,我们明确了计算机安全的四个核心目标:保密性身份验证完整性不可否认性。理解这些目标是学习具体安全技术和机制的第一步。在接下来的课程中,我们将深入探讨用于实现这些目标的具体方法。

L20.2:计算机安全机制 🔐

概述

在本节课中,我们将学习用于保护计算机网络安全的几种核心机制。这些机制旨在解决保密性、身份验证、完整性和不可否认性等安全问题,它们都基于密码学技术。

上一节我们介绍了网络安全涉及的几个核心问题。本节中,我们来看看解决这些问题的具体技术。

加密技术基础

加密技术是发送安全消息的基础。它涉及两个核心过程:

  • 加密:将原始明文消息转换为加密的密文消息。
  • 解密:将密文消息转换回原始明文消息。

加密技术已有数千年历史。我们将从一个简单的例子开始,以便理解其基本原理。

凯撒密码

凯撒密码是一种非常简单的加密方法,由朱利叶斯·凯撒使用。它的工作原理是将消息中的每个字母在字母表中向后移动固定的位数。

以下是凯撒密码(移位数为3)的加密过程示例:

  • 明文:meet for lunch
  • 加密过程:每个字母向后移动3位(a->d, b->e, m->p, e->h, t->w 等)。
  • 密文:phhw iru oxqfk

在这个例子中,发送者Alice将明文“meet for lunch”加密为密文“phhw iru oxqfk”。接收者Bob收到密文后,需要执行相反的解密过程(每个字母向前移动3位)才能理解消息内容。

凯撒密码包含两个组成部分:通用的移位算法和具体的移位数量(即密钥)。奥古斯都·凯撒使用了相同的算法,但改变了移位的数量。

然而,凯撒密码存在严重的安全问题。

凯撒密码的弱点

凯撒密码的主要弱点在于其密钥空间太小。对于英文字母,最多只有25种有效的移位可能性(移位26位等于没有移位)。这使得它极易受到暴力攻击

暴力攻击是指攻击者尝试所有可能的密钥组合,直到其中一个能产生有意义的明文。

此外,凯撒密码也容易受到密码分析的攻击。密码分析是指通过分析密文本身来推断出明文或密钥信息。

以下是密码分析可能利用的线索:

  • 字母频率:分析密文中字母出现的频率。例如,英文中“e”是最常见的字母。
  • 重复模式:寻找密文中重复的字母组合。例如,在示例密文“phhw iru oxqfk”中,“hh”是一个明显的重复。
  • 单字母单词:识别可能是“I”或“a”等单字母单词的密文片段。

通过这些分析,攻击者可以大大减少需要尝试的密钥数量,从而破解密码。

因此,现代加密算法需要克服这些弱点。它们主要依赖两点:

  1. 足够大的密钥空间:使暴力攻击在计算上不可行。
  2. 算法的复杂性:即使算法公开,也能保证在没有密钥的情况下无法从密文推导出明文。

现代加密类型

现代加密主要分为两种类型:对称加密和非对称加密。

对称加密

在对称加密中,加密和解密使用相同的密钥。凯撒密码就是一个对称加密的例子,加密和解密都依赖于“移位3”这个相同的密钥。

对称加密历史悠久,但面临一个关键挑战:密钥分发。通信双方如何在首次安全地共享同一个密钥?如果通过不安全的渠道(如电子邮件)发送密钥,密钥本身就可能被截获。

非对称加密(公钥加密)

非对称加密使用一对数学上关联的密钥:一个公钥和一个私钥

  • 公钥可以公开给任何人。
  • 私钥必须由所有者严格保密。
  • 用公钥加密的消息,只能用对应的私钥解密。
  • 从公钥推导出私钥在计算上是极其困难的。

非对称加密的概念在1976年由迪菲和赫尔曼首次公开提出(马丁·赫尔曼是斯坦福大学教授,也是图灵奖获得者)。有趣的是,英国情报机构GCHQ在1973年就独立发现了该技术,但未公开发表。

非对称加密完美解决了对称加密中的密钥分发问题。以下是如何使用它进行安全通信:

假设你想与亚马逊安全通信:

  1. 亚马逊将其公钥放在网站上。
  2. 你用亚马逊的公钥加密你的消息(如信用卡信息)。
  3. 你将加密后的密文发送给亚马逊。
  4. 亚马逊使用其私钥解密并读取消息。

任何截获流量的人即使拥有公钥,也无法解密消息,因为他们没有私钥。

在实际应用中(如HTTPS),通常结合使用两种加密:

  1. 连接开始时使用非对称加密来安全地交换一个随机的会话密钥
  2. 后续的通信则使用对称加密和该会话密钥进行,因为对称加密速度更快,计算开销更小。

公钥加密的应用

公钥加密技术除了保证通信机密性,还能解决其他安全问题。

1. 身份验证与数字证书

我们如何确认正在访问的网站(如amazon.com)是真的亚马逊,而不是假冒的?这依赖于数字证书证书颁发机构 体系。

以下是其工作流程:

  1. 网站向受信任的证书颁发机构 申请证书。
  2. CA验证网站的真实身份后,使用自己的私钥为该网站的信息(包含网站的公钥)生成一个数字签名(即证书)。
  3. 网站将这张证书部署到服务器上。
  4. 你的浏览器访问网站时,会收到这张证书。浏览器内置了各大CA的公钥
  5. 浏览器用CA的公钥验证证书上的签名。如果验证通过,则证明该证书是真实的,进而相信证书中包含的网站公钥也是真实的。
  6. 浏览器随后使用该网站的公钥发起安全通信。

当你使用https://(而非http://)访问网站时,就意味着正在使用这套基于证书的安全通信机制。

2. 不可否认性

公钥加密还能提供不可否认性,即发送者事后无法否认自己发送过的消息。

实现方式如下:

  1. 发送者使用自己的私钥对消息进行“签名”(一种特殊的加密处理)。
  2. 接收者使用发送者的公钥对签名进行验证。
  3. 如果验证通过,则证明该消息确实来自对应的私钥持有者。因为公钥只能解密由配对私钥加密的内容,发送者无法抵赖。

需要注意的是,整个系统的安全性取决于私钥的保密性。如果私钥泄露,那么不可否认性也就失效了。

数据完整性保障

完整性确保数据在传输过程中未被篡改。除了加密,我们还可以使用校验和加密散列来验证完整性。

校验和

校验和是一种简单的完整性检查方法。发送方对数据的所有字节执行某种计算(如求和),得到一个较小的值(校验和),并将其随数据一起发送。

接收方对收到的数据执行同样的计算,并将结果与发送方提供的校验和进行比较。如果两者匹配,则数据在传输过程中可能是完整的。

然而,简单的校验和容易被恶意攻击者破解。攻击者可以在修改数据的同时,也相应地修改校验和,使其匹配,从而绕过检查。

加密散列函数

为了更可靠地保证完整性,特别是防止恶意篡改,我们使用加密散列函数(如SHA-256)。

加密散列函数的特点:

  • 将任意长度的数据映射为固定长度的散列值(又称“指纹”)。
  • 即使输入数据发生微小改变,输出的散列值也会发生巨大且不可预测的变化。
  • 从散列值反向推导原始数据在计算上不可行。
  • 找到两个不同数据产生相同散列值(即“碰撞”)在计算上极其困难。

应用实例:
许多开源软件(如Audacity)的下载页面会提供文件的SHA-256散列值。下载文件后,你可以使用工具计算该文件的SHA-256散列值,并与官网提供的值进行比对。

  • 如果散列值匹配,说明你下载的文件与官方发布的完全一致,未被篡改或损坏。
  • 如果散列值不匹配,则说明文件可能已在传输中损坏,或者更危险的是,已被植入恶意软件。

总结

本节课我们一起学习了保护计算机网络安全的核心机制。

  • 我们首先从凯撒密码入手,理解了加密、解密、明文、密文和密钥等基本概念,并分析了简单密码的弱点。
  • 接着,我们探讨了两种现代加密方式:对称加密(使用相同密钥)和非对称加密(使用公钥/私钥对)。非对称加密解决了密钥分发的难题。
  • 然后,我们看到了非对称加密如何应用于身份验证(通过CA和数字证书确认网站身份)和不可否认性(通过数字签名确保行为无法抵赖)。
  • 最后,我们学习了如何通过校验和与更安全的加密散列函数(如SHA-256)来保障数据的完整性,确保信息在传输过程中未被意外或恶意修改。

这些机制共同构成了我们在互联网上进行安全通信和交易的基础。

L21.1:计算机安全(攻击):恶意软件 🛡️

在本节课中,我们将要学习计算机安全领域中的一个核心威胁:恶意软件。我们将了解不同类型的恶意软件,它们如何运作,以及它们可能对您的计算机造成何种危害。

恶意软件概述

上一节我们介绍了计算机安全的整体概念,本节中我们来看看攻击者可能使用的一种主要手段——恶意软件。恶意软件是一个统称,指任何设计用来损害或未经授权访问计算机系统的软件。

恶意软件的主要类型

以下是几种常见的恶意软件类型及其运作方式。

间谍软件 👁️

间谍软件旨在秘密监视用户活动并收集信息。它在现代计算机和移动设备上尤其成问题,因为这些设备集成了多种敏感功能。

间谍软件可以执行多种恶意操作:

  • 键盘记录:跟踪用户的所有击键。例如,当您访问网上银行时,它会记录您的用户名和密码。
    • 代码示例:一个简单的键盘记录器可能通过钩子(hook)函数捕获击键事件。
  • 复制文件:窃取计算机上的文件,例如包含财务信息的文档,并将其发送给攻击者。
  • 启用麦克风和摄像头:未经授权打开设备的麦克风进行窃听,或打开摄像头进行窥视。
  • 跟踪活动:监控用户在设备上的行为和位置信息。

勒索软件 🔐

当勒索软件侵入计算机后,它会加密整个硬盘驱动器(HDD)或固态驱动器(SSD)上的数据,然后要求支付赎金(通常为比特币)以解密文件。

  • 公式/过程感染 → 加密文件 → 索要赎金
  • 著名案例:WannaCry勒索软件在2017年爆发,感染了超过150个国家的20多万台计算机,严重扰乱了英国国家卫生服务等机构的运作。它利用了当时微软Windows中一个已知但未广泛修补的漏洞。

广告软件 📢

广告软件指在用户计算机上显示广告的软件。这个术语有两种用法:

  1. 指任何显示广告的程序(如一些免费的词典、天气应用),这类通常被认为是合法的,通过广告支持免费服务。
  2. 特指那些在用户不知情或未经完全同意的情况下,恶意安装并显示无关广告的软件。这类广告软件可能在任何时候弹出广告,甚至与用户当前浏览的网站无关。

一个相关的问题是“加密货币挖矿脚本”,即网站在用户访问时,未经明确同意就利用用户计算机的算力来挖掘加密货币。

僵尸网络与僵尸计算机 🤖

恶意软件可以将您的计算机变成“僵尸计算机”(或“机器人”),并将其纳入一个由许多受控计算机组成的“僵尸网络”。

  • 注意:“机器人”一词并非总是恶意,例如谷歌用来索引网页的“爬虫机器人”就是合法的。
  • 僵尸网络的恶意用途
    • 发送垃圾邮件:从全球分布的大量计算机发送垃圾邮件,使其更难被屏蔽。
    • 分布式拒绝服务攻击:指令僵尸网络中的所有计算机同时向目标服务器发送海量请求,使其超载瘫痪,无法为合法用户服务。
      • 公式大量请求(来自分散IP) → 目标服务器过载 → 服务拒绝
    • 点击欺诈:操纵僵尸计算机虚假点击在线广告,要么为自家网站骗取广告收入,要么消耗竞争对手的广告预算。
  • 实例:Conficker蠕虫/病毒曾感染超过3000万台计算机,形成了一个庞大的僵尸网络,第三方甚至可租用它进行非法活动。

逻辑炸弹 💣

逻辑炸弹不是外部植入的恶意软件,而是由原始程序员隐藏在合法程序中的恶意代码片段。它会在特定条件触发时执行恶意操作。

  • 触发条件:可能是特定日期、某个文件被读取、或某个特定事件(如程序员被从工资系统中删除)。
  • 恶意操作:可能是发送通知、删除文件或造成其他破坏。

恶意软件的传播方式

了解了恶意软件的类型后,我们来看看它们是如何进入您的计算机的。以下是几种主要的传播机制,它们之间常有重叠。

病毒 🦠

病毒是一种能够将自身附加到其他程序上的计算机程序,通过感染可执行文件进行自我复制和传播。

  • 运作方式:病毒运行时,会寻找计算机上的其他可执行程序(甚至包括一些文档中的宏脚本),并将自身代码插入其中。当被感染的程序运行时,病毒代码也随之执行。
  • 防护示例:现代办公软件(如Microsoft Office)默认在“保护模式”下打开外来文档,防止内嵌脚本自动运行,就是针对此类威胁的防护。

蠕虫 🐛

蠕虫是一种能够在网络上自我传播和复制的独立程序。

  • 运作方式:它不依赖感染宿主文件,而是利用网络连接(如电子邮件、系统漏洞)将自己拷贝到其他计算机上。著名的“ILOVEYOU”蠕虫就是通过电子邮件附件和Outlook通讯录进行传播的。

特洛伊木马 🎁

特洛伊木马伪装成合法或有用的软件,但在背后执行恶意操作。

  • 特点:它要么完全不是其所声称的东西,要么在完成声称功能的同时,携带“恶意负载”。
  • 实例:伪装成“西藏自由运动翻译”文档的间谍软件、声称是“天气程序”或“卡通伙伴”却暗中安装广告软件的应用程序,都是典型的特洛伊木马。

零日漏洞利用 0️⃣

零日漏洞利用是指利用软件中未被开发者知晓、因此也无补丁可修复的安全漏洞进行的攻击。

  • 特点:由于漏洞未知,防御极其困难。震网病毒就使用了多个零日漏洞。
  • 对比:与利用已知漏洞的“脚本小子”攻击不同,零日攻击更具威胁性,通常与高级持续性威胁相关。保持软件更新可以防范已知漏洞攻击,但对零日攻击效果有限。

本节课中我们一起学习了恶意软件的世界。我们探讨了间谍软件、勒索软件、广告软件、僵尸网络和逻辑炸弹等多种恶意软件类型,并了解了它们通过病毒、蠕虫、特洛伊木马和零日漏洞利用等方式进行传播的机制。理解这些攻击方式是迈向有效防御的第一步。在下一讲中,我们将重点关注如何保护自己免受这些威胁。

L21.2:计算机安全:攻击向量与技术 🔐

在本节课中,我们将学习计算机安全领域中的各种攻击向量与技术。我们将从最普遍的社会工程学攻击开始,逐步深入到更技术性的攻击方式,如SQL注入、跨站脚本等,并了解它们的基本原理。

社会工程学攻击 👥

上一节我们介绍了课程概述,本节中我们来看看最常见的一种攻击方式:社会工程学。社会工程学是危害某人计算机安全的最简单方法之一。这也是您作为非程序员,在保护您的计算机安全方面可以发挥巨大作用的一个领域。

在社会工程中,攻击者将忽略安全系统的技术方面,而是去寻找最薄弱的环节,即参与该过程的人。

以下是社会工程攻击的常见形式:

  • 假冒身份:例如,有人会打电话给技术支持人员,假装是公司高管并请求重置密码。攻击者可能通过与不同的支持人员交谈来慢慢收集信息,最终足以冒充目标人物。
  • 网络钓鱼:您收到据称来自公司的电子邮件或短信,试图诱骗您输入账户信息和密码。攻击者可以轻易地复制一个与真实网站完全一样的网页。
  • 鱼叉式网络钓鱼:这是更具针对性的网络钓鱼。攻击者会研究特定目标,制作看起来来自其同事或熟人的精心伪造的电子邮件,诱骗其点击链接或打开附件。
  • 物理媒介攻击:攻击者利用人们的好奇心或信任,诱使其将恶意USB设备插入电脑。例如,将带有公司标志的USB驱动器丢弃在停车场,或分发需要插入USB口供电的小风扇。

核心防御原则:永远不要点击电子邮件中的链接。如需操作,应手动在浏览器中输入公司官网地址或使用实体卡片上的电话号码联系。不要将任何来源不明的设备插入计算机端口。

技术性攻击:注入与劫持 💻

在了解了利用人性的攻击后,我们来看看攻击者利用系统漏洞的几种技术手段。

SQL注入攻击

SQL注入攻击是指攻击者在一个本应只输入文本的地方(如搜索框),输入了SQL代码。如果系统未做处理,这些代码可能会被数据库执行。

攻击原理示例:假设一个学生姓名输入框。如果攻击者输入 Robert”; DROP TABLE Students; --,并且系统未做防护,那么Robert”后的引号可能提前结束了原SQL命令中的字符串,使得后面的DROP TABLE Students;被当作独立命令执行,从而删除了学生表。

防御方法:程序员需要对用户输入进行“消毒”,确保所有输入都被严格视为文本数据,不会被意外解释为可执行命令。这通常通过参数化查询或对输入进行转义编码来实现。

跨站脚本攻击

跨站脚本攻击与SQL注入相反,它是将恶意脚本注入到其他人的网页中。

攻击原理示例:假设一个允许用户发表评论的网站。如果攻击者在评论框中输入一段JavaScript代码(如 <script>恶意代码</script>),而网站没有对评论内容进行消毒处理,那么这段脚本就会被存储并显示给所有访问该页面的用户,在他们的浏览器中执行。

防御方法:网站开发者必须对所有用户提交的内容进行消毒,过滤或转义掉HTML标签(如 <, >),确保其被当作纯文本显示,而不是可执行的代码。

点击劫持攻击

点击劫持攻击导致用户无意中点击了隐藏的页面元素。

攻击原理

  1. 攻击者使用 <iframe> 标签将目标网站(如亚马逊)嵌入自己的恶意网页。
  2. 使用CSS将这个iframe设置为完全透明,并利用绝对定位将其覆盖在网页的某个按钮(如“观看视频”)之上。
  3. 用户点击这个可见按钮时,实际上点击的是透明iframe中的“一键购买”按钮。

防御方法:网站可以通过设置HTTP响应头(如 X-Frame-Options: DENY)或使用JavaScript来阻止自己的网页被嵌入到iframe中。

系统与协议层面的攻击 🌐

除了针对特定应用漏洞的攻击,整个计算机系统和网络协议本身也可能存在弱点。

沙箱安全与驱动下载

  • 沙箱安全:现代浏览器采用沙箱机制来限制网站的能力。在沙箱内,网站可以操作浏览器窗口内容;在沙箱外(如访问用户本地文件),则通常被禁止。浏览器插件或扩展通常能访问沙箱外的资源,因此应谨慎安装。
  • 驱动下载:攻击者通过欺骗手段(如弹出虚假病毒警告)或利用浏览器漏洞,迫使用户下载并安装恶意软件。

中间人攻击

在中间人攻击中,攻击者在受害者与目标服务器(如银行网站)之间插入自己的计算机。

攻击过程

  1. 受害者所有发往银行的数据都先经过攻击者的机器。
  2. 攻击者可以窃取受害者的用户名、密码,甚至拦截银行发来的双因素认证码。
  3. 攻击者再将信息转发给银行,并将银行的回复返回给受害者,使整个过程对受害者不可见。

如何实现与防御

  • 实现条件:攻击者需要能够插入到通信路径中(例如,控制一个公共Wi-Fi),并且绕过身份验证和加密
  • 核心防御:使用HTTPS。它通过数字证书验证网站身份,并对通信内容进行加密。用户切勿忽略浏览器关于证书无效的警告,也不要随意添加不受信任的证书颁发机构。

供应链攻击与路由劫持

  • 供应链攻击:攻击者不直接攻击目标,而是入侵目标所依赖的第三方软件或服务(如网站插件、开源代码库)。当目标使用这些被篡改的组件时,就会受到攻击。防御方法是使用广泛审查的开源软件,并关注所用组件的安全状态。
  • 路由劫持:攻击者通过错误配置或欺骗,误导互联网流量经过其控制的网络。例如,某国电信公司曾错误宣告自己是通往某些美国网站的最优路径,导致大量流量(包括政府机构流量)被路由至该国。防御方法是对敏感通信进行端到端加密。

总结 📝

本节课我们一起学习了多种计算机安全攻击向量与技术。我们从最普遍的社会工程学(如网络钓鱼)开始,认识到人是安全链中最薄弱的环节。接着,我们探讨了SQL注入、跨站脚本等技术性应用漏洞。最后,我们了解了系统层面的风险,如中间人攻击和供应链攻击。

理解这些攻击的原理是实施有效防御的第一步。在下一节课中,我们将重点学习如何保护自己,防御这些类型的攻击。

L22.1:计算机安全防御技术 🛡️

在本节课中,我们将学习如何保护计算机系统免受攻击。上一节我们探讨了计算机可能受到的各种攻击方式,本节中我们来看看我们可以采取哪些防御技术来保护自己。

防火墙技术 🔥

首先,我们来讨论防火墙。防火墙是保护网络边界的关键技术。

企业防火墙

如果没有防火墙,公司内部网络(Intranet)中的每台计算机都可以直接访问互联网,这意味着所有计算机都容易受到外部攻击。同时,公司内部人员也可能在没有控制的情况下向外发送信息。这显然是一个糟糕的情况。

防火墙计算机是一台位于公司内部网和更广泛的互联网之间的计算机。它可以防止恶意流量进入公司,也可以防止恶意信息流出公司。

以下是防火墙可以执行的一些主要功能:

  • 阻止或白名单特定应用程序:例如,它可以阻止外部对内部计算机的FTP访问请求。
  • 阻止或白名单特定IP地址:可以限制员工可以访问的外部计算机,或限制外部人员可以访问的内部计算机。
  • 扫描流量信息:可以检查传入或传出的数据流,查找特定关键词或模式。

更复杂的架构会设立一个称为“DMZ”(非军事区)的特殊子网络。DMZ中放置着需要被外部访问的服务器(如Web服务器、文件服务器),而内部网络则受到更严密的保护。

个人防火墙

现代操作系统都内置了个人防火墙,其行为类似于企业防火墙。

在没有个人防火墙的情况下,计算机上的所有程序都直接与互联网通信。个人防火墙充当所有流量的中介,计算机上的程序需要通过它来发送或接收信息。防火墙会判断并决定是否允许该通信。

例如,如果计算机上被安装了键盘记录器,个人防火墙应能阻止该程序向黑客发送信息。同样,对于传入的流量,防火墙只允许访问那些你当前正在使用的服务。例如,如果你的计算机不充当FTP服务器,防火墙应阻止外部的FTP访问请求。

当你安装新程序时,很可能会触发防火墙提示,询问是否允许该程序访问互联网。你应该谨慎批准,如果不确定程序的来源或用途,最好先进行搜索确认。

代理服务器 🔄

接下来,我们看看代理服务器。代理服务器允许用户访问他们无法直接访问的资源。

我将用一个例子来说明:斯坦福大学付费订阅了Safari在线图书资源。在校园内(使用斯坦福IP地址)可以直接访问。但当我在家时,我的IP地址属于康卡斯特(Comcast),Safari在线会拒绝我的访问。

斯坦福为解决此问题运行了代理服务器。其工作方式是:我不直接访问Safari在线,而是将请求发送到斯坦福的代理服务器,由代理服务器代表我去访问Safari在线。Safari在线看到请求来自一个斯坦福IP地址(即代理服务器),就会允许访问并将数据返回给代理服务器,代理服务器再将其转发给我。

代理服务器还有其他用途,例如绕过某些网络限制(注意:这可能违反公司政策或当地法律)。早期,它曾被用来绕过中国的“防火长城”访问被屏蔽的网站。但这种方法的问题是流量通常没有加密,防火墙可以通过扫描请求内容来识别和阻止。

对于访问像斯坦福订阅资源这类合法目的,代理服务器在校外工作时非常有效。

虚拟专用网络 🌐

更复杂的技术是虚拟专用网络(VPN)。VPN可用于某些与代理服务器相同的目的,但也能用于其他场景。

假设一家公司在帕洛阿尔托和东京都有办公室,每个办公室都有自己的内部网络。最安全的连接方式是租用一条专用的物理线路,但这非常昂贵。通常,公司会通过互联网连接两个办公室。

问题在于,当数据包通过互联网传输时,路径上的计算机可以看到发送方和接收方的IP地址,甚至可能看到未加密的内容。这可能会泄露敏感信息,例如暗示公司正在进行收购谈判。

VPN可以解决这两个问题。其工作方式是:当从帕洛阿尔托办公室的计算机发送信息到东京时,信息首先被发送到本地的VPN网关(如DMZ中的计算机)。该网关会加密整个数据包,包括数据和原始的发送/接收IP地址,并用它自己的地址替换。然后,加密的数据包通过互联网发送到东京办公室的VPN网关。东京的网关解密数据包,还原出原始信息和IP地址,再将其传递给目标计算机。

这样,互联网上的旁观者只能看到两个VPN网关在通信,而不知道内部是哪些计算机在通信,也无法得知通信内容。

VPN也可用于单个计算机,例如让在家或旅行的员工安全地访问公司网络。

个人VPN

个人VPN目前是一个热门话题。人们使用个人VPN的主要原因之一是隐藏互联网活动,防止互联网服务提供商(ISP)或其他方跟踪。因为VPN会加密你所有的流量,ISP只能看到你连接到VPN服务器,而看不到你实际访问了哪些网站。

个人VPN的另一个用途是模拟位于不同国家。如果VPN提供商在意大利有服务器,你可以连接到该服务器,那么你访问的网站会认为你来自意大利。这可以用来访问某些地区限制的内容(如流媒体节目)。

但使用VPN也存在问题:一些网站(如流媒体服务、金融机构)会主动阻止已知的VPN IP地址;同时,来自VPN的流量更可能触发验证码(CAPTCHA)挑战。

物理隔离(气隙) 💻➡️🚫➡️🌐

如果你希望为计算系统提供最大的安全性,应该只使用“气隙”系统。

“气隙”是指计算机与网络完全断开连接,与互联网之间存在物理间隙。这意味着也不应有Wi-Fi或蓝牙等无线连接。例如,斯坦福大学为了保护成绩数据库,可能会将其放置在一个与互联网完全隔离的内部网络中,访问该数据库的唯一方法是亲自到场并向警卫出示身份证。

这提供了极高的安全性,因为攻击者需要物理接触。但另一方面,完成任何工作都会非常麻烦和低效。

安全通信实践 📧

我们之前讨论过电子邮件安全问题。有许多安全的电子邮件系统,但并非大多数人都在使用。

选择安全电子邮件系统时,需要考虑不同的安全目标:

  • 机密性:确保互联网上的其他人无法看到邮件内容。
  • 身份验证:确保邮件的发送者身份真实可靠。
  • 匿名性:在某些情况下(如人权活动家、举报人),发送者可能希望隐藏自己的身份。

一些安全电子邮件系统承诺不记录发送者的IP地址。关键在于,谁有能力解密信息?是服务器解密,还是只有通信双方能解密?如果服务器持有密钥,在某些情况下可能成为安全隐患。

安全网络浏览 🌐

HTTPS

标准的HTTP协议以明文形式发送数据,而HTTPS是HTTP的安全版本,会对进出网络服务器的流量进行加密。

但需要注意,某些网页可能并未加密所有内容(如图像、CSS文件),这可能会触发浏览器的安全警告。理想情况下,所有内容都应被加密。

你可以安装电子前哨基金会(EFF)的“HTTPS Everywhere”插件,它会强制浏览器在网站支持的情况下使用HTTPS连接。

禁用JavaScript

提高网络浏览安全性的一个重要建议是禁用JavaScript。因为客户端JavaScript可能被以各种恶意方式利用,例如触发“偷渡式下载”,或利用浏览器安全漏洞。

禁用JavaScript可以大大提高安全性,但也会使许多需要JavaScript才能正常工作的网页变得无法使用或体验很差。因此,一些安全工具(如Firefox的“NoScript”、Chrome的“SafeScript”)允许你将可信网站加入白名单,仅在这些网站上启用JavaScript。

多因素身份验证 🔑

多因素身份验证是指在登录网站时,除了用户名和密码(你知道的东西)之外,还需要提供其他因素。

这些因素包括:

  • 你拥有的东西:例如手机(接收短信)、安全令牌或身份验证器应用程序。
  • 你固有的特征:例如指纹、视网膜图案。

你应该在任何重要网站(尤其是电子邮件和金融网站)上启用多因素身份验证。因为电子邮件往往是重置其他账户密码的关键,必须保持超级安全。

最常见的双因素身份验证是向手机发送短信。但这并非绝对安全,因为短信系统本身可能不安全,且存在“SIM卡劫持”诈骗。尽管如此,基于短信的双因素验证仍然比没有要好。

更好的替代方案是使用身份验证器应用程序(如Google Authenticator)或物理安全密钥。它们基于“伪随机数”算法,通过一个共享的“种子”和当前时间生成一次性的验证码,安全性更高。

生物识别(如指纹)可以作为额外的因素,但安全专家通常不建议将其作为唯一因素,因为生物特征一旦泄露就无法更改。

密码安全 🔐

密码安全需要注意以下几点:

  1. 抵御暴力破解:密码越长,可能的组合就越多,破解所需时间呈指数级增长。使用大写字母、小写字母、数字和标点符号可以大大增加组合数量。
  2. 避免常见密码:不要使用“password123”等出现在常见密码列表中的密码。
  3. 使用唯一密码:为不同的网站使用不同的密码。如果一个网站被黑客攻击导致密码泄露,你不会在其他网站面临风险。切勿在次要网站使用你的电子邮件或银行密码。
  4. 考虑使用密码短语:密码短语是由多个单词组成的序列(例如“correct horse battery staple”)。它比随机字符更容易记忆,且长度更长。但要注意,人们倾向于选择符合语法规则的短语,这降低了随机性,应尽量选择看似不相关的单词组合。
  5. 使用密码管理器:密码管理器可以为你生成并存储高强度、唯一的随机密码。你只需要记住一个主密码即可访问管理器。密码管理器还能帮助抵御网络钓鱼攻击,因为它不会在伪造的网站上自动填写密码。

密码管理器本身也可能成为攻击目标,但安全专家普遍认为,使用信誉良好的密码管理器仍然是提高整体密码安全性的好方法。

总结 📝

本节课中,我们一起学习了多种计算机安全防御技术。我们探讨了防火墙如何监控和过滤网络流量;代理服务器如何帮助访问受限资源;VPN如何创建加密隧道保护数据传输;以及物理隔离提供的终极安全。

在个人实践方面,我们强调了使用HTTPS、谨慎管理JavaScript、为重要账户启用多因素身份验证的重要性。最后,我们深入讨论了密码安全的最佳实践,包括创建强密码、使用唯一密码以及考虑借助密码管理器。

通过综合运用这些技术和良好的安全习惯,我们可以显著提升个人和组织的计算机安全水平。

L22.2:计算机安全:实际措施 🔒

在本节课中,我们将探讨一系列实用的计算机安全措施。这些措施旨在帮助您保护个人数据和在线账户,抵御常见的网络威胁。我们将从软件更新、密码管理,一直讲到防范网络钓鱼和使用安全工具。

上一节我们介绍了计算机安全的基本概念和威胁类型,本节中我们来看看具体可以采取哪些行动来保护自己。

保持软件更新 🔄

最重要的事情是保持您的软件更新。这非常简单直接。请记住,许多攻击都是基于已知的、预先存在的攻击计算机的方式。因此,有许多现成的工具可供攻击者直接使用。如果您的软件保持更新,您将受到保护,免受除“零日漏洞”(即我们尚不知道的安全漏洞)之外的所有已知攻击。

创建强密码的建议 🔑

在创建密码时,请务必考虑以下建议。

以下是创建强密码的关键点:

  • 为不同的账户使用不同的密码。 请记住,如果您在一个不知名的随机网站上创建账户,您可能不知道运营者是谁。即使对方是您认识的好心人,他们也可能没有良好的安全措施,您的密码信息可能会因此泄露。所以,不同账户的密码一定要不同。
  • 要非常小心您的电子邮件密码和金融账户密码。
  • 使用长度合适的密码。 密码越长,越难被破解。
  • 使用大写字母、小写字母、数字和标点符号的组合。
  • 考虑使用密码短语而不是简单的密码。 但请记住,为了达到最佳效果,密码短语应该包含随机的单词序列,而不是某种常见的短语或运动队的口号。最好甚至不是英语中的常见搭配,只是一堆随机组合在一起的单词。
  • 考虑使用密码管理器。

使用多因素身份验证 ✅

如果可能,请使用多因素身份验证。

以下是关于多因素身份验证的说明:

  • 选择身份验证器(手机应用程序或物理身份验证器)比接收短信更可取,因为短信系统本身并不安全。
  • 但是,使用基于短信的双因素身份验证仍然比完全不使用双因素身份验证要好。

小心网络钓鱼攻击 🎣

小心网络钓鱼攻击。不要随意点击邮件中的链接。

以下是防范网络钓鱼的要点:

  • 记住电子邮件系统是完全不安全的。
  • 创建一个网站的逐像素精确再现非常容易。所以,仅仅因为它看起来和您银行的网站一模一样,并不意味着它就是您银行的真正网站。
  • 欺骗电子邮件系统并假装成您不是的人也很容易。
  • 不要点击邮件中的链接。直接通过浏览器输入主要网站的地址访问,不要从电子邮件中复制任何内容。
  • 如果您处于敏感职位并且特别担心安全性,请直接通过电话致电公司核实。
  • 不要忘记“鱼叉式网络钓鱼”,这是一种针对性攻击。攻击者会研究您的身份,弄清楚您应该收到什么样的电子邮件,然后精心设计一封专门用于诱骗您打开它的邮件。

使用安全工具 🛡️

使用反恶意软件。

以下是关于安全工具的建议:

  • 操作系统通常已经内置了良好的反恶意软件。例如,Windows Defender 实际上被认为相当不错。
  • 如果您需要,可以从 Stanford 的 ESS(Essential Stanford Software,斯坦福必备软件)网站获得免费的反恶意软件。
  • 为了获得最大的安全性,您可以考虑在浏览器中阻止 JavaScript。这可能会使浏览网页的体验更烦人,但会显著增强您的安全性。

本节课中我们一起学习了保护在线安全的多种实际措施,包括及时更新软件、创建和管理强密码、启用多因素身份验证、警惕网络钓鱼攻击以及合理使用安全工具。将这些习惯融入日常数字生活,能有效提升您的安全防护水平。

L23.1:隐私与大数据 🔒

在本节课中,我们将要学习数字时代的隐私问题。我们将探讨为什么隐私在当今世界变得尤为独特和重要,了解数据如何被收集和利用,并审视商业与政府使用数据时带来的挑战。

为什么数字时代的隐私不同?🤔

上一节我们介绍了课程概述,本节中我们来看看隐私在数字时代有何不同。数字世界中的隐私变得独特有趣,是因为最近发生了许多变化,这些变化严重影响了隐私。

数据追踪的演变 📍

所以让我们谈谈其中的一些变化。现在有更多的信息可用于跟踪。几乎每个人都有手机,手机可以跟踪我们的运动。当你在城市里开车时,你的手机正在联系不同的手机信号塔,这可以用来跟踪你的动作。

但事实证明,在许多情况下,有比手机信号塔更精细的跟踪方式。当你在一个区域移动时,你的手机会尝试连接不同的 Wi-Fi 网络和不同的 Wi-Fi 路由器。事实证明,这些也可用于跟踪您的活动。

以下是几个具体的追踪例子:

  • 零售追踪:例如,Nordstrom 通过观察手机在商店中移动时尝试连接不同网络的位置,可以尝试跟踪客户如何从商店的一个部分移动到另一个部分。
  • 视频监控:现在也到处都是闭路电视摄像机,它们也可以用来跟踪我们去哪里了。英国估计每 10 到 15 人就有一台闭路电视。
  • 智能设备:亚马逊的带摄像头的门铃等技术也可以用于跟踪运动。特别是一旦你在一个区域有相对大量的这些设备,那么你可以将来自每个不同门把手的视频放在一起,以跟踪运动。

媒体消费的数字化 📺

以及我们将媒体消费转移到数字设备上,这使得追踪我们的媒体消费习惯变得非常简单。数字报纸和杂志、数字图书、数字电视节目和电影,只要您不使用物理媒体,而是使用数字媒体,跟踪您正在查看的所有内容就非常简单。您阅读的所有内容,您看到的一切都可以被追踪。

数据的处理与利用 💾

被政府用来确定您的可靠性。事实上,稍后我会看到一个例子,说明中国如何将它用于他们的社会信用体系,被用来确定公民的可靠性并在此基础上给予他们奖励或惩罚。

随着我们转向具有一些智能技术的智能汽车,情况可能会变得更糟而不是更好。然后,我们转向自动驾驶汽车,这将非常轻松地跟踪您的活动。智能家居可以向可以访问您的活动和家庭的任何人提供信息。实际上,智能电视几乎是您现在购买的具有某种 AI 功能的电视,假设我可以调用一个操作系统。我认为其中许多是基于 Android 的,内置于它们中,允许您选择将不同的应用程序放在上面并选择不同的电影观看。其中一些公司实际上在出售有关您的观看习惯的信息。

因此,除了拥有所有这些信息之外,我们现在还有足够的处理能力来真正利用它。这还可以与存储大量信息的能力相结合,然后在事实发生后运行处理。这使我们能够剥离传统上被认为是私人的东西。

大数据与追溯分析 🔍

所以以前假设我们试图跟踪整个城市的特定车辆,也许有人被绑架了,我们有某种人质情况,警方可以使用某些街道上提供的闭路电视摄像机查看交通模式。他们可能会说,我们要继续观察人质车辆穿越城市,所以这是他们以前可以做的事情。

但我们现在可以做或随着存储变得越来越便宜,我们前进的方向是存储来自所有交通摄像头的所有信息,并在一段时间内开始处理。因此,我们能够存储的确切时间将取决于我们的存储容量以及存储的便宜程度。但您当然可以想象这样一种场景:您可以存储几天的流量,或者在某个时候您可以开始存储几个月的流量,然后再进行处理。也许现在什么都没有发生,但是你就像,某某有丑闻,让我们继续吧,把三个月前的录音带挖出来,这是他的车牌,我们看看我们是否可以搜索所有交通数据,看看我们是否可以找到他的车牌号码,然后看看他在做什么。

所以我认为,我们前进的方向可能会令人担忧。从隐私观点看,这种存储大量数据然后能够处理它的组合,就是有时被称为大数据的东西。它可供商业用户和政府用户使用,也是我们可能想要考虑的东西。我们想要规范这个,我们想要什么样的社会。我们稍后会在讲座中讨论这个问题。

法律保护与“被遗忘权” ⚖️

当我们讨论隐私的商业问题和政府的隐私问题时,好吧,就我们的法律而言,所以事实证明,我们法律提供的一些保护正在被我们的计算能力侵蚀。

例如,最高法院裁定个人有权实践实际默默无闻。因此这种情况的发生方式或如何发生的一个例子是,使用信息自由法的联邦调查局 RAP 表的请求被拒绝了,这是基于实际隐蔽性的需要。虽然该信息是公开可用的,但想法是有人必须浏览并收集所有公开可用的信息,这是一件耗时的事情。因此由于难以追踪所有信息,因此列出的那些实际上默默无闻。

但当然,现在追踪信息非常容易。事实上,公司会收集这些信息。您可能已经在网络上看到了他们的广告,例如,您想查看您高中的另一半在做什么,查看他们现在住的地方,了解他们的收入,是否已经结婚了。所以,实际的默默无闻在很大程度上已经消失了。

欧洲共同体可能正在做的计算能力方面,积极保护公民隐私的最重要工作。他们做过的一件事是 2014 年欧洲法院裁定,在某些情况下,其公民有权被遗忘。根据这项裁决,搜索引擎必须删除某些搜索结果。因此如果一家报纸有他们以前写过的文章,他们仍然可以将其留在他们的网站上,但搜索引擎无法直接链接到这些文章。关于被遗忘权的裁决半年后,谷歌收到了超过 120,000 项删除信息的请求。

通用数据保护条例(GDPR) 🛡️

欧洲共同体采取的另一项举措是欧洲通用数据保护条例。这个于 2016 年达成一致,并实施两年后。您最会注意到这一点,因为您每次访问网站时都会不断收到烦人的弹出窗口,说“我们使用 Cookie”等信息。加利福尼亚州最近也采取了类似的行动,所以您可能会因为加利福尼亚州的新法律而获得一些额外的弹出窗口。

所以一般数据保护法规实际上做得很好。以下是 GDPR 要求的关键事项:

  • 数据泄露通知:如果一家公司存储某人的信息被泄露,必须在泄露后 72 小时内通知个人。没有这项法律,公司甚至不告诉人们他们的数据在那里,或者尽管人们知道他们的数据在外面,但尽可能长时间等待,这对于这些人来说,保护自己可能是一件重要的事情。
  • 数据访问与删除权:个人有访问公司存储的有关他们的信息的权利。他们有权删除有关他们和公司的数据。
  • 隐私设计:提醒他们,他们应该从一开始就在他们的产品中设计隐私。
  • 数据保护官:此外,符合某些特征的公司必须有数据保护官,这些数据保护官必须向最高管理层报告。他们有责任确保数据受到适当保护。
  • 高额罚款:违反 GDPR 确实有一些问题,他们可能需要支付高达公司全球收入 4% 的罚款。

数据的商业用途:客户还是产品? 💰

让我们来看看数据的商业用途。所以隐私倡导者有时会谈论一件事,这个概念是:你是客户还是产品?所以这基本上是说,如果你正在使用产品并且你没有为它付费,并且你不是从非营利组织或者政府获得服务,那么你从中获得服务的公司需要以某种方式赚钱。如果你付钱的话,很好,这就是他们赚钱的方式。如果你不付钱,那么他们可能是通过出售对你的访问权或有关你的信息来赚钱的。所以你知道他们要么直接获取有关您的信息并直接销售,或者他们正在向您展示有针对性的广告。

不幸的是,您现在既可以成为客户,又可以成为产品。因此,作为 2017 年的示例,互联网服务提供商现在可以在您的网页浏览中出售信息给他人的习惯,即使你实际上是付费客户。

商业数据使用的风险与案例 🤨

所以我们应该担心。要考虑的一件事是,我使用谷歌或我使用必应,或我使用 DuckDuckGo 作为我的搜索引擎,显然他们必须支付账单,所以看到有针对性的广告因为我可以免费使用谷歌,这似乎是一件合理的事情。但是需要担心的是这条线在哪里,这条线是由习俗强制执行的,还是由法律强制执行的?

隐私倡导者已经提出的经典场景:如果我正在搜索有关癌症的网站,也许我有一个朋友患有癌症,或者父母患有癌症,或者我患有癌症,他们无法知道。但是,搜索引擎是否可以获取我正在搜索癌症的信息,然后将这些信息出售给我的保险公司,让我的保险公司根据我实际上可能患有癌症的理论来提高我的费率?我们对此是否满意?如果我们对此不满意,那如何执行呢?

另一件你应该做的事情:当您在网站上输入您认为是私人的信息或将其提供给公司时,您是否知道这两者是否合法,但他们与他人共享是合法的?那么是否有人可能非法获取这些信息?所以你知道除了思考“我认为这家公司有良好的意图”,你还应该考虑我是否相信这家公司有足够的安全性,这些信息不会被泄露,实际上泄露了,他们不会被黑客攻击,尽管他们可能有任何好意。

知名数据泄露案例 📉

所以让我们看几个例子,也许人们不应该信任这些公司保管我们的信息。

以下是几个重大的数据泄露案例:

  • Equifax:Equifax 是一家信用机构,它是那些跟踪我们在金融世界中的财务和信用卡所做的事情的公司之一,以及我们是否值得信用。所以他们被破坏了,许多人的姓名、社会安全号码、出生日期和地址在这次违规行为中被盗。此外,还有一些人的驾照号码被盗。事实证明,如果你有一个社会安全号码和一个驾照,你就可以创建假身份并为人们创建信用卡账户,这样组合就非常危险。只是为了给那些信息被泄露的人踢一些额外的沙子,破坏了,有人创建了一个虚假的帮助网站,并且有一个真正的帮助网站名为 equifaxsecurity2017.com,有人创建了一个名为 securityequifax2017.com 的网站,它足够接近,Equifax 不小心在他们的推特帐户上发布了错误的网站。但好消息是,在这种情况下,假网站是由安全专家创建的,目的是为了说明这一点。关于 Equifax 漏洞的另一件事有点奇怪:我们都不是 Equifax 的直接客户,我们真的不同意将我们的信息放在那里。我的意思是,我们注册的信用卡上可能有一些细则,或者我们工作的银行说他们是能够传递有关 Equifax 的信息,但我们都不是 Equifax 的直接客户,但他们拥有所有这些信息,而这些信息都丢失了。
  • Ashley Madison:Ashley Madison 是一个供通奸者使用的约会网站。他们被黑客入侵,窃取了 9.7 GB 的数据,包括会员姓名、地址、电话号码、出生日期和性取向。纽约时报指出的一件事是,当这个信息被发布时,黑客完全有可能在其中插入一些虚假数据。所以是的,无论如何,人们相信阿什利·麦迪逊的信息,他们不应该拥有。也许阿什利·麦迪逊的意图是好的,但他们没有良好的计算机安全性。
  • Facebook 与剑桥分析:Facebook 上有超过 5000 万份个人资料被盗用并出售给剑桥分析公司。一位教授告诉他们,他们的数据只会用于学术目的。嗯,不仅是那些认为它只会用于学术目的的人的数据被拿走了,而且事实证明,所涉及的教授能够访问他们朋友的信息。所以,这与之前的例子有点不同,因为这不是公司被黑客入侵,而是他们正在与某人合作,某人违反了协议条款并做了一些不诚实的事情。但尽管如此,所涉及的人的信息被盗取并出售给第三方。Facebook 并没有在技术上被黑客入侵,但人们有点被结果证明是一种骗局,那个人有意做一些不值得信赖的事情。您可能会争辩说这可以说是社会工程的一个案例。此外,Facebook 有几次不得不解雇员工,因为这些员工是利用他们在 Facebook 的职位进行网络跟踪的人。
  • 美国人事管理办公室(OPM):人事管理办公室充当联邦政府的人力资源部门,它处理安全许可。他们被黑客入侵,信息访问包括带有姓名、地址、出生日期和社会安全号码的人事档案。可能更麻烦的是,它包括安全许可的背景调查,其中包括诸如您是否吸毒之类的事情,你做过的事情可能会导致你被勒索了。所以这些都是可能进入背景调查的各种事情,它会被存储在他们的电脑上,这些电脑被黑了。此外,政府雇员的指纹数字图像现在也被盗了。至于是谁干的,黑客可能是中国,使用的工具与中国黑客有关。事实上,一名中国人被指控与人事管理办公室使用的工具之一相关。

所以这些都提醒你,如果你把信息放在那里,即使公司有良好的意图,不以某种方式分享这些信息,有可能会发生不好的事情,并且信息无论如何都会泄漏。

因此一种用于破坏隐私的技术称为网络信标或网络错误。它们用于跟踪互联网上的用户。你们可以弄清楚这些是如何工作的,因为您知道 HTML 是如何工作的,并且您知道 HTTP 的工作原理。

本质上,网络错误或网络信标是什么?它是一张显示在网页上的图像,它是透明的,因此人们实际上并不注意它就在那里。这有时被称为跟踪像素。这里的想法是这个跟踪像素有一个唯一的 ID 号,它标识了这个网页已被提供给的特定个人。

这可以用于几个不同的目的:

  • 电子邮件跟踪:因此如果您在 HTML 邮件文件上有一个不可见的图像,那么当有人打开该邮件文件时,HTML 文件的 img src 等于它的名称有一个此特定电子邮件的唯一 ID 号。当服务器收到对该隐形跟踪像素图像的请求时,它说“哦,拥有该 ID 号的人刚刚打开了电子邮件消息”。这就是为什么我收到该特定跟踪请求的原因。企业可以使用它来查看某人是否阅读了特定的电子邮件。垃圾邮件发送者也可以使用它来查看该电子邮件地址是否处于活动状态。因此垃圾邮件发送者会发送一堆 HTML 中的电子邮件,所有电子邮件都完全相同,除了跟踪信标。所以,会有 img src=,然后会有一个唯一的 ID 号 .jpeg 或唯一的 ID 号 .png。现在会发生什么?当您继续接收该电子邮件时,如果您的电子邮件设置为显示图像,您的网络浏览器将要显示的图像,或者您的电子邮件程序将向跟踪信标来自的任何地方发送 HTTP 请求,并且它将发送请求具有只有您在电子邮件中收到的唯一 ID 号的特定图像文件。然后另一端的服务器将看到请求了该特定 ID 号,并且能够将其与原始已发送的消息匹配。您可能已经注意到,许多电子邮件程序都有不显示图像的选项,直到您明确说“我相信发件人,现在继续显示图像”。这是主要的,他们这样做的原因是为了防止这种跟踪。
  • 跨网站跟踪:这些也可以用来跟踪人们在互联网上的活动,使用第三方 Cookie。当我们谈论第三方时,我们谈论的是前两个派对是你和你正在访问的网站。所以有一个是第一方,第二方,和第三方是其他方。所以这里的想法是网站可以允许第三方(例如广告网络)跟踪用户的跨多个网站的操作。这可以通过这些跟踪像素来完成,这些跟踪像素我们刚刚在这些不可见的像素图像之前讨论过,或者它们实际上可能是通过可见的广告来完成的。但是这里发生的是当您访问网页时,使用 Cookie 参与此第三方网络的情况是,除了该网页的常规内容之外,您还将 HTML 文件也将引用其中之一跟踪像素网络信标图像,或者它将引用显示在网页上的实际广告。无论是请求该网络信标还是该图像文件,不是来自他们的服务器,而是来自第三方服务器。第三方服务器发生的情况是,第三方向您发送该图像,无论它是跟踪信标还是实际广告。此外,它还在您的网络浏览器上放置一个 Cookie。因此 Cookie 存储是一种用于存储少量信息的技术,你的网络浏览器可以在你下次访问它时设置回网站。所以假设你去亚马逊,亚马逊会在你的网络浏览器中放一些信息,这样你下次访问亚马逊时,你的网络浏览器将把该信息发送回亚马逊,这就是亚马逊在您下次访问时实际上知道您是谁的方式。类似地,您知道您是否订阅了一些东西,所以就像你知道我订阅了纽约时报,纽约时报通过把这个小 Cookie 放在我的网络浏览器上来跟踪我,下次我访问纽约时报时,我的网络浏览器将那个 Cookie 发送回纽约时报,我访问并说“哦,那是帕特里克,他有纽约时报的学术访问权限,所以,嗯,他很好”。所以这里的想法是,这些第三方网络,因为 HTML 文件引用了来自这些第三方服务器的图像,服务器现在能够向您发送跟踪信息并将此 Cookie 信息放入您的网络浏览器。当您移动到另一个网站时,如果该网站也再次提供图像,无论它们是这些不可见的网络信标还是它们的可见广告,向第三方请求广告,服务器出现,他们之前放入的第三方 Cookie 也将发送给他们。所以,他们会像“哦,这是同一个用户,我在另一个网站上看到了这个用户,我现在在这个网站上看到了他们”。只要您访问该跟踪网络上的不同网站,他们就可以建立您访问的所有不同类型网站的图片,建立有关您的所有网络浏览习惯的所有信息,并根据他们想要做什么来为您创建个人资料。

此外,如果我在商店网页上输入了我的姓名和电话号码以及其他信息,这些信息可以与广告网络结合使用。广告网络如果该网站选择将该信息传递给广告网络,则广告网络不仅可以了解我的网络浏览习惯,但他们可能有我的姓名、地址和电话号码,以及我输入的任何其他信息。

如何保护个人隐私? 🛡️

与跟踪网站一起工作的主网站没问题,所以这听起来不太好。我们可以做些什么来保护我们的隐私?

上一课我们讨论了一些关于 VPN(虚拟专用网络)的内容,以及它们如何提供一些隐私。如果你想更进一步的话,有一种叫做 Tor 的东西。Tor 是一种特殊的路由系统,旨在让用户在互联网上保持匿名。你可以把它想象成一个具有更多保护的超级强大的 VPN,但它的速度要慢得多。Tor 实际上代表洋葱路由器,它指的是当用户访问 Tor 网络服务器时它使用的网络的多层性质。

他们的信息被加密并通过一系列 Tor 服务器传递,网络中有 6,000 多台服务器。因此当您继续并通过 Tor 访问某些内容时,发生的情况是您将通过 Tor 网络获得一个特殊的序列,您的信息将从一台 Tor 服务器传递到另一台 Tor 服务器,然后再传递到另一台服务器。当信息返回时,它应该遵循与信息相同的路径。该路由信息仅适用于 10 分钟,如果您在 10 分钟后再次访问,您将获得不同序列的随机服务器。中间 Tor 服务器无法看到数据是什么,但最终是为了让您访问常规互联网服务器。在远端,信息将必须被解密,并且需要发现您请求的目的地。因此最后一个 Tor 服务器将能够对您设置的信息有所了解。因此,如果您希望您的信息完全隐藏在 Tor 服务器之外,您需要在仅使用 Tor 的基础上使用加密,这样您就可以使用 HTTPS 或安全电子邮件。请注意,通过使用 Tor 本身,你并没有完全加密你的数据。

政府对数据的使用 🏛️

好吧,让我们谈谈政府对数据的使用以及政府对隐私的担忧。我经常在课堂上开玩笑的一件事是,我真的不在乎国家安全局是否可以阅读我的电子邮件。所以,你知道这里的基本思想是我确实想保护自己免受随机黑客的攻击,但是如果国家安全局想闯入我的帐户,我并没有声称国家安全局拥有如此良好的安全性,无法破坏我的东西并阅读我发送给我学生的电子邮件。所以请注意我发送给您的电子邮件以及您发送给我的电子邮件,我们应该对此担心吗?我是否应该担心美国政府?也许国家安全局实际上可以很好地闯入我的帐户,这是在线思考的一些事情。

匿名可以保护毒贩和恐怖组织,并允许在线骚扰,很多人

L23.2:隐私与大数据:大数据 📊

在本节课中,我们将要学习“大数据”这一核心概念。我们将了解大数据的定义、其关键特征(通常被称为“3V”),并通过实际案例探讨大数据技术如何被应用,以及它所带来的隐私挑战。

概述

大数据背后的基本思想是,我们可以利用不断增强的处理能力和不断降低的存储成本,从海量数据中提取有用的信息。随着处理能力的提高,我们能够收集越来越多的数据,并更轻松地处理它。人工智能领域的新技术,特别是机器学习,为我们提供了处理大数据的新方法。所以,大数据是指这种处理大量数据的能力。

大数据的“3V”特征

没有研究大数据的人有时会指大数据的“三个V”,这是描述其核心特征的常用框架。

以下是“三个V”的具体内容:

  • 第一个V是信息量(Volume):指我们拥有多少数据。如果我们需要处理太多信息,我们将需要大型存储设施来存储该信息,然后在稍后的某个时间点对信息进行批处理。
  • 第二个V是速度(Velocity):指数据到达的速度。如果数据快速到达,我们通常可以实时处理它,因为这很紧迫。然而,如果数据没有那么快到达,我们则可以采用不同的处理策略。
  • 第三个V是多样性(Variety):指有多少不同类型的数据。我们是在处理结构化数据还是非结构化数据?一般来说,我们可以将数据分为两种类型:
    • 结构化数据:这是一种易于计算机处理的数据,例如信息表之类的内容(如过去的乘客清单列表)。计算机非常容易处理它,因为它已经是计算机可以理解的格式。
    • 非结构化数据:对于计算机来说更难处理,这可能类似于视频。例如,假设我们有一个人们到达和离开客运大楼的视频,显然需要花费大量的工作去处理它并从中获取一些有用的信息。

数据的真实性(第四个V?)

一些研究人员在先前三个V的基础上增加了第四个V,这就是数据的真实性(Veracity)。这指的是我们正在使用的信息有多准确,以及我们拥有数据源的确定性如何。不幸的是,我认为在我们当前的环境中考虑这一点很有意义。

我特别认为,像深度伪造和俄罗斯机器人账户这样的不良行为者在Twitter上散布信息,使得考虑我们的数据源在哪里,以及这些数据源可以告诉我们什么,现在变得越来越重要。实际上,使用大数据技术研究俄罗斯机器人帐户本身也可以为您提供一些信息,但您确实需要仔细考虑您的数据来源。

大数据的应用与隐私挑战

上一节我们介绍了大数据的特征,本节中我们来看看大数据的具体应用及其引发的隐私问题。大数据可以用来做的一件事是消除匿名性,即从被认为是匿名的数据中识别出个人身份。

案例一:Netflix数据去匿名化

在一个示例中,Netflix发布了一组删除了用户名称的数据,并允许不同的数据研究人员使用那个数据并从中学习。结果是,德克萨斯大学奥斯汀分校的一个小组能够删除数据的匿名性。

他们所做的是,将Netflix提供的数据与不同人在其他平台(如IMDb)上观看或评分的数据进行比较。当时Netflix曾经能够让用户给电影评分。他们所做的是,将匿名数据中提供的评分,与各种用户在IMDb上提供的评分进行比较。即使有人在IMDb上只有几个评分,他们也能够将IMDb帐户与Netflix帐户相匹配,并在此基础上他们能够找出IMDb中哪些不同的用户实际观看了Netflix上的内容,即使那些IMDb用户没有打算在Netflix上公开他们的观看记录。

案例二:Target商店的怀孕预测

大数据的一个著名用途是Target商店的怀孕检测故事。这个故事来自《纽约时报》的一篇文章。

这个故事的背景通常基于加州大学洛杉矶分校的一些研究:客户会养成习惯并在同一家商店购买平凡的物品(如肥皂、牙膏、垃圾袋)。但是,那些购买习惯可能会在重大的生活事件中发生变化,例如结婚、购买新房子或生孩子。当有人生孩子时,这是一个很大的机会让商店介入并尝试改变不同人的购买习惯。此外,从商业角度看,怀孕的另一个好处是,新的准父母预期会购买很多东西。

然而,官方的公共出生记录每个人都可以访问。因此,如果您等待公共出生记录出来,您将在与其他竞争者用信息轰炸这些父母完全相同的时间投放广告。所以,Target想要一种方法来在他们的竞争对手之前发现人们怀孕。

为了做到这一点,Target实际上有一堆关键数据集真正帮助了他们:

  1. 客户购买习惯数据:如果客户有折扣卡,Target会了解一些客户的购买习惯。这些折扣卡用于跟踪您的信息,因此作为获得购买折扣的回报,商店将能够识别您的特定购买模式。此外,他们还有其他信息,例如可能知道您的年龄、婚姻状况、您拥有的孩子数量、您离最近的目标商店有多远,并估计了工资。
  2. 婴儿登记处数据:Target拥有的第二个数据集是注册了Target婴儿洗澡登记处的人的信息。因此他们有一组关于购买习惯的信息,他们能够具体确定其中一些人何时怀孕,并对时间线有一些了解。

所以他们所做的是获取所有信息并使用大数据技术。他们能够发现孕妇在怀孕第二个三个月开始时,购买了大量无香味的乳液。她们在前20周购买了钙、镁和锌补充剂。她们还购买了无味肥皂和特大袋棉球。总共,Target能够识别25种不同的项目作为他们的怀孕指标。

他们还学到了一个关于广告的教训:基本上他们了解到,如果你向人们发送装满婴儿用品的广告,他们会发现那种做法令人毛骨悚然。所以,他们决定最好混合广告。从文章中引用他们的话:“我们混合了所有这些广告,加入了一些我们知道孕妇永远不会购买的东西。所以婴儿广告看起来很随意。我们会在尿布旁边放一个割草机的广告。只要孕妇认为她没有被监视,她就会使用优惠券。”

一个有趣的轶事

这个故事还有一个有趣的轶事:一个人走进明尼阿波利斯郊外的Target,要求见经理。据一位参与对话的员工说,他很生气。“我女儿在邮件中收到了这个,”他说,“她还在上高中,你要给她寄婴儿衣服和婴儿床的优惠券?你是想鼓励她怀孕吗?”经理不知道这个男人在说什么。他看了看邮件,果然是写给这个男人的女儿的,里面有孕妇装、育儿家具的广告和微笑婴儿的照片。经理道歉,然后几天后再次打电话道歉。这时父亲说:“我不得不和我女儿谈谈……我欠你一个道歉。”原来,他的女儿确实怀孕了,而Target的大数据预测比这位父亲更早知道了这件事。

所以,这是另一个关于计算可以做什么的好故事。

总结

本节课中我们一起学习了“大数据”的概念。我们了解到,大数据是指利用强大计算能力处理海量、高速、多样(甚至可能真实性存疑)数据的技术。我们通过Netflix去匿名化和Target怀孕预测两个案例,看到了大数据强大的分析预测能力,同时也深刻认识到它对个人隐私构成的严峻挑战。这些技术能够在人们自己意识到之前揭示其敏感信息,这提醒我们需要在数据利用与隐私保护之间审慎权衡。

L24.1:什么是人工智能 🤖

概述

在本节课中,我们将要学习人工智能的基本概念。我们将回顾人工智能的历史,探讨其定义,并了解推动其发展的关键因素。通过本课,你将能够理解人工智能在日常生活中的应用,并对其核心思想有一个初步的认识。


人工智能的历史与定义

上一节我们介绍了本课程的主题。本节中,我们来看看人工智能是什么以及它是如何被定义的。

人工智能的常见例子

以下是人们在日常生活中可能遇到的一些人工智能应用示例:

  • 自动驾驶汽车:例如特斯拉的自动驾驶功能。
  • 游戏AI:经过训练来玩游戏的程序,例如击败国际象棋世界冠军的Deep Blue。
  • 语音助手:例如Siri、Google Assistant等能够理解和回应语音指令的系统。
  • 图像识别:能够识别图片中物体或人物的系统。
  • 算法交易:在股市中自动买卖股票的工具。
  • 推荐系统:例如购物网站根据你的浏览和购买历史推荐商品。
  • 健康监测:例如智能手表根据步数和心率等数据对你的活动进行分类。
  • 机器翻译:例如谷歌翻译,将一种语言的文本转换为另一种语言。
  • 垃圾邮件过滤器:自动将电子邮件分类为垃圾邮件或正常邮件。
  • 声音分离:从包含多种声音的录音中分离出特定声源的技术。
  • 人脸识别:例如社交媒体平台识别照片中的人物。

从以上例子可以看出,人工智能似乎涵盖了非常广泛的任务。那么,究竟什么是人工智能?

人工智能的定义

一个常见的定义是:人工智能是让机器执行通常需要人类智能才能完成的任务

这个定义的优点是直观,涵盖了大多数例子。但它也存在缺点:过于宽泛。例如,计算器能执行1 + 1的运算,这也需要人类智能,但我们通常不认为计算器是人工智能。

另一个定义是:人工智能是让机器执行没有明确编程指令的任务

这个定义引入了“没有明确编程”的概念,更贴近现代机器学习的思想。然而,这个定义也有些模糊,因为“明确编程”的界限并不清晰。所有程序最终都是由程序员编写的,区别在于指令的抽象层级。

虽然这两个定义都不完美,但它们帮助我们更好地理解人工智能的范畴。目前并没有一个唯一、完美的定义,因为人工智能领域本身在快速发展。

人工智能的演进:从AGI到ANI

早期的人工智能研究主要关注通用人工智能,即让计算机像人类一样思考和行动。图灵测试是衡量AGI的一个著名标准:如果一台机器能在对话中让人无法区分它是人还是机器,那么它就通过了测试。

然而,实现AGI极其困难。历史上曾出现过“人工智能冬天”,即因进展缓慢而导致的研究经费削减和兴趣衰退。

近年来,人工智能的复兴很大程度上得益于研究重点转向了狭义人工智能。ANI指的是在特定任务上达到或超越人类水平表现的AI系统,而不是追求全面的、人类般的智能。

这符合计算机科学的模块化思想。我们不再试图一次性构建一个完整的人类智能,而是将复杂能力分解为多个较小的、可管理的任务模块,并分别攻克它们。事实证明,这种方法非常成功。


推动现代人工智能发展的关键因素

上一节我们了解了人工智能定义的演变。本节中,我们来探讨是什么力量推动了现代人工智能,特别是机器学习的蓬勃发展。

现代人工智能的进步主要得益于三个方面的改进:更强大的计算硬件更先进的算法更大量的数据

计算硬件与算法

我们训练AI模型,本质上是让机器从数据中学习模式。这个过程通常涉及大量的数学运算,特别是线性代数中的矩阵计算。

  • 更快的CPU和GPU:图形处理器特别擅长并行处理这些运算,这大大加快了训练AI模型的迭代速度。
  • 更好的算法:更高效的算法可以用更少的计算资源和数据达到更好的效果。

数据的重要性

然而,如果没有数据,再强的硬件和算法也无用武之地。数据是训练AI的“燃料”。

为了理解数据量的重要性,可以看一个简单的性能曲线图。假设模型的性能随训练数据量增加而提升:

  • 一个简单的模型(如小型神经网络),其学习曲线可能较早达到平台期。
  • 一个复杂的模型(如大型神经网络),拥有更强的学习能力,在数据量充足时,性能可以远超简单模型。

关键点在于:拥有更好的算法和模型,只有在拥有足够数据来训练它们时,才能真正发挥优势。

实例分析

以下是数据驱动成功的典型案例:

  1. 谷歌广告:谷歌通过分析用户的海量搜索历史、邮件内容等数据,能够极其精准地预测用户兴趣并投放广告,这是其核心盈利模式。
  2. 特斯拉自动驾驶:特斯拉相比其他公司的一大优势在于其庞大的真实车队数据。每一辆行驶在路上的特斯拉汽车都在收集数据,用于持续改进其自动驾驶算法。
  3. 政治广告定向:类似Cambridge Analytica的公司,通过分析社交媒体数据来精准投放政治广告,影响选民。

这些例子都表明,访问独特、大规模的数据集,是构建强大AI系统的关键竞争优势


总结

本节课中,我们一起学习了人工智能的基础知识。我们首先通过日常例子感受了AI的广泛应用,然后探讨了人工智能的定义及其历史演变——从追求通用人工智能转向专注于特定任务的狭义人工智能。最后,我们分析了推动现代AI发展的三大支柱:计算硬件算法数据,并理解了海量数据在训练高级AI模型中的决定性作用。

通过本课,你应该对“什么是人工智能”有了更清晰、更结构化的认识。

L24.2:人工智能的子领域 🧠

概述

在本节课中,我们将要学习人工智能(AI)的各个子领域。我们经常听到机器学习、神经网络、深度学习等术语,但可能并不清楚它们的实际含义。本教程的目标是帮助你对这些常用的人工智能术语有一个大致的了解。

人工智能的广阔世界

如果整个屏幕代表人工智能的整个世界,那么目前最大的子领域是机器学习

什么是机器学习?

机器学习可以理解为对计算机系统所用算法和统计模型的研究。这些系统能够有效执行任务,其特点在于不使用依赖模式和推理的显式指令来完成特定任务。

为了更好地理解,接下来我们进入机器学习的几个核心子领域。

机器学习的子领域

1. 监督学习

监督学习是指当我们拥有由输入(x)预期输出(y) 组成的训练数据来训练模型时。它之所以被称为“监督”,是因为我们知道正确答案是什么,所以我们在训练时告诉模型:如果给定输入 x,你应该输出 y。

请注意,输入 x 可以由多个特征组成,所以我们通常将 x 写为 x₁, x₂, …, xₙ。在这种情况下,有 n 个不同的输入,模型会给出一个特定的输出 y。

一个例子是预测房价

  • 输入(x)可能是:平方英尺、卧室数量、浴室数量。
  • 输出(y)是:价格。
  • 训练这个模型的数据就是这样一个表格,其中 x₁ 是平方英尺,x₂ 是卧室数量,x₃ 是浴室数量,y 是价格。

一旦模型使用所有数据进行训练,我们将能够根据新的输入(平方英尺、卧室数、浴室数)来预测该房产的价格。

在这个框架下,可以应用许多不同的算法。最广为人知的算法之一是线性或逻辑回归。你可以在某些数学或统计学课程中看到这种方法。

基本思想是寻找最佳拟合线。你有一堆数据点,试图拟合一条最合适的线。一旦得到这条线,你就可以插入新的数据点,找出它在线上的位置,从而进行预测。

我们可以用多种方式表示这个模型。一种方式是画一张图:我们有一堆输入 x₁, x₂, x₃,将它们全部传递到一个函数中,该函数代表由非线性函数组成的最佳拟合线,最后输出 y。

另一种方法是使用神经网络

2. 神经网络

神经网络背后的想法是,我们不仅拥有一个函数,而是将多个函数链接在一起。我们称它们为神经网络的原因是,它借鉴了认知科学中关于神经元的想法。

如果你上过关于人脑的生物课,你就会知道它由一堆相互连接的神经元组成。一个神经元接收其他几个神经元的输出,并给出自己的输出。这里的图片就抓住了这个想法:它接受多个输入并给出一个输出。

因此,神经网络接收一些输入,并将它们传递给多个函数。图中的每一个方框都代表一个函数,每个函数可以接收所有可能的输入。我们可以进一步将更多的函数链接在一起。

这些函数接收先前函数的输出。我们拥有的每一组函数称为一层,每一层内部都可以有任意数量的神经元,所以它是一个非常灵活的结构。

当然,事情可以变得更复杂。有一堆不同类型的神经网络,最常被提到的是深度神经网络

3. 深度学习

为了理解深度神经网络,它基本上就是一个拥有许多相互连接层的神经网络。

现在我们已经“深入”了解了神经网络(双关语),所以让我们回溯一点,看看机器学习的其他领域。

4. 无监督学习

在机器学习领域,我们有监督学习。与此相反,我们也有无监督学习

回想一下,我们称监督学习为“监督”,是因为它接收的训练数据由输入 x 和预期输出 y 组成。在无监督学习中,我们将不再拥有预期输出 y。

无监督学习算法集对于当我们甚至不知道答案应该是什么时非常有用,我们只是试图找到所拥有数据中的内在趋势或结构。

因此,有许多不同的算法属于这一类别。一个可以解决的示例问题是聚类

假设我们有一堆只有两个特征(x₁ 和 x₂)的数据点。每个点有不同的 x₁ 和 x₂,我们可以在这个图表上绘制它。虽然人类可以直观地看到有两个不同的组,但无监督学习算法允许计算机自动识别这些分组。

这对人类也非常有用,因为我们并不总是能够看到不同的数据组。注意,在这个例子中只有两个不同的输入(x₁ 和 x₂)。现在想象一下,如果有 17 个甚至数千个不同的可能输入,你如何将所有数据点组合在一起?无监督学习算法将能够非常有效地告诉我们。

从应用的角度来看,像这样的技术可以用来找出消费者趋势,并更具体地针对人群定位广告。例如,在隐私和安全讲座中,我们看到一位女性成为怀孕相关广告的目标。最有可能发生的是,他们注意到她有特定的购物趋势。所以,像 x₁ 和 x₂ 这样的特征可能是她购买的不同类型的东西。算法把她放在一个主要由孕妇组成的小组里,所以他们才知道她有可能怀孕了,并开始给她推送怀孕广告。

5. 强化学习

除了监督学习和无监督学习,还有强化学习

这与前两者有点不同,因为我们没有大量带标签的数据,我们可能根本没有任何初始数据。我们不是尝试从数据中学习一个模型,而是已经有一个预先存在的模型(环境),该模型定义了代理可以在每个步骤中采取的不同行动,以及与在特定状态下采取行动相关的奖励

现在只需将其视为有“动作”和“奖励”。该模型的“奖励”方面正是“强化”发生的地方。算法的目标是最大化它随着时间的推移可以获得的整体奖励

从一个实际应用观点来看,就是弄清楚当一个 Roomba(扫地机器人)在吸尘时应该如何穿过房间。

假设我们有一个在房间里的 Roomba,房间可以表示为一个网格。假设 Roomba 在这里,这里有垃圾,然后这里和这里也有。在这种情况下,模型将有一个与其中有垃圾的方格相关的正奖励,和一个在没有垃圾的方格中的中性奖励。我们还可以指定具有负奖励的网格空间来表示我们想要避免的区域(如障碍物)。

因此,强化学习算法的目标是找出 Roomba 应该走哪条路径,以便最大化它的奖励。在这种情况下,可能的动作是向上、下、左、右移动。

一个有效的路径是让 Roomba 向上移动,然后向右移动,然后向下,然后向右移动,收集所有垃圾碎片。但是,如果模型设置为使更早获得的奖励更有价值,那么也许最好的动作是让 Roomba 首先向上移动收集一个,然后向右移动,然后向下再向右移动。

我们还可以指定强化学习模型来考虑 Roomba 还剩下多少电量。假设 Roomba 只剩五步电量。在这种情况下,与其向上移动来收集一个奖励,不如让 Roomba 向下移动一、二、三步,然后向右移动两步,这样它会得到两个奖励,而不是只有一个奖励并且无法达到其他两个。

因此,一般而言,强化学习算法能够通过采取最佳行动来随着时间的推移最大化奖励量。

非机器学习的子领域

到目前为止,我们讨论过的所有示例和子领域都属于机器学习的范畴。

现在让我们看看一些非机器学习的人工智能子领域。这里变得有点复杂,因为有很多人工智能技术结合了机器学习与非机器学习技术。我要列出的所有内容很可能与机器学习有很大的重叠。

1. 人工语音

这包括文本到语音语音到文本等技术。人工语音的目标是从音频翻译成文本,反之亦然。在这两种情况下,语音都侧重于“所说的话”,而不是其意义。

2. 自然语言处理

这通常被称为 NLP。它侧重于所讲单词背后的含义。因此,NLP 的示例包括:

  • 情感分析:找出句子背后的情感。
  • 机器翻译:在单词、句子甚至段落之间进行翻译。

请注意,有时人工语音和 NLP 之间的区别是模糊的。那是因为存在很多系统实际上同时使用两者。例如,当你告诉 Alexa “打开灯”时,它通过将你的语音转换为文本来使用语音识别,并使用一些 NLP(如意图识别)来弄清楚你实际上试图让 Alexa 做什么。

这两者不被严格视为“纯”机器学习的原因,是因为我们经常会明确编程这些系统的一些附加信息。这意味着我们违反了机器学习的“无显式指令”定义。我们可能会给 NLP 系统一个内置字典,或者在我们语言的语法结构中明确编程,以更好地帮助语音到文本的翻译。

但是,除了这些明确的指令之外,这些系统还经常使用额外的机器学习技术(例如深度神经网络)来完成其任务。顺便提一下,我们也将深度神经网络称为深度学习。所以,如果你听说过“深度学习”这个术语,它指的就是这个。

3. 规划、调度与优化

这个子领域与强化学习有很大的重叠,因为 Roomba 路径规划问题也属于规划、调度和优化,因为它试图找出最佳任务序列应该是什么。

另一个属于此类问题的任务示例是:如何在时间和教室资源限制的情况下最佳地安排课程表。

4. 计算机视觉

它接收视频和照片,并尝试确定这些媒体的主题是什么。这个领域和神经网络之间有巨大的重叠,但我们也添加了硬编码的东西,比如面部特征检测器等。

5. 专家系统

这些系统具有用于逻辑推论的硬编码规则。所以它包含一个知识库(一组已知为真的事实)和一个推理引擎。它使用推理引擎从已知事实中推导出新事实。

例如,如果我们已经知道“如果下雨,那么人们会使用雨伞”,并且我们也知道“正在下雨”,那么推理引擎可以用来推断“人们正在使用雨伞”。

6. 机器人学

也许这个领域与许多其他领域重叠,因为它太广泛了。例如:

  • 我们有像 Roombas 这样的东西,它可以利用规划、调度和优化,以及强化学习和计算机视觉。
  • 我们也有机器人,其主要任务是尝试模仿人类走路的方式,这是一个机器学习问题(我们试图弄清楚如何在走路时保持平衡)。
  • 最后,我们也有自动化机器人之类的东西,所以这可能是一个关于如何以最佳方式自动化任务的人工智能问题,这可能属于优化类别。

总结

本节课中,我们一起学习了人工智能的各个子领域。最终,人工智能领域是非常跨学科的,所以人工智能的子领域也遵循这一趋势。这就是为什么我们今天看到的人工智能的各个子领域之间有如此多的重叠。

请注意,这些框图都不是按比例绘制的,真的没有办法测量每个子领域的大小,而且所有这些边缘都应该变得非常模糊,因为各个子领域之间有太多重叠。但我希望,通过走出这些框图,并在对每个子领域进行分类之间进行思考,我已经让你更好地了解这些子领域可能如何相互关联。

L25:人工智能:它是如何完成的 🤖

在本节课中,我们将通过一个具体的例子来揭开人工智能(AI)的神秘面纱。我们将从构建一个垃圾邮件分类器开始,逐步深入到其背后的数学原理——逻辑回归,并最终将其推广到更强大的神经网络。我们的目标是让你理解AI是如何“实际完成”的,并了解AI工程师在其中扮演的角色。

垃圾邮件分类器示例 📧

上一节我们介绍了本课程的目标,本节中我们来看看一个具体的AI应用:垃圾邮件分类器。我们将以短信垃圾邮件分类为例。

分类器的目标是接收一条消息,并判断它是垃圾邮件(spam)还是正常邮件(ham,即非垃圾邮件)。为了训练这个AI,我们需要数据。

以下是训练数据的基本结构:

  • 每个训练样本包含一条文本消息以及其正确的分类(spam或ham)。
  • 例如,一条内容是“您的手机使用了11个月或更长时间……”的消息被标记为“spam”。
  • 另一条内容是“我一直在寻找合适的词来感谢您的喘息……”的消息被标记为“ham”。

训练数据中需要有大量样本。对于这个相对简单的问题,我们使用了大约5000条已标记的消息。而对于更复杂的问题(如人脸识别),则需要数十万甚至数百万个训练样本。

我们可以将这个问题建模为:输入x是消息,输出y是分类(spam或ham)。接下来,我将展示一个已训练好的简单逻辑回归分类器的实际效果。

逻辑回归的工作原理 🧮

上一节我们看到了分类器的应用,本节中我们来看看其核心——逻辑回归是如何工作的。我们将通过一个更简单的例子来理解它。

假设你在一家汽车保险公司工作,需要根据驾驶员的经验年数(e)和事故数量(a)来预测其驾驶员得分(y)。得分正数表示好司机,负数表示坏司机。

我们拥有一些历史数据,每个数据点都对应一个(e, a)组合及其得分。将这些点画在图上,好司机(正分)和坏司机(负分)大致可以被一条直线分开。

线性回归的目标就是找到这条最佳分割线。一条直线的方程可以表示为:
y = m*x + b
在我们的例子中,有两个输入变量ea,因此方程扩展为:
y = c0 + c1*e + c2*a
这里的c0c1c2就是我们需要寻找的常数(或称权重、参数)。

计算机通过以下方法找到这些常数:

  1. 随机初始化c0c1c2的值。
  2. 用这些值计算当前模型对所有训练数据的预测。
  3. 评估预测结果的好坏(即与真实得分的差距)。
  4. 根据评估结果,一点点地调整c0c1c2的值(例如,c0从200变为201)。
  5. 重复步骤2-4,直到找到一组能很好拟合数据的常数。

例如,计算机可能最终找到c0=0c1=50c2=-100。那么模型就是:
y = 0 + 50*e - 100*a
我们可以用这个模型来预测新数据。例如,一个9年经验、2次事故的司机,得分y = 50*9 - 100*2 = 250(正分,好司机)。

这个模型可以推广到任意数量(n个)的输入变量x1, x2, ..., xn
y = c0 + c1*x1 + c2*x2 + ... + cn*xn

逻辑回归在线性回归的基础上增加了一个sigmoid函数。sigmoid函数能将任何数值映射到0和1之间,其公式为:
g(z) = 1 / (1 + e^{-z})
其中e是自然常数(约2.718)。

因此,逻辑回归的预测公式是:
y = g(c0 + c1*x1 + c2*x2 + ... + cn*xn)
这个介于0和1之间的结果非常适合表示概率。例如,在垃圾邮件分类中,y可以表示一条消息是垃圾邮件的概率。

从逻辑回归到神经网络 🧠

上一节我们介绍了逻辑回归,它有时无法处理更复杂的数据模式。本节中我们来看看如何通过组合多个逻辑回归单元来构建更强大的模型——神经网络。

考虑一个新的司机评分问题,输入是经验年数(e)和车辆年龄(v)。我们希望有经验司机开新车得负分(维修贵),新司机开旧车也得负分(不安全)。数据点在图上形成两个分离的负分区,无法用一条直线完美分开。

单个逻辑回归单元(可视为一个“神经元”)的图示如下:输入ev进入一个方框(执行sigmoid(c0 + c1*e + c2*v)计算),然后输出y1

神经网络的思路是将多个这样的神经元连接起来。例如:

  • 第一层:我们可以有三个神经元。每个神经元都接收ev作为输入,但各自拥有独立的参数(c0, c1, c2c3, c4, c5c6, c7, c8),并产生三个不同的输出y1y2y3
  • 第二层:我们再设置一个神经元。它不直接接收ev,而是接收第一层三个神经元的输出y1y2y3作为输入,并拥有自己的参数(c9, c10, c11, c12),最终输出预测结果y

通过这种层级连接,模型获得了极大的灵活性,能够学习更复杂、非线性的数据边界。代价是需要调整的参数(c0c12)更多,因此需要更多的数据和计算来训练。

神经网络的层数和每层的神经元数量可以自由设计,形成非常灵活的结构。例如,输入层 -> 5个神经元的第一层 -> 3个神经元的第二层 -> 输出层。

要直观感受神经网络的工作,强烈建议访问 TensorFlow Playground (playground.tensorflow.org)。你可以:

  • 选择不同的数据集(如螺旋形数据)。
  • 调整网络层数和神经元数量。
  • 观察网络如何学习并形成决策边界。
  • 理解当简单模型(如逻辑回归)失效时,更复杂的网络或添加更多特征(如x1^2sin(x1))如何帮助解决问题。

AI工程师的角色 👨💻

上一节我们探讨了技术的核心,本节中我们来看看如何将这些技术应用于实际问题,以及AI工程师的角色。

回到我们的垃圾邮件分类器。在实现中,输入x通常是消息中单词的出现频率。例如,对于句子“goodnight room goodnight moon”,模型会为“goodnight”、“room”、“moon”等单词创建对应的特征x1x2x3...,并忽略单词的顺序和含义。

AI工程师的任务就是完善这个模型。他们需要考虑以下问题:

以下是AI工程师可能进行改进的一些方面:

  • 特征工程:如何预处理和设计输入特征?例如,将同义词映射到同一特征,考虑单词组合(n-grams)而非单个单词,或者添加像“消息长度”、“是否包含数字”等新特征。
  • 数据:收集的数据是否足够?数据是否真实反映了实际使用场景?(例如,用约会网站垃圾邮件训练的模型,可能不适用于推销短信)。
  • 模型选择:是使用简单的逻辑回归,还是更复杂的神经网络或其他算法?
  • 调优:如何设置模型参数?如何评估模型表现并迭代改进?

总结 📝

本节课中,我们一起学习了人工智能如何从概念走向实现。

  1. 我们从一个具体的垃圾邮件分类器例子入手,理解了训练数据的重要性。
  2. 我们深入剖析了逻辑回归的数学原理,明白了它如何通过调整权重来拟合数据,并用sigmoid函数输出概率。
  3. 我们看到了单一模型的局限性,并通过连接多个逻辑单元引入了神经网络的概念,它通过增加模型的复杂度和灵活性来解决更困难的问题。
  4. 最后,我们探讨了AI工程师的职责,他们通过特征工程、数据管理和模型选择与调优,将理论知识转化为有效的实际应用。

希望这节课帮助你揭开了AI的神秘面纱。AI并非魔法,而是建立在清晰的数学和工程原理之上的一套强大工具。

L26.1:云计算 ☁️

在本节课中,我们将要学习云计算的核心概念、不同模型及其与传统计算方式的区别。云计算是现代计算领域的重要基石,理解它有助于我们把握当今技术发展的脉络。

概述

术语“云计算”有多种用法,因此需要根据交谈对象和上下文来理解其具体含义。在其基本用途中,云计算可以指代网络上的任何内容,例如文件、服务器或媒体服务。然而,在商业和技术语境下,云计算通常意味着用远程管理的计算机资源替代本地物理服务器。

传统模型 vs. 云计算模型

上一节我们介绍了云计算的基本概念,本节中我们来看看它与传统计算模型的根本区别。

在传统计算模式下,企业需要在自有场所管理和存储计算机。这包括负责安全、供电、网络连接以及所有软硬件的维护和更新。如果需要更多计算能力,企业必须订购、接收并设置新的物理服务器。

云计算模型则旨在改变这一模式。它主要受到两种早期愿景的启发:效用计算和网格计算。

  • 效用计算:类比于水电等公共事业。用户无需提前通知或购买发电机,只需“插入”即可按需使用计算资源,并按实际使用量付费。
  • 网格计算:类比于电网。它强调计算能力应像电力一样成为一种广泛可用的商品,由外部服务商管理,用户可以随时按需伸缩。

这两种愿景的核心思想是相同的:计算能力应成为一种按需提供、可弹性伸缩的实用资源

云计算的主要优势

与传统模型相比,云计算模型为企业带来了显著优势:

以下是云计算的核心优势列表:

  • 灵活性与敏捷性:企业无需提前精确预测需求或经历漫长的采购部署周期,可以快速根据业务需求扩展或收缩资源。
  • 成本效益:企业只为实际使用的资源付费,避免了资源闲置的浪费,也无需承担资源不足导致的业务损失。
  • 降低运维负担:云服务提供商负责底层基础设施的安全性、电力可靠性、网络冗余和硬件维护,减轻了企业的IT运维压力。
  • 全球覆盖与低延迟:大型云服务商在全球拥有多个数据中心,可以将服务部署在靠近用户的位置,从而降低访问延迟,提升用户体验。

云计算的四种服务模型

随着技术发展,早期效用计算的愿景已接近实现,并演化出几种主流的服务模型。

1. 基础设施即服务

IaaS 提供商提供基本的计算基础设施(如虚拟机、存储、网络)。客户需要自己配置操作系统、网络架构和应用软件。

示例:亚马逊AWS的EC2实例和虚拟私有云。
工作方式:客户在AWS管理控制台上创建和配置虚拟机实例,并自行建立这些实例之间的网络。客户拥有对操作系统的完全控制权,但也要负责其维护、更新和安全。

优势:它提供了类似本地服务器的完全控制权和灵活性,同时具备了云计算的弹性伸缩和免硬件维护的优点。

2. 平台即服务

PaaS 提供商提供一个完整的计算平台(包括操作系统、运行时环境等)。客户只需专注于开发和部署自己的应用程序。

示例:Heroku, 亚马逊弹性Beanstalk。
工作方式:开发者使用Git等工具将应用程序代码推送到PaaS平台(如Heroku),平台会自动处理代码的部署、运行和扩展。开发者无需关心服务器、操作系统或网络的具体配置。

优势:极大简化了部署流程,让开发者能更专注于业务逻辑,减少了对底层基础设施的管理工作。

3. 无服务器计算

无服务器计算 更贴近效用计算的原始理念。开发者无需管理任何服务器实例,只需上传函数代码。

示例:AWS Lambda。
工作方式:开发者编写一个函数(例如,处理图像上传),并将其代码上传到云平台。当特定事件触发时(例如,新文件上传),云平台会自动运行该函数。开发者只需为函数执行所消耗的计算资源和时间付费。

优势:实现了极致的弹性伸缩和运维简化,真正做到按需付费,无需容量规划。

4. 软件即服务

SaaS 与前三种模型不同,它直接向最终用户提供完整的软件应用。

示例:Google Docs, Microsoft Office 365。
工作方式:用户通过浏览器或客户端访问提供商托管在云端的软件。所有软件运行、数据存储和维护工作都由提供商负责。

优势

  • 对客户:无需安装、更新或维护软件,IT部门负担轻;通常内置更好的协作功能。
  • 对提供商:采用订阅制,拥有稳定收入流;软件在受控环境中运行,降低了盗版风险。

边缘计算、雾计算与网格计算

除了将计算集中在“云”端,还有一种趋势是将计算推向网络“边缘”。

  • 边缘/雾计算:指在靠近数据源或用户的网络边缘处理数据,而不是全部传送到遥远的云端数据中心。这适用于带宽有限、延迟敏感或需要离线操作的场景(如海上钻井平台、太空任务)。
  • 网状计算:专注于构建去中心化、自我修复的网络。在这种网络中,设备可以直接相互通信,即使部分节点失效,网络依然能运行,增强了鲁棒性,适用于军事或应急通信。

这些模型并非要取代云计算,而是与之协同,形成“云-边-端”协同的计算架构。

总结

本节课中我们一起学习了云计算。我们首先明确了云计算在不同语境下的含义,然后对比了传统计算模型与云计算模型。接着,我们深入探讨了云计算的四大服务模型:IaaSPaaS无服务器计算SaaS,了解了它们各自的特点和适用场景。最后,我们简要介绍了边缘计算雾计算网格计算的概念,它们代表了计算能力向网络边缘扩散的新趋势。理解这些模型,是把握现代计算基础设施的关键。

L26.2:物联网 🌐

在本节课中,我们将要学习物联网的概念。物联网指的是由各种非传统计算设备(如家用电器、传感器等)连接到互联网所形成的网络。我们将探讨物联网的应用实例、关键技术、潜在问题以及安全考量。


上一节我们介绍了物联网的基本概念,本节中我们来看看物联网设备的具体示例,以便更好地理解其应用范围。

以下是物联网设备的一些常见示例:

  • 智能家居设备:例如智能灯泡,用户可以通过网络远程控制其开关、颜色和亮度。
  • 家用传感器与控制器:如运动传感器、智能恒温器,它们可以联网收集数据并接受远程控制。
  • 联网家电:例如连接到互联网的洗衣机和烘干机,用户可以通过网页或应用查看其使用状态和剩余时间。
  • 交通与车辆系统:现代汽车的导航、娱乐系统,以及未来车与车之间的通信,都属于物联网范畴。
  • 智能城市设施:例如联网的智能停车计时器,可以告知用户空闲车位并支持远程支付。

了解了物联网的广泛应用后,我们来看看其背后的一项关键技术:RFID。

RFID 是射频识别技术的缩写。它类似于条形码,能为每个物品提供唯一标识。其核心区别在于,RFID标签无需光学扫描,在一定距离内即可被RFID阅读器读取。RFID标签本身通常没有电源,由读取设备发出的射频信号激活。

RFID在库存管理等领域潜力巨大。员工只需手持阅读器走过货架,即可快速清点所有贴有RFID标签的商品,极大提升了效率。


在探讨了物联网的技术与应用后,我们需要关注其带来的挑战和问题。

以下是物联网面临的主要问题:

  • 连接与寻址:大量设备接入互联网会加速IPv4地址的耗尽,推动向IPv6的过渡。IPv4地址格式如 192.168.1.1,而IPv6提供了远多于IPv4的地址数量。
  • 能耗:许多物联网设备需要低功耗运行,因此常采用蓝牙低能耗等节能连接标准。
  • 安全风险:制造商为降低成本可能牺牲安全性,且许多初创公司可能缺乏安全专业知识。设备固件若不及时更新,会留下漏洞。
  • 隐私担忧:物联网设备收集的大量数据可能涉及用户隐私,需要谨慎对待。

物联网的安全问题值得深入探讨。设备被入侵可能导致严重后果。

以下是物联网设备安全漏洞的实例:

  • 智能灯泡被黑:黑客通过入侵智能灯泡,可能以此为跳板进入家庭内部网络,访问其他更敏感的设备。
  • 物联网摄像头漏洞:存在安全缺陷的家用监控摄像头可能被黑客控制,导致隐私泄露。
  • 智能锁故障:有案例显示,智能锁在进行固件更新时“变砖”,导致用户无法锁门或进门。

一个安全建议是:将物联网设备放置在独立的访客Wi-Fi网络上,与主要个人设备隔离,以降低风险。


本节课中我们一起学习了物联网。我们了解了物联网是由各种日常设备连接互联网构成的网络,并看到了它在智能家居、工业、交通等领域的应用。我们介绍了RFID这项关键技术,也重点讨论了物联网在连接、安全与隐私方面面临的挑战。随着物联网设备越来越多,在享受便利的同时,也需对其安全性和数据隐私保持警惕。

L27.1:理论:算法分析 🧮

在本节课中,我们将要学习如何从理论角度分析和比较不同的算法。选择合适的算法,对于程序能否在合理时间内处理数据至关重要。我们将通过具体的搜索算法例子,理解算法效率的衡量方式。

线性搜索 🔍

上一节我们介绍了算法分析的重要性,本节中我们来看看最简单的搜索方法:线性搜索。

线性搜索从列表的第一个项目开始,逐个比较每个项目,直到找到目标或遍历完整个列表。例如,在一个英国君主列表中寻找“Henry”,我们会从“Mary”开始,依次与“Anne”、“George”等比较,直到找到“Henry”。

线性搜索的运行时间高度依赖于目标在列表中的位置。以下是三种典型情况:

  • 最佳情况:目标项目位于列表开头,例如寻找“Mary”,只需一次比较。
  • 最坏情况:目标项目不在列表中,例如寻找“Melanie”,需要比较列表中的所有项目。
  • 平均情况:目标项目位于列表中间某个位置,平均需要比较约一半的项目。

当列表项目数量 n 增加时,线性搜索所需的时间也会线性增加。我们可以用一个通用公式来描述其平均时间:T = c * n,其中 c 是一个由计算机速度决定的常数。

二分搜索 ⚖️

上一节我们介绍了线性搜索,本节中我们来看看一种更快的搜索方法:二分搜索。但二分搜索要求列表中的项目是有序的。

二分搜索通过反复将有序列表对半分割来快速定位目标。其过程类似于在电话簿中查找名字:先翻到中间,判断目标名字在前半部分还是后半部分,然后对包含目标的那一半重复此过程,直到找到目标。

例如,在一个按字母顺序排列的君主列表中寻找“Edward”。首先,我们找到列表中间的项目“Henry”,由于“Edward”在字母顺序上位于“Henry”之前,我们丢弃后半部分。接着,在前半部分的中间找到“Elizabeth”,再次比较后,最终在剩余部分找到“Edward”。

二分搜索的效率远高于线性搜索,因为它每次比较都能排除一半的候选项目。其运行时间与列表大小的对数成正比。平均时间公式可以表示为:T = c * log₂(n)。这意味着,当数据量 n 翻倍时,所需时间仅增加一个常数,而非翻倍。

哈希表 🪄

上一节我们介绍了基于比较的搜索算法,本节中我们来看看一种近乎“魔术”般高效的查找方法:哈希表。哈希表能提供接近即时的访问速度,无论数据量多大。

哈希表的核心思想是使用一个哈希函数,将复杂的数据项(如单词)转换成一个数字(哈希值),这个数字直接对应数组(哈希表)中的一个索引位置。存储时,将数据项放入该索引位置;查找时,用同样的哈希函数计算目标项的索引,然后直接访问该位置即可。

以下是一个简单的哈希函数示例,它将单词中每个字母在字母表中的位置相加:

# 例如,单词 “cat”
# c=3, a=1, t=20
hash_value = 3 + 1 + 20 # 结果为 24

由于数组大小有限,我们通常用哈希值对数组大小取模,来确定最终位置。例如,数组有10个位置(索引0-9),那么 24 % 10 = 4,因此“cat”应放在索引4的位置。

哈希表在理想情况下,查找时间是一个常数,与表中数据项的数量 n 无关,其时间公式为:T = c。但需要注意哈希冲突(即不同数据项计算出相同的哈希值),有专门的技术(如链地址法)来处理,这超出了本课范围。

大O符号 📊

前面我们比较了不同算法的时间公式,本节中我们引入计算机科学中用于描述算法效率的标准工具:大O符号。它描述了算法运行时间或所需空间如何随输入规模 n 的增长而增长,并忽略常数因子和低阶项,专注于最重要的增长趋势。

以下是常见的算法复杂度分类:

  • O(1):常数时间。如哈希表查找,时间不随 n 变化。
  • O(log n):对数时间。如二分搜索,时间随 n 对数增长,效率非常高。
  • O(n):线性时间。如线性搜索,时间随 n 线性增长。
  • O(n log n):线性对数时间。许多高效排序算法(如归并排序)属于此类。
  • O(n²):平方时间。一些简单的排序算法(如冒泡排序)属于此类,当 n 增大时效率会显著下降。
  • O(2ⁿ):指数时间。通常用于解决某些复杂问题,在 n 较大时几乎不可行。

大O符号描述的是渐近上界,即算法运行时间不会超过该复杂度级别。与之相关的还有:

  • 大Ω符号:描述渐近下界,即算法运行时间至少需要这么多。
  • 大Θ符号:当上下界相同时使用,精确描述了算法的复杂度。

除了时间复杂度,算法还有空间复杂度,即所需内存空间随 n 的增长情况。通常需要在时间效率和空间效率之间进行权衡。

总结 🎯

本节课中我们一起学习了算法分析的核心概念。我们比较了线性搜索、二分搜索和哈希表这三种查找算法,理解了它们的时间复杂度分别为 O(n)、O(log n) 和 O(1)。我们引入了大O符号这一重要工具,用于从理论层面抽象地描述和比较不同算法的效率,重点关注输入规模增长时算法性能的变化趋势。掌握算法分析,能帮助我们在编程时做出明智的选择,确保程序能够高效地处理大规模数据。

L27.2:理论:不可判定的问题 🧠

在本节课中,我们将要学习计算机理论中的一个核心概念:不可判定的问题。我们将探讨计算机程序的计算能力是否存在限制,并通过一个著名的例子——停机问题,来证明确实存在计算机程序无法解决的问题。

概述

计算机程序的功能非常强大,但它们并非无所不能。有些问题是计算机程序无法解决的,这类问题被称为“不可判定的问题”。本节我们将通过一个具体的例子来理解这个概念。

什么是不可判定的问题?

不可判定的问题是指,我们无法编写一个计算机程序来解决它。一个最著名的例子就是“停机问题”。

在开发计算机程序时,我们经常使用其他程序来辅助。例如,带有语法高亮显示的编辑器,它本身就是一个程序,用于分析你正在编写的代码。同样,编译器也是一个程序,用于分析代码是否符合编程语言的规则。

由此引出的一个问题是:我们能否编写一个程序,来判断另一个程序在给定特定输入后,是否会停止运行(即不会陷入无限循环)?如果能,那将非常有用,可以帮助我们避免程序中的无限循环错误。这个问题就是停机问题

接下来,我们将证明,这样的程序实际上不可能被编写出来。

停机问题的矛盾证明

我们将采用“反证法”来证明停机问题不可解。首先,我们假设存在一个能解决停机问题的程序,然后通过逻辑推理得出矛盾,从而证明最初的假设是错误的。

假设存在“停机测试程序”

我们假设存在一个名为 HaltTester 的程序。它接受两个输入:

  1. 一个待分析的程序 P
  2. 提供给程序 P 的输入数据 I

HaltTester(P, I) 的功能是分析程序 P 在输入 I 下的行为。如果 P 会停止,则输出 True;如果 P 会无限循环,则输出 False

# 假设存在的程序 HaltTester
def HaltTester(program, input_data):
    # 神奇地分析 program(input_data) 是否会停止
    # 如果会停止,返回 True
    # 如果会无限循环,返回 False
    pass

构造一个矛盾程序

现在,我们利用这个假设存在的 HaltTester 来构造一个新程序 ParadoxParadox 程序的行为如下:

  1. 它以一个程序 X 作为输入。
  2. 它调用 HaltTester(X, X),即用程序 X 自身作为输入,来测试 X 是否会停止。
  3. 根据 HaltTester 的结果:
    • 如果 HaltTester(X, X) 返回 True(认为 X 在输入 X 时会停止),那么 Paradox 就故意进入一个无限循环。
    • 如果 HaltTester(X, X) 返回 False(认为 X 在输入 X 时会无限循环),那么 Paradox 就立即停止。

def Paradox(program_X):
    if HaltTester(program_X, program_X) == True:
        # 进入无限循环
        while True:
            pass
    else:
        # 立即停止
        return

推导矛盾

现在,让我们思考当 Paradox 程序以它自身的代码作为输入运行时,会发生什么。即,我们运行 Paradox(Paradox)

我们来分析 HaltTester(Paradox, Paradox) 可能得出的两种结论:

  • 情况一HaltTester 认为 Paradox(Paradox) 会停止,并返回 True

    • 根据 Paradox 的代码逻辑,如果收到 True,它将进入无限循环
    • 这与 HaltTester 的判断(“会停止”)相矛盾。
  • 情况二HaltTester 认为 Paradox(Paradox) 不会停止(即无限循环),并返回 False

    • 根据 Paradox 的代码逻辑,如果收到 False,它将立即停止
    • 这又与 HaltTester 的判断(“不会停止”)相矛盾。

无论 HaltTester 如何判断,都会导致矛盾。因此,我们最初的假设——存在一个能正确判断任意程序是否会停机的 HaltTester 程序——是错误的。

总结

本节课我们一起学习了计算机理论中的“不可判定的问题”。我们通过著名的停机问题,证明了并非所有问题都能通过编写计算机程序来解决。我们使用反证法,假设存在一个能判断程序是否会停机的程序,并通过构造一个逻辑上自相矛盾的程序,证明了这样的程序不可能存在。这揭示了计算机计算能力的根本性限制。

posted @ 2026-03-29 09:44  布客飞龙I  阅读(4)  评论(0)    收藏  举报