diff --git a/.vitepress/config.js b/.vitepress/config.js index 743b16a..09858dd 100644 --- a/.vitepress/config.js +++ b/.vitepress/config.js @@ -18,18 +18,81 @@ export default defineConfig({ ] }, { - text: '1. 杭电生存指南(最重要模块)', + text: '1.杭电生存指南(最重要模块)', items: [ - { text: '人文社科的重要性(韩健夫老师寄语)', link: '/1.杭电生存指南/人文社科的重要性(韩健夫老师寄语)' }, - { text: '竞赛指北', link: '/1.杭电生存指南/竞赛指北' }, - { text: '导师选择', link: '/1.杭电生存指南/导师选择' }, - { text: '小心项目陷阱', link: '/1.杭电生存指南/小心项目陷阱' }, - { text: '小组作业避雷指南', link: '/1.杭电生存指南/小组作业避雷指南' }, - { text: '正确解读GPA', link: '/1.杭电生存指南/正确解读GPA' }, - { text: '杭电出国自救指南', link: '/1.杭电生存指南/杭电出国自救指南' }, - { text: '转专业二三事', link: '/1.杭电生存指南/转专业二三事' }, - { text: '问题专题:好想进入实验室', link: '/1.杭电生存指南/问题专题:好想进入实验室' }, - { text: '如何读论文', link: '/1.杭电生存指南/如何读论文' }, + { text: '1.1人文社科的重要性(韩健夫老师寄语)', link: '/1.杭电生存指南/1.1人文社科的重要性(韩健夫老师寄语)' }, + { text: '1.2竞赛指北', link: '/1.杭电生存指南/1.2竞赛指北' }, + { text: '1.3导师选择', link: '/1.杭电生存指南/1.3导师选择' }, + { text: '1.4小心项目陷阱', link: '/1.杭电生存指南/1.4小心项目陷阱' }, + { text: '1.5小组作业避雷指南', link: '/1.杭电生存指南/1.5小组作业避雷指南' }, + { text: '1.6正确解读GPA', link: '/1.杭电生存指南/1.6正确解读GPA' }, + { text: '1.7杭电出国自救指南', link: '/1.杭电生存指南/1.7杭电出国自救指南' }, + { text: '1.8转专业二三事', link: '/1.杭电生存指南/1.8转专业二三事' }, + { text: '1.9问题专题:好想进入实验室', link: '/1.杭电生存指南/1.9问题专题:好想进入实验室' }, + { text: '1.10如何读论文', link: '/1.杭电生存指南/1.10如何读论文' }, + ] + }, + { + text: '2.高效学习', + items: [ + { text: '2.高效学习', link: '/2.高效学习/2.高效学习' }, + { text: '2.1高效的前提:摆脱高中思维', link: '/2.高效学习/2.1高效的前提:摆脱高中思维' }, + { text: '2.1.1悲壮的学习方式', link: '/2.高效学习/2.1.1悲壮的学习方式' }, + { text: '2.1.2浮躁的心理状态', link: '/2.高效学习/2.1.2浮躁的心理状态' }, + { text: '2.1.3错误的提问姿态', link: '/2.高效学习/2.1.3错误的提问姿态' }, + { text: '2.1.4书籍的盲目崇拜', link: '/2.高效学习/2.1.4书籍的盲目崇拜' }, + { text: '2.1.5错误的学习配比', link: '/2.高效学习/2.1.5错误的学习配比' }, + { text: '2.2优雅的使用工具', link: '/2.高效学习/2.2优雅的使用工具' }, + { text: '2.3高效的信息检索', link: '/2.高效学习/2.3高效的信息检索' }, + { text: '2.3.1阅读文档(B百度爬)', link: '/2.高效学习/2.3.1阅读文档(B百度爬)' }, + { text: '2.3.2检索论文核心内容', link: '/2.高效学习/2.3.2检索论文核心内容' }, + { text: '2.3.3优秀的开源社区', link: '/2.高效学习/2.3.3优秀的开源社区' }, + { text: '补充:为什么不要用百度', link: '/2.高效学习/补充:为什么不要用百度' }, + { text: '2.4提问的艺术', link: '/2.高效学习/2.4提问的艺术' }, + { text: '2.5优雅的记笔记', link: '/2.高效学习/2.5优雅的记笔记' }, + { text: '2.6以理工科的方式阅读英语', link: '/2.高效学习/2.6以理工科的方式阅读英语' }, + ] + }, + { + text: '3.编程思维体系构建', + items: [ + { text: '3.编程思维体系构建', link: '/3.编程思维体系构建/3.编程思维体系构建' }, + { text: '3.0 编程入门之道', link: '/3.编程思维体系构建/3.0 编程入门之道' }, + { text: '3.1该使用哪个编辑器???', link: '/3.编程思维体系构建/3.1该使用哪个编辑器???' }, + { text: '3.2算法杂谈', link: '/3.编程思维体系构建/3.2算法杂谈' }, + { text: '3.2.1为什么要选择ACM——谈谈我与ACM', link: '/3.编程思维体系构建/3.2.1为什么要选择ACM——谈谈我与ACM' }, + { text: '3.2.2手把手教你学算法——如何使用OJ(Online Judge)', link: '/3.编程思维体系构建/3.2.2手把手教你学算法——如何使用OJ(Online Judge)' }, + { text: '3.3如何选择编程语言', link: '/3.编程思维体系构建/3.3如何选择编程语言' }, + { text: '3.4C语言', link: '/3.编程思维体系构建/3.4C语言' }, + { text: '3.4.1FAQ:常见问题', link: '/3.编程思维体系构建/3.4.1FAQ:常见问题' }, + { text: '3.4.2用什么写 C 语言', link: '/3.编程思维体系构建/3.4.2用什么写 C 语言' }, + { text: '3.4.3解决编程问题的普适性过程', link: '/3.编程思维体系构建/3.4.3解决编程问题的普适性过程' }, + { text: '3.4.4C语言前置概念学习', link: '/3.编程思维体系构建/3.4.4C语言前置概念学习' }, + { text: '3.4.5阶段一:编程属性', link: '/3.编程思维体系构建/3.4.5阶段一:编程属性' }, + { text: '3.4.5.1C语言自测标准——链表', link: '/3.编程思维体系构建/3.4.5.1C语言自测标准——链表' }, + { text: '3.4.6阶段二:文字冒险(cool)', link: '/3.编程思维体系构建/3.4.6阶段二:文字冒险(cool)' }, + { text: '3.4.6.1.开始冒险', link: '/3.编程思维体系构建/3.4.6.1.开始冒险' }, + { text: '3.4.6.2.探索未知', link: '/3.编程思维体系构建/3.4.6.2.探索未知' }, + { text: '3.4.6.3.指明地点', link: '/3.编程思维体系构建/3.4.6.3.指明地点' }, + { text: '3.4.6.4.创建对象', link: '/3.编程思维体系构建/3.4.6.4.创建对象' }, + { text: '3.4.6.5.捡起物品', link: '/3.编程思维体系构建/3.4.6.5.捡起物品' }, + { text: '3.4.6.6.绘制地图', link: '/3.编程思维体系构建/3.4.6.6.绘制地图' }, + { text: '3.4.6.7.增大距离', link: '/3.编程思维体系构建/3.4.6.7.增大距离' }, + { text: '3.4.6.8.移动方向', link: '/3.编程思维体系构建/3.4.6.8.移动方向' }, + { text: '3.4.6.9.练习:生成代码', link: '/3.编程思维体系构建/3.4.6.9.练习:生成代码' }, + { text: '3.4.6.10.增添属性', link: '/3.编程思维体系构建/3.4.6.10.增添属性' }, + { text: '3.4.6.11.设置条件', link: '/3.编程思维体系构建/3.4.6.11.设置条件' }, + { text: '3.4.6.12.开启关闭', link: '/3.编程思维体系构建/3.4.6.12.开启关闭' }, + { text: '3.4.6.13.编写解析器', link: '/3.编程思维体系构建/3.4.6.13.编写解析器' }, + { text: '3.4.6.14.丰富命令', link: '/3.编程思维体系构建/3.4.6.14.丰富命令' }, + { text: '3.4.6.15.赋予明暗', link: '/3.编程思维体系构建/3.4.6.15.赋予明暗' }, + { text: '3.4.6.16.结语:你终将自由', link: '/3.编程思维体系构建/3.4.6.16.结语:你终将自由' }, + { text: '3.4.7C基础知识杂谈', link: '/3.编程思维体系构建/3.4.7C基础知识杂谈' }, + { text: '3.4.7.1GDB初探索(编程可阅览)', link: '/3.编程思维体系构建/3.4.7.1GDB初探索(编程可阅览)' }, + { text: '3.4.7.1.1调试理论', link: '/3.编程思维体系构建/3.4.7.1.1调试理论' }, + { text: '3.4.7.2C的历史问题:undefined behavior', link: '/3.编程思维体系构建/3.4.7.2C的历史问题:undefined behavior' }, + { text: '3.4.7.3C编译器干了什么', link: '/3.编程思维体系构建/3.4.7.3C编译器干了什么' }, + { text: '3.4.7.4Inline Assembly与链接加载', link: '/3.编程思维体系构建/3.4.7.4Inline Assembly与链接加载' }, ] } ], diff --git a/1.杭电生存指南/如何读论文.md b/1.杭电生存指南/1.10如何读论文.md similarity index 100% rename from 1.杭电生存指南/如何读论文.md rename to 1.杭电生存指南/1.10如何读论文.md diff --git a/1.杭电生存指南/人文社科的重要性(韩健夫老师寄语).md b/1.杭电生存指南/1.1人文社科的重要性(韩健夫老师寄语).md similarity index 100% rename from 1.杭电生存指南/人文社科的重要性(韩健夫老师寄语).md rename to 1.杭电生存指南/1.1人文社科的重要性(韩健夫老师寄语).md diff --git a/1.杭电生存指南/竞赛指北.md b/1.杭电生存指南/1.2竞赛指北.md similarity index 100% rename from 1.杭电生存指南/竞赛指北.md rename to 1.杭电生存指南/1.2竞赛指北.md diff --git a/1.杭电生存指南/导师选择.md b/1.杭电生存指南/1.3导师选择.md similarity index 100% rename from 1.杭电生存指南/导师选择.md rename to 1.杭电生存指南/1.3导师选择.md diff --git a/1.杭电生存指南/小心项目陷阱.md b/1.杭电生存指南/1.4小心项目陷阱.md similarity index 100% rename from 1.杭电生存指南/小心项目陷阱.md rename to 1.杭电生存指南/1.4小心项目陷阱.md diff --git a/1.杭电生存指南/小组作业避雷指南.md b/1.杭电生存指南/1.5小组作业避雷指南.md similarity index 100% rename from 1.杭电生存指南/小组作业避雷指南.md rename to 1.杭电生存指南/1.5小组作业避雷指南.md diff --git a/1.杭电生存指南/正确解读GPA.md b/1.杭电生存指南/1.6正确解读GPA.md similarity index 100% rename from 1.杭电生存指南/正确解读GPA.md rename to 1.杭电生存指南/1.6正确解读GPA.md diff --git a/1.杭电生存指南/杭电出国自救指南.md b/1.杭电生存指南/1.7杭电出国自救指南.md similarity index 100% rename from 1.杭电生存指南/杭电出国自救指南.md rename to 1.杭电生存指南/1.7杭电出国自救指南.md diff --git a/1.杭电生存指南/问题专题:好想进入实验室.md b/1.杭电生存指南/1.8问题专题:好想进入实验室.md similarity index 100% rename from 1.杭电生存指南/问题专题:好想进入实验室.md rename to 1.杭电生存指南/1.8问题专题:好想进入实验室.md diff --git a/1.杭电生存指南/转专业二三事.md b/1.杭电生存指南/1.9转专业二三事.md similarity index 100% rename from 1.杭电生存指南/转专业二三事.md rename to 1.杭电生存指南/1.9转专业二三事.md diff --git a/2.高效学习/2.1.1悲壮的学习方式.md b/2.高效学习/2.1.1悲壮的学习方式.md new file mode 100644 index 0000000..7482bc6 --- /dev/null +++ b/2.高效学习/2.1.1悲壮的学习方式.md @@ -0,0 +1,19 @@ +# 2.1.1 悲壮的学习方式 + +# 现状 + +古人刻苦学习的故事,直到现在还在我们的身边不断上演。学生趴在山一样高的习题集边上苦苦奋斗,绝对是我校作为国内一流大学的亮丽的风景线。 + +挖空心思研究解题技巧的学生们,与同样挖空心思研究出题技巧的老师们,构成了一个完美的圆环。在二者日复一日的机械劳动中,我只看到纸张、电力,以及粮食不断被浪费,却看不到中华之崛起。 + +我无意全盘否定同学们吃苦耐劳的精神,但这份精神充其量只能称为悲壮。我们耗费了大量的时间和精力掌握的那些考点、技巧,在真正的知识殿堂里根本登不上大雅之堂。哪怕我们特征值求得再熟练,积分积得再复杂,中国的载人飞船也不会因此而顺利上天。 + +# 解决 + +学习的时候,不要有负担心理。 + +学东西并非是折磨我们的手段,而是我们用好奇心探索未知的方式。 + +更大程度上我希望各位可以把学习与进步当成一种生活状态。 + +让探索未知世界的好奇心就好像吃饭睡觉一样顺其自然 diff --git a/2.高效学习/2.1.2浮躁的心理状态.md b/2.高效学习/2.1.2浮躁的心理状态.md new file mode 100644 index 0000000..8b8c7ee --- /dev/null +++ b/2.高效学习/2.1.2浮躁的心理状态.md @@ -0,0 +1,29 @@ +# 2.1.2 浮躁的心理状态 + +# 现状 + +> 我明明很努力了,但是就是学不懂,群里的同学好像啥都会 WOC
我周围的同学怎么参加竞赛的科研经历丰富的都有就我是废物呜呜呜
我的同学啥都有了但是我什么都没
为什么我室友都脱单了我还是单身狗 + +浮躁,往往来源于和他人的比较是具有社会属性的我们的人之常情。 + +然而,我们往往不能从中获得任何鼓励反而深受打击。 + +# 解决方案 + +如果我在这里说戒骄戒躁一定会被打的(逃) + +学不懂一门课程,大可不必着急,慢慢来,也可以问问学长学姐或者思考一下这门课到底为什么如此组织。 + +多学会一个知识点,多掌握一点知识,现在的自己比以前好了就值得肯定。 + +人生的道路还很长,不要因为刚出发的一点小小的劣势就否认最终到达终点的自己。 + +很多时候我们这一代这么累,就是因为一直看着别人才磕磕绊绊的。 + +从当下浮躁的集体价值观中走出来吧。看清脚下的路是更重要的事。 + +人生的道路上有且仅有你一个人。 + +如果实在不行,来找 ZZM 聊聊吧。 + +![](static/boxcnPDWiNgkgppK1XWq5cRQ71b.jpg) diff --git a/2.高效学习/2.1.3错误的提问姿态.md b/2.高效学习/2.1.3错误的提问姿态.md new file mode 100644 index 0000000..8d760e9 --- /dev/null +++ b/2.高效学习/2.1.3错误的提问姿态.md @@ -0,0 +1,37 @@ +# 2.1.3 错误的提问姿态 + +# 现状 + +我们假定一个情况,如果你是一个医生,患者过来告诉你,我浑身都疼,医生我该怎么办啊。 + +然后你要慢慢询问他的问题,接着你要问各种问题各种检查然后去看,如果有十个人一百个人都这么问,你肯定会受不了的吧。 + +事实上, 如果希望能提高得到回答的概率, 提问者应该学会如何更好地提问. 换句话说, 提问者应该去积极思考 "我可以主动做些什么来让对方更方便地帮助我诊断问题". + +如果你的提问方式非常不专业, 很可能没有人愿意关注你的问题, 因为这不仅让人觉得你随便提的问题没那么重要, 而且大家也不愿意花费大量的时间向你来回地咨询. + +# 解决 + +一种非常合适的提问方式是: + +我在写 xxx 的时候遇到了 xxx 的错误(请发截图不要复制粘贴) + +我的版本是 XXX,官方文档给的版本是 XXX + +我做了这个来试着修正(贴图)根据的是 XXX 上的方法 + +然后出现了 XXX + +··········· + +最后我做的尝试是 XXX + +问题还是没有解决,现在我该怎么做? + +![](static/boxcnhuhE7qBLHyJKaesHGC033b.png) + +欢迎大家阅读 + +[Stop-Ask-Questions-The-Stupid-Ways/README.md at master · tangx/Stop-Ask-Questions-The-Stupid-Ways](https://github.com/tangx/Stop-Ask-Questions-The-Stupid-Ways/blob/master/README.md) 别像弱智一样提问 + +[https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md) 提问的智慧 diff --git a/2.高效学习/2.1.4书籍的盲目崇拜.md b/2.高效学习/2.1.4书籍的盲目崇拜.md new file mode 100644 index 0000000..1e9ba62 --- /dev/null +++ b/2.高效学习/2.1.4书籍的盲目崇拜.md @@ -0,0 +1,29 @@ +# 2.1.4 书籍的盲目崇拜 + +# 现状 + +很多同学在学习一个知识的时候,总是喜欢 + +"我们要学 C 语言,我买一本大黑书看看!" + +![](static/boxcnqsCWmUTDr5UDLYca9YkhHh.png) + +诚然,上面的各种书写的非常好,但是我们需要思考的是,阅读这些真的能达到我们想要的目标吗??? + +这些书为了保证准确性和严谨性,通常会采用不是一般人能看懂得话来进行解释 + +通常情况是,如果你阅读了一句话,用了解释这个词的意思用了三个你不懂的额外的词汇去解释,你去查这三个词汇的时候,又发现了五个你不懂的,无限循环下去。 + +# 解决 + +因此,当你只是为了学习一个简单的知识,或者说为了完成一个简单的目标的时候,肝书可能不是最高效的选择。最高效的方法可能是需要什么的时候就去学习这么一个单一的知识点,并且将他和现有的体系联系起来 + +> 来自 zzm 惨痛的教训 + +但是如果你想要系统的建立对某一门学科完整的认知,并且决心投入大量的时间时,我还是非常建议大家去看书的。 + +但是还有要注意的点是,最贵的,最专业的不一定是最好的,有时候可能简单的入门一点的书会更适合你呢 + +非常不错的电子书网站 + +[zh.z-lib.org](https://zh.z-lib.org/) diff --git a/2.高效学习/2.1.5错误的学习配比.md b/2.高效学习/2.1.5错误的学习配比.md new file mode 100644 index 0000000..09e1762 --- /dev/null +++ b/2.高效学习/2.1.5错误的学习配比.md @@ -0,0 +1,21 @@ +# 2.1.5 错误的学习配比 + +在学计算机科学的时候,总有人会问一些问题,类似于“我需要把这本书看完然后再开始 blabla 吗” + +”我需要看完某些课程然后再开始吗“ + +其实这种想法是完全错误的。 + +在高中阶段,你可能需要先阅览课本,然后再做题,可计算机是一门实践学科,你是用这样的方法无疑是一个低效的行为。 + +我们推荐正确的操作是一边实践一边学习,如果你有什么不会的内容,再去额外查询,但是不要全陷进去,开始无限套娃模式的搜索。 + +你可以先阅览一小部分内容,有个大致的了解后再逐渐深入,并且最好是一边敲代码一边实践一边去做。 + +同时也有些同学可能犯的错误是我只看看我不写代码。 + +这也同样会让你飞速的忘记代码,或者说有的同学可能想偷懒,某些行代码他没看懂就跳过去了,其实这也是有问题的,因为你所有埋下的坑,可能都会在以后的实践中填回来。 + +在你完成这份讲义的时候,希望你可以有选择的阅览一部分,然后带着问题去看某些课,效率也会高很多。 + +![](static/boxcnSq1JzWhVrFs3MePPzp5Txg.jpg) diff --git a/2.高效学习/2.1高效的前提:摆脱高中思维.md b/2.高效学习/2.1高效的前提:摆脱高中思维.md new file mode 100644 index 0000000..92c651f --- /dev/null +++ b/2.高效学习/2.1高效的前提:摆脱高中思维.md @@ -0,0 +1,31 @@ +# 2.1 高效的前提:摆脱高中思维 + +# 高中思维 + +高考,诚然为大众提供了阶级跃迁的途径 + +但是代价也是显而易见的:过度强化的训练和某种扭曲的价值观潜移默化的影响着我们。 + +我们在意着各种评判标准,GPA,竞赛的奖项,读研,工作的工资。 + +诚然,这些很重要,但是生活是没有所谓最优解的,在你一味的拿所谓标准当作自身的标尺的时候,无形之中,也让自己失去了独立思考的能力。 + +就算你把课本上的内容搞得再烂熟,绝不代表你真正对这门课能有什么理解。 + +并且,全部依赖他人给你指明方向的人生已经结束了! + +![](static/boxcne9EK3xz8LHOXfM8w9ih5Ig.png) + +你无法依赖大学里的老师还是家里的父母来为你指明所谓的方向,你的人生只属于你自己,你的道路也只能由你自己来思考。 + +考研的老师会更加重视你是否有能力与他进行利益交换,公司更在乎你是否可以为公司创造价值,想当然的思考已经无法跟上这个飞速运转的世界了。 + +# 大学现状 + +在这里引用一段上海交通大学生存指南的一段话。 + +> 在当今流水线式的教育体制下,我们就像廉价的零件一样被生产出来。因为数量巨大,没人会对每一个人的教学质量负责。
领导不会为你负责。对于一个争做世界一流大学的研究型学校,管好科研,管好实验室才是当务之急。
相比之下,本科生教学显得无利可图。教授也不会为你负责。拉到足够的经费发表足够的论文,满足学院要求才是生存大计。
要说管学生,也肯定先要管好自己实验室的硕士博士,而非那一百多人大课堂里的某个本科生。就算是科研任务不太重的一些任课教师,他们也不会为你负责——学不懂?那是因为你智力低,要么就是自己底下不用功。为什么跟你一个班上的某某某同学,人家就能懂?
诚然,就算是老师上课说孟加拉语,一个班上也非常有可能冒出一两个翻翻书看看图就能学到八九不离十的同学(或者根本就是以前学过)。
真正在课堂上口传心授的教学,其质量是不会有人过问的。教学评估会考察实验报告格式是否合格,出勤率是否够,但是绝对不会考察上百人的班上到底有几个听懂了的。
试想一下,每个学院每个系有成百上千的学生,每人有着不同的思想、不同的目标、不同的知识背景、不同的接受力,我们怎么可能去指望一个统一的“教学培养计划”强制应用在每个人头上的时候,能够产生效果?好比说食堂师傅炒一大锅菜给上千人吃,我敢说我分到的那盘,不是炒糊就肯定得夹生。
所谓“教学培养计划”,其科学性必须经过教育权威的论证。然而现实中塞给我们的推荐课表,却让人失望。且不深究选修课的分类、学分、毕业条件每年一个样,三年大变样,使得不少同学毕业前夕竞相奔走;甚至连两门相依赖课程的教学先后顺序都搞错过,这样的教学培养计划,实在让人难以信任 + +诚然,杭电不可避免地也会受相应的“学术共同体”的影响,波及了包括但不限于竞赛,授课质量,氛围引导方面诸多的影响。 + +但是不可否认的,杭电也有不少优秀的老师愿意投身于教育事业当中。并且,杭电仍然有不少教育资源,可以满足一个人的所需所求。(保研除外) diff --git a/2.高效学习/2.2优雅的使用工具.md b/2.高效学习/2.2优雅的使用工具.md new file mode 100644 index 0000000..0e77b15 --- /dev/null +++ b/2.高效学习/2.2优雅的使用工具.md @@ -0,0 +1,63 @@ +# 2.2 优雅的使用工具 + +请大家记住使用工具的基本原则 你所感到不方便的!都有工具解决! + +因此本小节的核心要义在于推荐一些有趣的有助于提高效率的小工具 + +- [Everything](https://www.voidtools.com/zh-cn/downloads/) 电脑上的全局文件搜索 方便你找到不知道丢哪的文件 +- [SpaceSniffer](http://www.uderzo.it/main_products/space_sniffer/download.html) 快速分析硬盘空间占用情况 解放储存,不解放大脑 +- [Typora](https://typora.io/) 付费的,你可以去并夕夕啊淘宝啊花个不多于 5 块钱的钱买盗版 😋,( 正版 $14.99 ),真的好用,感觉没有 Markdown 编辑器能好用过 Typora🤥 +- [MarkText](https://github.com/marktext/marktext) 免费的 平替 Typora (?)感觉不太好用 😤 +- [思源笔记](https://b3log.org/siyuan/) 一个国产开源的笔记/知识库软件,优势是 本地化、双链、Markdown 语法,与 Obsidian 定位相似,但 Geek 成分和自定义空间相对更高 + +![](static/boxcnO1PEsVd4KY7reeU64spShf) + +- [IDM 及百度云脚本](https://greasyfork.org/zh-CN/scripts/436446-%E7%BD%91%E7%9B%98%E7%9B%B4%E9%93%BE%E4%B8%8B%E8%BD%BD%E5%8A%A9%E6%89%8B) 帮助你将百度云提速(暂不可用) +- [知云文献翻译](https://www.zhiyunwenxian.cn/):可以有效帮助你翻译论文和文章甚至英文书籍 +- [Zotero](https://www.zotero.org/):协助文献阅读还有写笔记,支持与平板同传(同时他是开源的,所以可以添加一些插件) +- [XDM](https://github.com/subhra74/xdm) :IDM 的跨平台版本。 +- [uTools](https://www.u.tools/) :自由组合插件集(最好用的是 Alt+Space 搜索功能,和 PowerToys 二选一)非常强大,比如安装 fileshare 可以在局域网共享超大文件,而且是跨平台的。 +- [PowerToys](https://github.com/microsoft/PowerToys) :微软官方出品包含诸多功能,解决 windows 下一些小痛点。 +- [Connect to Work or Games from Anywhere | Parsec](https://parsec.app/) :串流小工具,简单来说你就是可以在手机上玩电脑了,远程操作,极致体验(也可以玩游戏) +- [VMware workstation](https://gw9u39xwqi.feishu.cn/wiki/wikcnPquqfxujAgMWPbtRptk3BC):虚拟机就用它!但是最好自己找找盗版,正版要钱。 +- [Notion](http://notion.so): 笔记终结者,非常强大,(设计理念被钉钉,飞书,我来非常抄袭)。在线就可以使用。唯一的缺点是可能需要科学上网。 +- [cloc](https://github.com/AlDanial/cloc): 统计代码行数(空白行,注释行,代码行)的小工具 +- mv & cp 命令显示进度条: 在复制大文件的时候非常友好,可以通过以下脚本安装 + +```bash +#!/bin/bash +######################################################################### +# File Name: add-progess-bar-in-cp-mv.sh +# Author: steve +# mail: yqykrhf@163.com +# Created Time: Fri 05 Aug 2022 01:54:58 PM CST +# Reference: https://tinychen.com/20201128-add-progess-bar-in-cp-mv/ +######################################################################### +set -e +wget http://ftp.gnu.org/gnu/coreutils/coreutils-8.32.tar.xz +tar -xJf coreutils-8.32.tar.xz +cd coreutils-8.32/ +# Download patch +wget https://raw.githubusercontent.com/jarun/advcpmv/master/advcpmv-0.8-8.32.patch +# Patching display with process bar +patch -p1 -i advcpmv-0.8-8.32.patch +# Compile then install +./configure +make +# Copy +sudo cp src/cp /usr/local/bin/cp +sudo cp src/mv /usr/local/bin/mv +# remove tmp files +cd .. +rm coreutils-8.32 coreutils-8.32.tar.xz +``` + +- [Grammarly](https://www.grammarly.com/) : 英文语法纠正,有 word,浏览器等等插件 + +## 浏览器插件 + +- [沉浸式翻译](https://immersive-translate.owenyoung.com/installation):中英文对照翻译,可以给你英文下面写一小行中文翻译(里面免费的 api 只有谷歌,必应,腾讯,不过够了,也可以自行配置其他 api ) +- (你真的不玩原神吗)来试试这款原神浏览器插件 [派蒙 paimon](https://github.com/daidr/paimon-webext) : 可以实时显示你的树脂,委托,派遣等情况提示。 +- [wappalyzer](https://www.wappalyzer.com/):如果你是个 web 仔,这个插件可以帮你检测网页所用的前后端技术栈。 +- [FeHelper--Web 前端助手](https://github.com/zxlie/FeHelper):十几个小工具的集合,包括 base64 离线解码等。 +- [darkreader](https://github.com/darkreader/darkreader):适应网页的暗色模式,夜深人静冲浪更爽 diff --git a/2.高效学习/2.3.1阅读文档(B百度爬).md b/2.高效学习/2.3.1阅读文档(B百度爬).md new file mode 100644 index 0000000..f536f0b --- /dev/null +++ b/2.高效学习/2.3.1阅读文档(B百度爬).md @@ -0,0 +1,47 @@ +# 2.3.1 阅读文档(B 百度爬) + +# 查找教程 + +一般帮助我们快速入门的最好的教程是在官网上的。 + +比如 Pytorch 的官方文档(甚至有中文版哦)。 + +同时,在 Youtube 和 B 站 上的不少资料也值得称道。 + +我不建议各位看黑马程序员,尚硅谷这种培训班的教程,太过细致反而有些本末倒置。 + +当然,我在本教程中也会给大家一些推荐。 + +不要用百度百科!太多错误了! + +# 查找资料 + +你应该使用下表中推荐的网站: + +一些说明: + +- 一般来说, 百度对英文关键词的处理能力比不上 Google 。 +- 通常来说, 英文维基百科比中文维基百科和百度百科包含更丰富的内容。 +- 一些中文论坛内大家互相抄,很有可能你阅读了很久都没有找到正确的答案,并且英文社区内的内容远远比中文的要好。 + +# 英文阅读 + +随着科学技术的发展, 在国际学术交流中使用英语已经成为常态: 顶尖的论文无一不使用英文来书写, 在国际上公认的计算机领域经典书籍也是使用英文编著。 + +顶尖的论文没有中文翻译版; 如果需要获取信息, 也应该主动去阅读英文材料, 而不是等翻译版出版. "我是中国人, 我只看中文"这类观点已经不符合时代发展的潮流, 要站在时代的最前沿, 阅读英文材料的能力是不可或缺的。 + +阅读英文材料, 无非就是"不会的单词查字典, 不懂的句子反复读"。 + +如今网上有各种词霸可解燃眉之急, 但英文阅读能力的提高贵在坚持。"刚开始觉得阅读英文效率低", 是所有中国人都无法避免的经历。 + +如果你发现身边的大神可以很轻松地阅读英文材料, 那是因为他们早就克服了这些困难. 引用陈道蓄老师的话: 坚持一年, 你就会发现有不同; 坚持两年, 你就会发现大有不同。 + +当然也有一些巧妙地方法帮助大家进行阅读,比如知云文献翻译,不要依赖这类软件! + +# 科学上网 + +学会科学上网是非常重要的一步哦 + +机场无法给大家推荐,但是梯子经常用的无非就是 clash, ssr, v2ray 等 + +如果不知道怎么办,可以求助(找学长) diff --git a/2.高效学习/2.3.2检索论文核心内容.md b/2.高效学习/2.3.2检索论文核心内容.md new file mode 100644 index 0000000..3be608b --- /dev/null +++ b/2.高效学习/2.3.2检索论文核心内容.md @@ -0,0 +1,81 @@ +# 2.3.2 检索论文核心内容 + +首先,请克服对论文英文的恐惧,适当的利用翻译软件,社团会专门安排时间集体读论文的,届时我将纠正你们在实操方面的一些误区,首先请看理论部分。 + +由于笔者只阅读过 CV 领域和NLP领域的一些文章,且阅读量并不算太高,故对论文的理解不仅有限且仅限于该领域内的论文风格和内容技巧,望读者见谅。 + +# 论文的一般结构 + +## 1.title(标题) + +首先是标题部分。 + +一般的标题主要包括两个内容:使用什么方法解决什么问题。以此高度概括文章的内容和工作。 + +还有一种标题是比较新颖的,会使用一些比喻性的手法吸引眼球(一般人驾驭不住) + +论文作者会在标题下面指出,当我们的论文阅读量到一定程度之后可以关注一下作者。当我们在关注或研究某一个领域时,该领域的几篇重要论文读下来我们就可以知道哪个作者在该领域较为活跃,谁提出了 Backbone,谁在挖坑(填坑)。可以通过作者进而检索到你感兴趣的工作或判断论文写作质量。 + +## 2.abstract(摘要) + +Abstract 是论文中一篇具有独立性的短文,用简单、明确、易懂、精辟的语言对全文内容加以概括,提取论文的主要信息。作者的观点、论文的主要内容、研究成果、独到的见解,这些都会在摘要中体现出来,是需要重点阅读的地方。 + +摘要在资料交流方面承担着至关重要的作用。摘要会收录到大型资料库中并为读者提供信息,因此我们可以通过摘要索引查找论文。 + +摘要的四要素:目的、方法、结果和结论称为摘要的四要素。 +(1)目的:指出研究的范围、目的、重要性、任务和前提条件,不是主题的简单重复。 +(2)方法:根据研究的主要内容和发现的问题,说明在这个过程中都做了哪些工作。(摘要中的方法不会太过详细,一般只会给出一个名词) +(3)结果:陈述研究之后重要的新发现、新成果及价值,包括通过调研、实验、观察取得的数据和结果,并剖析其不理想的局限部分。 +(4)结论:通过对这个课题的研究所得出的重要结论,包括从中取得证实的正确观点,进行分析研究,比较预测其在实际生活中运用的意义,理论与实际相结合的价值。 + +## 3.introduction(导言) + +Introduction 主要是对整篇论文的一个介绍,读者看完 introduction 后就知道论文的几乎所有工作。 + +Introduction 会说明论文的主题、范围和研究目的。 + +然后阐明研究的起因、背景及相关领域简要历史回顾。(前人做了哪些工作、哪些尚未解决、目前进展到何种程度等)这一部分不同的论文情况不同,有些论文会单独拿出来作为一部分(related work),当我们刚进入到某一个领域时,我们可以通过这一部分了解该领域的大致研究风格和该篇论文的研究路径,get 到作者的研究思路(论文的这个课题存在有着哪些问题以及所面临怎样的挑战,发现前人工作的缺陷以及在此基础上的改进),有时可能会对我们的工作有启发。当然,如果我们对这一领域足够了解,可以不需要看这一部分,研究思路也可以在论文的方法部分自行体会。 + +## 4.method(提出的算法) + +此处为文章主体,详细介绍了他是怎么做的,如果需要复现的话需要仔细阅读这一部分,无论复现与否都需要详细阅读,理解具体操作与作者的理论并尽可能将二者结合(该领域的某些方面可解释性并不强)。读者不仅可以从该部分具体理解论文工作,还可以从中发现与前人工作的不同,并从中提出进一步改进。 + +## 5.experiment(实验) + +一般情况为介绍我为什么很牛逼,这里一般可以跳过如果不写文章的话 + +该部分一般会晒出工作的效果,我们可以从中更直观的体会工作的改进,甚至可以根据结果直接推断结果好坏的某些原因(不过一般论文中的图片当然都会放效果很好的以便作者吹逼,真想看效果建议复现工作),大胆并合理的假设推理也是科研工作中不可缺少的一个能力。 + +## 6.conclusion(结论) + +Conclusion 结论部分,一般阅读完开头直接阅读结尾,就基本清楚文章脉络结构和思考方案了 + +结论和摘要的内容基本相似,但某些论文的结论中可能还会指出对该工作的不足之处,还有该领域内对该工作的一些期望(挖坑)。 + +# 怎么用三遍读懂一篇论文 + +视频地址: [如何读论文【论文精读】](https://www.bilibili.com/video/BV1H44y1t75x) + +## 第一遍(海选): + +阅读标题、摘要、结论。花费十几分钟时间了解论文是否适合你的研究方向。 + +看完之后可以再看一看方法和实验部分重要的图和表,进而判断这篇论文是否适合自己,是否和自己当前在做的工作相似。 + +## 第二遍(大致把握): + +确定论文值得读之后,快速将整个论文过一遍,不需要知道所有的细节,先尝试去理解论文中重要的图和表,知道每一个部分在干什么,圈出比较重要的相关文献。 + +若到此为止:知道它解决什么问题,结果怎么样,大概用了什么方法,但是觉得文章很难看不太懂,可以去读他们之前引用的那些文章,读完之后再回头读这篇文章。 + +## 第三遍(重点研读): + +第三遍是最详细的一遍,当我们在读第三遍时通常意味着我们对该论文的工作很感兴趣了,这时我们需要力争做到知道每一段和每一句都在说什么、干什么。基本了解整个文章的细节,在之后基于他做研究,或者在之后提到它的时候,可以详详细细的复述一遍。 + +脑补工作过程,在读的过程中,思考自己来完成作者所提出的问题时需要怎么做,需要用什么方法来实现这个东西;在读实验部分时,思考自己能不能比作者做的更好;作者留下的问题,思考自己能不能继续往前走。 + +# 深度学习领域论文快速阅读 + +如果你已经看过一定数量的论文,并对自己的论文阅读能力有信心,你可以选择直接看论文的模型图,再从模型图去理解网络。(事实上大部分的论文不值得你去精读) + +此法适用于需要阅读大量领域内相关论文时,可以一周十几篇。 diff --git a/2.高效学习/2.3.3优秀的开源社区.md b/2.高效学习/2.3.3优秀的开源社区.md new file mode 100644 index 0000000..c47adbb --- /dev/null +++ b/2.高效学习/2.3.3优秀的开源社区.md @@ -0,0 +1,21 @@ +# 2.3.3 优秀的开源社区 + +[GitHub: Where the world builds software](https://github.com/)(最好科学上网) + +这里面包含着各式各样的信息和资源,简直好用到爆炸,并且非常多的论文源码都在其中哦 + +举例:如果你想学习某个语言,可以在 github 上搜索 + +awesome (你想学的东西) + +例如 awesome C + +可以看到不少有趣的整理好的资料 + +当然不止这些,更多的资源就交给你探索啦 + +但是 git 不等于 github + +那么这二者分别是干什么的,又有什么联系呢? + +这个问题就留给聪明的你啦 diff --git a/2.高效学习/2.3高效的信息检索.md b/2.高效学习/2.3高效的信息检索.md new file mode 100644 index 0000000..8c2cef4 --- /dev/null +++ b/2.高效学习/2.3高效的信息检索.md @@ -0,0 +1,3 @@ +# 2.3 高效的信息检索 + +善于运用各种信息,使自己迅速掌握时代的脉络也是不可获取的能力之一! diff --git a/2.高效学习/2.4提问的艺术.md b/2.高效学习/2.4提问的艺术.md new file mode 100644 index 0000000..f16b3a1 --- /dev/null +++ b/2.高效学习/2.4提问的艺术.md @@ -0,0 +1,35 @@ +# 2.4 提问的艺术 + +最好的提问方式就是自问自答。 + +请结合 2.1.3 共同观看 + +# 2.4.1 关于如何搜索代码 + +如果我现在想要把图片读取并转换成灰度图,我该怎么去搜索呢? + +首先,我打算使用 python,所以我会搜索:“python 图片转灰度图” + +以下是我从搜索到的博客中找到的代码: + +```python +import cv2 as cv +img = cv.imread('lbxx.jpg',1) +img_1 = cv.cvtColor(img,cv.COLOR_BGR2GRAY) +cv.imshow('gray',img_1) +cv.imshow('colour',img) +cv.waitKey(0) +``` + +接下来,我会去搜索每行代码的作用:(以下是搜索后我做的注释) + +```python +import cv2 as cv # 调opencv库 +img = cv.imread('lbxx.jpg',1) # 读取图片(“图片路径”) +img_1 = cv.cvtColor(img,cv.COLOR_BGR2GRAY) # 转换成灰度图(图片, 颜色模式) +cv.imshow('gray',img_1) # 展示图片(展示img_1为灰度图) +cv.imshow('colour',img) # 展示图片(展示img为彩色图) +cv.waitKey(0) # 保存展示窗口打开 +``` + +于是,我就不仅学会了如何转换灰度图,还学会了相关函数的用法。 diff --git a/2.高效学习/2.5优雅的记笔记.md b/2.高效学习/2.5优雅的记笔记.md new file mode 100644 index 0000000..b285a26 --- /dev/null +++ b/2.高效学习/2.5优雅的记笔记.md @@ -0,0 +1,15 @@ +# 2.5 优雅的记笔记 + +Notion + +Markdown + +Typora + +本节打算讲 markdown,还没来得及写 + +感兴趣查查上面 + +看看下面教程 + +https://www.markdown.xyz/ diff --git a/2.高效学习/2.6以理工科的方式阅读英语.md b/2.高效学习/2.6以理工科的方式阅读英语.md new file mode 100644 index 0000000..4cec91e --- /dev/null +++ b/2.高效学习/2.6以理工科的方式阅读英语.md @@ -0,0 +1,12 @@ +# 2.6 以理工科的方式阅读英语 + +作为一名理工科学生,也许英语并不是你的强势,但往往学习又难以避开英语。 + +![](static/G6zAbGrTKoBLsfxhmvHcUBVynpc.png) + +下面提供一些英语阅读的方法: + +1. 学好英语(顺便过四六级) +2. 文档阅读:使用浏览器插件,例如:[沙拉查词](https://saladict.crimx.com/)、[划词翻译](https://hcfy.app/)。 +3. Youtube 等视频网站的双语字幕 [languagereactor](https://www.languagereactor.com/)。 +4. 实用翻译软件[复制即翻译](https://copytranslator.github.io/)。 diff --git a/2.高效学习/2.高效学习.md b/2.高效学习/2.高效学习.md new file mode 100644 index 0000000..d4ef529 --- /dev/null +++ b/2.高效学习/2.高效学习.md @@ -0,0 +1,35 @@ +# 2.高效学习 + +author:zzm 邮箱 1264517821@qq.com + +本章节更多的是纠正同学们开始实验前的一些误区以及提出一些建议。 + +同时为大家推荐一些有趣的工具。 + +但是首先各位需要了解几个名词 + +# RTFM + +Read the friendly manual + +# STFW + +Search the "friendly" website + +# 为什么不能直接告诉我? + +因为本教程的目的除了让你学会知识以外,更重要的目的是教给你如何当一个合格的大学生。 + +一个合格的大学生理应具备独立解决问题的能力。 + +并且这是无论是学术界还是工业界都非常重视的基本素养 + +当遇到问题不是赶紧找个大神帮我,而是"我来试试 STFW 和 RTFM, 看能不能自己解决". + +# 如果真的不知道怎么解决怎么办? + +![](static/boxcnSmy1oqFO1glYIYGRZ9NhEb.jpg) + +来细看看本章节的内容吧! + +同时我们参考了很多上海交大生存指南:[https://survivesjtu.gitbook.io/survivesjtumanual/](https://survivesjtu.gitbook.io/survivesjtumanual/) diff --git a/2.高效学习/static/G6zAbGrTKoBLsfxhmvHcUBVynpc.png b/2.高效学习/static/G6zAbGrTKoBLsfxhmvHcUBVynpc.png new file mode 100644 index 0000000..e596c56 Binary files /dev/null and b/2.高效学习/static/G6zAbGrTKoBLsfxhmvHcUBVynpc.png differ diff --git a/2.高效学习/static/boxcnO1PEsVd4KY7reeU64spShf b/2.高效学习/static/boxcnO1PEsVd4KY7reeU64spShf new file mode 100644 index 0000000..4f98340 Binary files /dev/null and b/2.高效学习/static/boxcnO1PEsVd4KY7reeU64spShf differ diff --git a/2.高效学习/static/boxcnPDWiNgkgppK1XWq5cRQ71b.jpg b/2.高效学习/static/boxcnPDWiNgkgppK1XWq5cRQ71b.jpg new file mode 100644 index 0000000..8e0ab7a Binary files /dev/null and b/2.高效学习/static/boxcnPDWiNgkgppK1XWq5cRQ71b.jpg differ diff --git a/2.高效学习/static/boxcnSmy1oqFO1glYIYGRZ9NhEb.jpg b/2.高效学习/static/boxcnSmy1oqFO1glYIYGRZ9NhEb.jpg new file mode 100644 index 0000000..10ff967 Binary files /dev/null and b/2.高效学习/static/boxcnSmy1oqFO1glYIYGRZ9NhEb.jpg differ diff --git a/2.高效学习/static/boxcnSq1JzWhVrFs3MePPzp5Txg.jpg b/2.高效学习/static/boxcnSq1JzWhVrFs3MePPzp5Txg.jpg new file mode 100644 index 0000000..07e487f Binary files /dev/null and b/2.高效学习/static/boxcnSq1JzWhVrFs3MePPzp5Txg.jpg differ diff --git a/2.高效学习/static/boxcne9EK3xz8LHOXfM8w9ih5Ig.png b/2.高效学习/static/boxcne9EK3xz8LHOXfM8w9ih5Ig.png new file mode 100644 index 0000000..a7c11b3 Binary files /dev/null and b/2.高效学习/static/boxcne9EK3xz8LHOXfM8w9ih5Ig.png differ diff --git a/2.高效学习/static/boxcnhuhE7qBLHyJKaesHGC033b.png b/2.高效学习/static/boxcnhuhE7qBLHyJKaesHGC033b.png new file mode 100644 index 0000000..efcf96c Binary files /dev/null and b/2.高效学习/static/boxcnhuhE7qBLHyJKaesHGC033b.png differ diff --git a/2.高效学习/static/boxcnqsCWmUTDr5UDLYca9YkhHh.png b/2.高效学习/static/boxcnqsCWmUTDr5UDLYca9YkhHh.png new file mode 100644 index 0000000..2b14ed2 Binary files /dev/null and b/2.高效学习/static/boxcnqsCWmUTDr5UDLYca9YkhHh.png differ diff --git a/2.高效学习/补充:为什么不要用百度.md b/2.高效学习/补充:为什么不要用百度.md new file mode 100644 index 0000000..96623e4 --- /dev/null +++ b/2.高效学习/补充:为什么不要用百度.md @@ -0,0 +1,19 @@ +# 补充:为什么不要用百度 + +相信大家都用过百度来搜索一些非技术问题, 而且一般很容易找到答案. 但随着问题技术含量的提高, 百度的搜索结果会变得越来越不靠谱. 坚持使用百度搜索技术问题, 你将很有可能会碰到以下情况之一: + +- 搜不到相关结果, 你感到挫败 +- 搜到看似相关的结果, 但无法解决问题, 你在感到挫败之余, 也发现自己浪费了不少时间 +- 你搜到了解决问题的方案, 但没有发现原因分析, 结果你不知道这个问题背后的细节 + +你可能会觉得"可以解决问题就行, 不需要了解问题背后的细节". 但对于一些问题(例如编程问题), 你了解这些细节就相当于学到了新的知识, 所以你应该去了解这些细节, 让自己懂得更多. + +如果谷歌能以更高的概率提供可以解决问题的方案, 并且带有原因分析, 你应该没有理由使用百度来搜索技术问题. 如果你仍然坚持使用百度, 原因就只有一个: 你不想主动去成长. + +你或许会觉得翻阅手册太麻烦了, 所以可能会在百度上随便搜一篇博客来尝试寻找解决方案. 但是, 你需要明确以下几点: + +- 你搜到的博客可能也是转载别人的, 有可能有坑 +- 博主只是分享了他的经历, 有些说法也不一定准确 +- 搜到了相关内容, 也不一定会有全面的描述 + +最重要的是, 当你尝试了上述方法而又无法解决问题的时候, 你需要明确"我刚才只是在尝试走捷径, 看来我需要试试 RTFM 了". diff --git a/3.编程思维体系构建/3.0 编程入门之道.md b/3.编程思维体系构建/3.0 编程入门之道.md new file mode 100644 index 0000000..2f76508 --- /dev/null +++ b/3.编程思维体系构建/3.0 编程入门之道.md @@ -0,0 +1,139 @@ +# 3.0 编程入门之道 + +> 作者:[爱飞的鸟](https://github.com/aFlyBird0) + +## 缘由 + +### 为什么会有这篇讲义 + +原先的第三章,即 [3.构建不朽传奇(编程)](https://gw9u39xwqi.feishu.cn/wiki/wikcnIaiOQ1NIOC9tacl2dOqmIg)是从 3.1 开始的。但我简单翻阅一下之后“很不爽”,新人得有多牛逼才能看完这章的讲义? + +所以我强行塞了这篇教程进来,正好计算机一般从 0 开始计数,所以就有了 "3.0"。 + +### 这篇讲义讲什么 + +- 首先,如上文所述,如何轻松地利用本章乃至整个讲义 +- 在第一点的基础上,引申出我自己归纳的编程入门之“道” + +### 请随意喷这篇讲义 + +故意写这么大的题目,就是为了“噱头”,为了让读者有点开的欲望。 + +可能本文的所谓的“道”并不是普适的,甚至是错误的,请尽你所能地喷这篇讲义,让我们一起完善它。 + +## 如何利用好本章的讲义 + +(首先纠正一个心态,当我们获得一大片非常丰富的新的知识的时候,不应该感到焦虑。不知道自己不知道什么,远比知道自己不知道什么来得好。同时,虽然你收获的信息量很大,但也并不意味着你要全部看完,不需要焦虑。) + +1. 这里的文章的最大的作用是帮你打开信息壁垒,告诉你编程的世界有哪些东西,可以去学什么。 +2. 把讲义当成字典来用。很多文章并不是完全对新人友好的,你现在不需要看懂,甚至可能不需要看。你只要大概记下有这么个概念和工具,当下次要用到的时候,再查阅、再仔细学习就好。 + +简单来说就是,抱着平和的心态,随便看看,知道这一章都讲了哪些东西,看完有个印象就好,然后常回家看看。 + +技术细节本身永远都不是最重要的,重要的是思想和方法,如何快速掌握一门技术。 + +## 编程入门之道 + +### 先“run”起来 + +这是我的第一个也是最重要的建议。 + +无论是学一门语言,还是学一个工具:尽可能地先用最短的时间搞懂这个东西是做什么的,然后以最快的方式把它 “run”起来。 + +当你已经能跑起一个语言、一个工具的最简单的示例的时候,再去花时间慢慢了解背后的复杂的内容,再去拓展即可。先用起来,跑起来,带着问题去翻资料。 + +- 比如学写 C 语言,我建议大家直接跳过 [3.1 该使用哪个编辑器???](https://gw9u39xwqi.feishu.cn/wiki/wikcnE1gVsZuCpkiscqwYxjWueh)这章。直接打开在看的教程的第一章,把代码复制到这个[在线编译](https://rextester.com/l/c_online_compiler_gcc)的网站里,点一下 "Run it" 看效果。为什么要去详细了解编译器、编辑器、IDE、gcc、g++、make 这种东西?能最快地上手,最快地运行看到效果,对于初学者来说是最好的。等你把环境装好了,人家已经学完三章了。当你已经会了简单的循环、判断、函数,已经建立了信心,就可以慢慢地去看那些让人头疼的东西了。 +- 比如学 Linux,如果你电脑操作系统是 MacOS,直接打开“终端”,可以勉强当 Linux 使;如果你是 Windows,直接跟着微软的 [WSL 安装教程](https://docs.microsoft.com/zh-cn/windows/wsl/install),一步步无脑地用鼠标点击然后装起来。这时候你就拥有一个 Linux 了,再对着教程去敲。等闲下来了就可以了解了解内核是什么,发行版是什么,去了解云服务器,去了解不同的装 Linux 的方式。 + +为什么要这样? + +- 第一,计算机是很重实操的东西,不要光看所谓的理论不动手。动手是王道。 +- 第二,要尽可能多地、尽可能快地给自己找一些正反馈,学编程不是当苦行僧 +- 第三,很多东西没必要学,学了也忘。用到了再去学,是最省事最容易记住最高效的。 +- 第四,程序员这一辈子会接触无数的新的东西,如何快速上手一个东西,是非常重要的能力。 + +### 任务/项目驱动 + +任务/项目驱动的意思承接上文,意思就是不要瞎看文章,不要让阅读教程和学习课本成为你的驱动力,等你看完一本 C 语言的书,一行代码就没敲,你就 g 了。 + +那么该怎么学呢? + +先简单地会一样东西的最核心的部分,再去找一个实际的编程场景、编程任务、项目。你会在完成这个项目中遇到各种各样的问题,无论是遗漏了知识点还是压根没思路, 这时候不断地用搜索引擎来学习。( [2.3 高效的信息检索](https://gw9u39xwqi.feishu.cn/wiki/wikcn90HpO8RrIXVRgQnEcCatxd) + +举个例子:你想做一个小程序,来检测某电影院的电影预售。程序大概要做到不断刷新网页,一检测到这个电影预售了,就马上发短信给自己手机(或者直接帮你抢) + +1. 你通过搜索引擎或者从不知道哪个学长/学姐那里得知,这玩意叫爬虫(简单来说就是用程序抓取网页上的内容)。我们又通过搜索引擎得知,python 写爬虫最舒服。[3.6python(灵巧的胶水)](https://gw9u39xwqi.feishu.cn/wiki/wikcn8RxD1oJ4w5BVOIS9QpS4xQ) +2. 我们又通过例如菜鸟教程这种最简单的极速入门教程,在 3 个小时内掌握了 python 的核心语法。 +3. 这时候我们开始写代码了,但是我该怎么获取到网页啊?这时候,你在浏览器里分别搜索了以下几个内容:“程序如何获取网页内容”。 +4. 你会学习到 http 相关的知识,大概知道了我们平时打开网页可以简单理解为一次 http GET。 +5. 可是,还是好抽象啊,python 又怎么才能获取到网页的内容啊?这时候,你在浏览器里精确了一下搜索内容,“python 如何获取网页内容”。 +6. 搜索引擎会告诉你,可以用诸如 `requests` 一类的库来请求网页。但问题又来了,这个库一下子会返回整个网页内容,我怎么才能筛选出我要的电影有没有放出来? +7. 这时候你又去搜索,学会了怎么解析网页。比如用 `xpath`, `bs4`, 甚至直接用正则。 +8. 我们现在能不断地刷新网页,并且筛选出所有的电影的信息,然后分析出自己想看的那个电影有没有放出来了。但是问题又来了,怎么让程序给自己发信息啊? +9. 这时候又打开了浏览器,我们可以知道诸如阿里云、腾讯云这样的云服务商提供了发短信的服务,并且会教你怎么写代码,主要注册、申请一下,就能发短信了。 +10. 但是你又会发现这个申请流程真 tm 麻烦,我还是发邮箱吧!这时候又开始搜,如何用 python 发邮件。 +11. 然后你会了解到 python 用于发邮件的一些库,以及简单的邮件协议的知识,如 `POP3` +12. 程序真的能如期运行了,能不断刷新、获取网页内容、解析内容以判断电影是否上映,上映了还会发邮件给你。 +13. 又又又出问题了!!!你发现程序出错了,因为你请求频率过高,电影院的网站发现你是爬虫了,把你给 ban 了!这时候你知道了“反爬”这个概念,就是反爬虫,类似于外挂和反外挂。你开始学习反爬的知识。 +14. 你了解到可以给程序挂个“代理”,相当于每次请求网页的时候,都伪装成了世界上上某个角落的另外一台电脑的请求。 +15. 能走到这里太艰辛了,但你很快又发现了问题!现在程序是跑在自己的电脑上的,我们不知道电影院啥时候放票,所以程序得一直跑着,但是电脑会关机啊,关机了还怎么抢??? +16. 这时候你了解到了服务器的概念。服务器可以简单地了解为一台远程的几乎从不关机的电脑,一般用的是 Linux 操作系统。[3.2 快试试 Linux(必做)](https://gw9u39xwqi.feishu.cn/wiki/wikcnURN0Q2aX7z9hGVAywFQmQb) +17. 这时候你又开始去学 Linux,当然不是非常系统地学,而是像我之前说的,以最快的方式知道它是做什么的,然后运行起来。比如可以直接在阿里云、腾讯云、AWS 等云服务器商那里购买。甚至也可以用自己的旧手机装个 Linux 等等,这些都是后话了。 +18. 服务器是远程的,我们怎么连上去操纵它呢?我怎么把我的 python 代码传上去呢?这时候你又去求助万能的搜索引擎,或者神秘的学长学姐。你知道了世界上有 `ssh`、`ftp`、`sftp` 这些东西。 + +以上的“简单”的例子,就是所谓的任务驱动。带着目的去学,带着任务去学,带着问题去学,快速搞定。在上面的例子中,如果能成功走完,你会学到编程语言本身、学到网络知识、学到 Linux 服务器及其相关操作、学到云服务器和各种云服务(例如短信服务)。更重要的是,每一次从问题到答案的搜索与解决过程,你的编程内功就增强了一波。以后上手一个新东西,或者遇到问题再去解决它的速度,只会越来越快。 + +刚开始你可能什么都不会,什么地方都被阻塞,但当你把坑踩遍了。就发现,哎嘿,不好意思,这玩意我怎么又会! + +所以让我们基于这个“任务驱动”,再看看本章的内容。这些内容大多看了就忘,因为细节非常多,而且并不一定能解决你手头上的问题。但这些文档,带你领进了新的领域的大门,让你的工具箱里多了一个可以解决问题的工具,以后用到了可以想起他们。并且,这些文章多是通俗的,且作者多是讲述了 ta 所认为的该语言/工具的最核心、最精华的部分,或者说第一次入门最需要学习的部分。 + +## 圈子 + +先干了这碗鸡汤,我之前写的,就单纯想让你看看。 + +> 每个大学的同一个专业,录取的时候分数是差不多的,但是毕业的时候工资可以差几十倍。他们的大环境是相同的,外界对他们的学历评价也是一样的。外部的大环境,我称之为大圈子,最典型的是学校。大圈子决定的是外部对你的整体性的刻板印象式的评价,以及决定了你能以多大的成本来获取资源(比如有没有公司来学校主要招生,有没有免费出国交流的机会,有没有图书馆)。但是,这个大圈子,在一般情况下,在你思考这个问题的时候就已经决定了,比如大家的大学是不能改变的。但更重要的是,小圈子,即你自身所营造的环境。比如每个人都有几个要好的朋友,他们的编程水平,其实就决定了你的编程水平,比如你是否有加入有技术大佬坐镇的社团,这样他们能带你学。如果线下都没有的话,你是否主动去加一些网上的社群、编程社区,问问公司对计算机专业学生的要求是什么,问问能进大厂的人,都在学些什么(知道要学什么很重要,瞎学悔大学)小圈子能时刻让你知道重要的讯息,比如计算机哪个细分方向的就业好,什么厂最近又有提前批,等等。圈子这东西太重要了,我以前很长一段时间就是一个所谓的一直在学习,很想提高自己,但因为比较自闭没有认识牛逼的人,所以我压根不知道学什么啊。那就只能随便弄,瞎搞,做无用功。 + +圈子,从另外一个角度,也可以理解为“信息差”,“信息壁垒”。对于编程这东西,知道有哪些东西能学,哪些东西赚钱多好找工作,哪些地方有项目,然后有着志同道合的人一起唠,太重要了。 + +我最近的两次编程能力的飞跃,一次是在加入了某个社团认识了很多技术大佬以及有了非常多的项目可以写,一次是意外地获得了一份实习所以能够和行业的领跑人物经常聊天,以及在开源社区慢慢接触到来自世界各地的技术大牛。 + +“破圈”的话,我目前接触到的最好的方式是参与开源社区。参与开源的好处如下: + +1. 输入决定输出。开源的代码多是经过检验的牛逼的代码,通过多看看优雅的代码来提高编程能力,比自己无中生有简单地多。 +2. 开源圈牛人多。无论是拓宽视野,还是在 issue 下的交流,还是别人给你的 review 建议,都能学到很多。你会在开源的过程中认识很多的人,很多大厂的人,说不定就是你以后的面试官。 +3. 参与开源社区能极大地锻炼自己的编程能力,能给简历贴金 。 +4. 开源是程序员的浪漫。 + +对于学生而言,可以参加一些仅面向学生开放的开源活动。一般会有一个主办方,然后有许多知名的开源社区报名。他们会罗列出一些有一定难度的任务,学生可以提交申请书,陈述如何完成这个任务。中选后会分配单独的导师来带你,还会发奖金给你,一般是大几千起步。推荐阅读这个系列的文章:[https://erdengk.github.io/gsoc-analyse/](https://erdengk.github.io/gsoc-analyse/) + +## 不同的知识媒介 + +这里主要讲讲我对不同媒介来学习编程知识的看法,媒介主要指的是 + +- 图文 vs 音视频 + +我身边的牛逼的人,一般都更喜欢通过看图文来学习,而不是视频。主要有以下几个原因: + +1. 图文的信息密度最大。同样的时间内,看一篇图文获得的信息量比视频大很多。 +2. 图文易定位、检索。你可以通过搜索功能非常迅速地在一个工具的官网内找到想要的内容。 +3. 一些杂七杂八的原因:图文往往更新,因为视频制作耗时长,不容易更新;牛逼的人它不一定会做视频,或者说牛逼的人中有写博客习惯的远大于做视频习惯的。 + +当然,视频也有很大的好处,就是直观、简单。适合学习初期。 + +当然也不必强行把自己往上套,结合自身特质,以什么的方式学习效率最高,就采用什么样的方式。不过我建议你多锻炼阅读官方文档、文档教程的能力。 + +## 选择大于努力 + +[正确解读 GPA](https://gw9u39xwqi.feishu.cn/wiki/wikcnM0ZLBGvMT9jwpy1cucds5d) 这篇文档写得很好,和我的想法完全一致,但是被放得太后面了,我想把它提上来。 + +大学不是唯分数论的,起码编程不是这样。我的建议是,如果以后大概率考研,可以多抓一下绩点;如果以后大概率工作,就不必追求高绩点了(指把大部分时间都花在提高绩点上)。 + +至于为什么是“选择大于努力”这么笼统的标题,因为我想表达更多的意思(虽然我一时想不到那么多)。 + +选择真的比你想象的重要。 + +- 例如,你是选择把大部分的时间花在把成绩从 85 提高到 90 上,还是把大部分时间用来做实际的项目直接上手以后大厂需要的框架上? +- 例如,你选择什么技术栈,哪个发展方向(这个目前来说大家不需要考虑,只是简单提一下。大家选计算机这个行为本身相对来说就已经很对了,比土木工程当牛马好太多)? +- 例如,你是选择以老师上课教的内容为主,埋头搞 C 语言,还是多去扩展自己的视野去了解现行的流行的框架是什么,大厂需要什么样的能力,然后去做项目? + +以及,一旦作出了选择,就不要像祥林嫂那样自怨自艾。过去的事情无可改变,我们只能基于当前的状态和资源,去把接下来的事情做得更好。 diff --git a/3.编程思维体系构建/3.1该使用哪个编辑器???.md b/3.编程思维体系构建/3.1该使用哪个编辑器???.md new file mode 100644 index 0000000..9ac670e --- /dev/null +++ b/3.编程思维体系构建/3.1该使用哪个编辑器???.md @@ -0,0 +1,89 @@ +# 3.1 该使用哪个编辑器??? + +# 编辑器,编译器,集成开发环境 + +我们平时所说的程序,是指双击后就可以直接运行的程序,这样的程序被称为可执行程序(Executable Program)。 + +在 Windows 下,可执行程序的后缀主要有 .exe + +在类 UNIX 系统(STFW)下,可执行程序没有特定的后缀,系统根据文件的头部信息来判断是否是可执行程序 + +可执行程序的内部是一系列计算机指令和数据的集合,它们都是二进制形式的,CPU 可以直接识别,毫无障碍;但是对于程序员,它们非常晦涩,难以记忆和使用。 + +(你也不想用一沓纸带写程序吧) + +## 什么是编辑器 + +编辑器的概念很简单,百度百科上这么写道: + +> 编辑器是软件程序,一般是指用来修改电脑档案的编写软件,但也有人称 PE2、HE4(汉书)……等文书软件为编辑器。常见的编辑器有文本编辑器、网页编辑器、源程序编辑器、图像编辑器,声音编辑器和视频编辑器等。 + +当然在这里我们主要讲的是代码编辑器,一个好的编辑器可以节省开发时间,提高工作效率,它们都能提供非常方便易用的开发环境。你可以用它们来编写代码,查看源文件和文档等,简化你的工作。以下是一些常用的代码编辑器,每个不同的编辑器都有不尽相同的目标用户群体。 + +- Visual Studio Code : 微软 VS 系列的新作品,适用于多平台的代码编辑器,其很好服从了轻量化 + 拓展的 Unix 思想,在整体快捷方便的同时具有极强的功能拓展空间,是值得首要推荐的编辑器。 +- Vim : Vim 是从 vi 发展出来的一个文本编辑器,在程序员中被广泛使用,运行在 Linux 环境下。 +- GNU Emacs : Emacs 是一个轻便、可扩展、免费的编辑器,它比其它的编辑器要更强大,是一个整合环境,或可称它为集成开发环境。它可以处理文字,图像,高亮语法,将代码更直观地展现给开发者。 + +## 什么是编译器 + +C 语言代码由固定的词汇按照固定的格式组织起来,简单直观,程序员容易识别和理解,但是对于 CPU,C 语言代码就是天书,根本不认识,CPU 只认识几百个二进制形式的指令。这就需要一个工具,将 C 语言代码转换成 CPU 能够识别的二进制指令,也就是将代码加工成 .exe 程序;这个工具是一个特殊的软件,叫做编译器(Compiler)。 +编译器能够识别代码中的词汇、句子以及各种特定的格式,并将他们转换成计算机能够识别的二进制形式,这个过程称为编译(Compile)。 + +·机器语言、汇编语言和高级语言区别 + +1. 机器语言 + 计算机执行的二进制命令,都是 0 和 1 表示的。 +2. 汇编语言 + 具有一定意义的文字命令,与机器语言一一对应。汇编语言可以通过汇编得到机器语言,机器语言可以通过反汇编得到汇编语言。汇编过程还包括变量内存管理,即经过汇编之后所有的变量和函数都变成了地址,而常量也变成了对应的值。 + 但是汇编语言还是不够直观,一个简单的动作需要大量的语句来描述,因此又有了高级语言。 +3. 高级语言 + 更简单,符合人们的习惯,也更容易理解和修改。高级语言经过编译器编译之后可以得到目标程序。 + 编译器的作用就是把高级语言的源代码转换成对应平台的目标代码。高级语言书写比较简单,但是翻译起来比较复杂,同样的高级语言语句可以有不同的机器语言实现方法。 + +而编译器所做的就是进行这三种语言的互相转换。大多数情况下,编译是从更高级的语言(高级语言、汇编语言)编译成低级语言(汇编语言、机器语言)。 + +另一种情况是,从他人的可执行程序(低级语言)编译成高级语言,以推导出他人的软件产品所使用的思路、原理、结构、算法、处理过程、运行方法等设计要素,某些特定情况下可能推导出源代码。这个过程叫做反向编译。 + +编译器:将你所编辑的源代码编译成机器所能理解的语言,比如 VC++ 把你的.cpp 文件编译成.obj 文件(经过编译器编译这时的代码计算机已经可以识别),而最后的.exe 则是通过连接生成的(这里的工作是由连接器完成的,跟编译器无关)。 + +语言的编译器有很多种,不同的平台下有不同的编译器,例如: + +- Windows 下常用的是微软开发的 cl.exe,它被集成在 Visual Studio 或 Visual C++ 中,一般不单独使用; +- Linux 下常用的是 GUN 组织开发的 GCC,很多 Linux 发行版都自带 GCC; +- Mac 下常用的是 LLVM/Clang,它被集成在 Xcode 中(Xcode 以前集成的是 GCC,后来由于 GCC 的不配合才改为 LLVM/Clang,LLVM/Clang 的性能比 GCC 更加强大)。 + +你的代码语法正确与否,编译器说了才算,我们学习 C 语言,从某种意义上说就是学习如何使用编译器,让编译器生成可执行程序(例如 Windows 下的 .exe 程序)。 + +编译器可以 100% 保证你的代码从语法上讲是正确的,因为哪怕有一点小小的错误,编译也不能通过,编译器会告诉你哪里错了,便于你的更改。 + +## 什么是集成开发环境 + +实际开发中,除了编译器是必须的工具,我们往往还需要很多其他辅助软件,例如: + +- 编辑器:用来编写代码,并且给代码着色,以方便阅读; +- 代码提示器:输入部分代码,即可提示全部代码,加速代码的编写过程; +- 调试器:观察程序的每一个运行步骤,发现程序的逻辑错误; +- 项目管理工具:对程序涉及到的所有资源进行管理,包括源文件、图片、视频、第三方库等; +- 漂亮的界面:各种按钮、面板、菜单、窗口等控件整齐排布,操作更方便。 + +这些工具通常被打包在一起,统一发布和安装,例如 Visual Studio、Dev C++、Xcode、Visual C++ 6.0、C-Free、Code::Blocks、JetBrains Clion 等,它们统称为集成开发环境(IDE,Integrated Development Environment)。 + +集成开发环境就是一系列开发工具的组合套装。这就好比台式机,一个台式机的核心部件是主机,有了主机就能独立工作了,但是我们在购买台式机时,往往还要附带上显示器、键盘、鼠标、U 盘、摄像头等外围设备,因为只有主机太不方便了,必须有外设才能玩的爽。 + +集成开发环境也是这个道理,只有编译器不方便,所以还要增加其他的辅助工具。 + +# 我的推荐 + +作为个人使用比较顺手的几款 IDE + +Java: [JetBrains](https://www.jetbrains.com/zh-cn/idea/)[ IntelliJ ](https://www.jetbrains.com/zh-cn/idea/)[IDEA](https://www.jetbrains.com/zh-cn/idea/) + +C: Visual Studio(宇宙第一 IDE), [JetBrains](https://www.jetbrains.com/zh-cn/clion/)[ Clion](https://www.jetbrains.com/zh-cn/clion/), Visual Studio Code(编辑器 IDE 化需要额外配置) + +Python: [JetBrains](https://www.jetbrains.com/zh-cn/pycharm/)[ ](https://www.jetbrains.com/zh-cn/pycharm/)[P](https://www.jetbrains.com/zh-cn/pycharm/)[ycharm](https://www.jetbrains.com/zh-cn/pycharm/) + +Vim 在附加篇章里有额外介绍 + +[JetBrains](https://www.cnblogs.com/Coline1/p/15229244.html)[白嫖指南](https://www.cnblogs.com/Coline1/p/15229244.html) + +当然,适合你的才是最好的 diff --git a/3.编程思维体系构建/3.2.1为什么要选择ACM——谈谈我与ACM.md b/3.编程思维体系构建/3.2.1为什么要选择ACM——谈谈我与ACM.md new file mode 100644 index 0000000..e19e210 --- /dev/null +++ b/3.编程思维体系构建/3.2.1为什么要选择ACM——谈谈我与ACM.md @@ -0,0 +1,109 @@ +# 3.2.1 为什么要选择 ACM——谈谈我与 ACM + +author:ou_43295d8698ca06daaaa330b9d623ede0 + +先验条件:保证你可以在每天进行练习和学习此方面内容即使是假期也不能超过三天以上休息,如果你想验证一下这件事当然也可以,注意心态的保持很重要 + +# 将时间花在 ACM 上值得吗? + +初入大学,摆脱了高中的种种束缚,同学们想必对大学生活有着种种幻想。或许有同学依旧保持着高中的思维,希望刷取高绩点,用好成绩谋求保研。或许也有同学只想将课程草草应付,去探索一些偏实践的方向以谋求一份好工作。 + +但无论你渴望从大学生活谋求何物,我认为做为一位计算机专业的学生投身于 ACM 算法竞赛学习都是值得,无论你是否得奖。 + +# ACM 能为我带来什么? + +显然,做为一名计算机专业的学生,编程是一项必须掌握的技能。再次引用 Niklaus Emil Wirth 的一句话:程序=算法 + 数据结构。例如在大一开设的程序设计基础中,我们需要重点学习链表这一数据结构,熟悉运用分支与循环结构(勉强也算算法吧)。然而,在 ACM 中,这是基础到不值一提的事物,宛如空气与水一般基础。你们是否想过,花了大量课时学习的这些知识,其实小学生也可以学会(看看远处的小学编程补习班吧,家人们)那做为大学生去学习这些知识,是否应当得到一些不止于考试内容的知识呢? + +我认为有两个方向,一是我们去学习一些更底层的逻辑与原理,此外就是学习如何更好的利用链表,实现一些别的数据结构做不到的事情,我认为 ACM 可以极大的提升我们对后者的理解。 + +再从功利的角度而言,我认为学习 ACM 有以下好处 + +① 你在学习的过程中提前学习了大量接下来课堂中反复强调的知识,所有的编程对你而言都是水课,只要考试前应付一下那远离实践意义的考题即可 + +② 在招聘面试过程中,考官会问你一些短小精悍的算法题,为了应付这些算法题,你迟早要去刷 leetcode(力扣),做算法题是迟早的事。并且假若竞赛获奖的经历,也可以丰富简历 + +③ 假如你有幸活过筛选,并且获得比赛机会,并且得奖,恭喜你,你的绩点将被画上浓墨重彩的一笔。做为大学顶尖赛事,ACM 的奖项可以直接在你的最终绩点上加分(铜 0.5,银 1.0,金 1.5)这意味着你只要主课不要落下太多,奖学金随便拿(比赛获奖本身还有额外奖金)。 + +# 零基础学习 ACM 是否过于困难? + +我并不这么觉得,原因如下 + +①ACM 前期更看重你的思维能力而不是代码能力,很多题你可能需要很多纸上的推演,但最后代码可能只有十几行,到冲击金牌的阶段需要思维与码力齐驱,但那太远了。 + +固然,码力需要大量的练习,零基础尤其是第一次接触编程的同学会感到困难,但在度过初期后,限制你写题水平的是你的思维水平,而思维水平就看个人的悟性和理解,这点有无基础我并不认为很重要 + +②ACM 的刘教练对零基础有特别关照,每次选集训队队员都会固定留下一定量的零基础队员,这意味在名额竞争中,你们大概率并不需要和有基础的同学直接竞争(显然也存在零基础同学更强的例子),而是与其他同为零基础的同学竞争 + +③ 在初高中参加竞赛的学生的数量和质量有极可能已经有所下降,因为竞赛相关政策的紧缩,稀烂的强基计划替代了对竞赛友好的自主招生,选择全力投身竞赛,拼搏省队的学生有所下降,有基础的学生现在也不见得很强。 + +进队的学生零基础偏少,如果你选择这条路你可能需要克服不小的困难 + +# 我应该以什么态度学习 ACM? + +假如你是一位有信息竞赛基础,且得过省级奖项的前 oier,您也没什么必要看这篇文章,您已经完全熟悉了算法竞赛需要的一切,我希望您不要有太大压力,做最好的自己就行,不要留下遗憾。对于零基础的同学也一样,或许得奖后的绩点加成实在是过于诱人,但竞赛获奖这种事情还是太难强求,让自己压力太大得不偿失。 + +这里我想谈谈我的经历,我从初中开始学习算法竞赛,一开始只是抱着试试看的心态,结果发现自己还过的去,获得普及组一等奖,便继续学习去冲击一等了。 + +转折点在初二下的暑假,当时特长生这一机制仍旧活跃存在,只要拿到提高组一等奖或者分数在一等线附近,你就有机会无视中考成绩上一所好高中,于是我从这个暑假开始就几乎再也没有回到过初中(除了最后一个月,因为学校不希望我分数太低拉低平均分影响招生,虽然分数对我而言根本无所谓)这也成为我很大的一个遗憾,关键一年的缺失使我几乎与初中的同学到现在不存在任何感情纽带,名字也不记得几个了。 + +转回正题,封闭式的竞赛集训让我感到无比痛苦,一边是升学的巨大压力,竞赛比起中考存在着更多偶然性,倘若在十一月的比赛中失误,我将前功尽弃,用于竞赛的时间毫无意义,文化课也将因此与其他同学拉开差距;另一边是同一届的神仙太多,我能走到集训这一步也许已经比很多人优秀,但和那些”神“比起来还是差太多,能力上难以填平的鸿沟与青春期的烦恼让我活的浑浑噩噩。 + +如果不出意外的话,我的人生就要出意外了。在那一年的 NOIP(信息学竞赛省赛)中,我在 DAY1 取得了满分的好成绩,于是我飘了,我在做一个梦,我真的不如那些”神“吗,我是否也可以做到更多?于是我 DAY2 死磕难题,分数爆炸了,最终差五分够到一等线。 + +之后的日子是灰暗的,浑浑噩噩的训练,知难而退放弃最好的高中的特长生名额。故事很长,我只是想说学竞赛不要太功利,竞赛终究是少数高手的游戏,做不到就是做不到,但这也仅仅只代表你的竞赛能力不够,你的人生并不是只有竞赛,大学也不只有 ACM 一条路(这很显然,不然我们社团应该改名为 ACM 社) + +# 再谈 ACM 为我带来什么 + +我初中成绩并不差,但发挥失常的话确实上不了我毕业的高中。我高考发挥失常,竞赛通过杭电三一成为保底。 + +但倘若说存在一个一开始就没有学过竞赛的我,我是否在考试分数上会有更高的成就呢?可惜人生没有如果。 + +进入大学,再次拾起算法竞赛,这次,我并不觉得惶恐与焦虑,因为无论是否得奖,我享受研究算法的过程,欢喜于做出难题带来的成就感,我在愉悦的学习,这样就够了。 + +我在说的不止 ACM 能为我们带来什么,我也在说学习能为我们带来什么。或许,考试只是给我们一个去学习的借口;或许,考试周的应试复习只是生活中无可避免的无意义之事。需要应付的无意义事物总是存在,你不能保证自己的每分每秒都被投入于黄金般的事业中。如果学习 ACM 让你感到有趣,那就去学,不需要别的理由。 + +截止完成这篇文章为止,笔者仍在集训队中,我害怕自己被淘汰,不是因为我害怕自己失去参赛资格,而是我很难想象自己失去 ACM 的生活,我需要一个学习 ACM 的理由。给诸位讲个笑话,某一天我与朋友出门游玩,想不到话题,于是就开始讨论算法题的做法,从正午到日落。 + +# 算法思维与应试思维 + +众所周知,ACM 是开卷竞赛,你可以携带纸质资料进入考场。 + +这时可能就会有同学特别开心,哎呀,这不就是开卷考试嘛,我平时程设期末考试都不能开卷,这开卷了还不简单,我直接对着书抄抄抄不就好了。 + +熟悉编程的朋友可能知道,有一个词叫做”模块化编程“,这意味着你将程序的功能拆解,让你的主函数由一个个定义好的函数组成。 + +同样,在你对 acm 题面进行转换后,问题可以被拆成一个个小模块。例如我现在要对某个数组进行排序,其实我根本不用知道排序算法是怎么进行的,我只要从纸质资料上扒一段代码就行。 + +但是,假如你一直依赖这些现成的代码,不思考算法的内核,仅仅把他们看做一个个提供输入输出映射关系的”黑箱“,ACM 会容许你这样生搬硬套,死记硬背的选手占上风吗?不见得。 + +首先对于更高层次的题目,为了防止重题,直接套板子基本是不可行的,你需要对一些代码进行优化和调整。倘若你把代码看作一个个黑箱,又该如何去调整他呢? + +其次,将一个算法学的透彻,对我们的算法思维大有裨益。 + +比如大家以后耳熟能详的快速排序,他的思维可以实现 O(n)求出第 k 大的数;而归并排序的思维不仅可以用于大量分治问题,例如其衍生算法 cdq 分治,其合并区间的思想也可以用于线段树。 + +只会抄板子的选手能理解这些吗?假如我们对代码死记硬背,生搬硬套,我们的知识体系就是离散化的,缺少关联的。不利于我们今后的发展。 + +也许在一次次陈旧腐朽的选拔性考试中,应试思维取得了压倒性的胜利。但在 ACM 中,算法思维依旧有一片净土。 + +# 数学与算法思维 + +那么,如何培养算法思维呢?我认为首先我们得学好数学。然而,我最总是在大一中听到这样的声音:“哎呀,烦死了,我们是学编程的,为什么要花那么多精力学数学,还占那么多学分,真讨厌。“然而,比起枯燥乏味的编程课,我最喜欢的还是数学课。数学在算法中无处不体现,可以说学好算法就是要学好数学,我现在复盘我初中 OI 生涯的失败,很大程度归因于数学基础的薄弱。以下为几个体现数学在算法中重要性的例子。 + +① 众所周知,ACM 是组队比赛,比起国内三个狂战士无需分工嗯干,国外有较为成熟的组队合作机制。一般由一位擅长开题拆解题意转换模型的队员,一位码力超群擅长高级数据结构的队员,剩下一位,则会跨专业选取一位数学系的学生,由此可见数学的重要性。 + +② 计算机中有大量的算法直接运用了数学,例如,快速幂的正确性需要数学推导,利用快速幂求乘法逆元也直接用到了费马大定理。 + +③ 计算机算法本就是数学的一部分,翻开计算机学生必修课程离散数学的课本,你会发现图论中的最短路算法,甚至网络流算法,都是离散数学的一部分。 + +④ 获奖的 ACM 队员就没有数学差的。 + +现如今,学科与学科之间并不是的独立的,数学系的学生也会学习编程,去解决一些算法问题,例如我们学校理学院的一位老师就有在做算法理论方向的研究。 + +反过来说,计算机的学生也可以好好学习数学,加深对算法的理解。哪怕以后去从事热门的人工智能方向,数据处理、卷积(虽然此卷积非彼卷积)、反向传播、感知机、神经网络等知识点都离不开数学。 + +优秀的数学思维能使你在理解算法的路上事半功倍,当然,算法的学习也能加深你对数学的理解。 + +# 结论 + +大胆去学 ACM 吧,大一的空闲时间真的很多,去探索新事物,不试试怎么知道自己行不行。 diff --git a/3.编程思维体系构建/3.2.2手把手教你学算法——如何使用OJ(Online Judge).md b/3.编程思维体系构建/3.2.2手把手教你学算法——如何使用OJ(Online Judge).md new file mode 100644 index 0000000..ff885d2 --- /dev/null +++ b/3.编程思维体系构建/3.2.2手把手教你学算法——如何使用OJ(Online Judge).md @@ -0,0 +1,165 @@ +# 3.2.2 手把手教你学算法——如何使用 OJ(Online Judge) + +在之前的篇章中,我们向新手 acmer 推荐了两个编程网站——Luogu 与 Codeforces,下面由笔者向各位介绍一下网站的详细用法。 + +# Luogu + +进入 [https://www.luogu.com.cn/](https://www.luogu.com.cn/) + +![](static/Fzn4b3C9qoLUApxxwvXcHOFfnxe.PNG) + +## 社交模块 + +做为一个刷题网站,Luogu 提供了符合中文用户习惯的社交模块。体现于左侧边栏的讨论及主页的最近讨论,以及底部的“发射犇犇”系统。但是我并不建议 Acmer 使用该功能,因为 Luogu 主要面向初高中生甚至小学生等参加 NOIP 系列竞赛的用户,讨论不可避免存在一些低龄化现象。对于社交模块的使用,我推荐当且仅当一种做不出题的求助手段,这点放在之后题目模块讲解。 + +## 题目模块 + +点开题库,我们看见以下界面 + +![](static/MUEWbTv6QoShVRxmgTicEjMbnHz.PNG) + +在上方我们可以筛选我们想要的题目,接下来我们点开 P1000 为例 + +![](static/EbgHbImhaoxr2Kx4lr0c0cFSnje.PNG) + +右侧三个模块为折叠状态,下面介绍他们的作用 + +① 标签:假如你已经对算法有了基本的了解,面对一道题毫无思路,那么你可以试试看正解所使用的算法,寻找思路的突破口 + +② 讨论:假如你的代码因未知原因一直出错,你可以试试讨论(越难的题有效信息越多)看看自己是否犯下别人犯过的错误或者可以发帖求助(关于如何正确发帖求助,请参考《提问的艺术》) + +③ 推荐题目:没做爽?再来一道类似的(请勿沉迷刷水题,过题量除了炫耀毫无意义) + +右上方点击查看题解,查看其他用户撰写的参考答案和讲解。 + +点击提交答案 + +![](static/YMGmbGgfGoK0ZMxVGe4cGHgBnee.PNG) + +左侧可以选择语言类型,C++ 用户建议选择 C++14。 + +O2 优化是一种优化(废话)假如您的代码复杂度正确但 TLE,可以尝试该选项。 + +## 记录模块 + +怎么知道自己代码的问题出在哪里呢?记录模块是帮助你的好工具。 + +![](static/HP6qb4DkKoHpahx35hacFWAUnef.PNG) + +AC:通过该数据点 + +WA:答案错误 常见原因:没开 Long Long 导致数据溢出、少取模、格式错误、忘记删除调试代码 + +RE:运行错误 常见原因:数组访问越界、访问空指针、除零模零、主函数返回非 0,评测机炸了(极小概率) + +UKE:未知错误 常见于 Remote Judge,建议重交或者去原网站交 + +TLE:运行超时 请检查算法复杂度与是否存在死循环,也可尝试使用 O2 优化。搜索“卡常数”了解更多缩短运行时间小寄巧 + +MLE:空间超限 请检查是否递归爆栈、数组过大 + +OLE:输出超限 放心你见不到的 + +## 题单模块 + +点开侧栏题单 + +![](static/J2XmbpHfSoyABIxWhonco3jJnNp.PNG) + +建议新手从官方精选题单开始,由浅入深,由简到难。等到对算法形成概念,针对漏洞补习时可以尝试用户分享题单(到那个阶段已经有很多手段去找题了,刘教练的题单就够你做了) + +## 比赛模块 + +点开侧栏就能看见准备举办和已结束的比赛。笔者不建议大家在 Luogu 打比赛,首先赛制不一样,其次出题风格不一样,最后对于初学者 Luogu 比赛的难度曲线过大。 + +# Codeforces + +进入 [https://codeforces.com/?locale=en](https://codeforces.com/?locale=en) + +![](static/UCIabPwSmo9lfmxHIGvcsAcqn4d.PNG) + +比起 Luogu,这样的 UI 设计离 CN 互联网已经很远了(然而比起更硬核的一些做题网站,CF 的 UI 真是越看越顺眼) + +右上角注册登录切语言(哇塞,可以选俄语,你说的对,但是 CF 是一款由俄罗斯开发的多人在线竞技游戏) + +## HOME 模块 + +主页显示各种数据,主要为近期比赛的一些公告。 + +## TOP 模块 + +热帖,如果擅长英语的话,CF 的交流氛围还是不错的,做为一个答疑解惑的论坛肯定比国内强。 + +## CATALOG 模块 + +文档目录,你可以在这学习算法竞赛入门,体系化学习算法,只要你会英语 + +## CONTESTS + +重中之重!CF 的比赛系统可以说是我们选择这个网站的最大原因! + +进入比赛页面 + +![](static/PAA1bTNOroya0zxkdFTceYYdn4c.PNG) + +上方为将举办比赛,显示开始时间(UTC+8 也就是我们时区的时间)和持续时间大多都开始的比较晚,例如笔者就没有这么晚学习的习惯,所以一般赛后写题。比赛分为以下几种类型(例如写在括号里的 Div.2) + +Div.1、Div.2、Div.3、Div.4 数字越小难度越大。 + +建议新手从 Div.2 及以下的难度打起,在比赛时间内写的题目很少也不要气馁,CF 出题审题质量稳定,写到就是赚到,赛后补题就行 + +对于已经结束的比赛,我们可以直接点击“Enter”进入比赛看题补题,也可以点击“Virtual partipation”简称“VP”,重现赛时场景,例如显示赛时排行榜,即时过题人数等,在比赛完成后你也可以看见如果你以该状态参赛,你会获得怎样的排名。 + +下面以一场 Div.2 比赛为例,展示我们该如何打一场 CF。 + +## VP + +![](static/MEItb9zbBoYtIvxQMJlcLerCnuf.png) + +这是一场笔者之前赛后补过的 Div.2,画面右下角分别为赛后公告和题解,右侧便是开启 VP 的按钮。 + +![](static/RAZnb6ziGot3brxp4dac2Qp6ndg.png) + +VP模拟赛时的好处就是在虚拟参赛中获得真实比赛才能积累的经验,比如这里笔者发现通过前三题后,我应该先去看看 F 题,因为做出来的人更多,我有更大的可能性做出来,ACM 中题目并不是 100% 按难度排序。 + +![](static/JCocbTmlEorzjox0ORqcFCYznEd.png) + +进入 VP 后,我们可以发现比起正常赛后补题有了明显不同。 + +首先我们可以看见赛时某道题的通过人数,随比赛时间流逝 100% 仿真变化。而且也可以与当时的“虚拟选手”同步竞争,例如笔者这里就复制之前写过的代码荣登榜三(乐) + +对于大多数比赛,采用 ICPC 赛制,解决某题得到的分数由该题当前的分数减去(不成功的提交次数)*50,这里某道题的分数是由比赛开始时的分数随时间线性减少得到的。 + +也就是做题越快,错误次数越少,分数和排名就越高,这点大体是与 ACM 赛制相同的。 + +当然,CF 还有极具特色的 Hack 玩法,这些深入内容留给有上分兴趣的读者研究。 + +让我们点开 A 题,来看看如何提交答案 + +![](static/I3tAbSkNuoysdPxpFR6cCnFrnNd.png) + +可以看见,右侧有一个 submit,与 luogu 不同的是,你需要上传源代码文件(如 cpp)然后选择 G++17 为语言,提交。 + +当然,你也可以点开上侧的 submit code + +![](static/OGp0bXm4CoHo60xGC6NcNEzcnpt.png) + +选择题目、语言,填写代码后提交,就和 Luogu 的方式一样了。 + +同样,在上侧 MY SUBMISSIONS 处可以查看已提交的代码和状态 + +![](static/XswZbhqWZocIccxo4ibcfY9pnLe.png) + +## PROBLEMSET + +同样,CF 也有题库 + +![](static/Mdkzbdop2ooK2Lxoq2GcZ6eLn6r.png) + +如果你只想做某道题而不是某场比赛,这里也许更适合你。 + +不过 CF 的题库比较鸡肋,标签筛选也不是很方便(大概是把想要的标签在右上角分隔好) + +# 总结 + +笔者向读者详细介绍了两个 OJ,至于如何让 OJ 更好的辅助你的 ACM 学习,我应该在什么时间节点或训练阶段,出于什么训练目的选择哪个网站,笔者留到下一个篇章继续介绍。 diff --git a/3.编程思维体系构建/3.2算法杂谈.md b/3.编程思维体系构建/3.2算法杂谈.md new file mode 100644 index 0000000..1541a6e --- /dev/null +++ b/3.编程思维体系构建/3.2算法杂谈.md @@ -0,0 +1,37 @@ +# 3.2 算法杂谈 + +# 学计算机要先学算法吗? + +也许有的同学在高中阶段接触过信息学奥赛,那么也许你已经对基础的算法知识已经有了一定的了解。 + +那么对于没有接触过算法的同学来说呢?也许你们听过 Niklaus Emil Wirth 的一句话:“程序=算法 + 数据结构。”可能有的同学这个时候就要说了:“你看算法多重要,你没有算法就没有程序!”但这句话中的算法并不是你们所理解的狭义的“算法”。 + +广义上来说,“算法”可以被理解为是一组解决特定问题的有序步骤,它在描述一个“流程”,诸如做菜的流程、排课表都可以被认为是一种算法。利用计算机逻辑解决问题才是算法的核心。 + +学习算法的基础是拥有最基本的计算机素养,你需要优先学习一些基本的计算机概念、编程语言、简单的数据结构(数组、链表等),这些基本知识是你能够灵活利用算法的基础。 + +# 学了算法就相当于学好了计算机吗? + +学好了算法当然不等于学好了计算机科学。计算机科学是一个非常庞大的知识体系,包括更为底层的计算机组成原理、编译原理等,更为表层的 AI,开发等,是一门综合性学科。总的来说,算法是计算机科学中较为重要的一部分,但远远不是全部。 + +# 学算法就要用《算法导论》一类的书吗? + +我的答案是否定的。它更适合作为“工具书”(就像你英语的词典那样),而不是一本适合新生入门学习的书。可以使用《我的第一本算法书》一类的更为基础更为有趣的算法内容。相比于完全严谨的逻辑推导, 初学者的诉求是在"看得见, 摸得着的例子和环境下探索和吸收新概念". 像这样的大部头可以在之后进行阅读. + +# 学算法一定要用 C 语言吗?不用 C 语言可以吗? + +不一定要用 C 语言。但是 C 语言作为一种贴近底层面向过程语言,对日后学习其他的语言会有较大的帮助。你也可以先学习 Python、JAVA 等等。学校的课程仅仅是教授一些比较基础的知识,如果想要真正掌握一门语言,需要在学校课程的基础上更进一大大大步。 + +# ACM 怎么说? + +前情提要,请尽量不要以功利的心态去参加 ACM,你想要的与你能得到的可能存在过大落差 + +ACM 是美国计算机协会(Association for Computing Machinery)的缩写,原先是 ICPC(国际大学生程序设计竞赛)的赞助商(2018 年后不再赞助),在国内与 ICPC 齐名的比赛还有 CCPC(中国大学生程序设计竞赛)。 + +但常说的“ACM”一般泛指一切采用 ACM 赛制(给定一定数量的题目,采用罚时制,通过相同题目数量的选手依据罚时决定排名)的程序设计竞赛。 + +在我校,参加 ACM 社团(姑且叫做社团)并不代表能够参加有含金量的团体赛(ICPC、CCPC 等)。你需要先参加由我校教练刘春英老师组织的各种比赛,有资格进入集训队后,才有机会代表学校参加比赛(当然不限名额的个人赛想参加就参加)。 + +进入集训队后采取末位淘汰制度(最后留下来的人在 20 人左右),最后留下来的人才有机会参加比赛。因此个人并不推荐 0 基础的同学对于 ACM 过于执着,有 0 基础的同学最后进入校队的例子,不过这通常意味着你一天至少得刷一道算法题。如果还是想尝试的同学,可以去洛谷(www.luogu.com.cn)、Codeforces(www.codeforces.com)、Atcoder(atcoder.jp)等平台上注册账号,练习题目,参加这些网站定期组织的一些比赛。 + +如果经过一段时间的练习能够在 Codefoces([www.codeforces.com](http://www.codeforces.com))上达到 1400 以上的 Rating,那么可以再观望观望参与 ACM。 diff --git a/3.编程思维体系构建/3.3如何选择编程语言.md b/3.编程思维体系构建/3.3如何选择编程语言.md new file mode 100644 index 0000000..7cf136d --- /dev/null +++ b/3.编程思维体系构建/3.3如何选择编程语言.md @@ -0,0 +1,61 @@ +# 3.3 如何选择编程语言 + +# 编程语言的工具属性 + +在回答这个问题之前,需要各位同学明确的一点是,编程并不是一个独立的学科,像数学那样做题是学不好的。 + +编程语言的选择更像是锤子与扳手之间的选择,更大程度上看的是你需要解决什么样的问题。当你需要砸钉子的时候,使用螺丝刀总归是不顺手的,因此了解不同语言的特性,针对任务进行选择是非常有必要的。 + +# 编程语言特性 + +首先附上一张经典老图 + +![](static/boxcnW0YQY58RXhwdtRj5k6ndlc.jpeg) + +## C 语言/C++ + +C 语言/C 艹一脉同源,从图中来看,C 和 C 艹都像多功能瑞士军刀,说明其是用来做细活的工具,C 上面的优盘说明其可以进行硬件开发的相关工作。 + +不少同学对可能会感到困惑,为什么要从 C 语言学起(因为学校菜教不了别的) + +C 语言其实是一门优秀的承上启下的语言,既具有高级语言的特点,编写不依赖特定的计算机硬件应用程序,应用广泛,又具有底层汇编的特点,其指针可以让此语言操纵更为底层的内存空间。 + +但是其功能毕竟受限,有时候用起来会苦恼其操作受限以及各种奇奇怪怪的 bug 问题。 + +如果为了增强自身的编程能力和计算机素养,培养解决问题的能力,C 语言的你的不二选择。在这里强烈推荐 jyy 老师的各类课程。([http://jyywiki.cn/](http://jyywiki.cn/) + +我们的任务一部分会使用 C 语言,一方面培养大家编程能力,一方面辅助大家期末考试。 + +## C++ + +现代 C++ 程序可看成以下三部分组成。 + +- 更接近底层的语言,大多继承于 C +- 更高级的语言特征,可自定义数据类型 +- 标准库 + +C++ 既有 C 面向过程的特点,又拥有面向对象的特性,是一门系统级的语言。 + +编译器、操作系统的开发,高性能服务器的开发,游戏引擎的开发,硬件编程,深度学习框架的开发......只要是和底层系统或者是与性能相关的事情,通常都会有 C++ 的一席之地。 + +## Python + +Python 在图里是电锯,适合干比较“狂野”的任务,也是深度学习的主要编程语言。其在数据处理方面具有诸多出色成熟的库,编程语言也较为简单。 + +但是过度的包装可能造成出现意想不到的错误,出现的结果看不懂等等。 + +使用缩进控制语句是此语言的特点。 + +作为深度学习的主要使用语言,我们将以Python 为主。 + +## JAVA + +一门面向对象编程语言,不仅吸收了 C++ 语言的各种优点,还摒弃了 C++ 里难以理解的多继承,指针等概念,因此 java 语言具有功能强大和简单易用两个特征。 + +他太老了,虽然不少框架都依托于 Java,但是不得不说,一些地方略有落后。 + +频繁应用于Web 开发,安卓应用等等。 + +![](static/boxcnPv2FcyQxGLjYHThSaJNwRf.jpeg) + +当然还有各种形形色色的编程语言等着同学们去探索。 diff --git a/3.编程思维体系构建/3.4.1FAQ:常见问题.md b/3.编程思维体系构建/3.4.1FAQ:常见问题.md new file mode 100644 index 0000000..c822ee0 --- /dev/null +++ b/3.编程思维体系构建/3.4.1FAQ:常见问题.md @@ -0,0 +1,72 @@ +# FAQ:常见问题 + +# 我完全没基础觉得好难呜呜 + +教育除了知识的记忆之外, 更本质的是能力的训练, 即所谓的 training. 而但凡 training 就必须克服一定的难度, 否则你就是在做重复劳动, 能力也不会有改变. 如果遇到难度就选择退缩, 或者让别人来替你克服本该由你自己克服的难度, 等于是自动放弃了获得 training 的机会 + +# 我觉得无从下手 + +尝试借鉴他人的代码也未尝不可,但是要保证每一行都看懂哦 + +![](static/boxcnQ4rvJqVbXJaWMOwceHdrQb.png) + +# 我感觉讲义写的不够细 + +首先,我无法照顾到每一个人的情况,保证你每一个地方都看懂 + +其次,很多地方的坑是故意留给你让你尝试独立解决问题的。 + +# 我觉得我以后不会从事 C 相关的工作 + +这种"只要不影响我现在 survive, 就不要紧"的想法其实非常的利己和短视: 你在专业上的技不如人, 迟早有一天会找上来, 会影响到你个人职业生涯的长远的发展 + +更严重的是,他可能会透支学校的信誉。 + +同时,先学好 C 语言对你有以下帮助: + +1. 掌握计算机底层知识:C 语言是一种高效的系统级语言,它的语法和数据结构设计直接映射到底层计算机硬件,通过学习 C 语言可以更深入地了解计算机底层运作原理,为理解更高级的编程语言和开发工具奠定基础。 +2. 提高编程能力:C 语言的语法相对较为简单,但是它要求程序员手动管理内存,这需要编程者深入了解内存结构和指针的使用。通过学习 C 语言,可以锻炼编程能力,提高代码质量和效率。 +3. 能够理解其他语言:C 语言是很多编程语言的基础,如 C++、Java、Python 等语言都从 C 语言继承了很多特性。因此,学好 C 语言可以帮助你更好地理解其他编程语言的设计思路和工作原理。 +4. 开发底层软件:由于 C 语言具有高效、灵活、可移植等特点,因此它被广泛用于开发操作系统、嵌入式系统、网络协议、游戏引擎等底层软件。学习好 C 语言可以为你将来从事底层软件开发提供必要的基础知识。 + +# 我感觉我写了也不会学到啥 + +复杂的问题总是存在简单的解释,C 语言虽然不擅长带 GUI 界面的编写,但是我们每日在用的都和他息息相关,那些庞大的系统也无非就是由这些简单的东西搭建而成的 + +# 我觉得我没有学懂 C 语言就开始别的合适吗 + +学习本章内容更大程度上是为了让你搞清楚编程世界运行的基本原理 + +其实各类编程语言本质上没有什么不同(并且 C 语言是一切语言的爹),你熟悉了一门别的语言都会变得非常容易 + +想要彻彻底底的摸清楚这门语言并达到实操只能靠常年累月的积累,如果各位真的有打算,欢迎尝试本篇教程的爹 + +NJU-ICS-PA 南京大学计算机系统基础 + +但是建议大家大二再进行尝试,非常难 + +# 我总觉得文章没写清楚 + +你毕业后进入公司/课题组, 不会再有讲义具体地告诉你应该做什么, 总有一天你需要在脱离讲义的情况下完成任务. 我们希望你现在就放弃"讲义和框架代码会把我应该做的一切细节清楚地告诉我"的幻想, 为自己的成长负起责任: + +- 不知道在说什么, 说明你对知识点的理解还不够清楚, 这时候你应该去看书/看手册 +- 不知道要做什么/怎么做, 说明你的系统观好是零碎的, 理解不了系统中各个模块之间的联系, 这时候你应该 RTFSC, 尽自己最大努力梳理并理解系统中的一切细节 +- bug 调不出来, 说明你不清楚程序正确的预期行为, 你需要 RTFSC 理解程序应该如何运行; 此外也说明你不重视工具和方法的使用, 你需要花时间去体验和总结它们 + +如果你发现自己有以上情况, 你还是少抱怨, 多吃苦吧. + +当然,如果你发现有更好的想法欢迎联系我 + +# 这些对我太简单了 + +你可以从广度和深度两个角度对自己进行拔高 + +但是我的建议是涉猎更多的方向更多的领域帮助你建立系统的认知 + +有且仅有大学有这样好的资源帮助你了 + +# 坚持了好久还是搞不定, 我想放弃了 + +![](static/boxcnuNXrb5zOppCZAlGQ19wuDk.jpg) + +也许是你坚持的姿势不对,来和 ZZM 聊聊吧 diff --git a/3.编程思维体系构建/3.4.2用什么写 C 语言.md b/3.编程思维体系构建/3.4.2用什么写 C 语言.md new file mode 100644 index 0000000..5fea5f0 --- /dev/null +++ b/3.编程思维体系构建/3.4.2用什么写 C 语言.md @@ -0,0 +1,187 @@ +# 用什么写 C 语言 + +初学 C 语言,第一个问题莫过于用什么软件编写 C 语言程序。学校的老师可能会推荐包括但不限于 VC6.0,CodeBlocks,devC++,Visual Studio2013 等,如果你的电脑不是老年机,那么以上软件衷心建议你不要去使用,过于老旧了。 + +# Windows-Visual Studio + +[vs2022(Visual Studio 2022)指南&&技巧要领](https://www.bilibili.com/video/BV1Xt411g7jT?vd_source=699341ff80cb01917fb43665199a48dd) + +Visual Studio (以下简称 VS )是 Windows 下最完美的 C/C++ 等语言的开发平台,有“宇宙第一 IDE”之称,功能丰富,开箱即用。目前更新到 2022 版。 + +什么是 IDE,什么是代码编辑器,什么是编译器等等细碎问题参考文档 [3.1 该使用哪个编辑器???](https://gw9u39xwqi.feishu.cn/wiki/wikcnE1gVsZuCpkiscqwYxjWueh) 看不懂的话直接无脑装 + +## 下载 + +[https://visualstudio.microsoft.com/zh-hans/downloads/](https://visualstudio.microsoft.com/zh-hans/downloads/) + +选择社区版 + +![](static/boxcnhNeAnlrbcdJciMUY9oNTuc.png) + +社区版和专业版等的区别:社区版免费,功能上几乎无差别 + +## 安装 + +选择 C++ 桌面开发,其他不用选,有需要了再说。另外,Python 开发不好使,不要像我一样选 Python 开发。 + +![](static/boxcnkjmKcCxIgRIzA5kyUZckye.png) + +安装完成后,一般来说 VS 不会自动创建桌面快捷方式,你需要到开始菜单中启动 VS。 + +请勿使用鼠标右键中的“在 VS 中打开”来打开 VS + +首次打开应该会让你选择开发环境和主题,建议开发环境选择 C++ ,主题根据个人喜好选择。 + +## 创建项目 + +VS 是项目制,你需要创建一个项目才能开始编写代码并运行。 + +打开 VS,会打开如下界面(我使用深色主题),在此处单击“创建新项目” + +![](static/boxcn6MgNnY2qBd1yAudeirx6Sh.png) + +在创建新项目页面中选择项目模板为控制台应用(空项目亦可,后续手动添加.c 源文件),并单击下一步 + +![](static/boxcnFwZpWZ3fQkdd3mCO8Mr9Wj.png) + +为你的项目起一个名字,以及选择项目的位置,一般默认即可,如果你有强迫症,C 盘一定不能放个人数据,请自行修改。完成后单击“创建” + +![](static/boxcnkxd472wIT39DbEiBsyPWzf.png) + +自此就创建了一个项目了,你将会到达如下界面: + +![](static/boxcnvOGdjKLnvXvJM7nlE8yVcb.png) + +其中,左侧(如果在一开始没有选择 C++ 开发环境的话可能在右侧)为资源管理器,列出了本项目所用到的所有文件,包括代码(外部依赖项、源文件、头文件),以及将来开发图形化界面所需的资源文件;最中间占据面积最多的是代码编辑器窗口,你以后将会在这里编写你的 C 语言代码。最下面是输出窗口,源代码进行编译时,会在此处给出编译进度以及可能的代码中的错误。 + +请仔细阅读注释(绿色)中的各项内容,这很重要。 + +阅读完以后,就可以将代码全部删去,编写自己的代码了。 + +注意控制台项目初始源文件后缀为.cpp 为 C++ 文件,如果编写 C 语言建议将后缀改为.c。.cpp 存在隐患:如果不小心使用了 C++ 的语法而非 C 存在的语法,编译器并不会报错,且 C 与 C++ 在某些特性存在区别。 + +## “运行”你的 C 语言代码 + +C 语言是编译型语言,因此说“运行”代码其实并不是十分合适,不过我们初学,不用过分抠字眼,知道什么意思即可。 + +当你编写完自己的代码后,即可单击“本地 Windows 调试器”(或者使用快捷键 F5)进行“运行”。 + +![](static/boxcnhTxhUYMHeYHdrq0zWzLomb.png) + +你可能会发现在“本地 Windows 调试器”右侧还有一个绿色三角形,并且单击这个也可以“运行”,这两个的区别在于“本地 Windows 调试器”是调试运行,右侧那个是不调试直接运行。 + +## scanf 报错 + +如果你的代码被 VS 提示“This function or variable may be unsafe. Consider using scanf_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.” + +![](static/boxcnfrxYjk5CCjMfY0mLK1B1Ze.png) + +需要你在项目-xxx 属性(xxx 是你的项目名)-C/C++-代码生成-安全检查里将安全检查禁用 + +![](static/boxcniHhCIUQY0oB3ALlxqgciLd.png) + +## 调试 + +IDE 相比于代码编辑器,最强大的一点莫过于成熟的调试系统。通过调试,可以快速定位代码中没有被编译器检查出来的逻辑错误。如果需要调试,则可以在这个位置单击,打下断点,并且运行程序,程序运行时,就会在此处暂停下来,暂停时就可以查看各个变量的值了。 + +![](static/boxcnydHyaNPqUEVVWmbdGofX0d.png) + +## 深色主题 + +需要深色主题请在工具-主题里更改为深色 + +## Tips + +### 仔细查看报错 + +![](static/boxcnC6TAAdtS0P5HzebFgFn2lc.png) + +如果程序代码中出现红色波浪线,则表示该处代码有“错误”,并且该处的错误会同步显示在下面的这个位置,单击即可看到错误详情。如果代码中出现绿色波浪线,则表示该处代码中有警告。警告和错误的区别是警告可以通过编译运行,但编译器认为你这里可能写错了;错误是完全不可以通过编译。 + +![](static/boxcn7zL0QFakVTpYBdpOmmWOvc.png) + +### 善用提示 + +![](static/boxcn2ouk043lNQEUkVkIS7bSSd.png) + +当你打一些函数名或者关键字时,VS 会给出你语法提示,如果这个提示正确,按下 Tab 键即可将这个提示补全到你的代码里;或者你也可以跟着这个提示打一遍,防止打错关键字。 + +## VS 的缺点 + +过于庞大,很多功能对于初学者来说用不上,对电脑的性能也有略微的要求,但瑕不掩瑜,他的开箱即用的使用体验还是很不错的。 + +# Windows-Visual Studio Code + +Visual Studio Code(以下简称 vscode) 和 Visual Studio 都是微软开发的软件,区别在于 Visual Studio Code 是一个比较轻量的代码编辑器,在没有经过配置的情况下一般只能编写和查看代码,而不能运行,并且 Visual Studio Code 跨平台,在安装了丰富的插件后体验不输于一众 IDE。 + +## 安装 + +### 安装软件本体 + +[https://code.visualstudio.com/](https://code.visualstudio.com/) + +在该网站进行下载,并安装,安装完成并打开后可以根据右下角的提示来修改显示语言等 + +### 安装编译器 + +如果你电脑上下载有 VS,那么安装编译器这一环节可以省略。如果电脑上没有 VS,则需要安装 VS,或者下载其他 C 语言编译器,如 gcc,clang,icc 等 + +## 创建“项目” + +vscode 的项目和 VS 不同,vscode 的项目比较松散,并没有 VS 那样是一套非常完善的项目系统。 + +首先需要一个空文件夹,并在 vscode 里打开这个文件夹。然后点击文件-新建文本文件,并选择语言为 C 语言。此时如果你是第一次创建 C 语言文件,那么右下角会弹出提示,提示你安装 C/C++ 插件,安装即可。 + +## 编写代码并运行 + +编写完代码后,保存文件,并点击运行-启动调试 + +![](static/boxcnim98FJybpkGl8sfqxP9v9b.png) + +此时会弹出如下选择框,我的电脑上同时安装有 VS 和 gcc 编译器,因此有两个,大部分的电脑上应该只有一个“C++ (Windows)”,选择你电脑上的编译器并运行即可。 + +至此就已经完成了编程和调试的基本功能。如果你想要更丰富的功能,比如多文件编译等等,可以自行去网上搜索相关的配置教程。vscode 配置好了是非常好用的,但缺点就在于配置比较麻烦。 + +# Windows-CLion + +CLion 是 jetbrains 家族的 C 语言 IDE + +[https://www.jetbrains.com/clion/](https://www.jetbrains.com/clion/) + +收费软件,但可以从 GitHub 学生包里白嫖,喜欢折腾或者喜欢 jetbrains 家族软件风格的可以自己去折腾折腾。 + +# Mac OS-Visual Studio Code + +用法和 Windows 的差不多,但由于 Mac OS 自带 clang 编译器,所以无需额外安装编译器。 + +# Mac OS-CLion + +同样和 Windows 的差不多。 + +# Mac OS-Xcode + +XCode 是 mac 官方的 IDE,能编写所有 mac 家族设备的软件。但缺点是没有中文。 + +![](static/boxcn05Ca6Wu5TxFMplZCw2N8Jb.png) + +打开以后选择 Create a new Xcode project,选择 macOS-Command Line Tool + +![](static/boxcnbnrVCmNGfriHhU5pL76gsd.png) + +![](static/boxcnnjaObP5JzpICUx1PMO9MQg.png) + +两个空里第一个填项目名,第二个随便填就行 + +next 后选择项目保存的位置,之后即可到达以下界面: + +![](static/boxcnl06p0ZS8SSQsWJNLQLYIjc.png) + +点左上方小三角即可运行 + +在行号上点击并运行即可调试 + +![](static/boxcnmRygjmZfwFzODP2N6bVoEh.png) + +# Linux + +## 你都用 Linux 了你还来问我?一边玩去! diff --git a/3.编程思维体系构建/3.4.3解决编程问题的普适性过程.md b/3.编程思维体系构建/3.4.3解决编程问题的普适性过程.md new file mode 100644 index 0000000..fc0f4b9 --- /dev/null +++ b/3.编程思维体系构建/3.4.3解决编程问题的普适性过程.md @@ -0,0 +1,69 @@ +# 解决编程问题的普适性过程 + +- 本篇不需要任何前置知识,推荐在学习 C 语言和学完 C 语言后各看一遍。 +- 我们鼓励你在解决问题的时候进行思考,锻炼解决问题的能力,而不只是成为一个做代码翻译工作的“码农”。 + +![](static/YAOvb6gquofiAYxsn3tcxcCYngf.png) + +解决编程问题的常见误区: + +从编写代码入手,抓来就写(没有任何计划),对于简单编程问题,也许它是有效的,但往往它不可避免的不起作用。然后花费无数个小时试图修复代码(依旧没有任何计划),由于没有明确的计划来做什么,当“修复”代码时,往往导致它变得更复杂、更混乱。最终这个程序有点奏效,你对此心满意足。 + +相反,你应该以一种严谨的方式设计一种算法。下图显示了如何设计算法。然而,请注意,“编写代码”只有在你有了一个经过手动测试的算法之后,才能在你建立计划之前给你一些信心,证明你的计划是可靠的。 + +如果你计划得足够好并且代码编写得正确,你的代码将在第一次工作。即便它第一次不起作用,那么你至少有一个对于代码如何调试的可靠计划。 + +![](static/HMipbO4vSoM3jhxSZ7Kcuddqnxh.png) + +## Work an Example Yourself + +尝试设计算法的第一步是自己(手动)处理至少一个问题实例,为每个参数选择特定值。往往需要确定一个正确的示例,以及错误的示例。 + +如果你在这一步陷入困境,这通常意味着两件事中的一件。第一种情况是问题不明确,不清楚你应该做什么。在这种情况下,你必须在继续之前解决问题。如果你正在解决自己创造的问题,你可能需要更仔细地思考正确的答案应该是什么,并完善你对问题的定义。 + +第二种情况是,缺乏领域知识,即问题所涉及的特定领域或学科的知识。也许你应该适当补充学习对应的知识。注意,领域知识可能来自数学以外的领域。它可以来自任何领域,因为编程对于处理任何类型的信息都很有用。 + +## Write Down What You Just Did + +这一步中,必须思考解决问题所做的工作,并写下解决该特定实例的步骤。思考这一步骤的另一种方式是,写下一组清晰的指示,其他人可以按照这些指示来重现刚刚解决的特定问题实例的答案。如果在步骤 1 中执行了多个实例,那么也将重复步骤 2 多次,对步骤 1 中的每个实例重复一次。如果一条指令有点复杂,那没关系,只要指令稍后有明确的含义,我们将把这些复杂的步骤转化为它们自己的编程问题,这些问题将单独解决。 + +## Generalize Your Steps + +将步骤 2 得到的具体步骤,抽象为一般性的结论。有时可能很难概括步骤。发生这种情况时,返回步骤 1 和 2 可能会有所帮助。做更多的问题实例将提供更多的信息供参考,更能帮助深入算法。这个过程通常被称为编写“伪代码”,以编程方式设计算法,而不使用特定的目标语言。几乎所有的程序员在编写任何实际代码之前都会使用这种方法来确保他们的算法是正确的。 + +## Test Your Algorithm + +在步骤 3 之后,我们有了一个我们认为正确的算法。然而,我们完全有可能在这一路上搞砸了。步骤 4 的主要目的是确保我们的步骤在继续之前是正确的。为了实现这一点,我们使用不同于设计算法时使用的参数值来测试我们的算法。我们手动执行算法,并将其获得的答案与正确的答案进行比较。如果它们不同,那么我们知道我们的算法是错误的。我们使用的测试用例(参数值)越多,我们就越能确信我们的算法是正确的。不幸的是,通过测试无法确保我们的算法是正确的。要完全确定你的算法是正确的,唯一的方法就是正式证明它的正确性(使用数学证明),这超出了这个专门化的范围。 + +确定好的测试用例是一项重要的技能,可以随着实践而提高。对于步骤 4 中的测试,您需要使用至少产生几个不同答案的情况进行测试(例如,如果您的算法有“是”或“否”答案,则应使用同时产生“是”和“否”的参数进行测试)。您还应该测试任何角落情况,其中行为可能与更一般的情况不同。每当您有条件决定(包括计算位置的限制)时,您应该在这些条件的边界附近测试潜在的角点情况。 + +## Translation to Code + +既然你对你的算法很有信心,那么是时候把它翻译成代码了。大多数时候,你会想将代码输入编辑器,以便编译和运行程序。 + +## Test Program + +- [黑盒测试](https://zh.wikipedia.org/wiki/%E9%BB%91%E7%9B%92%E6%B5%8B%E8%AF%95) + +在黑盒测试中,测试人员只考虑功能的预期行为,而不考虑设计测试用例的任何实现细节。缺乏对实现细节的访问是这种测试方法的由来——函数的实现被视为测试人员无法查看的“黑盒子”。 + +事实上我们无需执行步骤 1-5 就能够为假设问题设想好的测试用例。实际上,在开始解决问题之前,您可以针对问题提出一组黑盒测试。一些程序员提倡测试优先的开发方法。一个优点是,如果您在开始之前编写了一个全面的测试套件,那么在实现代码之后就不太可能在测试上有所疏漏。另一个优点是,通过提前考虑你的情况,你在开发和实现算法时能够降低错误率。 + +- 选择测试用例的一些建议 + +1. 确保测试涵盖所有错误情况。 +2. 确保测试“太多”和“太少”。例如,如果程序需要一行正好有 10 个字符的输入,则用 9 个和 11 个进行测试。 +3. 任何给定的测试用例只能测试一个“错误”条件。这意味着,如果要测试两个不同的错误条件,则需要两个不同测试用例。 +4. 准确地在有效性边界处进行测试。 + +- [白盒测试](https://zh.wikipedia.org/wiki/%E7%99%BD%E7%9B%92%E6%B5%8B%E8%AF%95) + +与黑盒测试不同,白盒测试涉及检查代码以设计测试用例。白盒测试中的一个考虑因素是测试覆盖率——描述测试用例如何覆盖代码的不同行为。 + +请注意,虽然白盒和黑盒测试不同,但它们不是互斥的,而是互补的。可以从形成一组黑盒测试用例开始,实现算法,然后创建更多测试用例以达到所需的测试覆盖率。 + +## Debug Program + +一旦在代码中发现了问题,就需要修复它,这个过程称为调试。许多新手程序员(甚至一些经验丰富的程序员)以临时方式调试,试图更改代码中的某些内容,并希望它能解决他们的问题。这样的方法很少有效,常常会导致很多挫折。 + +[调试理论](https://gw9u39xwqi.feishu.cn/wiki/wikcnCw26MLTUPdgOAzerychEVe) diff --git a/3.编程思维体系构建/3.4.4C语言前置概念学习.md b/3.编程思维体系构建/3.4.4C语言前置概念学习.md new file mode 100644 index 0000000..3c3f2b9 --- /dev/null +++ b/3.编程思维体系构建/3.4.4C语言前置概念学习.md @@ -0,0 +1,39 @@ +# C 语言前置概念学习 + +如何学习 C 语言?第一步:Throw away the textbook。也许你可以通过以下途径: + +以下方式难度由易到难,但并不意味着收获由小到大: + +1.Video:[B 站翁恺的 C 语言课程](https://www.bilibili.com/video/BV19W411B7w1?spm_id_from=333.337.search-card.all.click&vd_source=da5a5affb1b0c71c0d7e410b1d1a3c17)(非常基础,缺点是只看视频学的过浅) + +2.MOOC:[翁凯 C 课程的 MOOC 慕课](https://www.icourse163.org/course/ZJU-9001)(同上,慕课的习题和 Projects 性价比不高,几乎没有差别) + +3.Web:[菜鸟教程](https://www.runoob.com/cprogramming/c-tutorial.html)(基础但是覆盖面较广,不够深入) + +4.Web:[CNote](https://github.com/coderit666/CNote)(例子密集,学习曲线平滑,覆盖面广且具有深度) + +5.Book:教材替换用书——《C Primer Plus》!(基础且深入的恰到好处,有一定拓展,可能后面的章节有点难懂,是一本不可多得的好书,不要忽视课本习题及 Projects) + +6.MOOC:[Introductory C Programming 专项课程](https://www.coursera.org/specializations/c-programming)(全英文,好处是涉及到计算机思维,包含许多常用 tools 的教学例如 git、make、emacs、gdb,视频讲解结合文档阅读,对于 C 的重要核心知识讲解透彻,难度颇高,建议用作提升) + +7.Web:[LinuxC 一站式编程](https://akaedu.github.io/book/)(难度大,枯燥硬核,收获多,基于 linux) + +### 学习建议:可以选择其一或多种学习。 + +- 对于缺乏计算机基础(这里的基础指的是计算机的日常使用)的同学,(1、2)是不错的选择,但在学完后要选择 4、5、6 进行补充巩固提高。 +- 对于有一定计算机基础的同学,直接上手 4、5、6 都是很不错的选择。 +- 对于有一定 linux 基础以及计算机基础的同学,或是同时想同步学习 linux 的同学,7 是可选择的。 + +关于“6.Introductory C Programming 专项课程”的一些思考 + +该课程的免费修读需要在 coursera 上申请助学金(并不困难)[申请教程](https://zhuanlan.zhihu.com/p/394479617)。 + +对于以上几种方式,6 有其特殊性,6 不仅仅是对于 C 语言的学习,其其中包含的计算机思维,Tools 的使用等等都有其普遍适用性。对比其他方式(包含学校课程)不难发现,对于计算机思维的训练与提升,在一定程度上,我们存在着缺失,但它又是极其重要的,如果你希望在学习 C 的同时又有其他多方面的提升,强烈建议你试试该课程(不要被英语劝退![2.6 以理工科的方式阅读英语](https://gw9u39xwqi.feishu.cn/wiki/wikcnlNoFdSGx5FYGa0x2FtCX6g) )。 + +当然你也可以通过其他方式培养计算机思维以及学习 Tools 的使用。但是越早培养,越有优势。 + +计算机思维与计算机科学与编码能力 + +![](static/Hqzbbs6iYobnxWxz11Ocfa9gnHd.png) + +### CS education is more than just “learning how to code”! diff --git a/3.编程思维体系构建/3.4.5.1C语言自测标准——链表.md b/3.编程思维体系构建/3.4.5.1C语言自测标准——链表.md new file mode 100644 index 0000000..03be730 --- /dev/null +++ b/3.编程思维体系构建/3.4.5.1C语言自测标准——链表.md @@ -0,0 +1,348 @@ +# C 语言自测标准——链表 + +## 链表(单链表)是什么 + +链表又称单链表、链式存储结构,用于存储逻辑关系为“一对一”的数据。 + +使用链表存储数据,不强制要求数据在内存中集中存储,各个元素可以分散存储在内存中。例如,使用链表存储 {1,2,3},各个元素在内存中的存储状态可能是: + +![](static/boxcnuwZzqX4dF8xKTYajwrDSxf.png) + +可以看到,数据不仅没有集中存放,在内存中的存储次序也是混乱的。那么,链表是如何存储数据间逻辑关系的呢? + +链表存储数据间逻辑关系的实现方案是:为每一个元素配置一个指针,每个元素的指针都指向自己的直接后继元素,如下图所示: + +![](static/boxcnAnkVAJmMT0NSNvo6crXYAd.png) + +显然,我们只需要记住元素 1 的存储位置,通过它的指针就可以找到元素 2,通过元素 2 的指针就可以找到元素 3,以此类推,各个元素的先后次序一目了然。像图 2 这样,数据元素随机存储在内存中,通过指针维系数据之间“一对一”的逻辑关系,这样的存储结构就是链表。 + +### 结点(节点) + +在链表中,每个数据元素都配有一个指针,这意味着,链表上的每个“元素”都长下图这个样子: + +![](static/boxcncRc5OKZROtxC9rpQYxrjvf.png) + +数据域用来存储元素的值,指针域用来存放指针。数据结构中,通常将这样的整体称为结点。 + +也就是说,链表中实际存放的是一个一个的结点,数据元素存放在各个结点的数据域中。举个简单的例子,图 3 中 {1,2,3} 的存储状态用链表表示,如下图所示: + +![](static/boxcn0VMYQlez7tQTNkTPDkCsvg.png) + +在 C 语言中,可以用结构体表示链表中的结点,例如: + +```c +typedef struct Node{ + int elem; //代表数据域 + struct Node * next; //代表指针域,指向直接后继元素 +}Node; +typedef struct Node* Link; +``` + +### 头结点、头指针和首元结点 + +图 4 所示的链表并不完整,一个完整的链表应该由以下几部分构成: + +头指针:是指向链表中一个结点所在存储位置的指针。如果链表中有头结点,则头指针指向头结点;若链表中没有头结点,则头指针指向链表中第一个数据结点(也叫首元结点)。 + +链表有头指针,当我们需要使用链表中的数据时,我们可以使用遍历查找等方法,从头指针指向的结点开始,依次搜索,直到找到需要的数据;反之,若没有头指针,则链表中的数据根本无法使用,也就失去了存储数据的意义。 + +结点:链表中的节点又细分为头结点、首元结点和其它结点: + +头结点:位于链表的表头,即链表中第一个结点,其一般不存储任何数据,特殊情况可存储表示链表信息(表的长度等)的数据。 + +头结点的存在,其本身没有任何作用,就是一个空结点,但是在对链表的某些操作中,链表有无头结点,可以直接影响编程实现的难易程度。 + +例如,若链表无头结点,则对于在链表中第一个数据结点之前插入一个新结点,或者对链表中第一个数据结点做删除操作,都必须要当做特殊情况,进行特殊考虑;而若链表中设有头结点,以上两种特殊情况都可被视为普通情况,不需要特殊考虑,降低了问题实现的难度。 + +链表有头结点,也不一定都是有利的。例如解决约瑟夫环问题,若链表有头结点,在一定程度上会阻碍算法的实现。 + +所以,对于一个链表来说,设置头指针是必要且必须的,但有没有头结点,则需要根据实际问题特殊分析。 + +首元结点:指的是链表开头第一个存有数据的结点。 + +其他节点:链表中其他的节点。 + +也就是说,一个完整的链表是由头指针和诸多个结点构成的。每个链表都必须有头指针,但头结点不是必须的。 + +例如,创建一个包含头结点的链表存储 {1,2,3},如下图所示: + +![](static/boxcnjAoO54txAhnu7Ry8ExjGvc.png) + +## 链表的创建 + +创建一个链表,实现步骤如下: + +1. 定义一个头指针; +2. 创建一个头结点或者首元结点,让头指针指向它; +3. 每创建一个结点,都令其直接前驱结点的指针指向它(尾插法/头插法)。 + +### 创建头指针和头结点(首元结点) + +```c +typedef struct Node{ + int elem; //代表数据域 + struct Node * next; //代表指针域,指向直接后继元素 +}Node; +typedef struct Node* Link; +----------------------------------------------------------------------- +Link* head = (Link*)malloc(sizeof(Link)); //创建头指针 +*head = (Link)malloc(sizeof(Node));//创建头结点(首元结点) +(*head)->elem = element;//头结点可以不存储数据或存储特殊数据 +(*head)->next = NULL;//初始头结点/首元结点的后继元素为空 +``` + +### 创建结点——头插法 + +```c +Link p; +while (Judgement) //for同理 +{ + p = (Link)malloc(sizeof(Node)); + p->elem = element; + p->next = (*head)->next; + (*head)->next = p; +} +``` + +![](static/boxcn8ZxT5oMkScArZjZhgM6TYb.png) + +### 创建结点——尾插法 + +```c +Link p; +Link r = (*head); //临时中间结构指针,在尾插法中始终指向最后一个结点 +while (Judgement) //for同理 +{ + p = (Link)malloc(sizeof(Node)); + p->elem = element; + p->next = NULL; + r->next = p; + r = p; +} +``` + +![](static/boxcnnMjc9pwgZgk1GBmBRlBS6d.png) + +## 链表的基本操作 + +学会创建链表之后,本节继续讲解链表的一些基本操作,包括向链表中添加数据、删除链表中的数据、读取、查找和更改链表中的数据。 + +### 链表读取元素 + +获得链表第 i 个数据的算法思路: + +1. 声明一个结点 p 指向链表的第一个结点,初始化 j 从 1 开始; +2. 当 jnext; //p指向第一个结点 + int j = 1; + while (p && j < i) //p不为空或者计数器j还没有等于i时,循环继续 + { + p = p->next; //p指向下一个结点 + j++; + } + if (!p) //第i个元素不存在 + return error; + *e = p->elem; //取第i个元素的数据 + return ok; +} +``` + +了解了链表如何读取元素,同理我们可以实现更新和查找链表元素。 + +### 链表插入元素 + +向链表中增添元素,根据添加位置不同,可分为以下 3 种情况: + +- 插入到链表的头部,作为首元节点; +- 插入到链表中间的某个位置; +- 插入到链表的最末端,作为链表中最后一个结点; + +对于有头结点的链表,3 种插入元素的实现思想是相同的,具体步骤是: + +1. 将新结点的 next 指针指向插入位置后的结点; +2. 将插入位置前结点的 next 指针指向插入结点; + +例如,在链表 `{1,2,3,4}` 的基础上分别实现在头部、中间、尾部插入新元素 5,其实现过程如图所示: + +![](static/boxcnxjex5Q3Lt9AAx6roN3ClUg.png) + +从图中可以看出,虽然新元素的插入位置不同,但实现插入操作的方法是一致的,都是先执行步骤 1 ,再执行步骤 2。实现代码如下: + +```c +/*在L中第i个位置(注意链表中的位置不一定为结点的个数)之前插入新的数据元素e, +L的长度加一(可以用头结点存储链表长度)*/ +int ListInsert(Link *L, int i, int e) +{ + Link p, r; //r为临时中间结构指针,用于实现插入 + p = *L; //p指向头结点 + int j = 1; + while (p && j < i) //寻找第i个结点, + { + p = p->next; + j++; + } + if (!p) + return error; + r = (Link)malloc(sizeof(Node)); + r->elem = e; + r->next = p->next; + p->next = r; + return ok; +} +``` + +注意:链表插入元素的操作必须是先步骤 1,再步骤 2;反之,若先执行步骤 2,除非再添加一个指针,作为插入位置后续链表的头指针,否则会导致插入位置后的这部分链表丢失,无法再实现步骤 1。 + +对于没有头结点的链表,在头部插入结点比较特殊,需要单独实现。 + +![](static/boxcn1hlL1Fk4kDK4CPT2hJxwnV.png) + +和 2)、3) 种情况相比,由于链表没有头结点,在头部插入新结点,此结点之前没有任何结点,实现的步骤如下: + +1. 将新结点的指针指向首元结点; +2. 将头指针指向新结点。 + +实现代码如下: + +```c +/*在L中第i个位置(注意链表中的位置不一定为结点的个数)之前插入新的数据元素e, +L的长度加一(可以用头结点存储链表长度)*/ +int ListInsert(Link *L, int i, int e) +{ + if (i == 1) + { + Link r = (Link)malloc(sizeof(Node)); + r->elem = e; + r->next = (*L)->next; + *L = r; + } + else + { + //...... + } +} +``` + +### 链表删除元素 + +从链表中删除指定数据元素时,实则就是将存有该数据元素的节点从链表中摘除。 + +对于有头结点的链表来说,无论删除头部(首元结点)、中部、尾部的结点,实现方式都一样,执行以下三步操作: + +1. 找到目标元素所在结点的直接前驱结点; +2. 将目标结点从链表中摘下来; +3. 手动释放结点占用的内存空间; + +从链表上摘除目标节点,只需找到该节点的直接前驱节点 temp,执行如下操作: + +```c +temp->next=temp->next->next; +``` + +例如,从存有 `{1,2,3,4}` 的链表中删除存储元素 3 的结点,则此代码的执行效果如图 3 所示: + +![](static/boxcnn3QHja0tzEwqJl9Mk4KnCg.png) + +实现代码如下: + +```c +/*删除L中的第i个数据元素,并用e返回其值,L的长度减一 +(可以用头结点存储链表长度)*/ +int ListDelete(Link *L, int i, int* e) +{ + Link p, r; + p = *L; + int j = 1; + while (p->next && j < i) //寻找删除元素中的前驱元素 + { + p = p->next; + j++; + } + if (!(p->next)) + return error; //L中不存在第i个元素 + r = p->next; //标记要删除的结点 + p->next = r->next; //移除结点 + *e = r->elem; //返回结点所存数据 + free(r); //释放结点 + return ok; +} +``` + +对于不带头结点的链表,需要单独考虑删除首元结点的情况,删除其它结点的方式和图 3 完全相同,如下图所示: + +![](static/boxcnXjwE0yDFvpQxLaPw7FifxV.png) + +实现代码如下: + +```c +/*删除L中的第i个数据元素,并用e返回其值,L的长度减一 +(可以用头结点存储链表长度)*/ +int ListDelete(Link *L, int i, int* e) +{ + if (i == 1) + { + Link r = *L; + *L= r->next; + *e = r->elem; + free(r); + } + else + { + //...... + } +} +``` + +### 链表查找元素 + +在链表中查找指定数据元素,最常用的方法是:从首元结点开始依次遍历所有节点,直至找到存储目标元素的结点。如果遍历至最后一个结点仍未找到,表明链表中没有存储该元素。 + +### 链表更新元素 + +更新链表中的元素,只需通过遍历找到存储此元素的节点,对节点中的数据域做更改操作即可。 + +## 约瑟夫环 + +约瑟夫环问题,是一个经典的循环链表问题,题意是:已知 n 个人(分别用编号 1,2,3,…,n 表示)围坐在一张圆桌周围,从编号为 k 的人开始顺时针报数,数到 m 的那个人出列;他的下一个人又从 1 开始,还是顺时针开始报数,数到 m 的那个人又出列;依次重复下去,直到圆桌上剩余一个人。 + +如图所示,假设此时圆周周围有 5 个人,要求从编号为 3 的人开始顺时针数数,数到 2 的那个人出列: + +![](static/boxcngx7ZPA7pONbJo82LbNCO1g.png) + +出列顺序依次为: + +- 编号为 3 的人开始数 1,然后 4 数 2,所以 4 先出列; +- 4 出列后,从 5 开始数 1,1 数 2,所以 1 出列; +- 1 出列后,从 2 开始数 1,3 数 2,所以 3 出列; +- 3 出列后,从 5 开始数 1,2 数 2,所以 2 出列; +- 最后只剩下 5 自己,所以 5 胜出。 + +那么,究竟要如何用链表实现约瑟夫环呢?如何让一个含 5 个元素的约瑟夫环,能从第 5 个元素出发,访问到第 2 个元素呢?上面所讲的链表操作显然是难以做到的,解决这个问题就需要用到循环链表。 + +## 循环链表 + +将单链表中终端结点的指针端由空指针改为指向头结点,使得整个单链表形成一个环,这种头尾相接的单链表成为单循环链表,简称循环链表。 + +循环链表解决了一个很麻烦的问题。如何从当中一个结点出发,访问到链表的全部结点。 + +为了使空链表和非空链表处理一致,我们通常设一个头结点,当然,并不是说,循环链表一定要头结点,这需要注意。循环链表带有头结点的空链表如图所示: + +![](static/boxcn3l30usevMTgv1ZbZ0mfJdh.png) + +对于非空的循环链表如图所示: + +![](static/boxcngoLTiM9wto9uCGzH7nkjkW.png) + +循环链表和单链表的主要差异就在于循环的判断条件上,原来是判断 p->next 是否为空,现在则是 p->next 不等于头结点,则循环未结束。 diff --git a/3.编程思维体系构建/3.4.5阶段一:编程属性.md b/3.编程思维体系构建/3.4.5阶段一:编程属性.md new file mode 100644 index 0000000..d2e68cb --- /dev/null +++ b/3.编程思维体系构建/3.4.5阶段一:编程属性.md @@ -0,0 +1,43 @@ +# 阶段一:编程属性 + +# [C 语言任务模块](https://github.com/E1PsyCongroo/HDU_C_Assignments/) + +作为一名合格的大学生,更应深谙“纸上得来终觉浅,绝知此事要躬行”的道理,编程语言就像是一个工具,无论你如何熟读说明书(语法、特性),未经实践终究是靠不住的。 + +- 该任务模块旨在帮助巩固 C 语言基础知识,传递一些编程思维,入门学习请看 [C 语言前置概念学习](https://gw9u39xwqi.feishu.cn/wiki/wikcnfjHGESZPgqkGsJqRoGSzPe) +- 你可以通过使用 git `git clone ``https://github.com/E1PsyCongroo/HDU_C_Assignments.git` 获取任务 +- 或者通过下载 zip 获取任务 + +# 任务一做前必查 + +1. 理解[解决编程问题的普适性过程](https://gw9u39xwqi.feishu.cn/wiki/wikcnEJCIih3HHrCtA2z2TIBH0f) 。 +2. 理解 C 语言语法基础:变量、表达式、函数、判断、循环、常用标准库函数。 +3. 理解 C 语言中的一切都是数字。 +4. 初步理解 C 语言各类数据类型:基本数据类型和复杂自定义数据类型。 +5. 初步理解 C 语言数组及字符串。 + +# 任务二做前必查 + +1. 深入理解 C 语言指针、数组和字符串。 +2. 理解递归思想。 +3. 理解复杂自定义数据类型。 + +### 请阅读各个任务的 README.md,了解完成任务所需的前置知识。 + +进阶:评价一个程序,大体分为以下四个层次。 + +1.程序没有语法错误。 + +2.程序对于合法的输入数据能够产生满足要求的输入结果。 + +3.程序对于非法的输入数据能够得出满足规格说明的结果。 + +4.程序对于精心选择的,甚至刁难的测试数据都有满足要求的输入结果。 + +在你写完这些代码后会不会感觉你的代码不够优雅呢? + +假设你的逻辑更为复杂,需要完成的功能更多,如果全部写在 main 里面你会不会觉得越来越困难呢? + +有没有一种方法可以让你更为优雅的把每一个功能拆分开呢? + +当然有,在下一章,你会深刻的体会到函数的意义 diff --git a/3.编程思维体系构建/3.4.6.1.开始冒险.md b/3.编程思维体系构建/3.4.6.1.开始冒险.md new file mode 100644 index 0000000..bf4ae6e --- /dev/null +++ b/3.编程思维体系构建/3.4.6.1.开始冒险.md @@ -0,0 +1,30 @@ +# 1.开始冒险 + +让我们从一个最基本的函数开始 + +```c +#include + +int main() +{ + printf("Welcome to Little Cave Adventure.\n"); + printf("It is very dark in here.\n"); + printf("\nBye!\n"); + return 0; +} +``` + +输出样例: + +Welcome to Little Cave Adventure. +It is very dark in here. + +Bye! + +尽管可能微不足道,但该程序确实展示 任何文本冒险中最重要的方面:描述性文本。一个好的故事是制作一款好的冒险游戏的要素之一。 + +# 为什么要用英文? + +因为中文的编码模式可能会带来奇怪的影响。 + +思考题:大家可自行去了解 utf-8,GDB 等编码模式及其历程 diff --git a/3.编程思维体系构建/3.4.6.10.增添属性.md b/3.编程思维体系构建/3.4.6.10.增添属性.md new file mode 100644 index 0000000..d830f1f --- /dev/null +++ b/3.编程思维体系构建/3.4.6.10.增添属性.md @@ -0,0 +1,506 @@ +# 10.增添属性 + +我们在第 4 章中介绍 object 时,它们只有三个属性。 + +在第 6 章中,我们增加了第四个。这他妈太少了! + +为了在我们的冒险中加入更多细节,我们需要更多的属性。下面是一些示例。 + +1. 环顾四周这个命令可以给出玩家位置的全局描述,包括在那里的物品、NPC 和其他对象的列表。很多冒险活动都需要玩家对这些对象进行查询,从而揭示出在游戏中获得进展所需要的某些线索,或者简单地提升游戏的氛围。我们将添加一个对每个对象都有详细说明的属性细节,加上一个与包含其他对象的对象一起使用的属性内容。 +2. 如果玩家开始一段话时,响应总是“OK”和新位置的描述。这似乎有点沉闷。所以,为每个段落提供自己的自定义消息会更好。我们将添加一个属性 textGo 来保存此消息。 +3. 有些通道可能是特殊设定的,比如说传送门,陷阱等等。 + +举个例子,一条林道可能隐藏着陷阱。虽然通道似乎从位置 A 通向位置 B,但实际上终点是位置 C,即掉进坑了。 + +假设我们的洞口被警卫挡住了。玩家就过不去,我们可以简单地将通道的目的地更改为终点位置(或 NULL),但这会导致对诸如 go cave 和 look cave 这样的命令做出不正确的回应:“你在这里看不到任何洞穴。我们需要一个将通道的实际终点和虚假终点分开的单独属性。为此,我们将引入一个属性 prospect 来表示后者。 + +1. 在许多冒险中,玩家以及游戏中的 NPC 在携带量方面受到限制。给每件物品一个重量,角色库存中所有物品的总重量不应超过该角色所能承载的最大重量。当然,我们也可以给一个物体一个非常高的重量,使它不可移动(一棵树,一座房子,一座山)。 +2. RPG 式的冒险游戏需要角色的整个属性范围( 玩家与非玩家 ),例如 HP。HP 为零的对象要么死了,要么根本不是角色。 + +我们在 object.txt 中定义了七个新属性: + +``` +#include +#include "object.h" + +typedef struct object { + const char *description; + const char **tags; + struct object *location; + struct object *destination; + struct object *prospect; + const char *details; + const char *contents; + const char *textGo; + int weight; + int capacity; + int health; +} OBJECT; + +extern OBJECT objs[]; + +- field + description "an open field" + tags "field" + details "The field is a nice and quiet place under a clear blue sky." + capacity 9999 + +- cave + description "a little cave" + tags "cave" + details "The cave is just a cold, damp, rocky chamber." + capacity 9999 + +- silver + description "a silver coin" + tags "silver", "coin", "silver coin" + location field + details "The coin has an eagle on the obverse." + weight 1 + //如果你觉得不合理,可以自己更改 + +- gold + description "a gold coin" + tags "gold", "coin", "gold coin" + location cave + details "The shiny coin seems to be a rare and priceless artefact." + weight 1 + +- guard + description "a burly guard" + tags "guard", "burly guard" + location field + details "The guard is a really big fellow." + contents "He has" + health 100 + capacity 20 + +- player + description "yourself" + tags "yourself" + location field + details "You would need a mirror to look at yourself." + contents "You have" + health 100 + capacity 20 + +- intoCave + description "a cave entrance to the east" + tags "east", "entrance" + location field + prospect cave + details "The entrance is just a narrow opening in a small outcrop." + textGo "The guard stops you from walking into the cave." + +- exitCave + description "an exit to the west" + tags "west", "exit" + location cave + destination field + details "Sunlight pours in through an opening in the cave's wall." + textGo "You walk out of the cave." + +- wallField + description "dense forest all around" + tags "west", "north", "south", "forest" + location field + details "The field is surrounded by trees and undergrowth." + textGo "Dense forest is blocking the way." + +- wallCave + description "solid rock all around" + tags "east", "north", "south", "rock" + location cave + details "Carved in stone is a secret password 'abccb'." + textGo "Solid rock is blocking the way." +``` + +注意:textGo 不仅对通道对象有用,而且对非通道对象也有用( 在这种情况下,以后我们将介绍“墙”这个概念) + +思考题:你能否自行实现上述伪代码? + +现在,我们已经可以使用新属性(如果你完成了上面的思考题),details 用于新识别的命令外观``,textGo 在我们的命令 go 实现中替换固定文本“OK”。 + +# location.c + +```c +#include +#include +#include +#include "object.h" +#include "misc.h" +#include "noun.h" + +void executeLook(const char *noun) +{ + if (noun != NULL && strcmp(noun, "around") == 0) + { + printf("You are in %s.\n", player->location->description); + listObjectsAtLocation(player->location); + } + else + { + OBJECT *obj = getVisible("what you want to look at", noun); + switch (getDistance(player, obj)) + { + case distHereContained: + printf("Hard to see, try to get it first.\n"); + break; + case distOverthere: + printf("Too far away, move closer please.\n"); + break; + case distNotHere: + printf("You don't see any %s here.\n", noun); + break; + case distUnknownObject: + // already handled by getVisible + break; + default: + printf("%s\n", obj->details); + listObjectsAtLocation(obj); + } + } +} + +static void movePlayer(OBJECT *passage) +{ + printf("%s\n", passage->textGo); + if (passage->destination != NULL) + { + player->location = passage->destination; + printf("\n"); + executeLook("around"); + } +} + +void executeGo(const char *noun) +{ + OBJECT *obj = getVisible("where you want to go", noun); + switch (getDistance(player, obj)) + { + case distOverthere: + movePlayer(getPassage(player->location, obj)); + break; + case distNotHere: + printf("You don't see any %s here.\n", noun); + break; + case distUnknownObject: + // already handled by getVisible + break; + default: + movePlayer(obj); + } +} +``` + +属性权重和容量一起成为不能将某些对象移动到周围的可能原因。而 HP 检查代替了角色的硬编码白名单。 + +# move.c + +```c +#include +#include +#include "object.h" +#include "misc.h" + +static int weightOfContents(OBJECT *container) +{ + int sum = 0; + OBJECT *obj; + for (obj = objs; obj < endOfObjs; obj++) + { + if (isHolding(container, obj)) sum += obj->weight; + } + return sum; +} + +static void describeMove(OBJECT *obj, OBJECT *to) +{ + if (to == player->location) + { + printf("You drop %s.\n", obj->description); + } + else if (to != player) + { + printf(to->health > 0 ? "You give %s to %s.\n" : "You put %s in %s.\n", + obj->description, to->description); + } + else if (obj->location == player->location) + { + printf("You pick up %s.\n", obj->description); + } + else + { + printf("You get %s from %s.\n", + obj->description, obj->location->description); + } +} + +void moveObject(OBJECT *obj, OBJECT *to) +{ + if (obj == NULL) + { + // already handled by getVisible or getPossession + } + else if (to == NULL) + { + printf("There is nobody here to give that to.\n"); + } + else if (obj->weight > to->capacity) + { + printf("That is way too heavy.\n"); + } + else if (obj->weight + weightOfContents(to) > to->capacity) + { + printf("That would become too heavy.\n"); + } + else + { + describeMove(obj, to); + obj->location = to; + } +} +``` + +这里还有一个模块可以使用 HP 来识别角色。 + +# inventory.c + +```c +#include +#include +#include "object.h" +#include "misc.h" +#include "noun.h" +#include "move.h" + +void executeGet(const char *noun) +{ + OBJECT *obj = getVisible("what you want to get", noun); + switch (getDistance(player, obj)) + { + case distSelf: + printf("You should not be doing that to yourself.\n"); + break; + case distHeld: + printf("You already have %s.\n", obj->description); + break; + case distOverthere: + printf("Too far away, move closer please.\n"); + break; + case distUnknownObject: + // already handled by getVisible + break; + default: + if (obj->location != NULL && obj->location->health > 0) + { + printf("You should ask %s nicely.\n", obj->location->description); + } + else + { + moveObject(obj, player); + } + } +} + +void executeDrop(const char *noun) +{ + moveObject(getPossession(player, "drop", noun), player->location); +} + +void executeAsk(const char *noun) +{ + moveObject(getPossession(actorHere(), "ask", noun), player); +} + +void executeGive(const char *noun) +{ + moveObject(getPossession(player, "give", noun), actorHere()); +} + +void executeInventory(void) +{ + if (listObjectsAtLocation(player) == 0) + { + printf("You are empty-handed.\n"); + } +} +``` + +思考题:仔细观察这段代码,看看与你写的有何不同? + +权重检查利用了新功能 weightOfContents,它将在misc.c中实现。在同一模块中,我们还对一些现有函数进行了修改,以支持最后几个属性。 + +属性内容将替换固定文本“You see”。在列出玩家的库存时,原始文本已经有点奇怪了,但是现在函数listObjectsAtLocation用于显示任何可能对象的内容(请参阅上面的函数expertLook),我们真的需要一些更灵活的东西。 + +在函数 getPassage 中我们将属性目标替换为 prospect,并改进对所有命令(而不仅仅是 go and look)的响应,这些命令应用于位于“隐藏通道”另一端的位置。 + +# misc.h + +```c +typedef enum { + distSelf, + distHeld, + distHeldContained, + distLocation, + distHere, + distHereContained, + distOverthere, + distNotHere, + distUnknownObject +} DISTANCE; + +extern bool isHolding(OBJECT *container, OBJECT *obj); +extern OBJECT *getPassage(OBJECT *from, OBJECT *to); +extern DISTANCE getDistance(OBJECT *from, OBJECT *to); +extern OBJECT *actorHere(void); +extern int listObjectsAtLocation(OBJECT *location); +``` + +# misc.c + +```c +#include +#include +#include "object.h" +#include "misc.h" + +bool isHolding(OBJECT *container, OBJECT *obj) +{ + return obj != NULL && obj->location == container; +} + +OBJECT *getPassage(OBJECT *from, OBJECT *to) +{ + if (from != NULL && to != NULL) + { + OBJECT *obj; + for (obj = objs; obj < endOfObjs; obj++) + { + if (isHolding(from, obj) && obj->prospect == to) + { + return obj; + } + } + } + return NULL; +} + +DISTANCE getDistance(OBJECT *from, OBJECT *to) +{ + return to == NULL ? distUnknownObject : + to == from ? distSelf : + isHolding(from, to) ? distHeld : + isHolding(to, from) ? distLocation : + isHolding(from->location, to) ? distHere : + isHolding(from, to->location) ? distHeldContained : + isHolding(from->location, to->location) ? distHereContained : + getPassage(from->location, to) != NULL ? distOverthere : + distNotHere; +} + +OBJECT *actorHere(void) +{ + OBJECT *obj; + for (obj = objs; obj < endOfObjs; obj++) + { + if (isHolding(player->location, obj) && obj != player && + obj->health > 0) + { + return obj; + } + } + return NULL; +} + +int listObjectsAtLocation(OBJECT *location) +{ + int count = 0; + OBJECT *obj; + for (obj = objs; obj < endOfObjs; obj++) + { + if (obj != player && isHolding(location, obj)) + { + if (count++ == 0) + { + printf("%s:\n", location->contents); + } + printf("%s\n", obj->description); + } + } + return count; +} +``` + +思考题:为什么上面的 getPassage 函数使用了函数指针这种语法? + +``` + 函数指针和指针函数有什么区别? +``` + +为了使整个画面完整,最好扩展前面生成的地图,我们可以用虚线表示“明显”的通道。 + +```c +BEGIN { print "digraph map {"; } +/^- / { outputEdges(); delete a; } +/^[ \t]/ { a[$1] = $2; } +END { outputEdges(); print "}"; } + +function outputEdges() +{ + outputEdge(a["location"], a["destination"], ""); + outputEdge(a["location"], a["prospect"], " [style=dashed]"); +} + +function outputEdge(from, to, style) +{ + if (from && to) print "\t" from " -> " to style; +} +``` + +注意: + +- 尽量不要太担心浪费仅在某些类型的对象中使用的属性上的内存空间(例如,textGo仅用于通道),或者许多重复的字符串文本。 +- 为了演示属性 prospect 的使用,我们使洞穴无法访问。当您查看新地图时,这一点立即变得很明显。进入洞穴的箭头是虚线的,这意味着这是一个虚假的通道,但不是实际的通道。请放心,洞穴将在下一章重新开放。 +- 请注意,更详细的描述往往需要一个更大的字典(更多的对象,更多的标签)。例如,命令 look silver coin 现在返回 "该硬币的正面有一只鹰"。玩家通过输入一个命令 look eagle 来查看银币,但程序并不知道鹰是什么意思(显然这样子是不行的)。 + +输出样例 + +Welcome to Little Cave Adventure. +You are in an open field. +You see: +a silver coin +a burly guard +a cave entrance to the east +dense forestall around + +--> look guard +The guard is a really big fellow. + +--> get guard +That is way too heavy. + +--> get coin +You pick up a silver coin. + +--> inventory +You have: +a silver coin + +--> give coin +You give a silver coin to a burly guard. + +--> look guard +The guard is a really big fellow. +He has: +a silver coin + +--> go cave +The guard stops you from walking into the cave. + +--> go north +Dense forest is blocking the way. + +--> quit + +Bye! diff --git a/3.编程思维体系构建/3.4.6.11.设置条件.md b/3.编程思维体系构建/3.4.6.11.设置条件.md new file mode 100644 index 0000000..ce88931 --- /dev/null +++ b/3.编程思维体系构建/3.4.6.11.设置条件.md @@ -0,0 +1,317 @@ +# 11.设置条件 + +到目前为止,所有对象的属性都是数据:文本、数字。但属性也可以是代码。 + +在上一章中,我们通过关闭洞穴入口(进入洞穴的通道)来限制玩家的行动自由。这已经让游戏变得更有挑战性了,但这并没有带来多少谜题,除非我们为玩家提供一个微小的可能性来打开通道。真正的挑战应该是让玩家找到通道打开的条件。 + +让我们举一个简单的例子。为了越过守卫进入山洞,玩家必须杀死或贿赂守卫(或两者兼而有之,这很有价值)。换句话说: + +- 当警卫死亡时(HP=0),入口开放 +- 当警卫拿着银币(贿赂警卫)时,入口开放 +- 两者都不是,入口关闭 + +打开一个封闭的通道(在这里是进入洞穴)涉及到改变一些属性值: + +- 目的地从 NULL(空地点)变为洞穴 +- textGo从 "警卫阻止你...... "改为 "你走进山洞" +- 在一些特殊情况下,描述和细节不需要改变。但对于一个门洞或栅栏,其中之一(或两者)通常会包含一些从 "开放 "到 "关闭 "的文字。 + +有许多方法来实现这一目标。在这里,我们将讨论一种简单、可维护和通用的方法。 + +首先,我们定义了两个独立的通道:一个代表开放通道,另一个代表封闭通道。除了上面列出的那些,这两条通道在每一个属性上都是相同的。(在你下面看到的生成的地图中,注意有两个箭头通向洞穴;一个是实线,一个是虚线)。 + +接下来,我们引入一个名为条件的新属性,它决定了某个对象是否存在。这两个通道将被赋予互斥的条件,因此在任何时候都只能有一个存在。 + +每个条件将被实现为一个布尔函数:TRUE意味着该对象存在,FALSE意味着它不存在。 + +```c +bool intoCaveIsOpen(void) +{ + return guard->health == 0 || silver->location == guard; +} + +bool intoCaveIsClosed(void) +{ + return guard->health > 0 && silver->location != guard; +} +``` + +思考题:你能仿照上面例子自己写一些条件函数吗? + +新的属性条件是一个指向这样一个函数的指针。 + +```c +bool (*condition)(void); +``` + +接下来,我们可以立即开始为 object.txt 中的新属性分配函数。 + +# object.txt + +``` +- intoCave + condition intoCaveIsOpen + description "a cave entrance to the east" + tags "east", "entrance" + location field + destination cave + detail "The entrance is just a narrow opening in a small outcrop.\n" + textGo "You walk into the cave.\n" + +- intoCaveBlocked + condition intoCaveIsClosed + description "a cave entrance to the east" + tags "east", "entrance" + location field + prospect cave + detail "The entrance is just a narrow opening in a small outcrop.\n" + textGo "The guard stops you from walking into the cave.\n" +``` + +思考题:尝试自己实现上面的伪代码 + +这两个 "条件 "函数是如此具体,每一个条件函数都只用这一次。现在,我们可以在我们需要的地方定义这些函数。许多编程语言都支持匿名函数,像这样: + +``` +- intoCave + condition { return guard->health == 0 || silver->location == guard; } + ... + +- intoCaveBlocked + condition { return guard->health > 0 && silver->location != guard; } + ... +``` + +所以现在我们可以把额外的段落和条件添加到 object.txt 中,就像前面解释的那样。 + +# object.txt + +``` +#include +#include +#include "object.h" + +typedef struct object { + bool (*condition)(void); + const char *description; + const char **tags; + struct object *location; + struct object *destination; + struct object *prospect; + const char *details; + const char *contents; + const char *textGo; + int weight; + int capacity; + int health; +} OBJECT; + +extern OBJECT objs[]; + +- field + description "an open field" + tags "field" + details "The field is a nice and quiet place under a clear blue sky." + capacity 9999 + +- cave + description "a little cave" + tags "cave" + details "The cave is just a cold, damp, rocky chamber." + capacity 9999 + +- silver + description "a silver coin" + tags "silver", "coin", "silver coin" + location field + details "The coin has an eagle on the obverse." + weight 1 + +- gold + description "a gold coin" + tags "gold", "coin", "gold coin" + location cave + details "The shiny coin seems to be a rare and priceless artefact." + weight 1 + +- guard + description "a burly guard" + tags "guard", "burly guard" + location field + details "The guard is a really big fellow." + contents "He has" + health 100 + capacity 20 + +- player + description "yourself" + tags "yourself" + location field + details "You would need a mirror to look at yourself." + contents "You have" + health 100 + capacity 20 + +- intoCave + condition { return guard->health == 0 || silver->location == guard; } + description "a cave entrance to the east" + tags "east", "entrance" + location field + destination cave + details "The entrance is just a narrow opening in a small outcrop." + textGo "You walk into the cave." + +- intoCaveBlocked + condition { return guard->health > 0 && silver->location != guard; } + description "a cave entrance to the east" + tags "east", "entrance" + location field + prospect cave + details "The entrance is just a narrow opening in a small outcrop." + textGo "The guard stops you from walking into the cave." + +- exitCave + description "an exit to the west" + tags "west", "exit" + location cave + destination field + details "Sunlight pours in through an opening in the cave's wall." + textGo "You walk out of the cave." + +- wallField + description "dense forest all around" + tags "west", "north", "south", "forest" + location field + details "The field is surrounded by trees and undergrowth." + textGo "Dense forest is blocking the way." + +- wallCave + description "solid rock all around" + tags "east", "north", "south", "rock" + location cave + details "Carved in stone is a secret password 'abccb'." + textGo "Solid rock is blocking the way." +``` + +思考题:尝试自己实现这些功能,并看看与你之前设计的有何不同 + +为了使这些条件发挥作用,我们需要调整函数 isHolding 和 getDistance。 + +# misc.c + +```c +#include +#include +#include "object.h" +#include "misc.h" + +bool isHolding(OBJECT *container, OBJECT *obj) +{ + return validObject(obj) && obj->location == container; +} + +OBJECT *getPassage(OBJECT *from, OBJECT *to) +{ + if (from != NULL && to != NULL) + { + OBJECT *obj; + for (obj = objs; obj < endOfObjs; obj++) + { + if (isHolding(from, obj) && obj->prospect == to) + { + return obj; + } + } + } + return NULL; +} + +DISTANCE getDistance(OBJECT *from, OBJECT *to) +{ + return to == NULL ? distUnknownObject : + !validObject(to) ? distNotHere : + to == from ? distSelf : + isHolding(from, to) ? distHeld : + isHolding(to, from) ? distLocation : + isHolding(from->location, to) ? distHere : + isHolding(from, to->location) ? distHeldContained : + isHolding(from->location, to->location) ? distHereContained : + getPassage(from->location, to) != NULL ? distOverthere : + distNotHere; +} + +OBJECT *actorHere(void) +{ + OBJECT *obj; + for (obj = objs; obj < endOfObjs; obj++) + { + if (isHolding(player->location, obj) && obj != player && + obj->health > 0) + { + return obj; + } + } + return NULL; +} + +int listObjectsAtLocation(OBJECT *location) +{ + int count = 0; + OBJECT *obj; + for (obj = objs; obj < endOfObjs; obj++) + { + if (obj != player && isHolding(location, obj)) + { + if (count++ == 0) + { + printf("%s:\n", location->contents); + } + printf("%s\n", obj->description); + } + } + return count; +} +``` + +思考题:想想我们调整了什么 + +注意: + +1. 警卫不可能会死,所以可以说我们的条件函数中的HP是很无用的。当然,这很容易通过添加一个 kill 命令来解决,见第 20 章。 +2. 这两个条件函数是互补的;它们有资格成为重复的代码。为了消除这一点,我们可能决定让一个函数调用另一个函数(用'!'操作符来否定结果)。一个匿名函数没有(稳定的)名字,但我们可以用它的对象来指代它。我们可以用 intoCaveBlocked 的条件函数代替。 +3. 为了简单起见,条件函数没有参数。实际上,传递一个参数 OBJECT *obj 可能更好;这使得编写更多的通用条件函数成为可能,可以在多个对象中重复使用。 +4. 在理论上,任何对象都可以成为 "条件"。在下一章,你可以看到一个类似的技术被应用于此。 + +思考题:想一想上面第二点要怎么用 C 来实现? + +输出样例 + +Welcome to Little Cave Adventure. +You are in an open field. +You see: +a silver coin +a burly guard +a cave entrance to the east +dense forest all around + +--> go entrance +The guard stops you from walking into the cave. + +--> get coin +You pick up a silver coin. + +--> give coin +You give a silver coin to a burly guard. + +--> go entrance +You walk into the cave. + +You are in a little cave. +You see: +a gold coin +an exit to the west +solid rock all around + +--> quit + +Bye! diff --git a/3.编程思维体系构建/3.4.6.12.开启关闭.md b/3.编程思维体系构建/3.4.6.12.开启关闭.md new file mode 100644 index 0000000..ee6985d --- /dev/null +++ b/3.编程思维体系构建/3.4.6.12.开启关闭.md @@ -0,0 +1,668 @@ +# 12.开启关闭 + +在上一章中,我们使用 "条件 "函数来使对象消失。当然,还有一个更简单的方法来实现同样的目的:只要清除对象的位置属性就可以了! + +洞口是一个典型的例子,条件函数在那里工作得特别好。这是因为入口受到其他对象(守卫和银币)中的属性的影响;我们可以使用函数使得所有的逻辑都能保持一致。 + +让我们举一个更直接的例子。假设山洞有一扇门通向一个密室。只是一个简单的门洞,玩家可以打开和关闭。就像前一章一样,我们将使用两个对象来表示这个通道;一个表示打开的门,另一个表示门关闭时。 + +``` +- backroom + description "a backroom" + tags "backroom" + details "The room is dusty and messy.\n" + +- openDoorToBackroom + description "an open door to the south" + tags "south", "door", "doorway" + destination backroom + details "The door is open.\n" + textGo "You walk through the door into the backroom.\n" + +- closedDoorToBackroom + description "a closed door to the south" + tags "south", "door", "doorway" + location cave + prospect backroom + details "The door is closed.\n" + textGo "The door is closed.\n" +``` + +思考题:尝试自己用 C 语言实现 + +自然,门也应该能从另一侧进入。 + +``` +- openDoorToCave + description "an open door to the north" + tags "north", "door", "doorway" + destination cave + details "The door is open.\n" + textGo "You walk through the door into the cave.\n" + +- closedDoorToCave + description "a closed door to the north" + tags "north", "door", "doorway" + location backroom + prospect cave + details "The door is closed.\n" + textGo "The door is closed.\n" +``` + +注意我们只给封闭的门洞一个位置,开放的门洞没有。所以一开始,门是关闭的(因此你在右边看到的生成的地图中,山洞和密室之间有虚线箭头)。要打开门,我们所要做的就是交换位置。 + +```c +openDoorToBackroom->location = cave; +closedDoorToBackroom->location = NULL; +openDoorToCave->location = backroom; +closedDoorToCave->location = NULL; +``` + +接下来,让我们创建一个辅助函数来适应这种情况。 + +```c +void swapLocations(OBJECT *obj1, OBJECT *obj2) +{ + OBJECT *tmp = obj1->location; + obj1->location = obj2->location; + obj2->location = tmp; +} +``` + +现在可以用下面的语句来打开这扇门;一旦它被打开,同样的语句将再次关闭它。 + +```c +swapLocations(openDoorToBackroom, closedDoorToBackroom); +swapLocations(openDoorToCave, closedDoorToCave); +``` + +函数 swapLocations 不依赖于固定的位置,因为它在两个对象之间来回传递当前位置。 + +当相关对象是可移动的时候,辅助功能就特别方便。例如,一个盒子可以被打开和关闭,但它也是一个可以被拿起并移动到其他地方的物品。换句话说,它的位置是不固定的。 + +当然,一个盒子不是一个通道:玩家总是在盒子外面。所以一对物体就够了,也就是说,我们调用一次 swapLocations 函数就够了。 + +```c +swapLocations(openBox, closedBox); +``` + +这或多或少是我们实现一些新命令 open 和 close 所需要的。下面是 open 的一个简单实现;close 的实现也类似。 + +```c +OBJECT *obj = parseObject(noun); +if (obj == closedDoorToBackRoom || obj == closedDoorToCave) +{ + swapLocations(openDoorToBackroom, closedDoorToBackroom); + swapLocations(openDoorToCave, closedDoorToCave); + printf("OK.\n"); +} +else if (obj == closedBox) +{ + swapLocations(openBox, closedBox); + printf("OK.\n"); +} +else if (obj == openDoorToBackRoom || obj == openDoorToCave || obj == openBox) +{ + printf("That is already open.\n"); +} +else +{ + printf("That cannot be opened.\n"); +} +``` + +思考题:你能不能仿照上面的代码实现 close 功能? + +为了使事情稍微复杂一些,我们可以在门上或盒子上加一把锁。这需要(至少)三个相互排斥的对象;每个可能的状态都有一个:打开、关闭和锁定。但是我们仍然可以使用同一个函数来交换对象的位置。例如,这里是如何解锁一个上锁的盒子;反之亦然。 + +```c +swapLocations(closedBox, lockedBox); +``` + +但这仅仅是不够的,我们对命令 open 的实现必须进行扩展,以处理新的对象 lockedBox。 + +```c +... +else if (obj == lockedBox) +{ + printf("You can't, it is locked.\n"); +} +... +``` + +显然,代码的行数与游戏中的门(以及盒子和其他可以打开的物体)的数量成正比。因此,如果你的游戏有不止几扇门,那么选择一个更通用的解决方案是个好主意。顺便说一下,这对每一个命令都是适用的:当它涉及到许多物体时,尽量写通用代码;但当你处理一两个特殊情况时,就坚持使用直接的、专门的代码。 + +思考题:我们可以使用什么方法来解决这个问题? + +提示:C++ 中的模板功能(这只是一种选择) + +下面我们将揭晓答案 + +通用代码通常带有数据驱动的方法。换句话说,我们需要向我们的对象结构添加一个或多个属性。在这种特殊情况下,我们将为我们希望支持的每个命令添加一个函数指针:打开、关闭、锁定和解锁。 + +# object.txt + +```c +#include +#include +#include "object.h" +#include "toggle.h" + +typedef struct object { + bool (*condition)(void); + const char *description; + const char **tags; + struct object *location; + struct object *destination; + struct object *prospect; + const char *details; + const char *contents; + const char *textGo; + int weight; + int capacity; + int health; + void (*open)(void); + void (*close)(void); + void (*lock)(void); + void (*unlock)(void); +} OBJECT; + +extern OBJECT objs[]; + +- field + description "an open field" + tags "field" + details "The field is a nice and quiet place under a clear blue sky." + capacity 9999 + +- cave + description "a little cave" + tags "cave" + details "The cave is just a cold, damp, rocky chamber." + capacity 9999 + +- silver + description "a silver coin" + tags "silver", "coin", "silver coin" + location field + details "The coin has an eagle on the obverse." + weight 1 + +- gold + description "a gold coin" + tags "gold", "coin", "gold coin" + location openBox + details "The shiny coin seems to be a rare and priceless artefact." + weight 1 + +- guard + description "a burly guard" + tags "guard", "burly guard" + location field + details "The guard is a really big fellow." + contents "He has" + health 100 + capacity 20 + +- player + description "yourself" + tags "yourself" + location field + details "You would need a mirror to look at yourself." + contents "You have" + health 100 + capacity 20 + +- intoCave + condition { return guard->health == 0 || silver->location == guard; } + description "a cave entrance to the east" + tags "east", "entrance" + location field + destination cave + details "The entrance is just a narrow opening in a small outcrop." + textGo "You walk into the cave." + open isAlreadyOpen + +- intoCaveBlocked + condition { return guard->health > 0 && silver->location != guard; } + description "a cave entrance to the east" + tags "east", "entrance" + location field + prospect cave + details "The entrance is just a narrow opening in a small outcrop." + textGo "The guard stops you from walking into the cave." + open isAlreadyOpen + +- exitCave + description "an exit to the west" + tags "west", "exit" + location cave + destination field + details "Sunlight pours in through an opening in the cave's wall." + textGo "You walk out of the cave." + open isAlreadyOpen + +- wallField + description "dense forest all around" + tags "west", "north", "south", "forest" + location field + details "The field is surrounded by trees and undergrowth." + textGo "Dense forest is blocking the way." + +- wallCave + description "solid rock all around" + tags "east", "north", "rock" + location cave + details "Carved in stone is a secret password 'abccb'." + textGo "Solid rock is blocking the way." + +- backroom + description "a backroom" + tags "backroom" + details "The room is dusty and messy." + capacity 9999 + +- wallBackroom + description "solid rock all around" + tags "east", "west", "south", "rock" + location backroom + details "Trendy wallpaper covers the rock walls." + textGo "Solid rock is blocking the way." + +- openDoorToBackroom + description "an open door to the south" + tags "south", "door", "doorway" + destination backroom + details "The door is open." + textGo "You walk through the door into a backroom." + open isAlreadyOpen + close toggleDoorToBackroom + +- closedDoorToBackroom + description "a closed door to the south" + tags "south", "door", "doorway" + location cave + prospect backroom + details "The door is closed." + textGo "The door is closed." + open toggleDoorToBackroom + close isAlreadyClosed + +- openDoorToCave + description "an open door to the north" + tags "north", "door", "doorway" + destination cave + details "The door is open." + textGo "You walk through the door into the cave." + open isAlreadyOpen + close toggleDoorToCave + +- closedDoorToCave + description "a closed door to the north" + tags "north", "door", "doorway" + location backroom + prospect cave + details "The door is closed." + textGo "The door is closed." + open toggleDoorToCave + close isAlreadyClosed + +- openBox + description "a wooden box" + tags "box", "wooden box" + details "The box is open." + weight 5 + capacity 10 + open isAlreadyOpen + close toggleBox + lock isStillOpen + unlock isAlreadyOpen + +- closedBox + description "a wooden box" + tags "box", "wooden box" + details "The box is closed." + weight 5 + open toggleBox + close isAlreadyClosed + lock toggleBoxLock + unlock isAlreadyUnlocked + +- lockedBox + description "a wooden box" + tags "box", "wooden box" + location backroom + details "The box is closed." + weight 5 + open isStillLocked + close isAlreadyClosed + lock isAlreadyLocked + unlock toggleBoxLock + +- keyForBox + description "a tiny key" + tags "key", "tiny key" + location cave + details "The key is really small and shiny." + weight 1 +``` + +注意: + +- 第 89 行:乍一看,isAlreadyOpen 在这里似乎不合适;从技术上讲,intoCaveBlocked 是一个封闭的通道。但从故事上讲,它是一个不错的开场白。 +- 第 169、180、191 行。如果你喜欢沉重的宝箱而不是盒子,那么你所要做的就是增加重量(并相应调整相关文字和标签)。 + +为了避免重复的代码,我们这次特意没有使用匿名函数。相反,我们将在一个单独的模块中实现必要的逻辑。函数 swapLocations 也在其中,这不过是一个稍微扩展的版本,它也会向用户输出反馈。 + +# toggle.h + +```c +extern void cannotBeOpened(void); +extern void cannotBeClosed(void); +extern void cannotBeLocked(void); +extern void cannotBeUnlocked(void); + +extern void isAlreadyOpen(void); +extern void isAlreadyClosed(void); +extern void isAlreadyLocked(void); +extern void isAlreadyUnlocked(void); + +extern void isStillOpen(void); +extern void isStillLocked(void); + +extern void toggleDoorToBackroom(void); +extern void toggleDoorToCave(void); +extern void toggleBox(void); +extern void toggleBoxLock(void); +``` + +# toggle.c + +```c +#include +#include +#include "object.h" + +static void swapLocations(const char *verb1, OBJECT *obj1, + const char *verb2, OBJECT *obj2) +{ + OBJECT *tmp = obj1->location; + OBJECT *obj = tmp != NULL ? obj1 : obj2; + const char *verb = tmp != NULL ? verb1 : verb2; + obj1->location = obj2->location; + obj2->location = tmp; + if (verb != NULL) printf("You %s %s.\n", verb, obj->description); +} + +void cannotBeOpened(void) { printf("That cannot be opened.\n"); } +void cannotBeClosed(void) { printf("That cannot be closed.\n"); } +void cannotBeLocked(void) { printf("That cannot be locked.\n"); } +void cannotBeUnlocked(void) { printf("That cannot be unlocked.\n"); } + +void isAlreadyOpen(void) { printf("That is already open.\n"); } +void isAlreadyClosed(void) { printf("That is already closed.\n"); } +void isAlreadyLocked(void) { printf("That is already locked.\n"); } +void isAlreadyUnlocked(void) { printf("That is already unlocked.\n"); } + +void isStillOpen(void) { printf("That is still open.\n"); } +void isStillLocked(void) { printf("That is locked.\n"); } + +void toggleDoorToBackroom(void) +{ + swapLocations(NULL, openDoorToCave, NULL, closedDoorToCave); + swapLocations("close", openDoorToBackroom, "open", closedDoorToBackroom); +} + +void toggleDoorToCave(void) +{ + swapLocations(NULL, openDoorToBackroom, NULL, closedDoorToBackroom); + swapLocations("close", openDoorToCave, "open", closedDoorToCave); +} + +void toggleBox(void) +{ + swapLocations("close", openBox, "open", closedBox); +} + +void toggleBoxLock(void) +{ + if (keyForBox->location == player) + { + swapLocations("lock", closedBox, "unlock", lockedBox); + } + else + { + printf("You don't have a key.\n"); + } +} +``` + +正如前面所宣布的,打开、关闭、锁定和解锁这四个命令的实现是完全通用的。 + +# openclose.h + +```c +extern void executeOpen(const char *noun); +extern void executeClose(const char *noun); +extern void executeLock(const char *noun); +extern void executeUnlock(const char *noun); +``` + +# openclose.c + +```c +#include +#include +#include "object.h" +#include "reach.h" + +void executeOpen(const char *noun) +{ + OBJECT *obj = reachableObject("what you want to open", noun); + if (obj != NULL) (*obj->open)(); +} + +void executeClose(const char *noun) +{ + OBJECT *obj = reachableObject("what you want to close", noun); + if (obj != NULL) (*obj->close)(); +} + +void executeLock(const char *noun) +{ + OBJECT *obj = reachableObject("what you want to lock", noun); + if (obj != NULL) (*obj->lock)(); +} + +void executeUnlock(const char *noun) +{ + OBJECT *obj = reachableObject("what you want to unlock", noun); + if (obj != NULL) (*obj->unlock)(); +``` + +上面,我们使用了一个通用函数 reachableObject 来处理不在这里的对象;其实现见下文。这样,我们就不必把同样的代码写四遍(每个执行函数写一遍)。更多的命令将在第 15 章中加入;这些命令将受益于同样的函数。 + +# reach.h + +```c +extern OBJECT *reachableObject(const char *intention, const char *noun); +``` + +# reach.c + +```c +#include +#include +#include "object.h" +#include "misc.h" +#include "noun.h" + +OBJECT *reachableObject(const char *intention, const char *noun) +{ + OBJECT *obj = getVisible(intention, noun); + switch (getDistance(player, obj)) + { + case distSelf: + printf("You should not be doing that to yourself.\n"); + break; + case distHeldContained: + case distHereContained: + printf("You would have to get it from %s first.\n", + obj->location->description); + break; + case distOverthere: + printf("Too far away, move closer please.\n"); + break; + case distNotHere: + case distUnknownObject: + // already handled by getVisible + break; + default: + return obj; + } + return NULL; +} +``` + +同样,我们也要对 parsexec.c 进行补充 + +# parsexec.c + +```c +#include +#include +#include +#include "location.h" +#include "inventory.h" +#include "openclose.h" + +bool parseAndExecute(char *input) +{ + char *verb = strtok(input, " \n"); + char *noun = strtok(NULL, "\n"); + if (verb != NULL) + { + if (strcmp(verb, "quit") == 0) + { + return false; + } + else if (strcmp(verb, "look") == 0) + { + executeLook(noun); + } + else if (strcmp(verb, "go") == 0) + { + executeGo(noun); + } + else if (strcmp(verb, "get") == 0) + { + executeGet(noun); + } + else if (strcmp(verb, "drop") == 0) + { + executeDrop(noun); + } + else if (strcmp(verb, "give") == 0) + { + executeGive(noun); + } + else if (strcmp(verb, "ask") == 0) + { + executeAsk(noun); + } + else if (strcmp(verb, "inventory") == 0) + { + executeInventory(); + } + else if (strcmp(verb, "open") == 0) + { + executeOpen(noun); + } + else if (strcmp(verb, "close") == 0) + { + executeClose(noun); + } + else if (strcmp(verb, "lock") == 0) + { + executeLock(noun); + } + else if (strcmp(verb, "unlock") == 0) + { + executeUnlock(noun); + } + else + { + printf("I don't know how to '%s'.\n", verb); + } + } + return true; +} +``` + +注意: + +- 你可能已经注意到,object.txt 在本章中的大小几乎增加了一倍。我们已经可以向你保证,这只是一个开始。object.txt 是我们游戏数据的主要来源;一旦我们认真开始添加地点、物品和演员,行数很容易就会增加到数千行。 +- 除了门和锁之外,函数 swapLocation 还可以用于许多其他事情。在第 15 章,它将再次被使用,这次是用来打开和关闭灯。 +- 正如你在样本输出中所看到的,玩家可以从盒子里拿到金子,但他无法再把金子放回去!我们的解析器无法处理像把硬币放进盒子这样的 "复杂 "命令。因此,在下一章中,我们将编写一个全新的解析器:目前的双线实现急需更换! + +输出样例 + +Welcome to Little Cave Adventure. +You are in an open field. +You see: +a silver coin +a burly guard +a cave entrance to the east +dense forest all around + +--> get coin +You pick up a silver coin. + +--> give coin +You give a silver coin to a burly guard. + +--> go cave +You walk into the cave. + +You are in a little cave. +You see: +an exit to the west +solid rock all around +a closed door to the south +a tiny key + +--> get key +You pick up a tiny key. + +--> go south +The door is closed. + +--> open door +You open a closed door to the south. + +--> go south +You walk through the door into a backroom. + +You are in a backroom. +You see: +solid rock all around +an open door to the north +a wooden box + +--> unlock box +You unlock a wooden box. + +--> open box +You open a wooden box. + +--> look box +The box is open. +You see: +a gold coin + +--> get gold +You get a gold coin from a wooden box. + +--> quit + +Bye! diff --git a/3.编程思维体系构建/3.4.6.13.编写解析器.md b/3.编程思维体系构建/3.4.6.13.编写解析器.md new file mode 100644 index 0000000..7a73c4b --- /dev/null +++ b/3.编程思维体系构建/3.4.6.13.编写解析器.md @@ -0,0 +1,521 @@ +# 13.编写解析器 + +每个文本冒险都有一个解析器,但是解析器也有高下之分。一个简单的 "动词-名词 "解析器(就像我们从第二章开始一直使用的那个)对于一个精心设计的冒险游戏来说可能已经足够了。 + +然而,Infocom 已经证明,一个更高级的解析器确实有助于制作一个令人愉快的游戏。它不一定要通过图灵测试。 + +记住,它只是一个游戏。但是解析器应该使玩家能够以一种或多或少的自然方式来表达他的意图。 + +我们目前的分析器主要由两行代码组成,隐藏在 parsexec.c 中。 + +```c +char *verb = strtok(input, " \n"); +char *noun = strtok(NULL, "\n"); +``` + +好吧,我们再加上将动词映射到命令的 strcmp 调用序列,以及将名词映射到对象的 noun.c 中的函数,但仅此而已。在过去的 12 章中,这个系统为我们提供了良好的服务,但它也有缺陷。 + +- 它只接受 "动词名词 "形式的简单命令;它不理解既有直接宾语又有间接宾语的句子,如把硬币放进盒子。 +- 它确实接受多字对象(如银币),但字与字之间的空格必须准确无误。我们的游戏拒绝银币和硬币之间的双空格。 +- 它是区分大小写的;"向北走 "的命令因为大写的 "G "而不被识别。 + +思考题:你能想到有什么办法解决这些问题吗? + +编写一个好的分析器并不是一件小事,但在这里我将给你一个相对简单的方法,我们将定义一个由模式列表组成的语法,类似于(但比)正则表达式要简单得多。 + +| look around | 仅仅是匹配而已。双空格、前导空格、尾部空格和大小写差异都被忽略了 | +| ----------- | ------------------------------------------------------------------ | +| go A | 匹配单词 go 和对象的标签(见第 5 章和第 9 章关于 "标签 "的解释)。 | +| put A in B | 匹配单词 put 后面的标签,单词 in 和另一个标签。 | + +为了解析用户的输入,我们将从上到下遍历模式列表,依次尝试将用户的输入与每个模式匹配。我们将在发现第一个匹配时停止。为了简单起见,我们将不使用回溯,尽管这可以在以后添加。 + +思考题:如果我们使用回溯,那该怎么编写代码? + +大写字母是我们语法中的非终端符号,它们可以匹配任何标签(任何对象)。当解析器在两个不同的标签(例如 "银币 "和 "银")之间进行选择时,较长的标签将被优先考虑。 + +然后,匹配的标签可以作为参数名词传递给其中一个执行函数。对于有一个以上名词的命令(在下一章介绍),参数传递变得有点不切实际。为了简单起见,我们将使用一个全局变量而不是参数(尽管全局变量的名声不好)。该变量将是一个字符串指针的数组。 + +```c +const char *params[26]; +``` + +该数组有 26 个元素;字母表中的每个(大写)字母都有一个,这足以满足一个模式中多达 26 个不同的非终端。对于一个(匹配的)模式中的每个非终端,将通过在非终端的数组元素中填充一个指向该特定标签的指针来 "捕获 "一个匹配的标签。 params[0](第一个数组元素)捕获非终端 "A",params[1]捕获 "B",以此类推。一个简单的宏定义可以用来找到属于某个非终端的数组元素。 + +```c +#define paramByLetter(letter) (params + (letter) - 'A') +``` + +注意:你可能会发现数组长度为 26 有点过头了,但它确实让我们省去了写一些边界检查代码的麻烦,以防止在出现畸形模式时出现缓冲区溢出。 + +现在,我们必须想出一个办法来处理缺失的或不被承认的名词。 + +假设用户打错了字,输入了 "go kave"。问题是,这个命令到底应不应该匹配 "go A "这个命令?如果我们不想出解决办法,那么这个命令将无法匹配任何其他命令,并最终进入一个通用的错误处理程序,它可能会回答 "我不知道如何去 kave "这样的话。这就失去了改进这些 "消极反应 "的所有机会;回答 "我不知道你要去哪里 "已经感觉更自然了。最好的办法是将所有关于命令去向的答复保持在函数 executeGo 内。 + +停下来想一想,可以怎么解决这个问题? + +有几种方法可以实现这一点,但最简单的方法是允许非终止符匹配任何东西。所以不仅仅是一个有效的标签,也包括完全的胡言乱语、空白或什么都没有这种语句。这种 "无效 "的输入将被捕获为一个空字符串("")。 + +在模式中间有这样一个 "松散 "的非终端,确实会使模式匹配过程复杂化;在匹配输入 "把 foo 放在盒子里 "和模式 "把 A 放在 B 里 "时,它需要回溯以正确对齐 "in "这个词。 + +为了简单起见,我们将只对出现在模式最末端的非终止符进行松散匹配。为了能够正确匹配上面的例子,我们需要两个独立的模式: + +- 模式 "put A in B "将匹配有效的命令 put coin in box,以及无效的命令 put coin in booox 和 put coin in。注意,非终端 A 只匹配有效的标签(在这个例子中是硬币)。 +- 模式 "put A "匹配所有剩余的无效命令:put coin, put koin, put koin in box, put koin in booox 和一个裸 put。还有最初的例子(put foo in in box)。 + +模式的顺序在这里至关重要:如果 "放 A "在上面(意味着它将被首先尝试),那么它将消耗每个放的命令;一个有效的 "put coin in box "命令将被认为是无效的,因为没有 "硬币在盒子里 "的标签。 + +对于以多种形式出现的命令也是如此,比如说 look 这个命令: + +- Look around +- look(作为环顾四周的缩写) +- Look A + +前两个命令形式可以为任何顺序,但第三个必须在最后。 + +思考题:你是否有办法解决这个问题? + +是时候将其付诸行动了。我们将抛弃模块 parsexec.c 的现有内容,用一个新的函数 parseAndExecute 的实现来取代它,该函数使用一个模式列表,应该能够匹配我们到目前为止实现的每一条命令。每个模式都与一个执行相应命令的函数相联系。 + +# parsexec.c + +```c +extern bool parseAndExecute(const char *input); +``` + +# parsexec.h + +```c +#include +#include +#include +#include "object.h" +#include "misc.h" +#include "match.h" +#include "location.h" +#include "inventory.h" +#include "openclose.h" + +typedef struct +{ + const char *pattern; + bool (*function)(void); +} COMMAND; + +static bool executeQuit(void) +{ + return false; +} + +static bool executeNoMatch(void) +{ + const char *src = *params; + int len; + for (len = 0; src[len] != '\0' && !isspace(src[len]); len++); + //计算玩家输入的第一个字符串的长度 + if (len > 0) printf("I don't know how to '%.*s'.\n", len, src); + return true; +} + +bool parseAndExecute(const char *input) +{ + static const COMMAND commands[] = + //模式数组 + { + { "quit" , executeQuit }, + { "look" , executeLookAround }, + { "look around" , executeLookAround }, + { "look at A" , executeLook }, + { "look A" , executeLook }, + { "examine A" , executeLook }, + { "go to A" , executeGo }, + { "go A" , executeGo }, + { "get A" , executeGet }, + { "drop A" , executeDrop }, + { "ask A" , executeAsk }, + { "give A" , executeGive }, + { "inventory" , executeInventory }, + { "open A" , executeOpen }, + { "close A" , executeClose }, + { "lock A" , executeLock }, + { "unlock A" , executeUnlock }, + { "A" , executeNoMatch } + }; + const COMMAND *cmd; + for (cmd = commands; !matchCommand(input, cmd->pattern); cmd++); + return (*cmd->function)(); +} +``` + +最难的部分是函数 matchCommand 的实现。但正如你在下面看到的,这也可以在不到 100 行的代码中完成。 + +# match.h + +```c +#define MAX_PARAMS 26 + +extern const char *params[]; + +#define paramByLetter(letter) (params + (letter) - 'A') + +extern bool matchCommand(const char *src, const char *pattern); +``` + +# match.c + +```c +#include +#include +#include +#include "object.h" +#include "misc.h" +#include "match.h" + +const char *params[MAX_PARAMS]; + +static const char *skipSpaces(const char *src) +{ + while (isspace(*src)) src++; + return src; +} + +static const char *matchSpaces(const char *src) +{ + return *src == '\0' || isspace(*src) ? skipSpaces(src) : NULL; +} + +static const char *matchTerminal(const char *src, char terminal) +{ + return terminal == ' ' ? matchSpaces(src) : + tolower(*src) == tolower(terminal) ? src + 1 : NULL; +} + +static const char *matchTag(const char *src, const char *tag, bool atEnd) +{ + while (src != NULL && *tag != '\0') + { + src = matchTerminal(src, *tag++); + } + return atEnd && src != NULL && *skipSpaces(src) != '\0' ? NULL : src; +} + +static const char *matchParam(const char *src, const char **par, bool loose) +{ + const char *restOfSrc = loose ? src + strlen(src) : NULL; + OBJECT *obj; + for (obj = objs; obj < endOfObjs; obj++) + { + const char **tag; + for (tag = obj->tags; *tag != NULL; tag++) + { + const char *behindMatch = matchTag(src, *tag, loose); + if (behindMatch != NULL && strlen(*tag) > strlen(*par)) + { + *par = *tag; + restOfSrc = behindMatch; + } + } + } + if (**par == '\0') + { + *par = src; + } + return restOfSrc; +} + +bool matchCommand(const char *src, const char *pattern) +{ + const char **par; + for (par = params; par < params + MAX_PARAMS; par++) + { + *par = ""; + } + for (src = skipSpaces(src); src != NULL && *pattern != '\0'; pattern++) + { + src = isupper(*pattern) + ? matchParam(src, paramByLetter(*pattern), pattern[1] == '\0') + : matchTerminal(src, *pattern); + } + return src != NULL && *skipSpaces(src) == '\0'; +} +``` + +我们调整各种命令的实现,以利用新的数组参数。 + +# inventory.h + +```c +extern bool executeGet(void); +extern bool executeDrop(void); +extern bool executeAsk(void); +extern bool executeGive(void); +extern bool executeInventory(void); +``` + +# inventory.c + +```c +#include +#include +#include "object.h" +#include "misc.h" +#include "match.h" +#include "noun.h" +#include "move.h" + +bool executeGet(void) +{ + OBJECT *obj = getVisible("what you want to get", params[0]); + switch (getDistance(player, obj)) + { + case distSelf: + printf("You should not be doing that to yourself.\n"); + break; + case distHeld: + printf("You already have %s.\n", obj->description); + break; + case distOverthere: + printf("Too far away, move closer please.\n"); + break; + case distUnknownObject: + // already handled by getVisible + break; + default: + if (obj->location != NULL && obj->location->health > 0) + { + printf("You should ask %s nicely.\n", obj->location->description); + } + else + { + moveObject(obj, player); + } + } + return true; +} + +bool executeDrop(void) +{ + moveObject(getPossession(player, "drop", params[0]), player->location); + return true; +} + +bool executeAsk(void) +{ + moveObject(getPossession(actorHere(), "ask", params[0]), player); + return true; +} + +bool executeGive(void) +{ + moveObject(getPossession(player, "give", params[0]), actorHere()); + return true; +} + +bool executeInventory(void) +{ + if (listObjectsAtLocation(player) == 0) + { + printf("You are empty-handed.\n"); + } + return true; +} +``` + +我们在上一章中添加的模块也是如此。 + +# opemclose.h + +```c +extern bool executeOpen(void); +extern bool executeClose(void); +extern bool executeLock(void); +extern bool executeUnlock(void); +``` + +# openclose.c + +```c +#include +#include +#include "object.h" +#include "match.h" +#include "reach.h" + +bool executeOpen(void) +{ + OBJECT *obj = reachableObject("what you want to open", params[0]); + if (obj != NULL) (*obj->open)(); + return true; +} + +bool executeClose(void) +{ + OBJECT *obj = reachableObject("what you want to close", params[0]); + if (obj != NULL) (*obj->close)(); + return true; +} + +bool executeLock(void) +{ + OBJECT *obj = reachableObject("what you want to lock", params[0]); + if (obj != NULL) (*obj->lock)(); + return true; +} + +bool executeUnlock(void) +{ + OBJECT *obj = reachableObject("what you want to unlock", params[0]); + if (obj != NULL) (*obj->unlock)(); + return true; +} +``` + +在 location.c 中,look around 命令被赋予了自己的功能,与检查特定对象的 look 命令分开(见第 7-12 行)。 + +# location.h + +```c +extern bool executeLookAround(void); +extern bool executeLook(void); +extern bool executeGo(void); +``` + +# location.c + +```c +#include +#include +#include "object.h" +#include "misc.h" +#include "match.h" +#include "noun.h" + +bool executeLookAround(void) +{ + printf("You are in %s.\n", player->location->description); + listObjectsAtLocation(player->location); + return true; +} + +bool executeLook(void) +{ + OBJECT *obj = getVisible("what you want to look at", params[0]); + switch (getDistance(player, obj)) + { + case distHereContained: + printf("Hard to see, try to get it first.\n"); + break; + case distOverthere: + printf("Too far away, move closer please.\n"); + break; + case distNotHere: + printf("You don't see any %s here.\n", params[0]); + break; + case distUnknownObject: + // already handled by getVisible + break; + default: + printf("%s\n", obj->details); + listObjectsAtLocation(obj); + } + return true; +} + +static void movePlayer(OBJECT *passage) +{ + printf("%s\n", passage->textGo); + if (passage->destination != NULL) + { + player->location = passage->destination; + printf("\n"); + executeLookAround(); + } +} + +bool executeGo(void) +{ + OBJECT *obj = getVisible("where you want to go", params[0]); + switch (getDistance(player, obj)) + { + case distOverthere: + movePlayer(getPassage(player->location, obj)); + break; + case distNotHere: + printf("You don't see any %s here.\n", params[0]); + break; + case distUnknownObject: + // already handled by getVisible + break; + default: + movePlayer(obj); + } + return true; +} +``` + +我们的游戏仍然只接受简单的动词-名词命令,但新的解析器确实有可能接受有多个名词的命令,如 "put coin in box",这将在下一章中演示。 + +新的解析器比原来的解析器有了很大的改进,但以今天的标准来看,它仍然远远不够完美。例如,没有结构性的方法可以用一个命令操纵多个对象("获得硬币、钥匙和灯")或连续执行两个或多个命令("获得钥匙然后向东走")。这对于一个 RPG 游戏来说限制实在太大了! + +在真正意义上,我们的分析器甚至不是一个分析器,它只是一个简单的模式匹配器。我们认为,对于一个简单的冒险游戏来说,它的工作已经足够好了。它的一些缺陷可以通过添加更多的模式而得到缓解。但最终,你会遇到它的局限性,你可能想转到更成熟的东西上。在这种情况下,我们推荐使用一个体面的分析器生成器(例如 Yacc)。请注意,这不是一件容易做到的事情。目前,这已经超出了本教程的范围。 +输出样例 + +Welcome to Little Cave Adventure. +You are in an open field. +You see: +a silver coin +a burly guard +a cave entrance to the east +dense forestall around + +--> get coin +You pick up a silver coin. + +--> give coin +You give a silver coin to a burly guard. + +--> go cave +You walk into the cave. + +You are in a little cave. +You see: +an exit to the west +solid rock all around +a closed door to the south +a tiny key + +--> get key +You pick up a tiny key. + +--> go south +The door is closed. + +--> open door +You open a closed door to the south. + +--> go south +You walk through the door into a backroom. + +You are in a backroom. +You see: +solid rock all around +an open door to the north +a wooden box + +--> unlock box +You unlock a wooden box. + +--> open box +You open a wooden box. + +--> examine box +The box is open. +You see: +a gold coin + +--> get gold +You get a gold coin from a wooden box. + +--> quit + +Bye! diff --git a/3.编程思维体系构建/3.4.6.14.丰富命令.md b/3.编程思维体系构建/3.4.6.14.丰富命令.md new file mode 100644 index 0000000..80f86f0 --- /dev/null +++ b/3.编程思维体系构建/3.4.6.14.丰富命令.md @@ -0,0 +1,364 @@ +# 14.丰富命令 + +是时候证明新的解析器确实有能力解释更复杂的命令了。 + +到目前为止,我们的解析器只接受简单的动词-名词命令。让我们试着解析一些更复杂的命令,比如: + +- get coin from box +- put coin in box +- ask coin from guard +- give coin to guard + +在 parsexec.c 中添加模式似乎很简单。 + +- get A from B +- put A in B +- ask A from B +- give A to B + +思考题:你能否自行实现这些命令 + +但是正如前一章所解释的,类似的命令(比如 "从 B 中获取 A "和已经存在的 "获取 A")必须以正确的顺序排列。如果'get A'首先出现,那么它将消耗任何以'get'开头的命令,包括所有应该被新模式匹配的命令。简而言之,"从 B 获取 A "必须出现在 "获取 A "之前。 + +有些命令(例如 put)没有单名词的变体。然而,我们将添加一个带有单非词的模式('put A')。我们需要这个模式来正确捕捉一个拼写错误的名词。完整的模式 "把 A 放进 B "不会匹配 "把 Koin 放进盒子 "这样的命令。正如前一章所解释的,只有模式末尾的非终端才能够捕捉到拼写错误的名词或其他不被识别的名词。 + +这也适用于有两个以上的名词的命令。有 n 个名词,你需要 n 个模式来处理所有可能的不匹配。一个有三个参数的例子: + +1. “paint A on B with C” +2. “paint A on B” +3. “paint A” + +同样,模式的顺序是至关重要的:如果 "涂抹 A "在最上面(意味着它将被首先尝试),那么它将消耗所有的涂抹命令,包括那些本应由 1 和 2 捕获的命令。 + +目前,我们不需要有两个以上非终结点的模式。 + +# parsexec.h + +```c +extern bool parseAndExecute(const char *input); +``` + +# parsexec.c + +```c +#include +#include +#include +#include "object.h" +#include "misc.h" +#include "match.h" +#include "location.h" +#include "inventory.h" +#include "inventory2.h" +#include "openclose.h" + +typedef struct +{ + const char *pattern; + bool (*function)(void); +} COMMAND; + +static bool executeQuit(void) +{ + return false; +} + +static bool executeNoMatch(void) +{ + const char *src = *params; + int len; + for (len = 0; src[len] != '\0' && !isspace(src[len]); len++); + if (len > 0) printf("I don't know how to '%.*s'.\n", len, src); + return true; +} + +bool parseAndExecute(const char *input) +{ + static const COMMAND commands[] = + { + { "quit" , executeQuit }, + { "look" , executeLookAround }, + { "look around" , executeLookAround }, + { "look at A" , executeLook }, + { "look A" , executeLook }, + { "examine A" , executeLook }, + { "go to A" , executeGo }, + { "go A" , executeGo }, + { "get A from B" , executeGetFrom }, + { "get A" , executeGet }, + { "put A in B" , executePutIn }, + { "drop A in B" , executePutIn }, + { "drop A" , executeDrop }, + { "ask A from B" , executeAskFrom }, + { "ask A" , executeAsk }, + { "give A to B" , executeGiveTo }, + { "give A" , executeGive }, + { "inventory" , executeInventory }, + { "open A" , executeOpen }, + { "close A" , executeClose }, + { "lock A" , executeLock }, + { "unlock A" , executeUnlock }, + { "A" , executeNoMatch } + }; + const COMMAND *cmd; + for (cmd = commands; !matchCommand(input, cmd->pattern); cmd++); + return (*cmd->function)(); +} +``` + +在一个新的模块 inventory2.c 中,我们实现了新的多名词命令 get、drop/put、ask 和 give。现在我们终于可以把金币放回盒子里了(可喜可贺,可喜可贺)。 + +# inventory2.h + +```c +extern bool executeGetFrom(void); +extern bool executePutIn(void); +extern bool executeAskFrom(void); +extern bool executeGiveTo(void); +``` + +# inventory2.c + +```c +#include +#include +#include "object.h" +#include "match.h" +#include "noun.h" +#include "move.h" +#include "reach.h" + +bool executeGetFrom(void) +{ + OBJECT *from = reachableObject("where to get that from", params[1]); + if (from != NULL && getVisible("what you want to get", params[0]) != NULL) + { + if (from->health > 0) + { + printf("You should ask %s nicely.\n", from->description); + } + else + { + moveObject(getPossession(from, "get", params[0]), player); + } + } + return true; +} + +bool executePutIn(void) +{ + OBJECT *obj = getPossession(player, "put", params[0]); + if (obj != NULL) + { + OBJECT *to = reachableObject("where to put that in", params[1]); + if (to != NULL) + { + if (to->health > 0) + { + printf("You should offer that nicely to %s.\n", to->description); + } + else + { + moveObject(obj, to); + } + } + } + return true; +} + +bool executeAskFrom(void) +{ + OBJECT *from = reachableObject("who to ask that", params[1]); + if (from != NULL) + { + if (from->health > 0) + { + if (getVisible("what you want to ask", params[0]) != NULL) + { + moveObject(getPossession(from, "ask", params[0]), player); + } + } + else + { + printf("There is no response from %s.\n", from->description); + } + } + return true; +} + +bool executeGiveTo(void) +{ + OBJECT *obj = getPossession(player, "give", params[0]); + if (obj != NULL) + { + OBJECT *to = reachableObject("who to give that to", params[1]); + if (to != NULL) + { + if (to->health > 0) + { + moveObject(obj, to); + } + else + { + printf("No eagerness is shown by %s.\n", to->description); + } + } + } + return true; +} +``` + +仔细观察上面的代码,你可能会注意到,像 "把硬币放进森林 "和 "把硬币放进盒子 "这样的命令(当盒子被关闭时)会得到以下奇怪的回答:"这太重了。" 这是因为大多数物体(包括封闭的盒子,以及像森林这样的场景)容纳其他物体的能力为零。这是正确的,但这个信息是很不恰当的。为了避免这种情况,我们将为那些容量为零的物体引入一个单独的信息:"这样做似乎不太适合。" + +先想想有没有什么办法解决? + +然而,当它作为对 "把钥匙放进盒子里 "的回应时,这个特殊的信息是完全误导的。所以我们将为这种特殊的对象组合做一个特殊的例外。 + +# move.c + +```c +#include +#include +#include "object.h" +#include "misc.h" + +static int weightOfContents(OBJECT *container) +{ + int sum = 0; + OBJECT *obj; + for (obj = objs; obj < endOfObjs; obj++) + { + if (isHolding(container, obj)) sum += obj->weight; + } + return sum; +} + +static void describeMove(OBJECT *obj, OBJECT *to) +{ + if (to == player->location) + { + printf("You drop %s.\n", obj->description); + } + else if (to != player) + { + printf(to->health > 0 ? "You give %s to %s.\n" : "You put %s in %s.\n", + obj->description, to->description); + } + else if (obj->location == player->location) + { + printf("You pick up %s.\n", obj->description); + } + else + { + printf("You get %s from %s.\n", + obj->description, obj->location->description); + } +} + +void moveObject(OBJECT *obj, OBJECT *to) +{ + if (obj == NULL) + { + // already handled by getVisible or getPossession + } + else if (to == NULL) + { + printf("There is nobody here to give that to.\n"); + } + else if (to->capacity == 0) + { + printf(obj == keyForBox && (to == closedBox || to == lockedBox) ? + "The key seems to fit the lock.\n" : + "It doesn't seem to fit in.\n"); + } + else if (obj->weight > to->capacity) + { + printf("That is way too heavy.\n"); + } + else if (obj->weight + weightOfContents(to) > to->capacity) + { + printf("That would become too heavy.\n"); + } + else + { + describeMove(obj, to); + obj->location = to; + } +} +``` + +毫无疑问,我们接受的命令越复杂,我们就需要花更多的精力来做出令人信服的答复。 +输出样例 + +Welcome to Little Cave Adventure. +You are in an open field. +You see: +a silver coin +a burly guard +a cave entrance to the east +dense forest all around + +--> get coin +You pick up a silver coin. + +--> give coin to guard +You give a silver coin to a burly guard. + +--> go cave +You walk into the cave. + +You are in a little cave. +You see: +an exit to the west +solid rock all around +a closed door to the south +a tiny key + +--> get key +You pick up a tiny key. + +--> go south +The door is closed. + +--> open door +You open a closed door to the south. + +--> go south +You walk through the door into a backroom. + +You are in a backroom. +You see: +solid rock all around +an open door to the north +a wooden box + +--> unlock box +You unlock a wooden box. + +--> open box +You open a wooden box. + +--> examine box +The box is open. +You see: +a gold coin + +--> get gold from box +You get a gold coin from a wooden box. + +--> inventory +You have: +a gold coin +a tiny key + +--> put gold in box +You put a gold coin in a wooden box. + +--> examine box +The box is open. +You see: +a gold coin + +--> quit + +Bye! diff --git a/3.编程思维体系构建/3.4.6.15.赋予明暗.md b/3.编程思维体系构建/3.4.6.15.赋予明暗.md new file mode 100644 index 0000000..af0e4e0 --- /dev/null +++ b/3.编程思维体系构建/3.4.6.15.赋予明暗.md @@ -0,0 +1,1005 @@ +# 15.赋予明暗 + +在许多冒险中,灯是一个重要的物品。没有它,你就无法穿越前方的黑暗洞穴。 + +在黑暗中的效果因游戏而异。通常情况下,它使命令 "look "没有效果。在一些游戏中(如 Zork),黑暗是致命的。在其他游戏中,只要你画出了黑暗区域的详细地图,在没有光源的情况下,你仍然可以取得进展。 + +我们的游戏将保持在这两者之间;在黑暗中不会让你被杀,但你也不能进入任何通道(具有光亮的通道将是一个例外)。对我们来说,让玩家跑进一个黑暗的区域,而没有不让他机会回到他来的地方,似乎是不公平的。 + +好吧,所以首先,我们来设计在黑暗中玩家无法看到周围环境。 + +# location.c + +```c +#include +#include +#include "object.h" +#include "misc.h" +#include "match.h" +#include "noun.h" + +bool executeLookAround(void) +{ + if (isLit(player->location)) + { + printf("You are in %s.\n", player->location->description); + } + else + { + printf("It is very dark in here.\n"); + } + listObjectsAtLocation(player->location); + return true; +} + +bool executeLook(void) +{ + OBJECT *obj = getVisible("what you want to look at", params[0]); + switch (getDistance(player, obj)) + { + case distHereContained: + printf("Hard to see, try to get it first.\n"); + break; + case distOverthere: + printf("Too far away, move closer please.\n"); + break; + case distNotHere: + printf("You don't see any %s here.\n", params[0]); + break; + case distUnknownObject: + // already handled by getVisible + break; + default: + printf("%s\n", obj->details); + listObjectsAtLocation(obj); + } + return true; +} + +static void movePlayer(OBJECT *passage) +{ + printf("%s\n", passage->textGo); + if (passage->destination != NULL) + { + player->location = passage->destination; + printf("\n"); + executeLookAround(); + } +} + +bool executeGo(void) +{ + OBJECT *obj = getVisible("where you want to go", params[0]); + switch (getDistance(player, obj)) + { + case distOverthere: + movePlayer(getPassage(player->location, obj)); + break; + case distNotHere: + printf("You don't see any %s here.\n", params[0]); + break; + case distUnknownObject: + // already handled by getVisible + break; + default: + movePlayer(obj); + } + return true; +} +``` + +其次,在黑暗中玩家无法看到或使用附近的物体。 + +# noun.c + +```c +#include +#include +#include +#include "object.h" +#include "misc.h" + +static bool objectHasTag(OBJECT *obj, const char *noun) +{ + if (noun != NULL && *noun != '\0') + { + const char **tag; + for (tag = obj->tags; *tag != NULL; tag++) + { + if (strcmp(*tag, noun) == 0) return true; + } + } + return false; +} + +static OBJECT ambiguousNoun; + +static OBJECT *getObject(const char *noun, OBJECT *from, DISTANCE maxDistance) +{ + OBJECT *obj, *res = NULL; + for (obj = objs; obj < endOfObjs; obj++) + { + if (objectHasTag(obj, noun) && getDistance(from, obj) <= maxDistance) + { + res = res == NULL ? obj : &ambiguousNoun; + } + } + return res; +} + +OBJECT *getVisible(const char *intention, const char *noun) +{ + OBJECT *obj = getObject(noun, player, distOverthere); + if (obj == NULL) + { + if (getObject(noun, player, distNotHere) == NULL) + { + printf("I don't understand %s.\n", intention); + } + else if (isLit(player->location)) + { + printf("You don't see any %s here.\n", noun); + } + else + { + printf("It's too dark.\n"); + } + } + else if (obj == &ambiguousNoun) + { + printf("Please be specific about which %s you mean.\n", noun); + obj = NULL; + } + return obj; +} + +OBJECT *getPossession(OBJECT *from, const char *verb, const char *noun) +{ + OBJECT *obj = NULL; + if (from == NULL) + { + printf("I don't understand who you want to %s.\n", verb); + } + else if ((obj = getObject(noun, from, distHeldContained)) == NULL) + { + if (getObject(noun, player, distNotHere) == NULL) + { + printf("I don't understand what you want to %s.\n", verb); + } + else if (from == player) + { + printf("You are not holding any %s.\n", noun); + } + else + { + printf("There appears to be no %s you can get from %s.\n", + noun, from->description); + } + } + else if (obj == &ambiguousNoun) + { + printf("Please be specific about which %s you want to %s.\n", + noun, verb); + obj = NULL; + } + else if (obj == from) + { + printf("You should not be doing that to %s.\n", obj->description); + obj = NULL; + } + return obj; +} +``` + +在这两种情况下,我们都使用了一个函数 isLit。它在 misc.c 中被定义(并且使用量增多了)。 + +# misc.h + +```c +typedef enum { + distSelf, + distHeld, + distHeldContained, + distLocation, + distHere, + distHereContained, + distOverthere, + distNotHere, + distUnknownObject +} DISTANCE; + +extern bool isHolding(OBJECT *container, OBJECT *obj); +extern bool isLit(OBJECT *location); +extern OBJECT *getPassage(OBJECT *from, OBJECT *to); +extern DISTANCE getDistance(OBJECT *from, OBJECT *to); +extern OBJECT *actorHere(void); +extern int listObjectsAtLocation(OBJECT *location); +``` + +# misc.c + +```c +#include +#include +#include "object.h" +#include "misc.h" + +bool isHolding(OBJECT *container, OBJECT *obj) +{ + return validObject(obj) && obj->location == container; +} + +bool isLit(OBJECT *target) +//检测光亮 +{ + OBJECT *obj; + if (validObject(target)) + { + if (target->light > 0) + { + return true; + } + for (obj = objs; obj < endOfObjs; obj++) + { + if (validObject(obj) && obj->light > 0 && + (isHolding(target, obj) || isHolding(target, obj->location))) + { + return true; + } + } + } + return false; +} + +static bool isNoticeable(OBJECT *obj) +{ + return obj->location == player || + isLit(obj) || isLit(obj->prospect) || isLit(player->location); +} + +OBJECT *getPassage(OBJECT *from, OBJECT *to) +{ + if (from != NULL && to != NULL) + { + OBJECT *obj; + for (obj = objs; obj < endOfObjs; obj++) + { + if (isHolding(from, obj) && obj->prospect == to) + { + return obj; + } + } + } + return NULL; +} + +DISTANCE getDistance(OBJECT *from, OBJECT *to) +{ + return to == NULL ? distUnknownObject : + !validObject(to) ? distNotHere : + to == from ? distSelf : + isHolding(from, to) ? distHeld : + !isNoticeable(to) ? distNotHere : + isHolding(to, from) ? distLocation : + isHolding(from->location, to) ? distHere : + isHolding(from, to->location) ? distHeldContained : + isHolding(from->location, to->location) ? distHereContained : + getPassage(from->location, to) != NULL ? distOverthere : + distNotHere; +} + +OBJECT *actorHere(void) +{ + OBJECT *obj; + for (obj = objs; obj < endOfObjs; obj++) + { + if (isHolding(player->location, obj) && obj != player && + isNoticeable(obj) && obj->health > 0) + { + return obj; + } + } + return NULL; +} + +int listObjectsAtLocation(OBJECT *location) +{ + int count = 0; + OBJECT *obj; + for (obj = objs; obj < endOfObjs; obj++) + { + if (obj != player && isHolding(location, obj) && isNoticeable(obj)) + { + if (count++ == 0) + { + printf("%s:\n", location->contents); + } + printf("%s\n", obj->description); + } + } + return count; +``` + +注意: + +- isLit 将被用来检查一个给定的位置是亮还是暗,但是,仅仅检查位置的属性光线是不够的,因为位置可能被一盏灯照亮。 +- isNoticeable 函数相比 isLit 更进一步。它对每个物体都有效,而不仅仅是地点和灯。它还考虑到玩家库存中的物体总是可以被使用,即使是在黑暗中。 +- 第 60 行:附近的物体仍然隐藏在黑暗中,被视为'不在这里'。这自然可以防止游戏泄露玩家不应该知道的物体的信息。 + +思考题:你还能想到那哪些可以改进的地方吗? + +我们为实现 isLit 函数的功能从而使用了一个新的属性 light。 + +# object.awk + +``` +BEGIN { + count = 0; + obj = ""; + if (pass == "c2") { + print "\nstatic bool alwaysTrue(void) { return true; }"; + print "\nOBJECT objs[] = {"; + } +} + +/^- / { + outputRecord(","); + obj = $2; + prop["condition"] = "alwaysTrue"; + prop["description"] = "NULL"; + prop["tags"] = ""; + prop["location"] = "NULL"; + prop["destination"] = "NULL"; + prop["prospect"] = ""; + prop["details"] = "\"You see nothing special.\""; + prop["contents"] = "\"You see\""; + prop["textGo"] = "\"You can't get much closer than this.\""; + prop["weight"] = "99"; + prop["capacity"] = "0"; + prop["health"] = "0"; + prop["light"] = "0"; + prop["open"] = "cannotBeOpened"; + prop["close"] = "cannotBeClosed"; + prop["lock"] = "cannotBeLocked"; + prop["unlock"] = "cannotBeUnlocked"; +} + +obj && /^[ \t]+[a-z]/ { + name = $1; + $1 = ""; + if (name in prop) { + prop[name] = $0; + if (/^[ \t]*\{/) { + prop[name] = name count; + if (pass == "c1") print "static bool " prop[name] "(void) " $0; + } + } + else if (pass == "c2") { + print "#error \"" FILENAME " line " NR ": unknown attribute '" name "'\""; + } +} + +!obj && pass == (/^#include/ ? "c1" : "h") { + print; +} + +END { + outputRecord("\n};"); + if (pass == "h") { + print "\n#define endOfObjs\t(objs + " count ")"; + print "\n#define validObject(obj)\t" \ + "((obj) != NULL && (*(obj)->condition)())"; + } +} + +function outputRecord(separator) +{ + if (obj) { + if (pass == "h") { + print "#define " obj "\t(objs + " count ")"; + } + else if (pass == "c1") { + print "static const char *tags" count "[] = {" prop["tags"] ", NULL};"; + } + else if (pass == "c2") { + print "\t{\t/* " count " = " obj " */"; + print "\t\t" prop["condition"] ","; + print "\t\t" prop["description"] ","; + print "\t\ttags" count ","; + print "\t\t" prop["location"] ","; + print "\t\t" prop["destination"] ","; + print "\t\t" prop[prop["prospect"] ? "prospect" : "destination"] ","; + print "\t\t" prop["details"] ","; + print "\t\t" prop["contents"] ","; + print "\t\t" prop["textGo"] ","; + print "\t\t" prop["weight"] ","; + print "\t\t" prop["capacity"] ","; + print "\t\t" prop["health"] ","; + print "\t\t" prop["light"] ","; + print "\t\t" prop["open"] ","; + print "\t\t" prop["close"] ","; + print "\t\t" prop["lock"] ","; + print "\t\t" prop["unlock"]; + print "\t}" separator; + delete prop; + } + count++; + } +} +``` + +默认情况下,亮度为零意味着一个物体不发光。在大白天的每一个地点(通常是除了地下的所有地点)都会被赋予一个正(大于 0)的光线值。其实是什么值并不重要,只要它不是零就可以了。我们还将添加一盏灯,玩家可以携带它来穿越黑暗区域。 + +# object.txt + +``` +#include +#include +#include "object.h" +#include "toggle.h" + +typedef struct object { + bool (*condition)(void); + const char *description; + const char **tags; + struct object *location; + struct object *destination; + struct object *prospect; + const char *details; + const char *contents; + const char *textGo; + int weight; + int capacity; + int health; + int light; + void (*open)(void); + void (*close)(void); + void (*lock)(void); + void (*unlock)(void); +} OBJECT; + +extern OBJECT objs[]; + +- field + description "an open field" + tags "field" + details "The field is a nice and quiet place under a clear blue sky." + capacity 9999 + light 100 + //到目前为止,场地是唯一有 "自然 "光线的地方。 + +- cave + description "a little cave" + tags "cave" + details "The cave is just a cold, damp, rocky chamber." + capacity 9999 + +- silver + description "a silver coin" + tags "silver", "coin", "silver coin" + location field + details "The coin has an eagle on the obverse." + weight 1 + +- gold + description "a gold coin" + tags "gold", "coin", "gold coin" + location openBox + details "The shiny coin seems to be a rare and priceless artefact." + weight 1 + +- guard + description "a burly guard" + tags "guard", "burly guard" + location field + details "The guard is a really big fellow." + contents "He has" + health 100 + capacity 20 + +- player + description "yourself" + tags "yourself" + location field + details "You would need a mirror to look at yourself." + contents "You have" + health 100 + capacity 20 + +- intoCave + condition { return guard->health == 0 || silver->location == guard; } + description "a cave entrance to the east" + tags "east", "entrance" + location field + destination cave + details "The entrance is just a narrow opening in a small outcrop." + textGo "You walk into the cave." + open isAlreadyOpen + +- intoCaveBlocked + condition { return guard->health > 0 && silver->location != guard; } + description "a cave entrance to the east" + tags "east", "entrance" + location field + prospect cave + details "The entrance is just a narrow opening in a small outcrop." + textGo "The guard stops you from walking into the cave." + open isAlreadyOpen + +- exitCave + description "an exit to the west" + tags "west", "exit" + location cave + destination field + details "Sunlight pours in through an opening in the cave's wall." + textGo "You walk out of the cave." + open isAlreadyOpen + +- wallField + description "dense forest all around" + tags "west", "north", "south", "forest" + location field + details "The field is surrounded by trees and undergrowth." + textGo "Dense forest is blocking the way." + +- wallCave + description "solid rock all around" + tags "east", "north", "rock" + location cave + details "Carved in stone is a secret password 'abccb'." + textGo "Solid rock is blocking the way." + +- backroom + description "a backroom" + tags "backroom" + details "The room is dusty and messy." + capacity 9999 + +- wallBackroom + description "solid rock all around" + tags "east", "west", "south", "rock" + location backroom + details "Trendy wallpaper covers the rock walls." + textGo "Solid rock is blocking the way." + +- openDoorToBackroom + description "an open door to the south" + tags "south", "door", "doorway" + destination backroom + details "The door is open." + textGo "You walk through the door into a backroom." + open isAlreadyOpen + close toggleDoorToBackroom + +- closedDoorToBackroom + description "a closed door to the south" + tags "south", "door", "doorway" + location cave + prospect backroom + details "The door is closed." + textGo "The door is closed." + open toggleDoorToBackroom + close isAlreadyClosed + +- openDoorToCave + description "an open door to the north" + tags "north", "door", "doorway" + destination cave + details "The door is open." + textGo "You walk through the door into the cave." + open isAlreadyOpen + close toggleDoorToCave + +- closedDoorToCave + description "a closed door to the north" + tags "north", "door", "doorway" + location backroom + prospect cave + details "The door is closed." + textGo "The door is closed." + open toggleDoorToCave + close isAlreadyClosed + +- openBox + description "a wooden box" + tags "box", "wooden box" + details "The box is open." + weight 5 + capacity 10 + open isAlreadyOpen + close toggleBox + lock isStillOpen + unlock isAlreadyOpen + +- closedBox + description "a wooden box" + tags "box", "wooden box" + details "The box is closed." + weight 5 + open toggleBox + close isAlreadyClosed + lock toggleBoxLock + unlock isAlreadyUnlocked + +- lockedBox + description "a wooden box" + tags "box", "wooden box" + location backroom + details "The box is closed." + weight 5 + open isStillLocked + close isAlreadyClosed + lock isAlreadyLocked + unlock toggleBoxLock + +- keyForBox + description "a tiny key" + tags "key", "tiny key" + location cave + details "The key is really small and shiny." + weight 1 + +- lampOff + description "a lamp" + tags "lamp" + location field + details "The lamp is off." + weight 5 + +- lampOn + description "a lamp" + tags "lamp" + details "The lamp is on." + weight 5 + light 100 +``` + +注意:对于灯,我们实际上有两个对象:打开的灯和关闭的灯,但玩家一次只能看到一个。它们一起作为一个单一的项目。 + +我们将添加一些命令,我们可以用来打开和关闭灯。 + +# parsexec.c + +```c +#include +#include +#include +#include "object.h" +#include "misc.h" +#include "match.h" +#include "location.h" +#include "inventory.h" +#include "inventory2.h" +#include "openclose.h" +#include "onoff.h" + +typedef struct +{ + const char *pattern; + bool (*function)(void); +} COMMAND; + +static bool executeQuit(void) +{ + return false; +} + +static bool executeNoMatch(void) +{ + const char *src = *params; + int len; + for (len = 0; src[len] != '\0' && !isspace(src[len]); len++); + if (len > 0) printf("I don't know how to '%.*s'.\n", len, src); + return true; +} + +bool parseAndExecute(const char *input) +{ + static const COMMAND commands[] = + { + { "quit" , executeQuit }, + { "look" , executeLookAround }, + { "look around" , executeLookAround }, + { "look at A" , executeLook }, + { "look A" , executeLook }, + { "examine A" , executeLook }, + { "go to A" , executeGo }, + { "go A" , executeGo }, + { "get A from B" , executeGetFrom }, + { "get A" , executeGet }, + { "put A in B" , executePutIn }, + { "drop A in B" , executePutIn }, + { "drop A" , executeDrop }, + { "ask A from B" , executeAskFrom }, + { "ask A" , executeAsk }, + { "give A to B" , executeGiveTo }, + { "give A" , executeGive }, + { "inventory" , executeInventory }, + { "open A" , executeOpen }, + { "close A" , executeClose }, + { "lock A" , executeLock }, + { "unlock A" , executeUnlock }, + { "turn on A" , executeTurnOn }, + { "turn off A" , executeTurnOff }, + { "turn A on" , executeTurnOn }, + { "turn A off" , executeTurnOff }, + { "A" , executeNoMatch } + }; + const COMMAND *cmd; + for (cmd = commands; !matchCommand(input, cmd->pattern); cmd++); + return (*cmd->function)(); + } +``` + +下面是这些命令的实现。 + +# onoff.h + +```c +extern bool executeTurnOn(void); +extern bool executeTurnOff(void); +``` + +# onoff.c + +```c +#include +#include +#include "object.h" +#include "match.h" +#include "reach.h" +#include "toggle.h" + +bool executeTurnOn(void) +{ + OBJECT *obj = reachableObject("what you want to turn on", params[0]); + if (obj != NULL) + { + if (obj == lampOff) + { + toggleLamp(); + } + else + { + printf(obj == lampOn ? "The lamp is already on.\n" + : "You cannot turn that on.\n"); + } + } + return true; +} + +bool executeTurnOff(void) +{ + OBJECT *obj = reachableObject("what you want to turn off", params[0]); + if (obj != NULL) + { + if (obj == lampOn) + { + toggleLamp(); + } + else + { + printf(obj == lampOff ? "The lamp is already off.\n" + : "You cannot turn that off.\n"); + } + } + return true; +} +``` + +为了打开和关闭灯,我们将使用我们用来打开和关闭门和盒子的相同技巧(见第 13 章)。 + +# toggle.h + +```c +extern void cannotBeOpened(void); +extern void cannotBeClosed(void); +extern void cannotBeLocked(void); +extern void cannotBeUnlocked(void); + +extern void isAlreadyOpen(void); +extern void isAlreadyClosed(void); +extern void isAlreadyLocked(void); +extern void isAlreadyUnlocked(void); + +extern void isStillOpen(void); +extern void isStillLocked(void); + +extern void toggleDoorToBackroom(void); +extern void toggleDoorToCave(void); +extern void toggleBox(void); +extern void toggleBoxLock(void); + +extern void toggleLamp(void); +``` + +# toggle.c + +```c +#include +#include +#include "object.h" +#include "misc.h" +#include "location.h" + +static void swapLocations(const char *verb1, OBJECT *obj1, + const char *verb2, OBJECT *obj2) +{ + OBJECT *tmp = obj1->location; + OBJECT *obj = tmp != NULL ? obj1 : obj2; + const char *verb = tmp != NULL ? verb1 : verb2; + obj1->location = obj2->location; + obj2->location = tmp; + if (verb != NULL) printf("You %s %s.\n", verb, obj->description); +} + +void cannotBeOpened(void) { printf("That cannot be opened.\n"); } +void cannotBeClosed(void) { printf("That cannot be closed.\n"); } +void cannotBeLocked(void) { printf("That cannot be locked.\n"); } +void cannotBeUnlocked(void) { printf("That cannot be unlocked.\n"); } + +void isAlreadyOpen(void) { printf("That is already open.\n"); } +void isAlreadyClosed(void) { printf("That is already closed.\n"); } +void isAlreadyLocked(void) { printf("That is already locked.\n"); } +void isAlreadyUnlocked(void) { printf("That is already unlocked.\n"); } + +void isStillOpen(void) { printf("That is still open.\n"); } +void isStillLocked(void) { printf("That is locked.\n"); } + +void toggleDoorToBackroom(void) +{ + swapLocations(NULL, openDoorToCave, NULL, closedDoorToCave); + swapLocations("close", openDoorToBackroom, "open", closedDoorToBackroom); +} + +void toggleDoorToCave(void) +{ + swapLocations(NULL, openDoorToBackroom, NULL, closedDoorToBackroom); + swapLocations("close", openDoorToCave, "open", closedDoorToCave); +} + +void toggleBox(void) +{ + swapLocations("close", openBox, "open", closedBox); +} + +void toggleBoxLock(void) +{ + if (keyForBox->location == player) + { + swapLocations("lock", closedBox, "unlock", lockedBox); + } + else + { + printf("You don't have a key.\n"); + } +} + +void toggleLamp(void) +{ + bool oldLit = isLit(player->location); + swapLocations("turn off", lampOn, "turn on", lampOff); + if (oldLit != isLit(player->location)) + { + printf("\n"); + executeLookAround(); + } +} +``` + +注意:当在黑暗区域打开灯光时,我们立即让玩家看一下周围的环境。这与'go'的命令的行为是一致的:当你把目光投向一个地方时,'look'就会自动执行。 + +你可能注意到我们在这里做了同样的事情,当把灯关掉的时候,很明显,这只会返回 "这里很黑",但这似乎也是一个相关的观察。 + +那么,既然我们无论如何都在做 "look around "的工作,64~68 中"if "语句的意义何在?好吧,这可以防止在大白天(即在野外)打开或关闭灯时,或在同一房间内任何其他光源仍在工作时,进行无用的 "环顾"。 + +最后,我们将在我们生成的地图上标记出黑暗的位置。 + +# map.awk + +```c +BEGIN { print "digraph map {\n\tnode [style=filled]"; } +/^- / { outputEdges(); obj = $2; delete a; } +/^[ \t]/ { a[$1] = $2; } +END { outputEdges(); outputNodes(); print "}"; } + +function outputEdges() +{ + color[obj] = a["light"] ? "white" : "grey"; + outputEdge(a["location"], a["destination"], ""); + outputEdge(a["location"], a["prospect"], " [style=dashed]"); +} + +function outputEdge(from, to, style) +{ + if (to) + { + nodes[to] = 1; + if (from) + { + nodes[from] = 1; + print "\t" from " -> " to style; + } + } +} + +function outputNodes() +{ + for (n in nodes) print "\t" n " [fillcolor=" color[n] "]"; +} +``` + +思考题:尝试将上面的伪代码用 C 语言实现其功能 + +玩家们,请注意不要把灯关掉,也不要把它丢掉。如果这样做,那么在黑暗中你将永远无法找回它。换言之,你会被困在黑暗之中! 幸运的是,下一章将提供一个撤销笨拙行动的方法。 + +输出样例:Welcome to Little Cave Adventure. +You are in an open field. +You see: +a silver coin +a burly guard +a cave entrance to the east +dense forestall around +a lamp + +--> get lamp +You pick up a lamp. + +--> get coin +You pick up a silver coin. + +--> give coin +You give a silver coin to a burly guard. + +--> go cave +You walk into the cave. + +It is very dark here. +You see: +an exit to the west + +--> get key +It's too dark. + +--> open door +It's too dark. + +--> go south +It's too dark. + +--> turn lamp on +You turn on a lamp. + +You are in a little cave. +You see: +an exit to the west +solid rock all around +a closed door to the south +a tiny key + +--> look around +You are in a little cave. +You see: +an exit to the west +solid rock all around +a closed door to the south +a tiny key + +--> get key +You pick up a tiny key. + +--> open door +You open a closed door to the south. + +--> go south +You walk through the door into a backroom. + +You are in a backroom. +You see: +solid rock all around +an open door to the north +a wooden box + +--> quit + +Bye diff --git a/3.编程思维体系构建/3.4.6.16.结语:你终将自由.md b/3.编程思维体系构建/3.4.6.16.结语:你终将自由.md new file mode 100644 index 0000000..660e468 --- /dev/null +++ b/3.编程思维体系构建/3.4.6.16.结语:你终将自由.md @@ -0,0 +1,15 @@ +# 结语:你终将自由 + +15 章的内容,你已经实现了一个最基本游戏的最基本功能,不知道你是不是感觉到满满的成就感呢? + +但是还是有非常多可以做的可以思考的。 + +比如说,你可以增加战斗功能,你可以设计自动生成的测试样例,你甚至可以添加联网联机功能,真正构造一个奇幻的世界。 + +我相信你经过上面的训练,已经初步理解了结构化编程的基本要义,可以走向更广阔罗的世界且游刃有余的自由的处理它了。 + +希望你可以牢记其中各种设计原则,并且不断精进不断练习。 + +也许有的同学没有完整的将他刷完,那也没关系,你完全可以在进行一小部分后就进行后面的实验。 + +不会造成什么太大的影响的,只不过你可能编程能力上会失去一个宝贵的锻炼机会罢了。 diff --git a/3.编程思维体系构建/3.4.6.2.探索未知.md b/3.编程思维体系构建/3.4.6.2.探索未知.md new file mode 100644 index 0000000..f036a42 --- /dev/null +++ b/3.编程思维体系构建/3.4.6.2.探索未知.md @@ -0,0 +1,147 @@ +# 2.探索未知 + +##### 驾驭项目, 而不是被项目驾驭 + +你和一个项目的关系会经历 4 个阶段: + +1. 被驾驭: 你对它一无所知 +2. 一知半解: 你对其中的主要模块和功能有了基本的了解 +3. 驾轻就熟: 你对整个项目的细节都了如指掌 +4. 为你所用: 你可以随心所欲地在项目中添加你认为有用的功能 + +如果你想要达成第二个阶段,你需要仔细学习不断探索更新的内容, 达到第三个阶段的主要手段是独立完成实验内容和独立调试. 至于要达到第四个阶段, 就要靠你的主观能动性了: 代码还有哪里做得不够好? 怎么样才算是够好? 应该怎么做才能达到这个目标? + +你毕业后到了工业界或学术界, 就会发现真实的项目也都是这样: + +1. 刚接触一个新项目, 不知道如何下手 +2. RTFM, RTFSC, 大致明白项目组织结构和基本的工作流程 +3. 运行项目的时候发现有非预期行为(可能是配置错误或环境错误, 可能是和已有项目对接出错, 也可能是项目自身的 bug), 然后调试. 在调试过程中, 对这些模块的理解会逐渐变得清晰. +4. 哪天需要你在项目中添加一个新功能, 你会发现自己其实可以胜任. + +这说明了: 如果你一遇到 bug 就找大神帮你调试, 你失去的机会和能力会比你想象的多得多 + +文字冒险游戏的基本交互很简单 + +1. 玩家输入命令。 +2. 程序 [解析](http://en.wikipedia.org/wiki/Parsing) 并执行命令。 +3. 重复步骤 1 和 2,直到玩家决定退出 + +那么,当命令很多的时候,如果你将他写在一起,一个文件有五六千行,我相信这样的情况你是不愿意去看的,因此,我们引入了函数的概念。 + +自行了解函数的概念,同时去了解当我需要引用别的文件的函数时该怎么办? + +了解一下什么是“驼峰原则”,我们为什么要依据它命名函数? + +下面的代码示例包含三个函数,每个步骤一个函数: + +1. 函数getInput。 +2. 函数parseAndExecute。 +3. 函数main,负责重复调用其他两个函数。 + +# main.c + +```c +#include +#include +#include "parsexec.h" +//当我需要引用别的文件 +static char input[100] = "look around"; +//定义全局变量 +static bool getInput(void) +{ + printf("\n--> "); + //你可以将他改成你喜欢的内容 + return fgets(input, sizeof input, stdin) != NULL; + //fgets用于收集键盘的输入 +} + +int main() +{ + printf("Welcome to Little Cave Adventure.\n"); + while (parseAndExecute(input) && getInput()); + printf("\nBye!\n"); + return 0; +} +``` + +注意:某些老版本的 C 语言不支持 bool 选项,你将他改为 int 是一样的。 + +思考题:static 是什么意思?我为什么要用他? + +# parsexec.h + +```c +extern bool parseAndExecute(char *input); +``` + +思考题:extern 是干什么的?.h 文件又在干嘛? + +哇,我用了一个指针!input 前面是个指针!!! + +指针是啥?[C 指针详解](https://www.runoob.com/w3cnote/c-pointer-detail.html) STFW(虽然都给你了) + +在这里用指针是为了传参的时候可以传字符串哦 + +# parsexec.c + +```c +#include +#include +#include + + +bool parseAndExecute(char *input) +{ + char *verb = strtok(input, " \n"); + char *noun = strtok(NULL, " \n"); + //strtok是string库下的一个函数 + if (verb != NULL) + { + if (strcmp(verb, "quit") == 0) + //strcmp也是 + { + return false; + } + else if (strcmp(verb, "look") == 0) + { + printf("It is very dark in here.\n"); + } + else if (strcmp(verb, "go") == 0) + { + printf("It's too dark to go anywhere.\n"); + } + else + { + printf("I don't know how to '%s'.\n", verb); + //%s是verb附加参数的占位符 + } + } + return true; +} +``` + +你的编译器可能会给出警告 the unused variable ‘noun’,这些不用担心,将会在下一章解决。 + +返回false将导致主循环结束。 + +RTFM&&STFW 搞懂 strtok 和 strcmp 的用法 + +考虑一下 NULL 是干什么的 + +测试样例 + +Welcome to Little Cave Adventure. +It is very dark in here. + +--> go north +It's too dark to go anywhere. + +--> look around +It is very dark in here. + +--> eat sandwich +I don't know how to 'eat'. + +--> quit + +Bye diff --git a/3.编程思维体系构建/3.4.6.3.指明地点.md b/3.编程思维体系构建/3.4.6.3.指明地点.md new file mode 100644 index 0000000..cd4b749 --- /dev/null +++ b/3.编程思维体系构建/3.4.6.3.指明地点.md @@ -0,0 +1,273 @@ +# 3.指明地点 + +某种极其糟糕的编程习惯 + +# Copy-paste + +我们很多同学在编程的过程中,可能会写出一大堆重复性很强的代码,在最近看的 pa 中,举了这样一个例子,你不需要看懂只需要感受到就可: + +``` +if (strcmp(s, "$0") == 0) + return cpu.gpr[0]._64; +else if (strcmp(s, "ra") == 0) + return cpu.gpr[1]._64; +else if (strcmp(s, "sp") == 0) + return cpu.gpr[2]._64; +else if (strcmp(s, "gp") == 0) + return cpu.gpr[3]._64; +else if (strcmp(s, "tp") == 0) + return cpu.gpr[4]._64; +else if (strcmp(s, "t0") == 0) + return cpu.gpr[5]._64; +else if (strcmp(s, "t1") == 0) + return cpu.gpr[6]._64; +else if (strcmp(s, "s2") == 0) + return cpu.gpr[7]._64; +else if (strcmp(s, "s0") == 0) + return cpu.gpr[8]._64; +else if (strcmp(s, "s1") == 0) + return cpu.gpr[9]._64; +else if (strcmp(s, "a0") == 0) + return cpu.gpr[10]._64; +else if (strcmp(s, "a1") == 0) + return cpu.gpr[11]._64; +else if (strcmp(s, "a2") == 0) + return cpu.gpr[12]._64; +else if (strcmp(s, "a3") == 0) + return cpu.gpr[13]._64; +else if (strcmp(s, "a4") == 0) + return cpu.gpr[14]._64; +else if (strcmp(s, "a5") == 0) + return cpu.gpr[15]._64; +else if (strcmp(s, "a6") == 0) + return cpu.gpr[16]._64; +else if (strcmp(s, "a7") == 0) + return cpu.gpr[17]._64; +else if (strcmp(s, "s2") == 0) + return cpu.gpr[18]._64; +else if (strcmp(s, "s3") == 0) + return cpu.gpr[19]._64; +else if (strcmp(s, "s4") == 0) + return cpu.gpr[20]._64; +else if (strcmp(s, "s5") == 0) + return cpu.gpr[21]._64; +else if (strcmp(s, "s6") == 0) + return cpu.gpr[22]._64; +else if (strcmp(s, "s7") == 0) + return cpu.gpr[23]._64; +else if (strcmp(s, "s8") == 0) + return cpu.gpr[24]._64; +else if (strcmp(s, "s8") == 0) + return cpu.gpr[25]._64; +else if (strcmp(s, "s10") == 0) + return cpu.gpr[26]._64; +else if (strcmp(s, "t2") == 0) + return cpu.gpr[27]._64; +else if (strcmp(s, "t3") == 0) + return cpu.gpr[28]._64; +else if (strcmp(s, "t4") == 0) + return cpu.gpr[29]._64; +else if (strcmp(s, "t5") == 0) + return cpu.gpr[30]._64; +else if (strcmp(s, "t5") == 0) + return cpu.gpr[31]._64; +``` + +以下是某论文的代码节选,可以说是错误的范例: + +```python +fx = torch.cat((xs[0], fs[0], xs[1], fs[1], xs[2], fs[2], xs[3], fs[3], xs[4], fs[4], xs[5], fs[5], xs[6], fs[6], xs[7], fs[7], + xs[8], fs[8], xs[9], fs[9], xs[10], fs[10], xs[11], fs[11], xs[12], fs[12], xs[13], fs[13], xs[14], fs[14], xs[15], fs[15], + xs[16], fs[16], xs[17], fs[17], xs[18], fs[18], xs[19], fs[19], xs[20], fs[20], xs[21], fs[21], xs[22], fs[22], xs[23], fs[23], + xs[24], fs[24], xs[25], fs[25], xs[26], fs[26], xs[27], fs[27], xs[28], fs[28], xs[29], fs[29], xs[30], fs[30], xs[31], fs[31]), 1) +bx = torch.cat((xs[0], bs[0], xs[1], bs[1], xs[2], bs[2], xs[3], bs[3], xs[4], bs[4], xs[5], bs[5], xs[6], bs[6], xs[7], bs[7], + xs[8], bs[8], xs[9], bs[9], xs[10], bs[10], xs[11], bs[11], xs[12], bs[12], xs[13], bs[13], xs[14], bs[14], xs[15], bs[15], + xs[16], bs[16], xs[17], bs[17], xs[18], bs[18], xs[19], bs[19], xs[20], bs[20], xs[21], bs[21], xs[22], bs[22], xs[23], bs[23], + xs[24], bs[24], xs[25], bs[25], xs[26], bs[26], xs[27], bs[27], xs[28], bs[28], xs[29], bs[29], xs[30], bs[30], xs[31], bs[31]), 1) +``` + +你想想,你遇到这么长的代码,你愿意看他吗? + +更可怕的是,这种编码模式可能会导致意想不到的 bug。 + +当你发现这些代码有 bug 的时候, 噩梦才刚刚开始. 也许花了好几天你又调出一个 bug 的时候, 才会想起这个 bug 你好像之前在哪里调过. 你也知道代码里面还有类似的 bug, 但你已经分辨不出哪些代码是什么时候从哪个地方复制过来的了. + +这种糟糕的编程习惯叫 Copy-Paste, 经过上面的分析, 相信你也已经领略到它的可怕了. 事实上, [周源源教授](https://cseweb.ucsd.edu/~yyzhou/)的团队在 2004 年就设计了一款工具 CP-Miner, 来自动检测操作系统代码中由于 Copy-Paste 造成的 bug. 这个工具还让周源源教授收获了一篇[系统方向顶级会议 OSDI 的论文](http://pages.cs.wisc.edu/~shanlu/paper/OSDI04-CPMiner.pdf), 这也是她当时所在学校 UIUC 史上的第一篇系统方向的顶级会议论文. + +后来周源源教授发现, 相比于操作系统, 应用程序的源代码中 Copy-Paste 的现象更加普遍. 于是她们团队把 CP-Miner 的技术应用到应用程序的源代码中, 并创办了 PatternInsight 公司. 很多 IT 公司纷纷购买 PatternInsight 的产品, 并要求提供相应的定制服务, 甚至 PatternInsight 公司最后还被 VMWare 收购了. + +这个故事折射出, 大公司中程序员的编程习惯也许不比你好多少, 他们也会写出 Copy-Paste 这种难以维护的代码. 但反过来说, 重视编码风格这些企业看中的能力, 你从现在就可以开始培养. + +传统上,文本冒险是由(许多)不同位置组成的虚拟世界。虽然这不是必需的(一些冒险发生在一个房间里!),但这是解释数据结构使用的好方法。 + +我们首先定义一个[结构](http://en.wikipedia.org/wiki/Struct_(C_programming_language))来表示一个位置。它包含两个简单的属性开始(稍后可能会有更多的属性)。 + +1. 描述:对物品进行描述 +2. 标记:具体的对其进行标记 + +```c +struct location { + const char *description; + const char *tag; + }; +``` + +思考题:我们为什么要用结构体来保存位置? + +``` + 这样子做有什么好处? +``` + +const 又是什么? + +接下来,我们定义一个位置数组。目前,我们保持它非常简单:只有两个位置。 + +```c +struct location locs[2]; +``` + +我们还可以使用初始值设定项立即填充所有静态数据。 + +```c +struct location locs[2] = { + {"an open field", "field"}, + {"a little cave", "cave"} +}; +``` + +让我们把它付诸实践。在上一章 (parsexec.c) 的代码示例中,我们更改了第 4、18 和 22 行)。 + +# parsexec.c + +```c +#include +#include +#include +#include "location.h" + +bool parseAndExecute(char *input) +{ + char *verb = strtok(input, " \n"); + char *noun = strtok(NULL, " \n"); + if (verb != NULL) + { + if (strcmp(verb, "quit") == 0) + { + return false; + } + else if (strcmp(verb, "look") == 0) + { + executeLook(noun); + } + else if (strcmp(verb, "go") == 0) + { + executeGo(noun); + } + else + { + printf("I don't know how to '%s'.\n", verb); + } + } + return true; +} +``` + +接下来,我们将一个新模块添加到项目中 + +# location.h + +```c +extern void executeLook(const char *noun); +extern void executeGo(const char *noun); +``` + +# location.c + +```c +#include +#include + +struct location { + const char *description; + const char *tag; +} +locs[] = { + {"an open field", "field"}, + {"a little cave", "cave"} +}; + +#define numberOfLocations (sizeof locs / sizeof *locs) +//欸?这个是干啥呢? +static unsigned locationOfPlayer = 0; + +void executeLook(const char *noun) +{ + if (noun != NULL && strcmp(noun, "around") == 0) + { + printf("You are in %s.\n", locs[locationOfPlayer].description); + } + else + { + printf("I don't understand what you want to see.\n"); + } +} + +void executeGo(const char *noun) +{ + unsigned i; + for (i = 0; i < numberOfLocations; i++) + { + if (noun != NULL && strcmp(noun, locs[i].tag) == 0) + { + if (i == locationOfPlayer) + { + printf("You can't get much closer than this.\n"); + } + else + { + printf("OK.\n"); + locationOfPlayer = i; + executeLook("around"); + } + return; + } + } + printf("I don't understand where you want to go.\n"); +} +``` + +在 C 语言中,你可以使用单个语句来定义类型(结构位置),声明变量(locs)并用其初始值填充它。 + +思考题:变量locs是[静态分配的](http://en.wikipedia.org/wiki/Static_memory_allocation),什么是静态分配? + +静态分配和动态分配有什么不同之处? + +复杂思考题:13 行宏定义好像实现了一个函数!很神奇!为什么不用函数来做这个知识呢? + +提示:这个问题涉及编程从代码到可执行文件的四个步骤,希望你可以认真学习和思考,如果你用 Linux 去完成。你可以尝试用 gcc 逐步输出编译结果。 + +测试样例: + +Welcome to Little Cave Adventure. +You are in an open field. + +--> go cave +OK. +You are in a little cave. + +--> go field +OK. +You are in an open field. + +--> go field +You can't get much closer than this. + +--> look around +You are in an open field. + +--> go kitchen +I don't understand where you want to go. + +--> quit + +Bye! diff --git a/3.编程思维体系构建/3.4.6.4.创建对象.md b/3.编程思维体系构建/3.4.6.4.创建对象.md new file mode 100644 index 0000000..ee03ffa --- /dev/null +++ b/3.编程思维体系构建/3.4.6.4.创建对象.md @@ -0,0 +1,355 @@ +# 4.创建对象 + +在我们继续之前,我在这里使用的是哲学意义上的“对象”一词。它与面向对象编程无关,也与JavaC#Python等编程语言中预定义的“对象”类型没有任何共同之处。下面,我将定义一个名为 object 的结构体。。 + +冒险游戏中的大多数谜题都围绕着物品。例子: + +- 必须找到一把钥匙,然后用来解锁某扇门。 +- 必须杀死守卫或者诱骗守卫才能开启房间 + +所以,为了表示这个物品,我们可以使用如下[结构](http://en.wikipedia.org/wiki/Struct_(C_programming_language)): + +- description: 对物品的描述 +- tag: 物品的类型 +- location: 物品所在的位置。这是对应上一章中定义的物品位置的指针。 + +```c +struct object { + const char *description; + const char *tag; + struct location *location; +} +objs[] = { + {"a silver coin", "silver", &locs[0]}, + {"a gold coin" , "gold" , &locs[1]}, + {"a burly guard", "guard" , &locs[0]} +}; +``` + +我们发现这一章的物品的信息和上一章好像也差不多呀,所以我们直接把他合并好了! + +```c +struct object { + const char *description; + const char *tag; + struct object *location; +} +objs[] = { + {"an open field", "field" , NULL}, + {"a little cave", "cave" , NULL}, + {"a silver coin", "silver", &objs[0]}, + {"a gold coin" , "gold" , &objs[1]}, + {"a burly guard", "guard" , &objs[0]} +}; +``` + +这样子我们的代码就会看起来更加简洁! + +我们发现 OBJECT 的结构体里面有一个指针和自己长得一样,不用担心,这和链表的操作类似。 + +思考题:链表是什么,为什么要有这么一个操作指针? + +链表和数组有什么异同点,他们分别在增删改查上有什么优劣? + +为了更容易地用那些所谓的物品或者是地点,我们将为每个元素定义一个名字 + +```c +#define field (objs + 0) +#define cave (objs + 1) +#define silver (objs + 2) +#define gold (objs + 3) +#define guard (objs + 4) +``` + +如何用各个元素的指针来方便的进行操作呢? + +```c +printf("You are in %s.\n", field->description); +``` + +然后用这样的操作可以列出一个物品里面所有的小东西 + +```c +struct object *obj; +for (obj = objs; obj < objs + 5; obj++) +{ + if (obj->location == cave) + { + printf("%s\n", obj->description); + } +} +``` + +暂停理解一下吧 + +那么,我们有合并这个物品(或地点)列表有什么好处呢?答案是这会让我们的代码变得更加简单,因为许多函数(如上面的函数通过这样的列表)只需要扫描单个列表就可以实现,而不是三个列表。有人可能会说没必要,因为每个命令仅适用于一种类型的对象: + +- 命令 go 适用于位置对象。 +- 命令 get 应用于获得物品。 +- 命令 kill 适应用于杀死人物。 + +但这种方法不太对劲,原因有三: + +1. 某些命令适用于多种类型的对象,尤其是检查。 +2. 有时候会出现很没意思的交互方式,比如说你要吃掉守卫,他说不行。 +3. 某些对象在游戏中可能具有多个角色。比如说队友系统,NPC 可以是你的物品也可以是对象 + +将所有对象放在一个大列表中,很容易添加一个名为“type”的属性来构造对象,以帮助我们区分不同类型的对象。 + +怎么做怎么遍历呢?先思考吧 + +但是,对象通常具有同样有效的其他特征: + +- Locations: 通过道路连接(将在后面介绍)。如果一个物体无法通过一条通道到达,那么它就不是一个位置。就是这么简单。 +- Items:玩家唯一可以捡起的物品;可以给他们整一个重量的属性 +- Actors:玩家唯一可以与之交谈,交易,战斗的对象;当然,前提是他们还活着!可以加一个 HP 属性 + +我们还要向数组中添加一个对象:玩家自己。 + +在上一章中,有一个单独的变量 locationOfPlayer。我们将删除它,然后换上用户的位置属性取代他! + +例如,此语句会将玩家移入洞穴: + +```c +player->location = cave; +``` + +此表达式返回玩家当前位置的描述: + +```c +player->location->description +``` + +是时候把它们放在一起了。我们从对象数组的全新模块开始 + +# Object.h + +```c +typedef struct object { + const char *description; + const char *tag; + struct object *location; +} OBJECT; + +extern OBJECT objs[]; + +#define field (objs + 0) +#define cave (objs + 1) +#define silver (objs + 2) +#define gold (objs + 3) +#define guard (objs + 4) +#define player (objs + 5) + +#define endOfObjs (objs + 6) +``` + +# Object.c + +```c +#include +#include "object.h" + +OBJECT objs[] = { + {"an open field", "field" , NULL }, + {"a little cave", "cave" , NULL }, + {"a silver coin", "silver" , field }, + {"a gold coin" , "gold" , cave }, + {"a burly guard", "guard" , field }, + {"yourself" , "yourself", field } +}; +``` + +注意:要编译此模块,编译器必须支持 Constant folding。这排除了一些更原始的编译器,如 [Z88DK](http://en.wikipedia.org/wiki/Z88DK)。 + +以下模块将帮助我们找到与指定名词匹配的对象。 + +# noun.h + +```c +extern OBJECT *getVisible(const char *intention, const char *noun); +``` + +# 指针?函数?希望你已经掌握这是什么了 + +# noun.c + +```c +#include +#include +#include +#include "object.h" + +static bool objectHasTag(OBJECT *obj, const char *noun) +{ + return noun != NULL && *noun != '\0' && strcmp(noun, obj->tag) == 0; +} + +static OBJECT *getObject(const char *noun) +{ + OBJECT *obj, *res = NULL; + for (obj = objs; obj < endOfObjs; obj++) + { + if (objectHasTag(obj, noun)) + { + res = obj; + } + } + return res; +} + +OBJECT *getVisible(const char *intention, const char *noun) +{ + OBJECT *obj = getObject(noun); + if (obj == NULL) + { + printf("I don't understand %s.\n", intention); + } + else if (!(obj == player || + //玩家本人。是的,这也是一个可见的物体。 + obj == player->location || + //玩家的当前位置。 + obj->location == player || + //玩家持有的物品 + obj->location == player->location || + //玩家当前位置的物体 + obj->location == NULL || + //玩家可以去的任意位置,具体完善在后面 + obj->location->location == player || + //玩家持有的另一个物体内的物体 + obj->location->location == player->location)) + //当前位置存在的另一个对象内部的对象 + { + printf("You don't see any %s here.\n", noun); + obj = NULL; + } + return obj; + //感受到注释有多伟大了吧 +} +``` + +这是另一个辅助程序的函数。它打印存在于特定位置的对象(物品,NPC)的列表。它将用于函数 executeLook,在下一章中,我们将介绍另一个需要它的命令。 + +# misc.h + +```c +extern int listObjectsAtLocation(OBJECT *location); +``` + +# misc.c + +```c +#include +#include "object.h" + +int listObjectsAtLocation(OBJECT *location) +{ + int count = 0; + OBJECT *obj; + for (obj = objs; obj < endOfObjs; obj++) + { + if (obj != player && obj->location == location) + //排除玩家在玩家的位置这种蠢东西 + { + if (count++ == 0) + //我们需要保证找到一个东西之前他不会打印you see + { + printf("You see:\n"); + } + printf("%s\n", obj->description); + } + } + return count; + //返回的是数目的数量,下一章对此做额外操作 +} +``` + +在 location.c 中,命令环顾四周的实现,并根据新的数据结构进行调整。旧的位置数组被删除,变量 locationOfPlayer 也是如此。 + +# location.h + +```c +extern void executeLook(const char *noun); +extern void executeGo(const char *noun); +``` + +# location.c + +```c +#include +#include +#include "object.h" +#include "misc.h" +#include "noun.h" + +void executeLook(const char *noun) +{ + if (noun != NULL && strcmp(noun, "around") == 0) + { + printf("You are in %s.\n", player->location->description); + listObjectsAtLocation(player->location); + //显示当前位置的玩家和物品 + } + else + { + printf("I don't understand what you want to see.\n"); + } +} + +void executeGo(const char *noun) +{ +//消除了函数executeGo中的循环,代码更优雅了~ + OBJECT *obj = getVisible("where you want to go", noun); + if (obj == NULL) + { + // already handled by getVisible + } + else if (obj->location == NULL && obj != player->location) + { + printf("OK.\n"); + player->location = obj; + executeLook("around"); + } + else + { + printf("You can't get much closer than this.\n"); + } +} +``` + +你可以自由添加对象哦,自己设计一个游戏道具一定很有意思 + +现在金银宝物散落一地可是我们捡不起来,下一章我们会试着解决问题 + +测试样例: + +Welcome to Little Cave Adventure. +You are in an open field. +You see: +a silver coin +a burly guard + +--> go cave +OK. +You are in a little cave. +You see: +a gold coin + +--> go field +OK. +You are in an open field. +You see: +a silver coin +a burly guard + +--> go field +You can't get much closer than this. + +--> look around +You are in an open field. +You see: +a silver coin +a burly guard + +--> quit + +Bye! diff --git a/3.编程思维体系构建/3.4.6.5.捡起物品.md b/3.编程思维体系构建/3.4.6.5.捡起物品.md new file mode 100644 index 0000000..7b626af --- /dev/null +++ b/3.编程思维体系构建/3.4.6.5.捡起物品.md @@ -0,0 +1,432 @@ +# 5.捡起物品 + +在上一章中,我们制作了一个大大大数组来存储所有对象,包括玩家本人。 + +通过将玩家视为对象的一部分,你已经成为了游戏的重要组成部分,而不是以局外人的视角看世界。 + +这种方法的优势在具有玩家角色的游戏中变得最为明显,你可以换人 hhhhh。 + +玩家属性不再需要存储在单独的变量中;我们可以使用与任何其他对象相同的数据结构。所以玩家,作为一个对象必须具有以下特点: + +- 所处位置(我在哪) +- 玩家可能持有的任何物品的位置。 + +这使得某些常见操作非常容易实现: + +``` + Action Typical +``` + + command Example + +| 玩家从一个位置移动到另一个位置 | go | player->location = cave; | +| ------------------------------ | --------- | ------------------------------------ | +| 列出某个位置存在的项和参与者 | look | listObjectsAtLocation(cave); | +| 玩家获取物品 | get | silver->location = player; | +| 玩家掉落物品 | drop | silver->location = player->location; | +| 列出玩家的物品栏 | inventory | listObjectsAtLocation(player); | +| 玩家将物品赠送给演员 | give | listObjectsAtLocation(player); | +| 玩家从演员那里收到物品 | ask | silver->location = player; | +| 列出其他演员的库存 | examine | listObjectsAtLocation(guard); | + +你可以尝试去使用这些命令(上面的前两个示例已经在上一章中实现了)。现在,我们将为玩家和非玩家角色介绍一些典型的物品栏操作(命令获取掉落给予询问物品栏)。 + +思考题:你能不能尝试自己实现一下上面的命令? + +如果你可以在不参考下面内容的情况下就写出基本内容会有很大收获的 + +# parsexec.c + +```c +#include +#include +#include +#include "location.h" +#include "inventory.h" //这是一个新模块 + +bool parseAndExecute(char *input) +{ + char *verb = strtok(input, " \n"); + char *noun = strtok(NULL, " \n"); + //第二次使用strtok要用NULL传参 + if (verb != NULL) + { + if (strcmp(verb, "quit") == 0) + { + return false; + } + else if (strcmp(verb, "look") == 0) //使游戏识别上述命令 + { + executeLook(noun); + } + else if (strcmp(verb, "go") == 0) + { + executeGo(noun); + } + else if (strcmp(verb, "get") == 0) + { + executeGet(noun); + } + else if (strcmp(verb, "drop") == 0) + { + executeDrop(noun); + } + else if (strcmp(verb, "give") == 0) + { + executeGive(noun); + } + else if (strcmp(verb, "ask") == 0) + { + executeAsk(noun); + } + else if (strcmp(verb, "inventory") == 0) + { + executeInventory(); + } + else + { + printf("I don't know how to '%s'.\n", verb); + } + } + return true; +} +``` + +新命令由以下模块实现。 + +# inventory.h + +```c +extern void executeGet(const char *noun); +extern void executeDrop(const char *noun); +extern void executeAsk(const char *noun); +extern void executeGive(const char *noun); +extern void executeInventory(void); +``` + +# inventory.c + +```c +#include +#include "object.h" +#include "misc.h" +#include "noun.h" +#include "move.h" + +void executeGet(const char *noun) +{ + OBJECT *obj = getVisible("what you want to get", noun); + if (obj == NULL) + { + // already handled by getVisible + } + else if (obj == player) + { + printf("You should not be doing that to yourself.\n"); + } + else if (obj->location == player) + { + printf("You already have %s.\n", obj->description); + } + else if (obj->location == guard) + { + printf("You should ask %s nicely.\n", obj->location->description); + } + else + { + moveObject(obj, player); + //用于将对象传输到其新位置 + } +} + +void executeDrop(const char *noun) +{ + moveObject(getPossession(player, "drop", noun), player->location); +} + +void executeAsk(const char *noun) +{ + moveObject(getPossession(actorHere(), "ask", noun), player); +} + +void executeGive(const char *noun) +{ + moveObject(getPossession(player, "give", noun), actorHere()); +} + +void executeInventory(void) +{ + if (listObjectsAtLocation(player) == 0) //函数返回值告诉我们有多少个对象 + { + printf("You are empty-handed.\n"); //告诉用户啥也没有 + } +} +``` + +注意:由于动词名词比较好弄,命令 askgive 只有一个参数:item。 + +思考题:为什么我们要这样设计? + +你能否为这些命令多加几个参数? + +从本质上讲,get, drop, give and ask 这些命令除了将项目从一个地方移动到另一个地方之外,什么都不做。单个函数 move 对象可以对所有四个命令执行该操作。 + +# move.h + +```c +extern void moveObject(OBJECT *obj, OBJECT *to); +``` + +# move.c + +```c +#include +#include "object.h" + +static void describeMove(OBJECT *obj, OBJECT *to) //确认移动命令 +{ + if (to == player->location) + { + printf("You drop %s.\n", obj->description); + } + else if (to != player) + { + printf(to == guard ? "You give %s to %s.\n" : "You put %s in %s.\n", + obj->description, to->description); + } + else if (obj->location == player->location) + { + printf("You pick up %s.\n", obj->description); + } + else + { + printf("You get %s from %s.\n", + obj->description, obj->location->description); + } +} + +void moveObject(OBJECT *obj, OBJECT *to) +{ + if (obj == NULL) //不移动的各种条件 + { + // already handled by getVisible or getPossession + } + else if (to == NULL) + { + printf("There is nobody here to give that to.\n"); + } + else if (obj->location == NULL) //有些物体无法拾取,具体识别条件将在后面得到改进 + { + printf("That is way too heavy.\n"); + } + else + { + describeMove(obj, to); + obj->location = to; //移动对象 + } +} +``` + +思考题:识别一些我们拿不了的物品需要考虑什么因素? + +命令“get”使用函数getVisible将名词转换为 object,就像命令“go”一样;请参阅上一章。但是对于对玩家(或其他一些参与者)已经持有的对象进行drop, ask, give 等命令时,我们需要稍微不同的东西。我们将在 noun.c 中添加一个函数 getPossession。 + +# noun.h + +```c +extern OBJECT *getVisible(const char *intention, const char *noun); +extern OBJECT *getPossession(OBJECT *from, const char *verb, const char *noun); +``` + +# noun.c + +```c +#include +#include +#include +#include "object.h" + +static bool objectHasTag(OBJECT *obj, const char *noun) +{ + return noun != NULL && *noun != '\0' && strcmp(noun, obj->tag) == 0; +} + +static OBJECT *getObject(const char *noun) +{ + OBJECT *obj, *res = NULL; + for (obj = objs; obj < endOfObjs; obj++) + { + if (objectHasTag(obj, noun)) + { + res = obj; + } + } + return res; +} + +OBJECT *getVisible(const char *intention, const char *noun) +{ + OBJECT *obj = getObject(noun); + if (obj == NULL) + { + printf("I don't understand %s.\n", intention); + } + else if (!(obj == player || + obj == player->location || + obj->location == player || + obj->location == player->location || + obj->location == NULL || + obj->location->location == player || + obj->location->location == player->location)) + { + printf("You don't see any %s here.\n", noun); + obj = NULL; + } + return obj; +} + +OBJECT *getPossession(OBJECT *from, const char *verb, const char *noun) +{ + OBJECT *obj = NULL; + if (from == NULL) + { + printf("I don't understand who you want to %s.\n", verb); + } + else if ((obj = getObject(noun)) == NULL) + { + printf("I don't understand what you want to %s.\n", verb); + } + else if (obj == from) + { + printf("You should not be doing that to %s.\n", obj->description); + obj = NULL; + } + else if (obj->location != from) + { + if (from == player) + { + printf("You are not holding any %s.\n", noun); + } + else + { + printf("There appears to be no %s you can get from %s.\n", + noun, from->description); + } + obj = NULL; + } + return obj; +} +``` + +注意:新函数(45-75 行) getPossessiongetObject 的装饰器(wrapper )(参见第 52 行),它要么返回匹配的对象,要么返回 NULL(如果没有合适的对象与名词匹配)。 + +函数 actor 这里用于命令 giveask,但它也可能由其他命令调用。所以我们在misc.c中定义了它。 + +# misc.h + +```c +extern OBJECT *actorHere(void); +extern int listObjectsAtLocation(OBJECT *location); +``` + +# misc.c + +```c +#include +#include "object.h" + +OBJECT *actorHere(void) +{ + OBJECT *obj; + for (obj = objs; obj < endOfObjs; obj++) + { + if (obj->location == player->location && obj == guard) + { + return obj; + } + } + return NULL; +} + +int listObjectsAtLocation(OBJECT *location) +{ + int count = 0; + OBJECT *obj; + for (obj = objs; obj < endOfObjs; obj++) + { + if (obj != player && obj->location == location) + { + if (count++ == 0) + { + printf("You see:\n"); + } + printf("%s\n", obj->description); + } + } + return count; +} +``` + +思考题:上面第四行中的函数 actorHere 返回的指针指向什么? + +在第 9 行中,有一个详尽的,硬编码的非玩家角色列表(到目前为止,只有一个:守卫)。 + +在第 10 章中,我们将开始使用属性作为区分角色与项目和其他非参与者对象的更优雅方式。 + +测试样例 + +Welcome to Little Cave Adventure. +You are in an open field. +You see: +a silver coin +a burly guard + +--> get silver +You pick up a silver coin. + +--> inventory +You see: +a silver coin + +--> look around +You are in an open field. +You see: +a burly guard + +--> give silver +You give a silver coin to a burly guard. + +--> inventory +You are empty-handed. + +--> ask silver +You get a silver coin from a burly guard. + +--> inventory +You see: +a silver coin + +--> go cave +OK. +You are in a little cave. +You see: +a gold coin + +--> give silver +There is nobody here to give that to. + +--> drop silver +You drop a silver coin. + +--> look around +You are in a little cave. +You see: +a silver coin +a gold coin + +--> inventory +You are empty-handed. + +--> quit + +Bye! diff --git a/3.编程思维体系构建/3.4.6.6.绘制地图.md b/3.编程思维体系构建/3.4.6.6.绘制地图.md new file mode 100644 index 0000000..015960c --- /dev/null +++ b/3.编程思维体系构建/3.4.6.6.绘制地图.md @@ -0,0 +1,336 @@ +# 6.绘制地图 + +作为一个 RPG 游戏怎么能没有地图呢,是时候绘制地图了! + +绘制地图的最佳工具始终是:一支铅笔和一张纸。基本地图由位置(矩形)组成,由道路(箭头)连接。我们已经在第 3 章中创建了位置,现在我们将开始添加道路。 + +在虚拟世界中,“道路”可能是连接两个位置的任何东西:一条路,一扇门,沙漠中。基本上,一段经文具有以下属性: + +- 起点(位置)。 +- 目标(位置)。 +- 叙述性描述,例如“森林小径”。 +- 在 go 命令中往哪里走的描述性标记 + +考虑到这些属性,第 4 章中定义的结构对象就非常适合存储道路了。事实上,一个道路与一个项目或 NPC 并没有太大的不同,它作为“可见出口”存在于某个位置(该位置是起点)。它只是与某些命令的行为不同,特别是命令“go”:应用于道路,go将改变玩家的位置。 + +```c +struct object { + const char *description; + const char *tag; + struct object *location; + struct object *destination; +}; +``` + +注意: + +- 显然,目的地在大多数其他对象(物品,NPC)中都没有使用 +- 通道总是朝一个方向运行;要双向连接两个位置,我们总是必须创建两个单独的通道。乍一看,这似乎很笨拙,但它确实给了我们很大的灵活性来完善命令“go”的行为 +- 在大地图上,你可能会发现手动创建所有通道很乏味。所以,我强烈建议你使用自定义工具生成地图中重复性更强的部分。这里不会介绍这一点,但您可能会在第 9 章中找到一些灵感,我们将在其中讨论自动胜场。 + +思考题:为什么创建两个通道可以使我们的程序更加灵活? + +接下来我们将展开对象数组 + +# object.h + +```c +typedef struct object { + const char *description; + const char *tag; + struct object *location; + struct object *destination; +} OBJECT; + +extern OBJECT objs[]; + +#define field (objs + 0) //是不是觉得这个很熟悉 +#define cave (objs + 1) +#define silver (objs + 2) +#define gold (objs + 3) +#define guard (objs + 4) +#define player (objs + 5) +#define intoCave (objs + 6) +#define exitCave (objs + 7) + +#define endOfObjs (objs + 8) +``` + +# object.c + +```c +#include +#include "object.h" + +OBJECT objs[] = { + {"an open field" , "field" , NULL , NULL }, + {"a little cave" , "cave" , NULL , NULL }, + {"a silver coin" , "silver" , field, NULL }, + {"a gold coin" , "gold" , cave , NULL }, + {"a burly guard" , "guard" , field, NULL }, + {"yourself" , "yourself", field, NULL }, + {"a cave entrance", "entrance", field, cave }, + {"an exit" , "exit" , cave , field } +}; +``` + +我们将在 misc.c 中添加一个小的帮助函数,以确定两个给定位置之间是否存在通道。 + +# misc.h + +```c +extern OBJECT *getPassage(OBJECT *from, OBJECT *to); +extern OBJECT *actorHere(void); +extern int listObjectsAtLocation(OBJECT *location); +``` + +# misc.c + +```c +#include +#include "object.h" + +OBJECT *getPassage(OBJECT *from, OBJECT *to) +{ + if (from != NULL && to != NULL) + { + OBJECT *obj; + for (obj = objs; obj < endOfObjs; obj++) //寻找物品之间是否具有通道 + { + if (obj->location == from && obj->destination == to) + { + return obj; //找到了 + } + } + } + return NULL;//找不到 +} + +OBJECT *actorHere(void) +{ + OBJECT *obj; + for (obj = objs; obj < endOfObjs; obj++) + { + if (obj->location == player->location && obj == guard) + { + return obj; + } + } + return NULL; +} + +int listObjectsAtLocation(OBJECT *location) +{ + int count = 0; + OBJECT *obj; + for (obj = objs; obj < endOfObjs; obj++) + { + if (obj != player && obj->location == location) + { + if (count++ == 0) + { + printf("You see:\n"); + } + printf("%s\n", obj->description); + } + } + return count; +} +``` + +我们将在命令“go”的实现中使用新功能getPassage来确定是否存在可以将玩家带到所需位置的通道。 + +# location.h + +```c +extern void executeLook(const char *noun); +extern void executeGo(const char *noun); +``` + +# location.c + +```c +#include +#include +#include "object.h" +#include "misc.h" +#include "noun.h" + +void executeLook(const char *noun) +{ + if (noun != NULL && strcmp(noun, "around") == 0) + { + printf("You are in %s.\n", player->location->description); + listObjectsAtLocation(player->location); + } + else + { + printf("I don't understand what you want to see.\n"); + } +} + +void executeGo(const char *noun) +{ + OBJECT *obj = getVisible("where you want to go", noun); + if (obj == NULL) + { + // already handled by getVisible + } + else if (getPassage(player->location, obj) != NULL) + //go只会在有地方的时候才会运行起来 + { + printf("OK.\n"); + player->location = obj; + executeLook("around"); + } + else if (obj->location != player->location) + { + printf("You don't see any %s here.\n", noun); + } + else if (obj->destination != NULL) + { + printf("OK.\n"); + player->location = obj->destination; + executeLook("around"); + } + else + { + printf("You can't get much closer than this.\n"); + } +} +``` + +我们还将使用新功能getPassage来确定从玩家站立的位置是否可以看到某个位置。未通过通道连接到当前位置的位置不被视为可见。 + +# noun.h + +```c +extern OBJECT *getVisible(const char *intention, const char *noun); +extern OBJECT *getPossession(OBJECT *from, const char *verb, const char *noun); +``` + +# noun.c + +```c +#include +#include +#include +#include "object.h" +#include "misc.h" + +static bool objectHasTag(OBJECT *obj, const char *noun) +{ + return noun != NULL && *noun != '\0' && strcmp(noun, obj->tag) == 0; +} + +static OBJECT *getObject(const char *noun) +{ + OBJECT *obj, *res = NULL; + for (obj = objs; obj < endOfObjs; obj++) + { + if (objectHasTag(obj, noun)) + { + res = obj; + } + } + return res; +} + +OBJECT *getVisible(const char *intention, const char *noun) +{ + OBJECT *obj = getObject(noun); + + if (obj == NULL) + { + printf("I don't understand %s.\n", intention); + } + else if (!(obj == player || + obj == player->location || + obj->location == player || + obj->location == player->location || + getPassage(player->location, obj) != NULL || //检查两个位置是否相邻 + (obj->location != NULL && + (obj->location->location == player || + obj->location->location == player->location)))) + { + printf("You don't see any %s here.\n", noun); + obj = NULL; + } + return obj; +} + +OBJECT *getPossession(OBJECT *from, const char *verb, const char *noun) +{ + OBJECT *obj = NULL; + if (from == NULL) + { + printf("I don't understand who you want to %s.\n", verb); + } + else if ((obj = getObject(noun)) == NULL) + { + printf("I don't understand what you want to %s.\n", verb); + } + else if (obj == from) + { + printf("You should not be doing that to %s.\n", obj->description); + obj = NULL; + } + else if (obj->location != from) + { + if (from == player) + { + printf("You are not holding any %s.\n", noun); + } + else + { + printf("There appears to be no %s you can get from %s.\n", + noun, from->description); + } + obj = NULL; + } + return obj; +} +``` + +显然,此示例中的地图是微不足道的:只有两个位置,并且它们在两个方向上都连接在一起。第 12 章将增加第三个地点。 + +思考题:你能否绘制一张更精细的地图,并将其变成对象列表(位置和通道) + +``` + 注:不用当成任务,自行实验即可 +``` + +输出样例 + +Welcome to Little Cave Adventure. +You are in an open field. +You see: +a silver coin +a burly guard +a cave entrance + +--> go entrance +OK. +You are in a little cave. +You see: +a gold coin +an exit + +--> go exit +OK. +You are in an open field. +You see: +a silver coin +a burly guard +a cave entrance + +--> go cave +OK. +You are in a little cave. +You see: +a gold coin +an exit + +--> quit + +Bye! diff --git a/3.编程思维体系构建/3.4.6.7.增大距离.md b/3.编程思维体系构建/3.4.6.7.增大距离.md new file mode 100644 index 0000000..bcb0ce3 --- /dev/null +++ b/3.编程思维体系构建/3.4.6.7.增大距离.md @@ -0,0 +1,504 @@ +# 7.增大距离 + +一个典型的冒险包含许多谜题。众所周知,Infocom的冒险很难完成。解决每个难题可能需要数周甚至数月的反复试验。 + +当玩家操纵角色失败后,如果只是返回“你不能这么操作”来回应玩家,会很 nt,很没意思 + +它忽略了电脑游戏的一个重要方面,而这也是生活本身的一部分:玩家必须从错误中吸取教训。 + +当你的游戏反复输入东西都是,你不能这样做的时候,会显得很无聊的。 + +冒险游戏至少应该做的是解释为什么玩家的命令无法完成:“你不能这样做,因为......”这有助于使虚拟世界更具说服力,故事更可信,游戏更有趣。 + +我们已经付出了相当大的努力让游戏解释为什么某些命令是无效的。只需看看名词.c,inventory.c,location.cmove.c中的许多printf调用。 但随着游戏变得越来越复杂,这正成为一个相当大的负担。我们需要一种更结构化的方法来检测和处理错误情况。这就是我们在本章中将要讨论的内容。 + +大多数命令对一个或多个对象进行操作,例如: + +- 玩家拿起一件物品,然后把它交给另一个 NPC。 +- 玩家沿着一条通道到另一个位置。 + +首先要检查的(在[解析器](http://en.wikipedia.org/wiki/Parsing)捕获检测是否会有明显[拼写错误](http://en.wikipedia.org/wiki/Typographical_error)之后)是这些对象是否存在; + +失败应该导致类似“这里没有...“或”你看不到任何东西...”等文字出现。在本章中,我们将构建一个通用函数,每个命令都可以使用它来找出玩家是否在可及的范围内。 + +你可能认为我们只需要区分两种情况:对象在这里,或者它不在这里。 + +但是许多命令需要更多的渐变,而不仅仅是“这里”和“不在这里”。例子: + +- 要使用武器或工具,玩家必须持有它;仅仅在现场存在是不够的。 +- 你不能放下一个你没拿起来的道具,也不能拿起一个已经有的东西 +- 如果你跟商人买东西的时候,他店里东西你不能随便拿 + +| distSelf | 对象是玩家 | object == player | +| ----------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------ | +| distHeld | 玩家持有物体 | object->location == player | +| distHeldContained | 玩家拿着另一个包含该物体的物体(例如袋子) | object->location != NULL &&
object->location->location == player | +| distLocation | 对象是玩家的位置 | object == player->location | +| distHere | 对象位于玩家的位置 | object->location == player->location | +| distHereContained | 一个物体(NPC 或“容器”)存在于玩家的位置,正在拿着另一个物体 | object->location != NULL &&
object->location->location == player->location | +| distOverthere | 对象是附近的位置 | getPassage(player->location, object) != NULL | + +第一种情况(对象是玩家)可能看起来微不足道,但它仍然很重要。例如,命令“examine yourself”不应该返回“这里没有你自己”。 + +我试图遵循一个逻辑顺序:附近的事物最高优先级,随后优先级会变低。 + +| distNotHere | 对象不在这里(或看起来)不在这里 | | +| ----------------- | -------------------------------- | -------------- | +| distUnknownObject | 解析器无法识别输入的名词 | object == NULL | + +请注意,我们有七种不同的“这里”案例,但只有一种是“不在这里”。这是因为通常,游戏只需要提供有关玩家可以感知的事物的信息。如果它不在这里,那么就没什么可说的了。 + +在最左边的列中,我们为每个案例提出了一个符号名称。我们将在名为 DISTANCE 的[枚举](http://en.wikipedia.org/wiki/Enumerated_type)中收集这些名称。 + +```c +typedef enum { + distSelf, + distHeld, + distHeldContained, + distLocation, + distHere, + distHereContained, + distOverthere, + distNotHere, + distUnknownObject +} DISTANCE; +``` + +typedef 以及枚举类 enum 之前有接触过吗?没有接触过的话就去学习一下吧。 + +在最右边的列中,我们为每个情况提出了一个满足条件。通过一些重新洗牌,我们可以很容易地将其转换为计算对象“距离”的函数(从玩家的角度来看): + +```c +DISTANCE getDistance(OBJECT *from, OBJECT *to) +{ + return to == NULL ? distUnknownObject : + to == from ? distSelf : + to->location == from ? distHeld : + to == from->location ? distLocation : + to->location == from->location ? distHere : + getPassage(from->location, to) != NULL ? distOverthere : + to->location == NULL ? distNotHere : + to->location->location == from ? distHeldContained : + to->location->location == from->location ? distHereContained : + distNotHere; +} +``` + +思考题:你是否有其他方法实现这个功能? + +注:自行实验即可 + +就这样!我们可以调用此函数并对其返回值进行比较。例如,我们在 noun.c中有以下代码: + +```c +else if (!(obj == player || + obj == player->location || + obj->location == player || + obj->location == player->location || + getPassage(player->location, obj) != NULL || + (obj->location != NULL && + (obj->location->location == player || + obj->location->location == player->location)))) +``` + +现在,我们可以用适当的距离检查替换每个子条件: + +```c +else if (!(getDistance(player, obj) == distSelf || + getDistance(player, obj) == distLocation || + getDistance(player, obj) == distHeld || + getDistance(player, obj) == distHere || + getDistance(player, obj) == distOverthere || + getDistance(player, obj) == distHeldContained || + getDistance(player, obj) == distHereContained) +``` + +这可以简化为: + +```c +else if (getDistance(player, obj) >= distNotHere) +``` + +尝试理解一下这样做的意义 + +这只是一个例子,让你对这个概念有所了解;您将在下面找到noun.c的实际实现,看起来略有不同。 + +是时候把事情落实到位了。枚举 DISTANCE 和函数 getDistance 的定义被添加到 misc.hmisc.c 中,因为我们将在多个模块中使用它们。 + +# misc.h + +```c +typedef enum { + distSelf, + distHeld, + distHeldContained, + distLocation, + distHere, + distHereContained, + distOverthere, + distNotHere, + distUnknownObject +} DISTANCE; + +extern bool isHolding(OBJECT *container, OBJECT *obj); + //是否持有物体 +extern OBJECT *getPassage(OBJECT *from, OBJECT *to); + //获取通道 +extern DISTANCE getDistance(OBJECT *from, OBJECT *to); + //计算距离 +extern OBJECT *actorHere(void); +extern int listObjectsAtLocation(OBJECT *location); +``` + +# misc.c + +```c +#include +#include +#include "object.h" +#include "misc.h" + +bool isHolding(OBJECT *container, OBJECT *obj) +{ + return obj != NULL && obj->location == container; +} + +OBJECT *getPassage(OBJECT *from, OBJECT *to) +{ + if (from != NULL && to != NULL) + { + OBJECT *obj; + for (obj = objs; obj < endOfObjs; obj++) + { + if (isHolding(from, obj) && obj->destination == to) + { + return obj; + } + } + } + return NULL; +} + +DISTANCE getDistance(OBJECT *from, OBJECT *to) +{ + return to == NULL ? distUnknownObject : + to == from ? distSelf : + isHolding(from, to) ? distHeld : + isHolding(to, from) ? distLocation : + isHolding(from->location, to) ? distHere : + isHolding(from, to->location) ? distHeldContained : + isHolding(from->location, to->location) ? distHereContained : + getPassage(from->location, to) != NULL ? distOverthere : + distNotHere; +} + +OBJECT *actorHere(void) +{ + OBJECT *obj; + for (obj = objs; obj < endOfObjs; obj++) + { + if (isHolding(player->location, obj) && obj == guard) + { + return obj; + } + } + return NULL; +} + +int listObjectsAtLocation(OBJECT *location) +{ + int count = 0; + OBJECT *obj; + for (obj = objs; obj < endOfObjs; obj++) + { + if (obj != player && isHolding(location, obj)) + { + if (count++ == 0) + { + printf("You see:\n"); + } + printf("%s\n", obj->description); + } + } + return count; +} +``` + +注意:isHolding 这个函数之后我们将在各个地方使用 + +# location.h + +```c +extern void executeLook(const char *noun); +extern void executeGo(const char *noun); +``` + +在函数 executeGo 中,我们可以用检查距离来替换大多数 if 条件。 + +# location.c + +```c +#include +#include +#include +#include "object.h" +#include "misc.h" +#include "noun.h" + +void executeLook(const char *noun) +{ + if (noun != NULL && strcmp(noun, "around") == 0) + { + printf("You are in %s.\n", player->location->description); + listObjectsAtLocation(player->location); + } + else + { + printf("I don't understand what you want to see.\n"); + } +} + +void executeGo(const char *noun) +{ + OBJECT *obj = getVisible("where you want to go", noun); + switch (getDistance(player, obj)) + { + case distOverthere: + printf("OK.\n"); + player->location = obj; + executeLook("around"); + break; + case distNotHere: + printf("You don't see any %s here.\n", noun); + break; + case distUnknownObject: + // already handled by getVisible + break; + default: + //上述情况均为出现 + if (obj->destination != NULL) + { + printf("OK.\n"); + player->location = obj->destination; + executeLook("around"); + } + else + { + printf("You can't get much closer than this.\n"); + } + } +} +``` + +思考题:你能否为 switch 函数增加更多 case 来完善判断条件? + +函数 executeGet 也是如此。 + +# inventory.h + +```c +extern void executeGet(const char *noun); +extern void executeDrop(const char *noun); +extern void executeAsk(const char *noun); +extern void executeGive(const char *noun); +extern void executeInventory(void); +``` + +# inventory.c + +```c +#include +#include +#include "object.h" +#include "misc.h" +#include "noun.h" +#include "move.h" + +void executeGet(const char *noun) +{ + OBJECT *obj = getVisible("what you want to get", noun); + switch (getDistance(player, obj)) + { + case distSelf: + printf("You should not be doing that to yourself.\n"); + break; + case distHeld: + printf("You already have %s.\n", obj->description); + break; + case distOverthere: + printf("Too far away, move closer please.\n"); + break; + case distUnknownObject: + // already handled by getVisible + break; + default: + if (obj->location == guard) + { + printf("You should ask %s nicely.\n", obj->location->description); + } + else + { + moveObject(obj, player); + } + } +} + +void executeDrop(const char *noun) +{ + moveObject(getPossession(player, "drop", noun), player->location); +} + +void executeAsk(const char *noun) +{ + moveObject(getPossession(actorHere(), "ask", noun), player); +} + +void executeGive(const char *noun) +{ + moveObject(getPossession(player, "give", noun), actorHere()); +} + +void executeInventory(void) +{ + if (listObjectsAtLocation(player) == 0) + { + printf("You are empty-handed.\n"); + } +} +``` + +最后,我们将调整 noun.c中的约束。我们正在向函数getObject添加两个参数,以便找到特定名词的匹配项,同时忽略任何被认为不存在的对象。这将在下一章中得到真正的回报,我们将在下一章中介绍具有相同标签的不同对象。 + +# noun.h + +```c +extern OBJECT *getVisible(const char *intention, const char *noun); +extern OBJECT *getPossession(OBJECT *from, const char *verb, const char *noun); +``` + +# noun.c + +```c +#include +#include +#include +#include "object.h" +#include "misc.h" + +static bool objectHasTag(OBJECT *obj, const char *noun) +{ + return noun != NULL && *noun != '\0' && strcmp(noun, obj->tag) == 0; +} + +static OBJECT *getObject(const char *noun, OBJECT *from, DISTANCE maxDistance) +{ + OBJECT *obj, *res = NULL; + for (obj = objs; obj < endOfObjs; obj++) + { + if (objectHasTag(obj, noun) && getDistance(from, obj) <= maxDistance) + //只考虑与对象距离小于或等于最大距离的物体 + { + res = obj; + } + } + return res; +} + +OBJECT *getVisible(const char *intention, const char *noun) +{ + OBJECT *obj = getObject(noun, player, distOverthere); + //玩家看不到的自动忽略 + if (obj == NULL) + { + if (getObject(noun, player, distNotHere) == NULL) + { + printf("I don't understand %s.\n", intention); + } + else + { + printf("You don't see any %s here.\n", noun); + } + } + return obj; +} + +OBJECT *getPossession(OBJECT *from, const char *verb, const char *noun) +{ + OBJECT *obj = NULL; + if (from == NULL) + { + printf("I don't understand who you want to %s.\n", verb); + } + else if ((obj = getObject(noun, from, distHeldContained)) == NULL) + //限制范围 + { + if (getObject(noun, player, distNotHere) == NULL) + { + printf("I don't understand what you want to %s.\n", verb); + } + else if (from == player) + { + printf("You are not holding any %s.\n", noun); + } + else + { + printf("There appears to be no %s you can get from %s.\n", + noun, from->description); + } + } + else if (obj == from) + { + printf("You should not be doing that to %s.\n", obj->description); + obj = NULL; + } + return obj; +} +``` + +思考题:你能理解什么时候加 const,什么时候不用吗? + +在本章中,距离的概念主要用于在游戏可以给用户的不同响应之间进行选择。但是,距离的好处并不局限于输出端;它可以同样很好地用于在输入端进行改进。在下一章中,我们将使用距离来提高对用户输入的名词的识别。 + +输出样例 + +Welcome to Little Cave Adventure. +You are in an open field. +You see: +a silver coin +a burly guard +a cave entrance + +--> go guard +You can't get much closer than this. + +--> give silver +You are not holding any silver. + +--> ask silver +There appears to be no silver you can get from a burly guard. + +--> get silver +You pick up a silver coin. + +--> get gold +You don't see any gold here. + +--> give silver +You give a silver coin to a burly guard. + +--> go cave +OK. +You are in a little cave. +You see: +a gold coin +an exit + +--> get gold +You pick up a gold coin. + +--> give gold +There is nobody here to give that to. + +--> quit + +Bye! diff --git a/3.编程思维体系构建/3.4.6.8.移动方向.md b/3.编程思维体系构建/3.4.6.8.移动方向.md new file mode 100644 index 0000000..443c99e --- /dev/null +++ b/3.编程思维体系构建/3.4.6.8.移动方向.md @@ -0,0 +1,252 @@ +# 8.移动方向 + +传统的文本冒险使用指南针方向进行导航。 + +例如,我们在第 6 章中绘制的地图上,玩家可能想向东移动,从田野移动到洞穴。我们可以通过给连接Cave的通道标上“east”来实现这一点。但是,我们首先需要解决两个问题。 + +1. 我们可能仍然想把这段通道称为“entrance”和“east”。但现在,一个对象只能有一个标签。 +2. 在更大的地图上,具有更多的位置和道路,标签“east”将被定义多次。到目前为止,标签在我们的游戏中是全球独一无二的,没有重复项。 + +思考题:你能否想出解决办法? + +这些问题同样适用于其他对象,而不仅仅是通道。 + +在我们的冒险中,我们有一枚银币和一枚金币。一方面,如果不接受在只有一枚硬币存在的地点得到硬币,那将是愚蠢的。 + +另一方面,在两种硬币都在同一位置存在的情况下,玩家应该可以有两个拾取选择。 + +这立即将我们带到了解析器的第三个问题: + +1. 一个标签只能是一个单词;“ sliver coin”这是俩单词,他不接受啊 + +所有三个问题都将在本章中解决,从问题#1 开始。它通过为每个对象提供一个标签列表来解决,而不仅仅是一个标签。 + +# object.h + +```c +typedef struct object { + const char *description; + const char **tags; + struct object *location; + struct object *destination; +} OBJECT; + +extern OBJECT objs[]; + +#define field (objs + 0) +#define cave (objs + 1) +#define silver (objs + 2) +#define gold (objs + 3) +#define guard (objs + 4) +#define player (objs + 5) +#define intoCave (objs + 6) +#define exitCave (objs + 7) +#define wallField (objs + 8) +#define wallCave (objs + 9) + +#define endOfObjs (objs + 10) +``` + +# object.c + +```c +#include +#include "object.h" + +static const char *tags0[] = {"field", NULL}; +static const char *tags1[] = {"cave", NULL}; +static const char *tags2[] = {"silver", "coin", "silver coin", NULL}; +static const char *tags3[] = {"gold", "coin", "gold coin", NULL}; +static const char *tags4[] = {"guard", "burly guard", NULL}; +static const char *tags5[] = {"yourself", NULL}; +static const char *tags6[] = {"east", "entrance", NULL}; +static const char *tags7[] = {"west", "exit", NULL}; +static const char *tags8[] = {"west", "north", "south", "forest", NULL}; +static const char *tags9[] = {"east", "north", "south", "rock", NULL}; +//我们不固定标签长度,在结束的时候用NULL来标记 +OBJECT objs[] = { + {"an open field" , tags0, NULL , NULL }, + {"a little cave" , tags1, NULL , NULL }, + {"a silver coin" , tags2, field, NULL }, + {"a gold coin" , tags3, cave , NULL }, + {"a burly guard" , tags4, field, NULL }, + {"yourself" , tags5, field, NULL }, + {"a cave entrance to the east", tags6, field, cave }, + {"an exit to the west" , tags7, cave , field }, + {"dense forest all around" , tags8, field, NULL }, + {"solid rock all around" , tags9, cave , NULL } + //我们用NULL来阻绝进入一个你不知道的地方 +}; +``` + +当然,要让这个改动生效,我们还需要调整noun.c中的objectHasTag函数。 + +同时,我们将让函数 getVisiblegetPossession 告知玩家他必须更具体的选择你到底是银币还是金币 ,而不是随机选择任何一个对象。 + +# noun.h + +```c +extern OBJECT *getVisible(const char *intention, const char *noun); +extern OBJECT *getPossession(OBJECT *from, const char *verb, const char *noun); +``` + +# noun.c + +```c +#include +#include +#include +#include "object.h" +#include "misc.h" + +static bool objectHasTag(OBJECT *obj, const char *noun) +{ + if (noun != NULL && *noun != '\0') + { + const char **tag; + for (tag = obj->tags; *tag != NULL; tag++) + { + if (strcmp(*tag, noun) == 0) return true; + }//扫描对象的tag列表 + } + return false; +} + +static OBJECT ambiguousNoun;//我们需要这玩意的地址帮助我们把它当成一个返回值 + +static OBJECT *getObject(const char *noun, OBJECT *from, DISTANCE maxDistance) +{ + OBJECT *obj, *res = NULL; + for (obj = objs; obj < endOfObjs; obj++) + { + if (objectHasTag(obj, noun) && getDistance(from, obj) <= maxDistance) + { + res = res == NULL ? obj : &ambiguousNoun;//标签不明确的解决方案哦 + } + } + return res; +} + +OBJECT *getVisible(const char *intention, const char *noun) +{ + OBJECT *obj = getObject(noun, player, distOverthere); + if (obj == NULL) + { + if (getObject(noun, player, distNotHere) == NULL) + { + printf("I don't understand %s.\n", intention); + } + else + { + printf("You don't see any %s here.\n", noun); + } + } + else if (obj == &ambiguousNoun)//模糊匹配 + { + printf("Please be specific about which %s you mean.\n", noun); + obj = NULL; + } + return obj; +} + +OBJECT *getPossession(OBJECT *from, const char *verb, const char *noun) +{ + OBJECT *obj = NULL; + if (from == NULL) + { + printf("I don't understand who you want to %s.\n", verb); + } + else if ((obj = getObject(noun, from, distHeldContained)) == NULL) + { + if (getObject(noun, player, distNotHere) == NULL) + { + printf("I don't understand what you want to %s.\n", verb); + } + else if (from == player) + { + printf("You are not holding any %s.\n", noun); + } + else + { + printf("There appears to be no %s you can get from %s.\n", + noun, from->description); + } + } + else if (obj == &ambiguousNoun)//模糊匹配 + { + printf("Please be specific about which %s you want to %s.\n", + noun, verb); + obj = NULL; + } + else if (obj == from) + { + printf("You should not be doing that to %s.\n", obj->description); + obj = NULL; + } + return obj; +} +``` + +问题 #3 可以通过从函数parseAndExecute中删除一个 [空格](http://en.wikipedia.org/wiki/Space_(punctuation))字符来解决(下面的第 10 行)。这个解决方案远非完美('silver' 和 'coin' 之间的双空格是大咩的),但直到我们在第 13 章中让自己成为一个更好的解析器之前。 + +# parsexec.c + +```c +#include +#include +#include +#include "location.h" +#include "inventory.h" + +bool parseAndExecute(char *input) +{ + char *verb = strtok(input, " \n"); + char *noun = strtok(NULL, "\n"); + if (verb != NULL) + { + if (strcmp(verb, "quit") == 0) + { + return false; + } + else if (strcmp(verb, "look") == 0) + { + executeLook(noun); + } + else if (strcmp(verb, "go") == 0) + { + executeGo(noun); + } + else if (strcmp(verb, "get") == 0) + { + executeGet(noun); + } + else if (strcmp(verb, "drop") == 0) + { + executeDrop(noun); + } + else if (strcmp(verb, "give") == 0) + { + executeGive(noun); + } + else if (strcmp(verb, "ask") == 0) + { + executeAsk(noun); + } + else if (strcmp(verb, "inventory") == 0) + { + executeInventory(); + } + else + { + printf("I don't know how to '%s'.\n", verb); + } + } + return true; +} +``` + +模块main.cinventory.clocation.cmove.cmisc.c保持不变 + +现在对象数组 ( object.c ) 开始在多个维度上增长(特别是在引入多个标签的情况下),我们需要一种使其更易于维护的方法。 + +猜猜看该怎么办? diff --git a/3.编程思维体系构建/3.4.6.9.练习:生成代码.md b/3.编程思维体系构建/3.4.6.9.练习:生成代码.md new file mode 100644 index 0000000..aa1447a --- /dev/null +++ b/3.编程思维体系构建/3.4.6.9.练习:生成代码.md @@ -0,0 +1,90 @@ +# 9.练习:生成代码 + +*到目前为止,我们的冒险游戏有10个对象。每个对象由有5 个属性组成。一个真正的文本冒险可能有数百个甚至数千个对象,并且每个对象的属性数量也可能增加(请参阅下一章)。在目前的形式下,维护如此庞大的对象和属性列表将很困难。* + +例如,当我们在添加对象 *wallField* 和 *wallCave* 时,我们必须在三个不同的位置执行此操作:一次在 *object.h* 中(作为*#define*),两次在 *object.c* 中(数组 *objs* 中的一个元素,以及一个单独的标签数组)。这显然十分笨拙并且容易出错。 + +我们将不再手工维护object. h和object. c,而是从更适合我们需要的单一源开始生成文件。这个新的源文件可以用你喜欢的任何语言( 典型的是某些特定领域的语言 ),只要你有工具把它转换回C。下面是一个简单的例子,考虑下列布局来组织我们的对象: + +```Plain + /* Raw C code (declarations) */ +- ObjectName + AttributeName AttributeValue + AttributeName AttributeValue + ... +- ObjectName + AttributeName AttributeValue + AttributeName AttributeValue + ... +- ... +``` + +根据到目前为止收集的对象,我们可以构造以下源文件。文件名并不重要;我只是简单地将其命名为*object.txt*,以明确它与*object.h*和*object.c*相关。 + +# object.txt + +```Plain +#include +#include "object.h" + +typedef struct object { + const char *description; + const char **tags; + struct object *location; + struct object *destination; +} OBJECT; + +extern OBJECT objs[]; +//对象 +- field + description "an open field" + tags "field" + +- cave + description "a little cave" + tags "cave" + +- silver + description "a silver coin" + tags "silver", "coin", "silver coin" + location field + +- gold + description "a gold coin" + tags "gold", "coin", "gold coin" + location cave + +- guard + description "a burly guard" + tags "guard", "burly guard" + location field + +- player + description "yourself" + tags "yourself" + location field + +- intoCave + description "a cave entrance to the east" + tags "east", "entrance" + location field + destination cave + +- exitCave + description "an exit to the west" + tags "west", "exit" + location cave + destination field + +- wallField + description "dense forest all around" + tags "west", "north", "south", "forest" + location field + +- wallCave + description "solid rock all around" + tags "east", "north", "south", "rock" + location cave +``` + +思考题:你能否自己用C来实现这段伪代码? \ No newline at end of file diff --git a/3.编程思维体系构建/3.4.6阶段二:文字冒险(cool).md b/3.编程思维体系构建/3.4.6阶段二:文字冒险(cool).md new file mode 100644 index 0000000..0295202 --- /dev/null +++ b/3.编程思维体系构建/3.4.6阶段二:文字冒险(cool).md @@ -0,0 +1,43 @@ +# 阶段二:文字冒险(cool) + +# 前言 + +本来打算让各位做下面的任务来进行进一步的学习的,但是想了想,实在是,太无聊啦! + +又想让你们来做一个管理系统,但是又想到你们可能会进行无数个管理系统,这也太无聊啦! + +因此呢,我打算带大家玩一个文字冒险游戏![源头取自这里](https://github.com/helderman/htpataic),如果你想自己体验全流程的话可以试试玩哦! + +当然,在编写的过程中,难免会出现你感到困惑的地方,比如说,你觉得这样更好,或者说,你不明白为什么要这样编写代码,欢迎你进行更多的独立的尝试。 + +其次我要说的是: + +这个学习过程会非常硬核,所以你感觉非常多东西不会是很正常的! + +我希望你可以通过这种方式,以后在面临一个大项目或者别人的代码时(你经常要借鉴别人的代码)保持冷静。 + +可以保证的是,如果你成功坚持下来了,你将会在接下来的编程生涯中保持长时间的游刃有余。 + +当然,如果你选择跳过,也不会对 python 开发那里造成非常大的影响但是你会错失一个非常宝贵的学习机会。 + +![](static/boxcnustZBhjMu8FPN0Kxi4Mwvf.jpg) + +在 1980 年代, [文字冒险](http://en.wikipedia.org/wiki/Text_adventure) 是一种受人尊敬的电脑游戏类型。但是时代已经变了,在 21 世纪,它们与 带有 3D 引擎的现代 [MMORPG 相比显得苍白无力。](http://en.wikipedia.org/wiki/Mmorpg)书籍在电影的兴起中幸存下来,而基于文本的游戏很快就输掉了与图形游戏的战斗。“互动小说”由一个活跃的社区保持活力,但它的商业价值早已不复存在。 + +# 系统调试的黄金法则:KISS 原则 + +这里的 `KISS` 是 `Keep It Simple, Stupid` 的缩写, 它的中文翻译是: 不要在一开始追求绝对的完美. + +随着以后代码量会越来越多, 各个模块之间的交互也越来越复杂, 工程的维护变得越来越困难, 一个很弱智的 bug 可能需要调好几天. + +在这种情况下, 系统能跑起来才是王道, 跑不起来什么都是浮云, 追求面面俱到只会增加代码维护的难度. + +唯一可以把你从 bug 的混沌中拯救出来的就是 KISS 法则, 它的宗旨是从易到难, 逐步推进, 一次只做一件事, 少做无关的事. + +如果你不知道这是什么意思, 我们以可能发生的 `str` 成员缓冲区溢出问题来作为例子. KISS 法则告诉你, 你应该使用 `assert(0)`, 就算不"得体"地处理上述问题, 仍然不会影响表达式求值的核心功能的正确性. + +如果你还记得调试公理, 你会发现两者之间是有联系的: 调试公理第二点告诉你, 未测试代码永远是错的. 与其一下子写那么多"错误"的代码, 倒不如使用 `assert(0)` 来有效帮助你减少这些"错误". + +如果把 KISS 法则放在软件工程领域来解释, 它强调的就是多做[单元测试](http://en.wikipedia.org/wiki/Unit_testing): 写一个函数, 对它进行测试, 正确之后再写下一个函数, 再对它进行测试... 一种好的测试方式是使用 assertion 进行验证, + +KISS 法则不但广泛用在计算机领域, 就连其它很多领域也视其为黄金法则, [这里](http://blog.sciencenet.cn/blog-414166-562616.html)有一篇文章举出了很多的例子, 我们强烈建议你阅读它, 体会 KISS 法则的重要性. diff --git a/3.编程思维体系构建/3.4.7.1.1调试理论.md b/3.编程思维体系构建/3.4.7.1.1调试理论.md new file mode 100644 index 0000000..e79e5df --- /dev/null +++ b/3.编程思维体系构建/3.4.7.1.1调试理论.md @@ -0,0 +1,65 @@ +# 调试理论 + +# 调试公理 + +- The machine is always right. (机器永远是对的) + + - Corollary: If the program does not produce the desired output, it is the programmer's fault. +- Every line of untested code is always wrong. (未测试代码永远是错的) + + - Corollary: Mistakes are likely to appear in the "must-be-correct" code. + +这两条公理的意思是: 抱怨是没有用的, 接受代码有 bug 的现实, 耐心调试. + +# 如何调试 + +- 不要使用"目光调试法", 要思考如何用正确的工具和方法帮助调试 + + - 程序设计课上盯着几十行的程序, 你或许还能在大脑中像 NEMU 那样模拟程序的执行过程; 但程序规模大了之后, 很快你就会放弃的: 你的大脑不可能模拟得了一个巨大的状态机 + - 我们学习计算机是为了学习计算机的工作原理, 而不是学习如何像计算机那样机械地工作 +- 使用 `assert()` 设置检查点, 拦截非预期情况 + + - 例如 `assert(p != NULL)` 就可以拦截由空指针解引用引起的段错误 +- 结合对程序执行行为的理解, 使用 `printf()` 查看程序执行的情况(注意字符串要换行) + + - `printf()` 输出任意信息可以检查代码可达性: 输出了相应信息, 当且仅当相应的代码块被执行 + - `printf()` 输出变量的值, 可以检查其变化过程与原因 +- 使用 GDB 观察程序的任意状态和行为 + + - 打印变量, 断点, 监视点, 函数调用栈... + +![](static/boxcnaqLMfwqNMTcYEPuF3vFjqg.png) + +# 调试理论 + +如果我们能判定任意程序状态的正确性,那么给定一个 failure,我们可以通过二分查找定位到第一个 error 的状态,此时的代码就是 fault (bug)。 + +# 正确的方法:理解程序的执行过程,弄清楚到底为何导致了 bug + +- `ssh`:使用 `-v` 选项检查日志 +- `gcc`:使用 `-v` 选项打印各种过程 +- `make`:使用 `-n` 选项查看完整命令 + + - `make -nB | grep -ve '^\(echo\|mkdir\)'` 可以查看完整编译 nemu 的编译过程 + +各个工具普遍提供调试功能,帮助用户/开发者了解程序的行为 + +# 错误概念 + +我们来简单梳理一下段错误发生的原因. 首先, 机器永远是对的. 如果程序出了错, 先怀疑自己的代码有 bug . 比如由于你的疏忽, 你编写了 `if (p = NULL)` 这样的代码. 但执行到这行代码的时候, 也只是 `p` 被赋值成 `NULL`, 程序还会往下执行. 然而等到将来对 `p` 进行了解引用的时候, 才会触发段错误, 程序彻底崩溃. + +我们可以从上面的这个例子中抽象出一些软件工程相关的概念: + +- Fault: 实现错误的代码, 例如 `if (p = NULL)` +- Error: 程序执行时不符合预期的状态, 例如 `p` 被错误地赋值成 `NULL` +- Failure: 能直接观测到的错误, 例如程序触发了段错误 + +调试其实就是从观测到的 failure 一步一步回溯寻找 fault 的过程, 找到了 fault 之后, 我们就很快知道应该如何修改错误的代码了. 但从上面的例子也可以看出, 调试之所以不容易, 恰恰是因为: + +- fault 不一定马上触发 error +- 触发了 error 也不一定马上转变成可观测的 failure +- error 会像滚雪球一般越积越多, 当我们观测到 failure 的时候, 其实已经距离 fault 非常遥远了 + +理解了这些原因之后, 我们就可以制定相应的策略了: + +- 尽可能把 fault 转变成 error. 这其实就是测试做的事情, 所以我们在上一节中加入了表达式生成器的内容, 来帮助大家进行测试, 后面的实验内容也会提供丰富的测试用例. 但并不是有了测试用例就能把所有 fault 都转变成 error 了, 因为这取决于测试的覆盖度. 要设计出一套全覆盖的测试并不是一件简单的事情, 越是复杂的系统, 全覆盖的测试就越难设计. 但是, 如何提高测试的覆盖度, 是学术界一直以来都在关注的问题. diff --git a/3.编程思维体系构建/3.4.7.1GDB初探索(编程可阅览).md b/3.编程思维体系构建/3.4.7.1GDB初探索(编程可阅览).md new file mode 100644 index 0000000..e7650b1 --- /dev/null +++ b/3.编程思维体系构建/3.4.7.1GDB初探索(编程可阅览).md @@ -0,0 +1,61 @@ +# GDB 初探索(编程可阅览) + +请在开始进行 C 语言编程之后查阅使用 + +![](static/boxcnHXggg6eLy86vFmb4shOksh.png) + +# GDB 是什么? + +调试器,简单来说就是当你代码跑不通时候修正错误用的 + +可搭配插件 gef pwndbg pwngdb peda + +# 基本操作 + +### GDB 使用表 + +run (r)运行程序 + +b打断点,可以在函数和位置打断点 + +info b查看打断点的位置 + +n下一步,跳过函数的 + +list查看源代码 + +-p走 PID 路线 + +edit [file:]function 看现在停下的函数位置 + +step 进入任何函数 + +p打印变量 + +shell输入命令 + +set logging on记录日志 + +watchpoint观察变量是否变化的观察点 + +watch设置观察点位置,watch*(地址) + +layout split开启 TUI 模式 + +whatis查看变量类型 + +ptype查看详细信息 + +#### TUI + +ctrl +x +a开启 + +ctrl+p+n往前 + +ctrl +l重新整理页面 + +# 官方手册 + +[GDB User Manual](https://sourceware.org/gdb/current/onlinedocs/gdb) + +有非常多高级用法,可以在必要的时候进行查阅,受益无穷 diff --git a/3.编程思维体系构建/3.4.7.2C的历史问题:undefined behavior.md b/3.编程思维体系构建/3.4.7.2C的历史问题:undefined behavior.md new file mode 100644 index 0000000..78e003e --- /dev/null +++ b/3.编程思维体系构建/3.4.7.2C的历史问题:undefined behavior.md @@ -0,0 +1,67 @@ +# C 的历史问题:undefined behavior + +![](static/boxcnIdOChXQUGMvnxWcB7uTWLh.png) + +简写为 UB + +“Anything at all can happens; the Standard imposes no requirements. The program may fail to compile, or it may execute incorrectly (either crashing or silently generating incorrect results), or it may fortuitously do exactly what the programmer intended” + +简单来说就是:他没定义这种操作该怎么办。 + +比如说/0 以及一些你们可能没接触过的操作。 + +给你们看个恶心的例子: + +```c +int main(int argc, char **argv) { + int i = 3; + int r = (i++*++i+i--*--i); + printf("The answer : %dn", r); + system("pause"); + return 0; +} +``` + +讓我們看看不同編譯器的 Debug 模式下執行的結果 + +Visual C++ 6.0 + +> The answer : 25 + +Visual C++ 2008 Express + +> The answer : 18 + +MinGW(GCC) + +> The answer : 25 + +我們試試看在 Release 下執行的結果 + +Visual C++ 6.0 + +> The answer : 18 + +Visual C++ 2008 Express + +> The answer : 18 + +MinGW(GCC) + +> The answer : 25 + +C 語言最初為了開發 UNIX 和系統軟體而生,本質是低階的程式語言, + +在語言規範層級存在 UB,可允許編譯器引入更多最佳化。比方說 `X * 2 / 2` 在沒有 overflow 發生的時候,可最佳化為 `X`。 + +而且值得注意的是,在你的程序初始化之前,栈里面塞的一堆东西也是 UB。 + +但是高级语言比如说 java,python 都不会出现这样的问题了。 + +因为发现少一个时钟周期的运算多一堆问题,完全得不偿失啊。 + +当然,有更多兴趣的话可以看看这个东西 + +[万恶的未定义行为 | 程式设计 遇上 小提琴](https://blog.ez2learn.com/2008/09/27/evil-undefined-behavior/) + +关键是,老师喜欢出题刁难你啊!真烦啊! diff --git a/3.编程思维体系构建/3.4.7.3C编译器干了什么.md b/3.编程思维体系构建/3.4.7.3C编译器干了什么.md new file mode 100644 index 0000000..1d7db6a --- /dev/null +++ b/3.编程思维体系构建/3.4.7.3C编译器干了什么.md @@ -0,0 +1,27 @@ +# C 编译器干了什么 + +# 以 gcc 为例 + +1、预处理,生成 .i 的文件[预处理器 cpp] + +2、将预处理后的文件转换成汇编语言, 生成文件 .s [编译器 egcs] + +3、有汇编变为目标代码(机器代码)生成 .o 的文件[汇编器 as] + +4、连接目标代码, 生成可执行程序 [链接器 ld] + +# 有啥用 + +有一天你发现,某一段 C 语言只有几行,但是用了大量的魔法宏定义以及魔法操作以及神奇的元编程。 + +你想看懂他,你可能需要学会 gcc -E。将他们都翻译成.i 文件。 + +如果有一天你要学习汇编语言,或者说出现了在代码中看不出的 bug,你可能需要翻译成.s 文件 + +# 了解更多 + +当然不止如此,编译器还承担了优化的过程,有时候同一份代码,经过 O1 和 O2 不同优化可能最后代码都不一样。 + +推荐阅读 + +[http://www.ruanyifeng.com/blog/2014/11/compiler.html](http://www.ruanyifeng.com/blog/2014/11/compiler.html) diff --git a/3.编程思维体系构建/3.4.7.4Inline Assembly与链接加载.md b/3.编程思维体系构建/3.4.7.4Inline Assembly与链接加载.md new file mode 100644 index 0000000..cd8424d --- /dev/null +++ b/3.编程思维体系构建/3.4.7.4Inline Assembly与链接加载.md @@ -0,0 +1,85 @@ +# Inline Assembly 与链接加载 + +# Inline Assembly + +可以在 C 代码里嵌入汇编语言的骚操作。毕竟编译器本身也就是个复制,翻译,,粘贴的过程。 + +> C 语言是高级的汇编语言! + +你可以把每一个 C 语言的语句都直译成一条一条的汇编代码。反正也是顺序执行的。 + +C 艹可能没那么容易了····比如说虚函数调用,那你就不太好翻译嘛。 + +最简单的就是用用个 asm(.....) + +当然这里可以参考 c inline Asm 的教程 但是已经脱离了本文的主题了 + +这里给指条路 [https://dmalcolm.fedorapeople.org/gcc/2015-08-31/rst-experiment/how-to-use-inline-assembly-language-in-c-code.html](https://dmalcolm.fedorapeople.org/gcc/2015-08-31/rst-experiment/how-to-use-inline-assembly-language-in-c-code.html) + +> 诸如 Go 的高级语言 也可以通过 inline C 来 内链汇编 + +你可以写任何一个指令,他完全不会检查 也不会优化 编译器默认你知道你在干什么。 + +然后 C 编译器就会将这部分代码 原封不动地 拷贝进你的二进制代码当中 + +当然,你可以通过 RTFM 来将 C 语言的变量塞到汇编中处理。 + +在 Windows 平台下 VS 没有 Code 可以以 `__asm {}` 代码块来进行实验 但是注意 只能在 x86 模式下使用 x64 不支持 可以参考 [https://docs.microsoft.com/zh-tw/cpp/assembler/inline/asm?view=msvc-170](https://docs.microsoft.com/zh-tw/cpp/assembler/inline/asm?view=msvc-170) + +以上两种平台的方案都其实本质是编译器特殊宏 并不包括在 C 的标准内 所以要针对不同的编译器 寻找对应的文档 + +# 静态链接 + +当你使用 GCC 生成可执行文件./a.out 时,到底发生了什么? + +为什么就可以直接执行呢?当你问及这个问题时,那么就大有一番探索的空间了 + +# 链接器 + +链接器的功能:将一个可执行程序所需的目标文件和库最终整合在一起。 + +就是说,你调用的一些库,是必须要有外部的东西支持的 + +这个就是帮你和外部库连接起来的重要部分。 + +一个程序包含三个段:.text 、.data 和 .bss 段。 + +``` +而各目标文件和库都包含这三段,所以,ld 链接器的功能就是将各个目标文件和库的三段合并在一起。 + +当然,链接器所完成的链接工作并非只是简单地将各个目标文件和库的三段内存堆砌在一起,而是还要完成 “重定位” 的工作。 +``` + +## 查看二进制文件的工具 + +使用 objdump 查看 data 节的 x,y + +查看 main 对应的汇编代码 + +使用 readelf 查看 relocation 的信息 + +使用 IDA BinaryNInja 一类反汇编工具 + +# 动态链接 + +静态链接一次用那么多,实在是太大太大了 + +比如说一个 printf 就要几十 KB,完全没必要把 libc 代码包含到程序里面 + +可以等程序加载好之后再去做一个链接 + +Windows 下一般是 DLL 作为程序使用的 动态链接库 + +Linux 下一般是 .so 如果你看到了 .a 那个一般是 archive 的缩写 + +使用动态链接的好处在于 可以热加载和热更新 + +# 共享连接的加载 + +使用 ldd 来查看 a.out 就可以查看动态链接库 + +不过 ldd 这个是个神奇的脚本!!! + +他做的事情就是挨个调用去试着加载 a.out + +加载会读取头中的一些表 比如全局 GOT 然后根据名称查找 diff --git a/3.编程思维体系构建/3.4.7C基础知识杂谈.md b/3.编程思维体系构建/3.4.7C基础知识杂谈.md new file mode 100644 index 0000000..5c1b1bc --- /dev/null +++ b/3.编程思维体系构建/3.4.7C基础知识杂谈.md @@ -0,0 +1,3 @@ +# C 基础知识杂谈 + +本章节内容涉及 C 的调试,C 的历史遗留问题以及 C 的执行过程,可以作为补充资料让大家学习和思考 diff --git a/3.编程思维体系构建/3.4C语言.md b/3.编程思维体系构建/3.4C语言.md new file mode 100644 index 0000000..ef4bc94 --- /dev/null +++ b/3.编程思维体系构建/3.4C语言.md @@ -0,0 +1,13 @@ +# 3.4C 语言 + +对于计算机学院的学生来说,C 语言要陪伴你以后要非常多的时光,以后的程序设计实践,数据结构,操作系统等等课程都是用 C 语言写的,因此我希望各位同学可以尝试着多完成一些内容 + +对于自动化等别的学院的同学来说,C 语言更贴近计算机底层的运行原理,可以帮助你进行更为底层的各类开发,也是非常有帮助的。 + +值得一提的是,我不会在本教程讲授过于基础的概念,但是会贴出你可能需要学习的内容。 + +![](static/boxcnAnXUHDqsMYVrDlBfFunoVf.png) + +同时我要说的是:C 语言为了适配多种多样的硬件以及各式各样的操作,他对非常多的 undefined 操作不做太多限制,也就是说你可能会出现各种各样的问题,甚至把你电脑炸了 + +但是不用担心,不会造成什么严重后果的,只不过你需要在编程的时候多加留心 diff --git a/3.编程思维体系构建/3.编程思维体系构建.md b/3.编程思维体系构建/3.编程思维体系构建.md new file mode 100644 index 0000000..d0ab6e8 --- /dev/null +++ b/3.编程思维体系构建/3.编程思维体系构建.md @@ -0,0 +1,19 @@ +# 3.编程思维体系构建 + +author:zzm,赵尚凯,张家鹏 + +无论怎么说,编程都是大学生涯里不可或缺的一部分。 + +他的核心思想在于用将可量化的重复性劳作交给计算机来完成,达成提高效率的。 + +这里引用一位老师的比喻就是,将编程形容为开车,你可以讨厌他,也可以喜欢他。 + +但是,他作为一个交通工具的属性是不可或缺的属性。 + +因此本章节的目标是让大家面对一个实际问题,有使用编程解决的思路和能力。 + +如果要做人工智能部分,请做完 C 语言必做部分以及 python 必做部分帮助大家更为丝滑的入门。 + +如果你对计算机一无所知,我推荐你在茶余饭后来看这个教程,计算机速成课,可以让你轻轻松松学会最基本的内容。 + +![](static/boxcnOrKXUsIPJAUXyGB3Txewve.png) diff --git a/3.编程思维体系构建/static/EbgHbImhaoxr2Kx4lr0c0cFSnje.PNG b/3.编程思维体系构建/static/EbgHbImhaoxr2Kx4lr0c0cFSnje.PNG new file mode 100644 index 0000000..54ab72e Binary files /dev/null and b/3.编程思维体系构建/static/EbgHbImhaoxr2Kx4lr0c0cFSnje.PNG differ diff --git a/3.编程思维体系构建/static/Fzn4b3C9qoLUApxxwvXcHOFfnxe.PNG b/3.编程思维体系构建/static/Fzn4b3C9qoLUApxxwvXcHOFfnxe.PNG new file mode 100644 index 0000000..dfca7d8 Binary files /dev/null and b/3.编程思维体系构建/static/Fzn4b3C9qoLUApxxwvXcHOFfnxe.PNG differ diff --git a/3.编程思维体系构建/static/HMipbO4vSoM3jhxSZ7Kcuddqnxh.png b/3.编程思维体系构建/static/HMipbO4vSoM3jhxSZ7Kcuddqnxh.png new file mode 100644 index 0000000..1cbfc09 Binary files /dev/null and b/3.编程思维体系构建/static/HMipbO4vSoM3jhxSZ7Kcuddqnxh.png differ diff --git a/3.编程思维体系构建/static/HP6qb4DkKoHpahx35hacFWAUnef.PNG b/3.编程思维体系构建/static/HP6qb4DkKoHpahx35hacFWAUnef.PNG new file mode 100644 index 0000000..54f370f Binary files /dev/null and b/3.编程思维体系构建/static/HP6qb4DkKoHpahx35hacFWAUnef.PNG differ diff --git a/3.编程思维体系构建/static/Hqzbbs6iYobnxWxz11Ocfa9gnHd.png b/3.编程思维体系构建/static/Hqzbbs6iYobnxWxz11Ocfa9gnHd.png new file mode 100644 index 0000000..5f2d7fe Binary files /dev/null and b/3.编程思维体系构建/static/Hqzbbs6iYobnxWxz11Ocfa9gnHd.png differ diff --git a/3.编程思维体系构建/static/I3tAbSkNuoysdPxpFR6cCnFrnNd.png b/3.编程思维体系构建/static/I3tAbSkNuoysdPxpFR6cCnFrnNd.png new file mode 100644 index 0000000..b42a57f Binary files /dev/null and b/3.编程思维体系构建/static/I3tAbSkNuoysdPxpFR6cCnFrnNd.png differ diff --git a/3.编程思维体系构建/static/J2XmbpHfSoyABIxWhonco3jJnNp.PNG b/3.编程思维体系构建/static/J2XmbpHfSoyABIxWhonco3jJnNp.PNG new file mode 100644 index 0000000..417a6d2 Binary files /dev/null and b/3.编程思维体系构建/static/J2XmbpHfSoyABIxWhonco3jJnNp.PNG differ diff --git a/3.编程思维体系构建/static/JCocbTmlEorzjox0ORqcFCYznEd.png b/3.编程思维体系构建/static/JCocbTmlEorzjox0ORqcFCYznEd.png new file mode 100644 index 0000000..66669fd Binary files /dev/null and b/3.编程思维体系构建/static/JCocbTmlEorzjox0ORqcFCYznEd.png differ diff --git a/3.编程思维体系构建/static/MEItb9zbBoYtIvxQMJlcLerCnuf.png b/3.编程思维体系构建/static/MEItb9zbBoYtIvxQMJlcLerCnuf.png new file mode 100644 index 0000000..e9642e9 Binary files /dev/null and b/3.编程思维体系构建/static/MEItb9zbBoYtIvxQMJlcLerCnuf.png differ diff --git a/3.编程思维体系构建/static/MUEWbTv6QoShVRxmgTicEjMbnHz.PNG b/3.编程思维体系构建/static/MUEWbTv6QoShVRxmgTicEjMbnHz.PNG new file mode 100644 index 0000000..3a880e6 Binary files /dev/null and b/3.编程思维体系构建/static/MUEWbTv6QoShVRxmgTicEjMbnHz.PNG differ diff --git a/3.编程思维体系构建/static/Mdkzbdop2ooK2Lxoq2GcZ6eLn6r.png b/3.编程思维体系构建/static/Mdkzbdop2ooK2Lxoq2GcZ6eLn6r.png new file mode 100644 index 0000000..e420745 Binary files /dev/null and b/3.编程思维体系构建/static/Mdkzbdop2ooK2Lxoq2GcZ6eLn6r.png differ diff --git a/3.编程思维体系构建/static/OGp0bXm4CoHo60xGC6NcNEzcnpt.png b/3.编程思维体系构建/static/OGp0bXm4CoHo60xGC6NcNEzcnpt.png new file mode 100644 index 0000000..7117c9b Binary files /dev/null and b/3.编程思维体系构建/static/OGp0bXm4CoHo60xGC6NcNEzcnpt.png differ diff --git a/3.编程思维体系构建/static/PAA1bTNOroya0zxkdFTceYYdn4c.PNG b/3.编程思维体系构建/static/PAA1bTNOroya0zxkdFTceYYdn4c.PNG new file mode 100644 index 0000000..40997aa Binary files /dev/null and b/3.编程思维体系构建/static/PAA1bTNOroya0zxkdFTceYYdn4c.PNG differ diff --git a/3.编程思维体系构建/static/RAZnb6ziGot3brxp4dac2Qp6ndg.png b/3.编程思维体系构建/static/RAZnb6ziGot3brxp4dac2Qp6ndg.png new file mode 100644 index 0000000..dcbe21e Binary files /dev/null and b/3.编程思维体系构建/static/RAZnb6ziGot3brxp4dac2Qp6ndg.png differ diff --git a/3.编程思维体系构建/static/UCIabPwSmo9lfmxHIGvcsAcqn4d.PNG b/3.编程思维体系构建/static/UCIabPwSmo9lfmxHIGvcsAcqn4d.PNG new file mode 100644 index 0000000..efaa1b6 Binary files /dev/null and b/3.编程思维体系构建/static/UCIabPwSmo9lfmxHIGvcsAcqn4d.PNG differ diff --git a/3.编程思维体系构建/static/XswZbhqWZocIccxo4ibcfY9pnLe.png b/3.编程思维体系构建/static/XswZbhqWZocIccxo4ibcfY9pnLe.png new file mode 100644 index 0000000..cd560d0 Binary files /dev/null and b/3.编程思维体系构建/static/XswZbhqWZocIccxo4ibcfY9pnLe.png differ diff --git a/3.编程思维体系构建/static/YAOvb6gquofiAYxsn3tcxcCYngf.png b/3.编程思维体系构建/static/YAOvb6gquofiAYxsn3tcxcCYngf.png new file mode 100644 index 0000000..1fc659d Binary files /dev/null and b/3.编程思维体系构建/static/YAOvb6gquofiAYxsn3tcxcCYngf.png differ diff --git a/3.编程思维体系构建/static/YMGmbGgfGoK0ZMxVGe4cGHgBnee.PNG b/3.编程思维体系构建/static/YMGmbGgfGoK0ZMxVGe4cGHgBnee.PNG new file mode 100644 index 0000000..4d39763 Binary files /dev/null and b/3.编程思维体系构建/static/YMGmbGgfGoK0ZMxVGe4cGHgBnee.PNG differ diff --git a/3.编程思维体系构建/static/boxcn05Ca6Wu5TxFMplZCw2N8Jb.png b/3.编程思维体系构建/static/boxcn05Ca6Wu5TxFMplZCw2N8Jb.png new file mode 100644 index 0000000..2dd1014 Binary files /dev/null and b/3.编程思维体系构建/static/boxcn05Ca6Wu5TxFMplZCw2N8Jb.png differ diff --git a/3.编程思维体系构建/static/boxcn0VMYQlez7tQTNkTPDkCsvg.png b/3.编程思维体系构建/static/boxcn0VMYQlez7tQTNkTPDkCsvg.png new file mode 100644 index 0000000..55f1d16 Binary files /dev/null and b/3.编程思维体系构建/static/boxcn0VMYQlez7tQTNkTPDkCsvg.png differ diff --git a/3.编程思维体系构建/static/boxcn1hlL1Fk4kDK4CPT2hJxwnV.png b/3.编程思维体系构建/static/boxcn1hlL1Fk4kDK4CPT2hJxwnV.png new file mode 100644 index 0000000..3720f9d Binary files /dev/null and b/3.编程思维体系构建/static/boxcn1hlL1Fk4kDK4CPT2hJxwnV.png differ diff --git a/3.编程思维体系构建/static/boxcn2ouk043lNQEUkVkIS7bSSd.png b/3.编程思维体系构建/static/boxcn2ouk043lNQEUkVkIS7bSSd.png new file mode 100644 index 0000000..41838f3 Binary files /dev/null and b/3.编程思维体系构建/static/boxcn2ouk043lNQEUkVkIS7bSSd.png differ diff --git a/3.编程思维体系构建/static/boxcn3l30usevMTgv1ZbZ0mfJdh.png b/3.编程思维体系构建/static/boxcn3l30usevMTgv1ZbZ0mfJdh.png new file mode 100644 index 0000000..7970890 Binary files /dev/null and b/3.编程思维体系构建/static/boxcn3l30usevMTgv1ZbZ0mfJdh.png differ diff --git a/3.编程思维体系构建/static/boxcn6MgNnY2qBd1yAudeirx6Sh.png b/3.编程思维体系构建/static/boxcn6MgNnY2qBd1yAudeirx6Sh.png new file mode 100644 index 0000000..a347eda Binary files /dev/null and b/3.编程思维体系构建/static/boxcn6MgNnY2qBd1yAudeirx6Sh.png differ diff --git a/3.编程思维体系构建/static/boxcn7zL0QFakVTpYBdpOmmWOvc.png b/3.编程思维体系构建/static/boxcn7zL0QFakVTpYBdpOmmWOvc.png new file mode 100644 index 0000000..c9dd1b6 Binary files /dev/null and b/3.编程思维体系构建/static/boxcn7zL0QFakVTpYBdpOmmWOvc.png differ diff --git a/3.编程思维体系构建/static/boxcn8ZxT5oMkScArZjZhgM6TYb.png b/3.编程思维体系构建/static/boxcn8ZxT5oMkScArZjZhgM6TYb.png new file mode 100644 index 0000000..b378dc8 Binary files /dev/null and b/3.编程思维体系构建/static/boxcn8ZxT5oMkScArZjZhgM6TYb.png differ diff --git a/3.编程思维体系构建/static/boxcnAnXUHDqsMYVrDlBfFunoVf.png b/3.编程思维体系构建/static/boxcnAnXUHDqsMYVrDlBfFunoVf.png new file mode 100644 index 0000000..0391251 Binary files /dev/null and b/3.编程思维体系构建/static/boxcnAnXUHDqsMYVrDlBfFunoVf.png differ diff --git a/3.编程思维体系构建/static/boxcnAnkVAJmMT0NSNvo6crXYAd.png b/3.编程思维体系构建/static/boxcnAnkVAJmMT0NSNvo6crXYAd.png new file mode 100644 index 0000000..544a7cc Binary files /dev/null and b/3.编程思维体系构建/static/boxcnAnkVAJmMT0NSNvo6crXYAd.png differ diff --git a/3.编程思维体系构建/static/boxcnC6TAAdtS0P5HzebFgFn2lc.png b/3.编程思维体系构建/static/boxcnC6TAAdtS0P5HzebFgFn2lc.png new file mode 100644 index 0000000..9b9fa1f Binary files /dev/null and b/3.编程思维体系构建/static/boxcnC6TAAdtS0P5HzebFgFn2lc.png differ diff --git a/3.编程思维体系构建/static/boxcnFwZpWZ3fQkdd3mCO8Mr9Wj.png b/3.编程思维体系构建/static/boxcnFwZpWZ3fQkdd3mCO8Mr9Wj.png new file mode 100644 index 0000000..9ea9e38 Binary files /dev/null and b/3.编程思维体系构建/static/boxcnFwZpWZ3fQkdd3mCO8Mr9Wj.png differ diff --git a/3.编程思维体系构建/static/boxcnHXggg6eLy86vFmb4shOksh.png b/3.编程思维体系构建/static/boxcnHXggg6eLy86vFmb4shOksh.png new file mode 100644 index 0000000..a31439f Binary files /dev/null and b/3.编程思维体系构建/static/boxcnHXggg6eLy86vFmb4shOksh.png differ diff --git a/3.编程思维体系构建/static/boxcnIdOChXQUGMvnxWcB7uTWLh.png b/3.编程思维体系构建/static/boxcnIdOChXQUGMvnxWcB7uTWLh.png new file mode 100644 index 0000000..82d5365 Binary files /dev/null and b/3.编程思维体系构建/static/boxcnIdOChXQUGMvnxWcB7uTWLh.png differ diff --git a/3.编程思维体系构建/static/boxcnOrKXUsIPJAUXyGB3Txewve.png b/3.编程思维体系构建/static/boxcnOrKXUsIPJAUXyGB3Txewve.png new file mode 100644 index 0000000..b85efe9 Binary files /dev/null and b/3.编程思维体系构建/static/boxcnOrKXUsIPJAUXyGB3Txewve.png differ diff --git a/3.编程思维体系构建/static/boxcnPv2FcyQxGLjYHThSaJNwRf.jpeg b/3.编程思维体系构建/static/boxcnPv2FcyQxGLjYHThSaJNwRf.jpeg new file mode 100644 index 0000000..3842128 Binary files /dev/null and b/3.编程思维体系构建/static/boxcnPv2FcyQxGLjYHThSaJNwRf.jpeg differ diff --git a/3.编程思维体系构建/static/boxcnQ4rvJqVbXJaWMOwceHdrQb.png b/3.编程思维体系构建/static/boxcnQ4rvJqVbXJaWMOwceHdrQb.png new file mode 100644 index 0000000..60d9aa6 Binary files /dev/null and b/3.编程思维体系构建/static/boxcnQ4rvJqVbXJaWMOwceHdrQb.png differ diff --git a/3.编程思维体系构建/static/boxcnW0YQY58RXhwdtRj5k6ndlc.jpeg b/3.编程思维体系构建/static/boxcnW0YQY58RXhwdtRj5k6ndlc.jpeg new file mode 100644 index 0000000..3045685 Binary files /dev/null and b/3.编程思维体系构建/static/boxcnW0YQY58RXhwdtRj5k6ndlc.jpeg differ diff --git a/3.编程思维体系构建/static/boxcnXjwE0yDFvpQxLaPw7FifxV.png b/3.编程思维体系构建/static/boxcnXjwE0yDFvpQxLaPw7FifxV.png new file mode 100644 index 0000000..8eefb06 Binary files /dev/null and b/3.编程思维体系构建/static/boxcnXjwE0yDFvpQxLaPw7FifxV.png differ diff --git a/3.编程思维体系构建/static/boxcnaqLMfwqNMTcYEPuF3vFjqg.png b/3.编程思维体系构建/static/boxcnaqLMfwqNMTcYEPuF3vFjqg.png new file mode 100644 index 0000000..c781a1b Binary files /dev/null and b/3.编程思维体系构建/static/boxcnaqLMfwqNMTcYEPuF3vFjqg.png differ diff --git a/3.编程思维体系构建/static/boxcnbnrVCmNGfriHhU5pL76gsd.png b/3.编程思维体系构建/static/boxcnbnrVCmNGfriHhU5pL76gsd.png new file mode 100644 index 0000000..1c6de62 Binary files /dev/null and b/3.编程思维体系构建/static/boxcnbnrVCmNGfriHhU5pL76gsd.png differ diff --git a/3.编程思维体系构建/static/boxcncRc5OKZROtxC9rpQYxrjvf.png b/3.编程思维体系构建/static/boxcncRc5OKZROtxC9rpQYxrjvf.png new file mode 100644 index 0000000..802238c Binary files /dev/null and b/3.编程思维体系构建/static/boxcncRc5OKZROtxC9rpQYxrjvf.png differ diff --git a/3.编程思维体系构建/static/boxcnfrxYjk5CCjMfY0mLK1B1Ze.png b/3.编程思维体系构建/static/boxcnfrxYjk5CCjMfY0mLK1B1Ze.png new file mode 100644 index 0000000..bcb24b3 Binary files /dev/null and b/3.编程思维体系构建/static/boxcnfrxYjk5CCjMfY0mLK1B1Ze.png differ diff --git a/3.编程思维体系构建/static/boxcngoLTiM9wto9uCGzH7nkjkW.png b/3.编程思维体系构建/static/boxcngoLTiM9wto9uCGzH7nkjkW.png new file mode 100644 index 0000000..9c6d383 Binary files /dev/null and b/3.编程思维体系构建/static/boxcngoLTiM9wto9uCGzH7nkjkW.png differ diff --git a/3.编程思维体系构建/static/boxcngx7ZPA7pONbJo82LbNCO1g.png b/3.编程思维体系构建/static/boxcngx7ZPA7pONbJo82LbNCO1g.png new file mode 100644 index 0000000..f88e7e7 Binary files /dev/null and b/3.编程思维体系构建/static/boxcngx7ZPA7pONbJo82LbNCO1g.png differ diff --git a/3.编程思维体系构建/static/boxcnhNeAnlrbcdJciMUY9oNTuc.png b/3.编程思维体系构建/static/boxcnhNeAnlrbcdJciMUY9oNTuc.png new file mode 100644 index 0000000..e04dbd6 Binary files /dev/null and b/3.编程思维体系构建/static/boxcnhNeAnlrbcdJciMUY9oNTuc.png differ diff --git a/3.编程思维体系构建/static/boxcnhTxhUYMHeYHdrq0zWzLomb.png b/3.编程思维体系构建/static/boxcnhTxhUYMHeYHdrq0zWzLomb.png new file mode 100644 index 0000000..e0cd849 Binary files /dev/null and b/3.编程思维体系构建/static/boxcnhTxhUYMHeYHdrq0zWzLomb.png differ diff --git a/3.编程思维体系构建/static/boxcniHhCIUQY0oB3ALlxqgciLd.png b/3.编程思维体系构建/static/boxcniHhCIUQY0oB3ALlxqgciLd.png new file mode 100644 index 0000000..2c4e5fd Binary files /dev/null and b/3.编程思维体系构建/static/boxcniHhCIUQY0oB3ALlxqgciLd.png differ diff --git a/3.编程思维体系构建/static/boxcnim98FJybpkGl8sfqxP9v9b.png b/3.编程思维体系构建/static/boxcnim98FJybpkGl8sfqxP9v9b.png new file mode 100644 index 0000000..5762e16 Binary files /dev/null and b/3.编程思维体系构建/static/boxcnim98FJybpkGl8sfqxP9v9b.png differ diff --git a/3.编程思维体系构建/static/boxcnjAoO54txAhnu7Ry8ExjGvc.png b/3.编程思维体系构建/static/boxcnjAoO54txAhnu7Ry8ExjGvc.png new file mode 100644 index 0000000..eefcaff Binary files /dev/null and b/3.编程思维体系构建/static/boxcnjAoO54txAhnu7Ry8ExjGvc.png differ diff --git a/3.编程思维体系构建/static/boxcnkjmKcCxIgRIzA5kyUZckye.png b/3.编程思维体系构建/static/boxcnkjmKcCxIgRIzA5kyUZckye.png new file mode 100644 index 0000000..0096ae6 Binary files /dev/null and b/3.编程思维体系构建/static/boxcnkjmKcCxIgRIzA5kyUZckye.png differ diff --git a/3.编程思维体系构建/static/boxcnkxd472wIT39DbEiBsyPWzf.png b/3.编程思维体系构建/static/boxcnkxd472wIT39DbEiBsyPWzf.png new file mode 100644 index 0000000..efc6e45 Binary files /dev/null and b/3.编程思维体系构建/static/boxcnkxd472wIT39DbEiBsyPWzf.png differ diff --git a/3.编程思维体系构建/static/boxcnl06p0ZS8SSQsWJNLQLYIjc.png b/3.编程思维体系构建/static/boxcnl06p0ZS8SSQsWJNLQLYIjc.png new file mode 100644 index 0000000..2012797 Binary files /dev/null and b/3.编程思维体系构建/static/boxcnl06p0ZS8SSQsWJNLQLYIjc.png differ diff --git a/3.编程思维体系构建/static/boxcnmRygjmZfwFzODP2N6bVoEh.png b/3.编程思维体系构建/static/boxcnmRygjmZfwFzODP2N6bVoEh.png new file mode 100644 index 0000000..ca5da29 Binary files /dev/null and b/3.编程思维体系构建/static/boxcnmRygjmZfwFzODP2N6bVoEh.png differ diff --git a/3.编程思维体系构建/static/boxcnn3QHja0tzEwqJl9Mk4KnCg.png b/3.编程思维体系构建/static/boxcnn3QHja0tzEwqJl9Mk4KnCg.png new file mode 100644 index 0000000..1cbbd7d Binary files /dev/null and b/3.编程思维体系构建/static/boxcnn3QHja0tzEwqJl9Mk4KnCg.png differ diff --git a/3.编程思维体系构建/static/boxcnnMjc9pwgZgk1GBmBRlBS6d.png b/3.编程思维体系构建/static/boxcnnMjc9pwgZgk1GBmBRlBS6d.png new file mode 100644 index 0000000..4eba1bd Binary files /dev/null and b/3.编程思维体系构建/static/boxcnnMjc9pwgZgk1GBmBRlBS6d.png differ diff --git a/3.编程思维体系构建/static/boxcnnjaObP5JzpICUx1PMO9MQg.png b/3.编程思维体系构建/static/boxcnnjaObP5JzpICUx1PMO9MQg.png new file mode 100644 index 0000000..4b4b4fe Binary files /dev/null and b/3.编程思维体系构建/static/boxcnnjaObP5JzpICUx1PMO9MQg.png differ diff --git a/3.编程思维体系构建/static/boxcnuNXrb5zOppCZAlGQ19wuDk.jpg b/3.编程思维体系构建/static/boxcnuNXrb5zOppCZAlGQ19wuDk.jpg new file mode 100644 index 0000000..3f772f8 Binary files /dev/null and b/3.编程思维体系构建/static/boxcnuNXrb5zOppCZAlGQ19wuDk.jpg differ diff --git a/3.编程思维体系构建/static/boxcnustZBhjMu8FPN0Kxi4Mwvf.jpg b/3.编程思维体系构建/static/boxcnustZBhjMu8FPN0Kxi4Mwvf.jpg new file mode 100644 index 0000000..4f44eb6 Binary files /dev/null and b/3.编程思维体系构建/static/boxcnustZBhjMu8FPN0Kxi4Mwvf.jpg differ diff --git a/3.编程思维体系构建/static/boxcnuwZzqX4dF8xKTYajwrDSxf.png b/3.编程思维体系构建/static/boxcnuwZzqX4dF8xKTYajwrDSxf.png new file mode 100644 index 0000000..36c22bd Binary files /dev/null and b/3.编程思维体系构建/static/boxcnuwZzqX4dF8xKTYajwrDSxf.png differ diff --git a/3.编程思维体系构建/static/boxcnvOGdjKLnvXvJM7nlE8yVcb.png b/3.编程思维体系构建/static/boxcnvOGdjKLnvXvJM7nlE8yVcb.png new file mode 100644 index 0000000..0f1c10c Binary files /dev/null and b/3.编程思维体系构建/static/boxcnvOGdjKLnvXvJM7nlE8yVcb.png differ diff --git a/3.编程思维体系构建/static/boxcnxjex5Q3Lt9AAx6roN3ClUg.png b/3.编程思维体系构建/static/boxcnxjex5Q3Lt9AAx6roN3ClUg.png new file mode 100644 index 0000000..53fe4ef Binary files /dev/null and b/3.编程思维体系构建/static/boxcnxjex5Q3Lt9AAx6roN3ClUg.png differ diff --git a/3.编程思维体系构建/static/boxcnydHyaNPqUEVVWmbdGofX0d.png b/3.编程思维体系构建/static/boxcnydHyaNPqUEVVWmbdGofX0d.png new file mode 100644 index 0000000..8c59d76 Binary files /dev/null and b/3.编程思维体系构建/static/boxcnydHyaNPqUEVVWmbdGofX0d.png differ diff --git a/f29f22b2-7a21-4b55-8013-7941b883a98f.png b/f29f22b2-7a21-4b55-8013-7941b883a98f.png new file mode 100644 index 0000000..a30b54e Binary files /dev/null and b/f29f22b2-7a21-4b55-8013-7941b883a98f.png differ diff --git a/简介.md b/简介.md index 0340d46..3982a5b 100644 --- a/简介.md +++ b/简介.md @@ -16,6 +16,7 @@ zzm花费了一年的时间动员了大伙对讲义进行修缮和完备,不得不说这确实不是一个小的工作,不过在2023年3月,在计院领导的支持下,计算机学院科协成立了。我们将在学院的支持下继续完善这个内容,同时也欢迎大伙加入我们,共同参与到讲义的学习与撰写中来! +![](f29f22b2-7a21-4b55-8013-7941b883a98f.png) 欢迎加群669895692 # 学习原则