摘要
这节课开启本课程的第六单元,主题是复合数据。它说明本单元的用途:教会表示数据的集合(固定大小和可变大小的序列、表格和记录),并用基本算法处理它们,这些算法既有亲手写的,也有取自库的,同时估计其代价;介绍本单元的六节课以及把它们串起来的线索;回顾本单元假定已有的知识,尤其是带不变式的循环和按常量引用传递;提出本单元要解决的问题,即一个程序必须保存数量未知的数据,而若用一个个单独的变量来写,它会把多出来的数据默默略过;预先介绍代价,作为衡量算法的第二个尺度,与正确性并列;并说明本单元通向何处,包括后面各单元以及“C++ 程序设计 II”课程。
学习目标
- 描述本单元的内容顺序,并说明每节课为后一节课提供了什么。
- 回顾循环、循环的不变式和参数的传递(按值、按引用和按常量引用),必要时加以复习。
- 借助一个例子,预先理解算法的代价,把它作为衡量算法质量的第二个尺度,与正确性并列。
- 认清学完本单元时应当会做什么,并把它与后面各单元将要学习的内容区分开来。
入门测评
在学习本单元之前,先测一测自己的起点是有益的。这项测评是自我测评:不计分,唯一的目的是为学习指引方向。前四道题检查本单元假定已经掌握的内容:求最大值的循环的不变式、大参数的传递方式、两层嵌套循环的轮数,以及无符号算术。其后的六道题按课的顺序涉及本单元的核心思想:序列的有效下标、向量的创建、一个大小能被对半分多少次、在有序数据中查找、表格的双重下标,以及把同一实体的数据存放在彼此分离的集合中的危险。没有人必须现在就答对,其中有几道题里,“我还不知道”是诚实的回答:每个答案,无论对错,都会解释要点,并指出讲解这一要点的那节课。同样的能力会在单元末尾的总结课中再次测量。
开始本单元之前,先测一测你的起点。这是一次简短的自我测评:不计分,结果只保存在你的浏览器中。
第 1 题来自前面的单元代码片段
int largest{0}; int t{}; while (std::cin >> t) { if (t > largest) { largest = t; } }应当在largest中留下读到的温度中最大的一个。它在什么输入下会出错?第一轮把这个数据与 0 比较,若为正数就保存下来:只有一个正数时结果是正确的。回答之前,最好先跟踪一遍。用>时,与最大值相等的数据不会取代它,但最大值仍是同一个数:重复不影响结果。这说明没有用不变式检查初始化:一个正确的循环要求不变式在第一轮之前就成立。第 2 题来自前面的单元一个函数计算一段存放在
std::string中、长达一百万个字符的文本里有多少个元音,并且不修改它。它的形参最好怎样声明?按值传递在每次调用时都复制这一百万个字符:这是正确的,但付出了毫无用处的代价。它不复制,但无端允许修改文本,而且不接受字面量和临时对象:把“不复制”与“允许修改”混为一谈。传递方式是接口的一部分:按值传递向函数承诺一份它自己的副本,程序就必须制作这份副本。常量引用读取文本而不复制它,也无权修改它,此外还能接受临时对象。在第 6 单元,这一选择成为规则:读取一个含一百万个数据的向量的函数,以const std::vector<int>&接收它,而不必要的复制则成为最常见的代价错误。复习: 参数与作用域
第 3 题来自前面的单元
++count在for (int i{0}; i < 6; ++i) { for (int j{0}; j < i; ++j) { ++count; } }中执行多少次?第 4 题来自前面的单元
std::println("{}", 0u - 1);在unsigned int为 32 位的系统上输出什么?以为无符号类型可以容纳负数。以为减法会停在该类型的最小值;无符号算术是回绕,而不是饱和。把可能出现的警告与错误混为一谈:这个表达式是合法的,其值也是确定的。第 5 题在一个有 个元素、像 C++ 那样编号的序列中,哪些下标 指示一个元素?
像日常语言那样从一开始编号;在 C++ 中,a[n]已经在序列之外。这是最常见的差一错误:数出了 个下标,而元素只有 个。这是诚实的回答:第 6.1 课会解释它,并说明为什么从零开始计数。第 6 题执行
v的定义std::vector<int> v(3, 7);之后,它包含什么?把圆括号当成了花括号。把两个数的作用颠倒了。这是诚实的回答:第 6.2 课会介绍向量及其创建方式。第 7 题
for (int m{1024}; m > 1; m /= 2) { ... }转多少轮?第 8 题一本电话簿按字母顺序列有一百万个名字。要找其中一个,就从中间翻开,舍弃名字不可能在其中的那一半,再对另一半重复这一做法。最多需要翻开多少次?
这是逐个检查名字的查找的次数:不利用顺序时,平均要翻过半本电话簿。这是一百万的平方根:把将部分对半分与把它分成若干块混为一谈。用了常用对数:每翻开一次舍弃的是一半,而不是十分之九。第 9 题对于
std::vector<std::vector<int>> t{{1, 2, 3}, {4, 5, 6}};,t[1][0]和t.size()各是多少?把第一个下标读作列,并数了列数而不是行数。以为size()数的是表格中所有的格子。这是诚实的回答:第 6.5 课把表格作为向量的向量来介绍。第 10 题一个程序把一组人的名字存放在一个向量里,把他们的成绩存放在另一个向量里,使得成绩
grades[i]就是names[i]的成绩。如果只对成绩向量排序,会发生什么?对编译器来说,这两个向量是相互独立的:这种关系只存在于写程序的人的头脑里。这个算法只接收一个向量,并不知道另一个向量的存在。每次访问都是有效的,每个结果都是确定的:这是逻辑上的缺陷,因此本课程的两个检测器(内存错误检测器 AddressSanitizer 和未定义行为检测器 UndefinedBehaviorSanitizer)都不会报告它。
正在加载内容…
已完成
登录账户以保存您的进度