摘要
这节课开启本课程的第七单元,主题是文本与文件。它说明本单元的用途:教会把文本当作数据来处理,并顾及它真实的编码,让程序在读写文件时不因格式错误的输入而崩溃;介绍本单元的四节课以及把它们串起来的线索;回顾本单元假定已有的知识,尤其是对机器而言字符就是一个数,以及第 5 单元和第 6 单元的向量、记录和函数;提出本单元要解决的问题,即一个看似正确的程序会悄无声息地丢失数据,在第一行意料之外的输入处停下,并且量错含有非 ASCII 字符的名字;预先指出一个字母不一定只占一个字节;并说明本单元通向何处,包括第 8 单元以及“C++ 程序设计 II”课程。
学习目标
- 描述本单元的内容顺序,并说明每节课为后一节课提供了什么。
- 回顾把字符表示为数字编码的方法(第 2.4 课),并用二进制和十六进制写出一个编码。
- 借助一个例子,预先认识到一个字母不一定是一个字节,因此数字节并不等于数字母。
- 认清学完本单元时应当会做什么,并把它与后面一个单元以及“C++ 程序设计 II”课程中将要学习的内容区分开来。
入门测评
在学习本单元之前,先测一测自己的起点是有益的。这项测评是自我测评:不计分,唯一的目的是为学习指引方向。前六道题检查本单元假定已经掌握的内容:字符的编码、数的二进制与十六进制写法、位掩码、筛选元素的向量遍历、读取遇到字母而不是数字时会发生什么,以及记录的定义。其后的四道题按课的顺序涉及本单元的核心思想:查找不到任何东西时返回什么、含有非 ASCII 字母的单词的大小、混用两种读取方式时出现的空行,以及无法打开的文件。没有人必须现在就答对这四道题,其中有几道题里,“我还不知道”是诚实的回答:每个答案,无论对错,都会解释要点,并指出讲解这一要点的那节课。同样的能力会在单元末尾的总结课中再次测量。
开始本单元之前,先测一测你的起点。这是一次简短的自我测评:不计分,结果只保存在你的浏览器中。
第 1 题来自前面的单元
std::println("{:d} {:d}", 'A', 'a');输出什么?以为小写字母紧跟在大写字母之后;两者之间相隔 32 个编码,中间还有一些符号。忽略了{:d}要求的是编码;没有它,std::println才会写出字母。把顺序弄反了:在 ASCII 中,大写字母的编码小于小写字母。char保存的是一个数,即字符的编码,而{:d}要求写出这个数而不是字母。在 ASCII 中,大写字母从 65 开始,小写字母从 97 开始:相差 32,恰好是一个位。文本不只是 ASCII一课从这里出发,解释不在 ASCII 中的字母会怎样。复习: 字符、布尔值、类型转换与常量
第 2 题来自前面的单元233 用二进制和十六进制怎样写?
把低四位分组弄错了:1001是 9,不是 1。把位的顺序读反了,从最低位读到最高位。把十六进制写法和十进制写法混为一谈。。从右边起每四位一组,1110和1001分别是 14 和 9,即e和9。这是“é”的编码,本单元将会说明,在如今的文本文件中,这个编码不是存成一个字节e9,而是存成两个字节。复习: 整数及其表示
第 3 题来自前面的单元
0xB1 & 0xC0的值是多少?把&与|混淆了:按位或会把高位置为 1,而不是把它们选出来。以为掩码什么也不改变;它会清除低六位。把&与^(异或)混淆了。复习: 位与数学库
第 4 题来自前面的单元
std::vector<int> v{4, 7, 10, 3}; std::vector<int> w{}; for (const int x : v) { if (x % 2 == 0) { w.push_back(x); } } std::println("{}", w);输出什么?把条件弄反了:x % 2 == 0对偶数为真。以为push_back会复制整个向量,或者以为筛选作用于v,输出的也是v。以为push_back在开头添加;它在末尾添加。循环遍历v,只把偶数按出现的顺序复制到w中;v不变。本单元对文本使用同样的模式:遍历一个字符串的字节并保留其中一些,或者遍历一个文件的各行并保留有效的行。复习: 向量
第 5 题来自前面的单元执行
int n{-1}; std::cin >> n;之后,用户输入abc。n的值是多少?后续的读取会怎样?以为失败的读取不留痕迹;它在变量和流的状态中都留下了痕迹。以为 C++ 会对无效输入发出提醒;程序会照常继续,仿佛读取成功了一样。把读取整数与读取字符混为一谈。第 6 题来自前面的单元下列定义中,哪一个正确地声明了一个包含书名和年份的记录?
忘了右花括号之后的分号,这是写记录的人最先犯的错误;编译器会准确地指出它。像列表元素那样用逗号分隔字段;每个字段都是一个声明,以分号结束。把记录(字段类型可以不同,且各有名字)与向量(元素全都是同一类型)混为一谈。第 7 题若
s是std::string s{"casa azul"};,s.find("x")返回什么?这是其他语言的惯例;std::size_t没有符号,把-1转换成它恰好就是npos,但把结果与-1比较之所以可行,只是因为这次转换,而且会引起编译器警告。把“未找到”与位置 0 混为一谈,位置 0 是出现在字符串开头时的位置。这是诚实的回答:第 7.1 课会介绍find和npos。第 8 题在一个以 UTF-8 保存的程序中(本课程的程序都是这样),
std::println("{}", std::string{"año"}.size());输出什么?数的是字母;size数的是写出这些字母所用的字节。以为每个字母都占两个字节;ASCII 中的字母只占一个。这是诚实的回答:第 7.2 课会逐字节地解释它。size数的是字节,不是字母:在西班牙语单词“año”(年)中,“a”和“o”各占一个字节,而不在 ASCII 中的“ñ”在 UTF-8 中占两个字节。文本不只是 ASCII一课解释其中的原因,并教你如何数字母。复习: 文本不只是 ASCII
第 9 题一个程序执行
int age{}; std::string name{}; std::cin >> age; std::getline(std::cin, name);,用户输入36,按 Enter,再输入娜娜,按 Enter。name中留下了什么?以为getline从后面一行开始读;它从>>停下的地方开始。以为getline会重新读取>>已经消耗的内容。这是诚实的回答:第 7.3 课正是从这个缺陷讲起。第 10 题一个程序执行
std::ifstream in{"data.txt"}; int x{-1}; in >> x;,但运行它的文件夹中不存在data.txt。会发生什么?以为打开一个不存在的文件是 C++ 会报告的错误;程序会毫无提醒地继续。把为读取而打开与为写入而打开混为一谈,后者确实会创建文件。这是诚实的回答:第 7.4 课正是从这一检查讲起。
正在加载内容…
已完成
登录账户以保存您的进度