计算机科学速成课(Crash Course Computer Science)
一、早期的编程方式及发展
早期计算机的使用为了正确执行不同计算,需要某种控制面板,上面有很多小插孔,可以插电线让机器的不同部分互相传数据和信号
随着技术的发展,程序存在内存变得可行;程序易于修改、方便 CPU 快速读取,这类机器称为存储程序计算机
冯诺依曼结构
如果内存足够,不仅可以存要运行的程序还可以存程序需要的数据,包括程序运行时产生的新数据。
程序和数据都存在一个地方,叫 "冯诺依曼结构"
冯诺依曼计算机的标志: 一个处理器(有ALU)+数据寄存器+指令寄存器+指令地址寄存器+内存(负责存数据和指令)
编程语言
不管是插线板、开关或穿孔纸卡早期编程都很困难,要非常了解底层硬件(操作码, 寄存器等)因此编程很难,并不能完全发挥计算机的能力。 需要一种更简单的编程方式告诉计算机要做什么?
计算机能处理二进制,只能处理二进制,称其为机器码(机器语言)
对程序的高层次描述,叫 "伪代码"
汇编器
在 1940~1950 年代开发出一种新语言, 更可读、更高层次地为每个操作码分配一个简单名字,叫 “助记符”,其后面紧跟数据,形成完整指令
但是CPU并不知道这些指令代表着什么,通过写二进制程序来让它可以读懂文字指令,自动转成二进制指令,这种程序叫汇编器
汇编器读取用"汇编语言"写的程序,然后转成"机器码"
汇编只是修饰了一下机器码。一般情况下一条汇编指令对应一条机器指令,所以汇编码还是和底层硬件的连接很紧密。 汇编器仍然要求使用者考虑用什么寄存器和内存地址
一次编写,到处运行
FORTRAN(Formula Translation) 这门语言由 IBM 在 1957 年发布
1950 年代大多数编程语言和编译器,只能运行在一种计算机上,因此在1959 年组建了一个联盟数据系统语言委员会,开发一种通用编程语言,可以在不同机器上通用 (普通面向商业语言,COBOL)
为了兼容不同底层硬件,每个计算架构需要一个 COBOL 编译器。不管是什么电脑,这些编译器都可以接收相同 COBOL 代码
二、编程原理(语句和函数)
语句
编程语言有"语句",语句用于表达单个完整思想。用不同词汇可以代表不同含义
规定句子结构的一系列规则叫语法
赋值语句: 把一个值赋给一个变量,变量名可以随意取
程序由一个个指令组成,从第一条语句开始,一句一句运行到结尾。具体语法略有不同,但主体结构一样
函数
程序由许多条指令组成,为了隐藏复杂度,可以把代码打包成 “函数”(“方法” 或 “子程序”),其他地方想用这个函数,直接写函数名就可以了
现代软件由上千个函数组成,每个负责不同的事模块化编程。 不仅可以让单个程序员独立制作 App,也让团队协作可以写更大型的程序。不同人写不同函数,只需要确保自己的代码工作正常,整个程序也能正常运作
现代编程语言有很多预先写好的函数集合,叫 "库"
三、算法入门
算法: 解决问题的具体步骤,即使结果一致,有些算法会更好,所需步骤越少越好或者占多少内存等
算法的输入大小和运行步骤之间的关系叫算法的复杂度,表示运行速度的量级,算法复杂度用O( )表示
排序算法
选择排序: 用循环遍历数组,每个数组位置都跑一遍循环,找最小数然后互换位置,算法复杂度为 O(N²)
归并排序:
1) 首先检查数组大小是否 > 1,如果是就把数组分成两半
2) 从前两个数组开始,读第一个值,比较后小的放在前面
3) 重复这个过程,按序排列
4) 然后再归并一次
5) 归并后取前两个数组,比较第一个数
6) 合并成更大的有序数组,比较两个数组的第一个数,取最小数
7) 重复这个过程,直到完成
归并排序的算法复杂度是 O(n * log n),n 是需要 比较+合并 的次数(和数组大小成正比)log N 是合并步骤的次数
Dijkstra算法
从一个顶点到其余各顶点的最短路径算法,解决有权图中最短路径问题
Dijkstra 算法总是从成本最低的节点开始
从成本最低的节点跑到所有相邻节点,记录成本
再执行一次Dijkstra 算法,记录所有相邻节点的成本
再执行一次Dijkstra 算法,找到下一个成本最低的节点
Dijkstra算法的初始版本,算法复杂度是 O(n² ),这个效率不够好(输入不能很大)
类似 Dijkstra 的算法用于在地图中导航中找最佳路线
四、数据结构
通过数据结构来使数据结构化,从而方便读取
数组
数组(Array),也叫列表(list)或向量(Vector)
数组的值一个个连续存在内存里,可以把多个值存在数组变量里。为了拿出数组中某个值,要指定一个下标(index),大多数编程语言里,数组下标都从 0 开始(用方括号 [ ] 代表访问数组)
字符串在内存里以二进制值0结尾,这叫字符"null",表示字符串结尾
指针和链表
多个变量打包在一起叫结构体 (Struct)
有一种结构体,叫节点(node),它存一个变量一个指针(pointer) 这是一种特殊变量,指向一个内存地址
节点可以做链表(linked list),链表是一种灵活数据结构,能存很多个 节点 (node),灵活性通过每个节点指向下一个节点实现
队列、栈、树
队列: 谁先来就排前面,先进先出(FIFO),要新加入一个要遍历整个链表到结尾,然后把结尾的指针指向新加入的
栈: 后进先出(LIFO),称为 “入栈”(push) “出栈”(pop)
如果节点改一下,改成 2 个指针就成为了树,下图节点最多只有 2 个子节点,因此叫 二叉树(binary tree)
最高的节点叫"根节点"(root),“根节点"下的所有节点 都叫"子节点”(children)
任何子节点的直属上层节点,叫"母节点"(parent node)
没有任何"子节点"的节点,是"树"结束的地方,叫"叶节点"(leaf)
五、软件工程
在一些工程中可能会用到数量庞大的函数给使用带来不便,通过把函数打包成层级,把相关代码都放在一起,打包成对象(objects)
从最外面的对象往里找,最后找到想执行的函数:把函数打包成对象的思想叫 "面向对象编程"
把大型软件拆成一个个更小单元,适合团队合作,每个人仅仅需要负责其中的一部分
API: 程序编程接口,帮助不同成员合作,不用知道具体细节,只要知道怎么使用就行了,API 控制哪些函数和数据让外部访问,哪些仅供内部
面向对象的编程语言可以指定函数是 public 或 private,来设置权限
函数标记成 private,意味着只有同一个对象内的其他函数能调用它
面向对象编程的核心是隐藏复杂度,选择性的公布功能
现代软件开发会用专门的工具来写代码,工具里集成了很多有用功能帮助写代码,整理,编译和测代码 (集成开发环境,简称 IDE)
IDE 可以定位到出错代码,还会提供信息帮助解决问题,称为调试(debug)