简介:数据库关系代数习题.pdf围绕数据库查询语言中的关系代数展开,主要面向正在学习数据库原理、需要强化关系运算与查询表达式训练的高校学生与备考者。资源以单份PDF文件提供,整个包体仅304KB,内容紧凑、易打印,适合在课堂练习、期末复习或考研冲刺阶段反复使用。目前已有2805人学习下载,属于较高人气的数据库专项练习资料。习题选取了学生-课程-学习、供应商-零件-工程、图书借阅等经典关系模式,覆盖选择、投影、连接、差、除等核心运算,并从“检索英语专业学生课程信息”“查询没学C135课程的学生”“找出没有任何不及格科目的学生”到“选修全部课程的学生姓名”“由S1提供产品的工程名”等多类典型问题,均给出关系代数表达式与简要解释,可帮助读者在书写表达式的同时,深入理解关系代数与SQL、实际查询需求之间的对应关系。部分题目还涉及ER图与关系模式转换,可兼顾数据库设计基础。
1. 关系代数习题不是拿来背的:这套 PDF 真正帮你解决什么问题
关系代数习题几乎是每个数据库学习者的第一道坎:期末考试要考,考研专业课要考,不少公司在技术面追问 SQL 场景题时,也会用关系代数的表达式来考察建模思路。市面上这种习题 PDF 铺天盖地,但多数人拿到手只会对着答案背,背完换一道题照样懵。这篇文章不是来给你划重点的,我想讲的是我自己刷过多轮之后的用法:怎么从自然语言题干里识别它考的是哪个算子,怎么把一句中文拆成关系代数表达式,再顺手用 SQL 跑出来验证对错。内容适合两类人——正在被数据库习题折磨的学生,以及想把复杂 SQL 写得更严谨、少踩坑的从业者。
2. 先认清楚题目在考哪个算子:关系代数八大运算与题目识别
关系代数并不是一团“背就完了”的符号,它背后的设计逻辑非常直接:把“查什么行、取什么列、合并哪些表、去掉哪些结果”用算子显式表达出来。你在数据库里写的每一条 SQL,优化器本质上都是在关系代数表达式上做等价变换,所以理解算子之间的推导关系,比记住单个公式有用得多。做题第一步不是套模板,而是先看出这道题到底在考哪个算子。
2.1 五种基本运算:为什么考试和面试只围绕它们出题
几乎所有教材都会强调:关系代数真正的基本运算只有五个,选择 σ、投影 π、并 ∪、差 −、笛卡尔积 ×。剩下常见的交、连接、除法都是从这五个推出来的。习题集之所以爱拿基本运算出题,是因为一旦你能用基本运算把一道题完整搭出来,说明你对题目语义的理解是完整的;反过来,如果你只会“看到××就写自然连接”,题目换个条件限制就会露馅。
| 基本运算 | 符号 | 作用 | 题干里最常见的信号词 |
|---|---|---|---|
| 选择 | σ | 按行过滤,留下满足条件的元组 | “满足……条件的”“年龄小于……的” |
| 投影 | π | 保留指定列,去掉其余列 | “查询……的学号和姓名” |
| 并 | ∪ | 合并两个关系的元组 | “或者”“至少参加过其中一项” |
| 差 | − | 把第二个关系里的元组从第一个里去掉 | “没有”“不是”“从不” |
| 笛卡尔积 | × | 两个关系的全组合 | “匹配”“配对”“关联” |
明白这张表之后,你会发现习题里很多“难题”其实是在变着法子考你的推导能力。比如交运算的教科书定义是 R ∩ S = R − (R − S),意思是“两个集合都有的东西,等于把 S 从 R 里去掉两次的补集”。自然连接的定义则可以写成:先对两张表做笛卡尔积,再用公共属性相等的条件做选择,最后把重复列投影掉。除法的形式更复杂,但同样能拆成投影、差、笛卡尔积的组合。我建议你在刷题初期拿一张纸把这些推导都写一遍,写完之后再看那些标着“高级”的题,会发现它们只是把基本运算嵌套了几层而已。
2.2 连接家族:等值连接、自然连接和 θ 连接差在哪
在关系代数习题里,“连接”是重灾区。很多初学者看到“连接”就默认是自然连接,但题目恰恰要区分三种:θ 连接、等值连接和自然连接。θ 连接最通用,它是“从笛卡尔积里挑出满足任意条件的元组”,写成 σ_θ(R × S),θ 可以是大于、小于、不等于,条件随便写。等值连接是 θ 连接里条件为“某列 = 某列”的特例。自然连接则是在等值连接基础上,把所有公共属性自动合并成一份,并且只保留一份。
这三者最容易被忽略的差别是结果列数。θ 连接和等值连接会保留两侧参与比较的列,比如 R.A 和 S.A 都叫 A,它们在结果里仍然是两列,只是名字可能相同;自然连接则会把公共列合并成一列。习题常在这里设坑:问“两个关系自然连接后有几列”,如果你按等值连接去数,结果一定多一列。反过来说,如果题目让你“写出等值连接表达式”,你就不能用自然连接的简写,必须明确写出列相等条件,否则考试会被扣分。
| 连接类型 | 条件 | 结果列 | 典型习题点 |
|---|---|---|---|
| θ 连接 | 任意比较条件 | 保留两侧全部属性,公共列不合并 | 让你自己写连接条件的题目 |
| 等值连接 | 指定列相等 | 同样保留两侧属性 | 条件里直接出现“=”,列没合并 |
| 自然连接 | 所有公共属性相等 | 公共列只保留一份 | 结果列数变少,常见陷阱 |
我一般建议在练习时把自然连接显式改写成“等值连接 + 投影”的形式,因为这个过程能看出哪些列被合并了,也能帮你在后面把表达式翻译成 SQL 时,想清楚 JOIN 的 ON 条件到底要写几个。
2.3 除运算:几乎所有习题集里最劝退的一个算子
除运算为什么劝退?因为它的语义是全称量词,翻译成人话就是“全部”。题干里出现“选修了全部课程”“跟所有供应商都有合作”“包含所有零件”这种“全部、所有”字眼时,用除法是对的。但除法难在它不是“两张表除一下”这么简单,而是要先理解结果集合长什么样。
除运算的定义式是:R ÷ S = π_X(R) − π_X(π_X(R) × S − R),其中 X 是 R 里有而 S 里没有的属性集合。这个式子乍一看像天书,拆开看其实就四步。以最经典的“查询选修了全部课程的学生学号”为例,R 是选课关系 SC(sno, cno),S 是课程关系 Course(cno)。第一步,π_sno(SC) 得到所有出现过选课记录的学生;第二步,把这一步的结果和 Course 做笛卡尔积,得到“每个学生 × 每门课”的全部组合;第三步,用这个全组合减去真实的 SC,剩下的就是“哪个学生缺了哪门课”;第四步,把缺过课的学生从第一步的全集里减掉,剩下的就是一门课都没缺的学生。
练习阶段我强烈建议你亲手把这三张集合画出来,不要只背公式。你会发现除法的本质是“补全测试”:构造一个理想中的全组合,再用它和真实数据做差,缺的就是不合格的,剩下的就是答案。这个思路在后面对应 SQL 的双重 NOT EXISTS 写法时,几乎是完全一致的。
3. 从读题到写出表达式:用学生选课库把习题拆成四步走
习题 PDF 里出现频率最高的一套背景库,几乎全国教材通用,就是学生选课库。这个库足够简单,能覆盖单表查询、多表连接、集合运算、除法四大类题型,所以我建议你用这套库把所有题重新捋一遍。把表结构固定下来,后面做题不用每次重新猜属性名,效率会高很多。
3.1 学生选课库的表约定:先定义清楚练习场再动手
学生选课库通常有三张核心表:
| 关系名 | 属性 | 说明 |
|---|---|---|
| Student | sno, sname, sex, age, dept | 学生表,主键 sno |
| Course | cno, cname, cpno, credit | 课程表,主键 cno,cpno 是先修课 |
| SC | sno, cno, grade | 选课表,联合主键 (sno, cno) |
在此基础上,我总结了一个四步解题流程,照着走能减少大部分低级错误。第一步,把自然语言拆成主语、条件、限量词三个部分;第二步,确定每个部分作用在哪张表上;第三步,从内向外写出表达式,先写选择、投影,再写连接和集合运算;第四步,检查最终结果关系的属性集合是否和题干要求的输出列一致。
举个例子,题干是“查询年龄小于 20 岁的男学生的学号与姓名”,主语是“学生”,对应 Student;条件是“年龄小于20且性别为男”,这是选择运算;输出列是“学号与姓名”,这是投影。于是表达式就是 π_sno,sname(σ_age<20 ∧ sex='男'(Student))。这类题唯一的争议点在于先投影还是先选择,我习惯先选择再投影,后面会细说为什么。
3.2 单表题怎么下手:先投影还是先选择的顺序问题
单表题最常问的就是“先投影还是先选择”。如果你在做题时拿不准,默认先选择、后投影,这个顺序最安全。原因很简单:选择可能要引用某列做条件,一旦你先投影,那列就被丢掉了,后面的选择会直接写不出来。
比如“查询年龄小于 20 岁的学生的学号”,如果你先写成 π_sno(Student),得到的结果只有学号,年龄信息已经没了,再想 σ_age<20 就是无米之炊。正确的顺序是 π_sno(σ_age<20(Student)),先过滤行,再裁列。
当然,也有先投影后选择仍然成立的情况,前提是选择条件里用到的列正好也在投影列表中。比如“查询所有学生的学号,且学号以 S 开头”,π_sno(σ_sno LIKE 'S%'(Student)) 和 σ_sno LIKE 'S%'(π_sno(Student)) 在语义上是等价的。但为了形成稳定的做题习惯,我建议你在草稿纸上画的任何时候,都先把 σ 写在 π 里面,让选择距离它依赖的原始表更近,这个习惯能直接避开后面要讲的坑一。
3.3 多表题怎么下手:连接条件放 σ 里还是放连接里
多表题比单表题多一个决策点:连接条件放哪。以经典题“查询选修了‘数据库’课程的学生姓名”为例,它涉及 Student、SC、Course 三张表。两个连接条件分别是 Student.sno = SC.sno 和 SC.cno = Course.cno,还有一个过滤条件是 Course.cname = '数据库'。
常见的两种写法都能得到正确答案。第一种是先做自然连接再统一过滤,写成 π_sname(σ_cname='数据库'(Student ⋈ SC ⋈ Course));第二种是先过滤 Course 再做连接,写成 π_sname(Student ⋈ SC ⋈ σ_cname='数据库'(Course))。我推荐第二种,因为先过滤小表能让中间结果的行数明显减少,手算不容易算花眼,也更贴近 SQL 里“能提前 WHERE 就先 WHERE”的习惯。
做题时我一般会在草稿纸上先画出三张表的连接关系,再标出过滤条件该放在哪一层。如果题目里出现“选修了课程名为‘数据库’的课程”,你要注意课程名在 Course 表,选课关系在 SC 表,学生姓名在 Student 表,三个缺一不可。漏掉 SC 表是这题最常见的翻车写法,结果会变成“所有学生 × 符合条件的课程”,而不是“真正选了这门课的学生”。
3.4 “全部/至少/不存在”题:用集合差和除运算拆语义
“全部、至少、不存在”这三类词,是关系代数习题里语义最丰富的地方,也是区分初学者和熟练者的分水岭。
| 题干关键词 | 语义 | 推荐算子 |
|---|---|---|
| 至少一门 | 存在量词 | 连接、并、交 |
| 至少两门 | 两个集合都满足 | 交 |
| 一门都没有 | 全称否定的补集 | 差 |
| 全部/所有 | 全称量词 | 除 |
举例,“查询至少选修了 C1 和 C2 两门课程的学生学号”,本质是“选了 C1 的学生”和“选了 C2 的学生”的交集,即 π_sno(σ_cno='C1'(SC)) ∩ π_sno(σ_cno='C2'(SC))。如果你看到“至少”就直接写并集,那就是把“至少一门”和“至少两门”搞混了,结果是成绩单上多了一大片错误。
再看“查询一门课都没选的学生姓名”,这个不是除运算,而是差运算:从全体学生里减去选过课的学生。表达式是 π_sname(Student) − π_sname(Student ⋈ SC)。注意第一步要先做自然连接,把学生表和选课表关联起来,得到“选过课的那些学生”,再投影出姓名,最后用全体学生减去它。直接写 π_sname(Student) − π_sname(SC) 是错的,因为 π_sname(SC) 根本没有意义,SC 表里没有学生姓名。
而“查询选修了全部课程的学生学号”,对应的就是第 2.3 节里的除法,写作 π_sno(SC) ÷ π_cno(Course)。这类题是所有习题集里区分度最高的地方,你把除法理解透了,后面写 SQL 的 NOT EXISTS 双重否定也会顺很多。
4. 把关系代数表达式翻译成可执行 SQL:用数据库反向验证答案
做题没有反馈,等于白做。我刷题时有个习惯,每写出一道关系代数表达式,就把它翻译成 SQL 跑一遍,用结果反推自己有没有理解对。这个过程不需要老师批改,数据库本身就是最好的裁判。翻译的关键是掌握关系代数和 SQL 之间的映射关系,下面这张表是我认为最核心的六组对应,记牢它就能覆盖绝大多数习题。
4.1 关系代数到 SQL 的映射:六组对应关系先记牢
| 关系代数 | SQL | 注意点 |
|---|---|---|
| σ_条件(R) | SELECT 列 FROM R WHERE 条件 | 条件列必须来自 FROM/JOIN 涉及的表 |
| π_属性(R) | SELECT DISTINCT 属性 FROM R | 代数投影默认去重,SQL 不加 DISTINCT 不去重 |
| R ∪ S | SELECT ... UNION SELECT ... | UNION 去重,UNION ALL 不去重 |
| R − S | SELECT ... EXCEPT SELECT ... | MySQL 没有 EXCEPT,改用 NOT EXISTS |
| R × S | SELECT ... FROM R, S 或 CROSS JOIN | 记得补 WHERE,否则会产生爆炸性冗余 |
| R ⋈_θ S | SELECT ... FROM R JOIN S ON θ | 连接条件写在 ON,普通过滤写在 WHERE |
这张表里最容易出错的是投影对应的 DISTINCT。关系代数的关系是集合,所以 π_sno(SC) 必然没有重复;而 SQL 的 SELECT 默认保留重复行,如果查询的是“选了任意一门课的学生学号”,不加 DISTINCT 就会返回重复学号。我在后面的章节会专门聊怎么用这个差异做自查。
4.2 用三个经典题跑一遍验证流程:建表、插数据、查结果
验证流程最简单的方式,是把这个学生选课库在你本地的数据库里建出来,插入几条固定数据,然后把关系代数表达式一条条翻译成 SQL。下面这套 SQL 用 PostgreSQL 方言写,MySQL 用户把 EXCEPT 替换成 NOT EXISTS 写法即可,结构和表名不影响演示。
-- 建表:学生、课程、选课,字段与习题库保持一致 CREATE TABLE student ( sno text PRIMARY KEY, sname text NOT NULL, sex text, -- 性别只存 '男' / '女' age int, -- 年龄直接存整数 dept text -- 所在系 ); CREATE TABLE course ( cno text PRIMARY KEY, cname text NOT NULL, cpno text, -- 先修课,没有则为 NULL credit int ); CREATE TABLE sc ( sno text REFERENCES student(sno), cno text REFERENCES course(cno), grade numeric, PRIMARY KEY (sno, cno) ); -- 插入数据:注意 SC 里让 S2 缺 C2,方便验证除法 INSERT INTO student VALUES ('S1', '张三', '男', 19, '计算机'), ('S2', '李四', '女', 20, '软件工程'); INSERT INTO course VALUES ('C1', '数据库', NULL, 4), ('C2', '数据结构', 'C1', 4); INSERT INTO sc VALUES ('S1', 'C1', 85), ('S1', 'C2', 90), ('S2', 'C1', 78);这套建表语句里,sc 表故意让 S2 缺了 C2,这样我们验证“选了全部课程”这道题时,结果只应该出现 S1。字段类型里 sno 和 cno 用 text 是为了少操心长度,实际业务中你可以换成 varchar。数据量就这么几行,手算和 SQL 结果一对照就知道表达式写得对不对。
接下来把三道对应的关系代数表达式翻译成 SQL:单表选择、多表连接、集合差。这里的关键是理解 SQL 的每一步对应代数里的哪个运算符。
-- 题目1:σ_age < 20(Student),查询年龄小于20的学生 -- 对应表达式:π_sno,sname(σ_age < 20(Student)) SELECT sno, sname FROM student WHERE age < 20; -- 题目2:查询选修了“数据库”课程的学生姓名 -- 对应表达式:π_sname(Student ⋈ SC ⋈ σ_cname='数据库'(Course)) SELECT DISTINCT s.sname FROM student s JOIN sc ON s.sno = sc.sno JOIN course c ON sc.cno = c.cno WHERE c.cname = '数据库'; -- 题目3:查询一门课都没选的学生姓名 -- 对应表达式:π_sname(Student) − π_sname(Student ⋈ SC) SELECT sname FROM student EXCEPT SELECT DISTINCT s.sname FROM student s JOIN sc ON s.sno = sc.sno;题目 1 没有任何需要解释的陷阱,SELECT 的输出列就是代数的投影列表,WHERE 就是选择条件。题目 2 是标准的三表连接,把两个 JOIN ON 条件写清楚,再把课程名过滤放到 WHERE,结果相当于先过滤再连接,和我在第 3.3 节推荐的写法一致。题目 3 用 EXCEPT 实现了集合差:上半部分取所有学生姓名,下半部分取选过课的学生姓名,相减之后剩下“没选过任何课的人”。
最后再验证一道除法的 SQL 写法。关系代数里一个除法,翻译成 SQL 是双重 NOT EXISTS,语义是“这个学生不存在没选过的课程”:
-- 题目4:查询选修了全部课程的学生学号(除法) -- 对应表达式:π_sno(SC) ÷ π_cno(Course) SELECT sno FROM student WHERE NOT EXISTS ( SELECT 1 FROM course c WHERE NOT EXISTS ( SELECT 1 FROM sc WHERE sc.sno = student.sno AND sc.cno = c.cno ) );这段 SQL 的阅读顺序是从外往内的。外层遍历每个学生,内层遍历每一门课,最内层检查这个学生是否选了这门课。如果存在某门课该学生没选,内层 NOT EXISTS 就不成立,外层就会把这个学生排除。整套逻辑和我在 2.3 节讲的“补全测试”是一模一样的:先构造所有可能组合,再查找缺失项,最后排除缺失者。你在本地跑一遍,结果只会是 S1,和手算的除法结果一致,这就完成了闭环验证。
5. 刷题避坑指南:关系代数习题里最典型的五个翻车点
这章我只写自己见过最多、也在学生作业里反复批改过的五个坑。每一条都是按“现象、原因、解决”的顺序展开,你可以在刷题时当诊断手册用,遇到答案对不上就回来翻一遍。
5.1 坑一:投影之后再选择,属性没了
现象:题目要求“查询年龄小于 20 岁的学生的学号”,有人写成 π_sno(σ_age<20(π_sno(Student))),然后对着报错或者错误结果发呆。
原因:这是典型的把投影和选择的顺序搞反了。π_sno(Student) 已经把年龄列丢掉了,外层 σ_age<20 根本找不到 age 这个属性,表达式在逻辑上就不成立。关系代数里的投影是“列裁剪”,裁剪后的关系只包含你指定的列。
解决:把选择运算向内移动,和原始表直接接触。正确写法是 π_sno(σ_age<20(Student))。做题时养成先 σ 后 π 的固定习惯,可以完全避免这类问题。
5.2 坑二:自然连接悄悄合并了公共列
现象:R 和 S 都有公共属性 C,题目问自然连接后结果有几列,你按 R 的列数加 S 的列数算,多算了一列。更隐蔽的一种是,你用手算结果属性值时,发现公共列的取值变成了单个值,而不是两边各保留一个。
原因:自然连接的定义就是公共属性值相等的元组配对,并把公共列合并成一份。这是自然连接和等值连接最大的区别,等值连接不会合并列,只是把满足条件的元组拼在一起。
解决:做题时先圈出两张关系里的公共属性名,再决定用自然连接还是等值连接。如果题目明确规定用自然连接,结果里公共列只出现一次;如果题目只写了“连接”但没说是自然连接,建议先向老师确认,在表达式中显式写出连接条件更稳妥。
5.3 坑三:除运算写成单层 NOT IN,结果还是错
现象:“查询选修了全部课程的学生”,有人写成 sno NOT IN (SELECT cno FROM course),逻辑上说不通,运行结果也是空的或者错的。
原因:单层 NOT IN 表达的是“学号不在课程号集合里”,这完全是两回事。除运算处理的是“全部”这种全称量词,要用双重否定来翻译,本质是“不存在某一个学生没选的课程”。
解决:严格按除法公式写,或者记住 SQL 双重 NOT EXISTS 的模板。宁可先把除法公式 π_X(R) − π_X(π_X(R) × S − R) 抄在草稿纸上,再对照着翻译。
5.4 坑四:笛卡尔积写完忘掉选择条件
现象:写 R × S 时很爽,写完就直接把这个笛卡尔积当成“连接结果”,忘了后面还必须接一个 σ 条件来配对元组,导致结果集合巨大且毫无意义。
原因:很多初学者把连接和笛卡尔积当作两种不相干的运算,其实连接就是“笛卡尔积 + 选择条件”的组合。题目里只要出现“关联、配对、连接”,隐含动作是先做笛卡尔积再用连接条件过滤。
解决:每次表达式里出现 × 符号,就习惯性地问自己一句“选择条件在哪”。如果题目本身就是求全组合,那另当别论;如果题干里有“满足……的配对”,那 × 后面一定跟着 σ 或 ⊙。
5.5 坑五:集合运算两边的属性数对不上
现象:写 R ∪ S 或 R − S 时,两边的元组属性数量不一样,做题时还觉得没问题,等到手算才发现没法统一。
原因:集合运算要求两个关系是并兼容的,也就是属性数量相同、对应属性的类型相同或兼容。教材里的表达往往省略这一步,但做题时不能省,尤其是题目里给了两个结构不同的关系时。
解决:在写表达式之前,先把两边的属性列表拉出来对比。如果属性数量不一致,通常需要在某一侧先做投影,让它和另一侧对齐。比如要合并“学生姓名”和“教师姓名”,两边属性数一致但属性名不同,那就先分别投影成统一的列名,再做集合运算。
6. 把习题用出附加值:用关系代数给复杂 SQL 当草稿纸
刷完一轮习题之后,我建议你别把这些 PDF 扔进回收站,它们还能当另一件事的辅助工具:写复杂 SQL 之前,先在草稿纸上画关系代数表达式。很多从业者拿到需求就直接敲 SELECT,敲到 WHERE 和 JOIN 乱成一团才反应过来,其实多花两分钟写一个代数表达式,能省下后面半小时的调试时间。
我自己的固定流程很简单,任何一条查询超过两个表或者一个子查询,就先写代数式。第一步,列出目标输出属性,对应到 π;第二步,把题干里的过滤条件拆成两类,属于单表的放 σ,属于表间关联的放进连接条件;第三步,看题干有没有“全部、至少、没有”这类关键词,决定用除、交还是差。这三步走完,SQL 的结构基本就定下来了。
这里有个特别实用的映射技巧:用关系表达式判断要不要写 DISTINCT。比如代数式是 π_sno(σ_cno='C1'(SC)),关注的是单个学号列,因为关系是集合,这个 π 已经隐含去重了;翻译成 SQL 时必须写 SELECT DISTINCT sno,否则同一学号出现在多行会返回重复值。反过来,如果代数式里投影的是学号加姓名,而姓名在 Student 表中每个学号唯一,那么这个 π 的结果按集合语义也天然没有重复,SQL 里写不写 DISTINCT 影响不大,但写上也不算错。判断标准就是一句:投影列里包不包含主键,不包含主键就加 DISTINCT。
再举一个我常教给新人的例子:一个需求是“找出选了 C1 和 C2 两门课的学生”,新手往往写成 WHERE cno = 'C1' OR cno = 'C2'。如果你先在纸上画 π_sno(σ_cno='C1'(SC)) ∩ π_sno(σ_cno='C2'(SC)),就会明确发现这是交集语义,交集在 SQL 里要么用 INTERSECT,要么用 JOIN 把同一张表关联两次。这个觉察,就是关系代数习题训练出来的收益。
我自己早期属于拿到 SQL 就开写、写完跑出来才发现错在哪的那种人,直到一次上线前,用一个简单的除法思想检查出同事写的 NOT EXISTS 少了外层条件,才彻底改了这习惯。现在无论写多简单的查询,我都习惯先在心里过一遍代数结构,再落到键盘上。这些习题 PDF 的价值从来不在答案本身,而是帮你把脑子里模糊的 SQL 思路,变成可拆解、可验证、可讲给别人听的结构。希望帮到你。
本文还有配套的精品资源,点击获取