什么是编程
在上一篇什么是程序中,我们得出结论:编程就是按照我们的目的,组合指令。
我们的目的就是通过程序的执行解决现实生活中的问题,
组合指令就是我们为了解决现实生活中的问题,构思解决方案,根据解决方案,写出正确的指令组合,指导设备的运行。
所以整体上看,编程是通过机器设备的运行,解决现实生活的问题,具体看,编程是把解决方案用机器设备能理解的指令描述出来。
所以写代码之前,请想清楚,要解决什么问题,方案是什么,然后再考虑怎么写的问题。
编程语言
上一篇中,我们使用 0001 这种形式写程序,随着设备增多,逻辑复杂,这种写法会很难写,所以人们考虑用符号代替 0001,比如MOV 把数据从一个地方设置到另一个地方, ADD 表示加法运算,abc(随意命名)表示某个设备的状态值,但是时间久了之后,人们发现代码实在是太多了,看起来费劲,写的时候,也容易忘记写到什么状态了,想要知道程序是干什么的,很不容易。之后就是高级程序语言诞生了,提出了函数,数据结构等概念,帮助我们更加有组织,有结构的写程序。
虽然高级语言人看起来不费劲了,但是机器看不懂了,其实用符号,机器就已经看不懂了,那机器如何根据程序去执行呢,我们需要把程序再转化成0001这种机器能看懂的指令,这个过程就是编译。
0001这种是 机器语言
MOV这种是 汇编语言
高级程序语言 包括 C C++ Java 等等
平时我们一般用高级程序语言写代码,经过编译,会转化为汇编语言,然后是机器语言,最终机器运行的是机器语言所描述的指令
跨平台
硬件设备有很多,不同的硬件设备组合,形成更复杂的设备,如果只为一种设备编写代码,那还好,但是如果我们为不同的设备写代码,我就需要考虑设备的不同点,他们接受的命令是否一样,状态变化是否一样等,一套代码很难在多个设备上运行,怎么办呢?人们考虑用标准化的方法,对待每个设备,比如在设计的时候,指令都用一样的,比如相同的CPU指令,同类型的设备,使用相同的接口协议,比如USB协议,所有的USB设备的数据传输都是一样的,所以在写代码的时候,就不用考虑太多设备的不同,(只需要在特别的地方考虑,比如有些设备存放数据使用32位二进制,有的用64位),虽然代码相同,但这是高级程序语言,需要执行的机器语言,还是会根据设备不同而改变,此时我们就需要针对设备进行编译,让编译器帮我们转化为正确的机器语言。
这样可以用一套代码,多处编译运行,还有没有其他解决方案呢?
有:一套代码,多处解释执行,比如Java
Java编译生成字节码(类似于汇编语言),然后在设备安装一个java运行环境,在执行时,通过java运行环境,解释为当前设备的机器语言去执行
一套代码,多处编译运行,代码所使用的语言称为 编译型语言
一套代码,多处解释执行,代码所使用的语言称为 解释型语言
编译型语言需要为不同的设备提前编译好机器语言代码,运行时,直接执行就行
解释型语言需要为不同的设备安装不同的运行环境,在每次运行时,解释执行
明显编译型语言运行效率高,但较麻烦,解释型语言不麻烦,但是执行的效率低一些
编程思路
前面提到写代码之前,需要想清楚,要解决什么问题,方案是什么,然后再考虑怎么写的问题,我们举个例子:
写一个计算器,能计算加减乘除,能计算两个数的运算即可,比如输入3+5= 可以输出8
要解决的问题,很明确就是会计算加减乘除,
解决方案呢,因为高级语言就支持加减乘除,直接利用即可,所以整体的解决方案就是
- 读取输入,解析出来第一个数 什么运算 第二个数
- 直接加减乘除
- 将结果输出
代码怎么写呢(我用文字描述下)
读取一行输入,获取字符串
判断运算符在字符串第几位,
运算符之前的数字解析为第一个数据
判断等号在字符串第几位,
运算符之后,等号之前的数字解析为第二个数据
根据运算符,执行加减乘除,得到结果
输出结果
这样程序就写好了,我们在仔细想一下,这个编程的过程:一开始因为问题比较简单,所以解决方案也没有多想,直接思考的是机器的执行过程,然后就将代码写出来了。
本质上,我们思考解决方案的套路是从机器执行过程的角度,考虑解决方案是什么,
现在我们增加一下复杂度,支持括号,不限于只有2个数
那怎么办呢?不限个数没关系,把等号之前的数都找出来即可,但是支持括号,就要考虑括号里的数先算,就需要先找出括号和里面的数,找出来之后,先计算,然后再和剩下数再计算,所以解决方案就是
- 读取输入
- 解析出来括号和里面的数
- 计算括号里的结果
- 解析剩下数
- 计算最终结果
- 输出
代码就不描述了
这个过程,细想一下,我们是从运算本身的优先级逻辑考虑,得出我们应该先做什么,再做什么,其实计算器到底应该如何计算,我们小学就学过,也就是说,计算过程(或者说解决方案中如何计算和实现的过程)我们是清楚的,只不过我们现在用机器执行的逻辑去描述而已。
本质上,我们思考的角度是这个问题解决机器执行的过程是什么样的,然后我们在转化为机器能理解的代码。
我们再增加一下复杂度,支持多个括号
现在怎么办?括号有多少个我们不确定,有没有嵌套我们也不确定,我们再从问题解决过程的角度考虑,一步步想机器计算过程,似乎有点复杂,脑子不容易转过来,这个时候我们应该换一个思路:简单的运算我们会写,有一个括号的运算我们也会写,而多个括号,嵌套括号其实只是把我们会写的组合起来。
我们可以把问题分解,假设我们称代表计算的这些数字和符号为表达式,问题其实就是求一个表达式的值,
其中括号优先级最高,而括号里面的东西取出来,其实还是数字和符号,即还是一个表达式,先计算括号里的数,其实就是先求括号里表达式的值,
其次优先级是乘除,把乘号除号及其左右的数取出来,其实也是一个表达式,
最后是加减,其实也是表达式,
所以求解表达式的步骤为
- 求括号里的表达式的值,替换原来括号的位置
- 求仅包括乘除的表达式的值,替换原来乘除的未知
- 此时仅剩加减法,直接计算
- 得到结果
计算每个表达式,都按照上面的步骤来计算
比如
3+(2*4+(28/(2*(7-6))*3+(4-3)*2)-8)*4 先计算括号里的
3+A *4 先计算A的表达式
(2*4+(28/(2*(7-6))*3+(4-3)*2)-8)
2*4+B -8 先计算B的表达式
(28/(2*(7-6))*3+(4-3)*2)
28/C *3+D *2 先计算C和D的表达式
(2*(7-6)) (4-3)
2*E 先计算E的表达式
(7-6)
2*1 把简单表达式的结果放到原来复杂的表达式中
28/2 *3+1 *2 先计算乘除
F +G 计算F和G的表达式
28/2*3 1*2
42 +2
2*4+44 -8 先计算乘除
8 +44 -8
3+44 *4 先计算乘除
3+176
179
通过这个例子我们看到,我们把复杂的表达式分解成了小一点表达式,如果小一点的表达式还复杂,就再分解,直到这个表达式可以轻松的计算出来,然后把小一点的表达式的结果放到复杂表达式中,让复杂表达式变的不复杂,从而计算出最终结果。
我们再回想一下上面的过程,面对复杂问题,它的解决方案可能我们是知道的(先计算高优先级,再计算低优先级),但是这个解决方案对于机器来说,不能理解(怎么计算高优先级,怎么计算低优先级),此时,我们就把问题分解为:
如何计算括号里的表达式,如何计算乘除,如何计算加减,每一个子问题,我们都能解决,此时复杂问题的解决方案对于机器来说就能理解了,复杂问题就变成不复杂的问题。
问题的解决方案,对于人来说,很好理解,但是对于机器来说,是无法直接理解的,编程就需要把解决方案中机器不能理解的地方,作为要解决的问题,再出解决方案,直到机器能理解为止,本质上就是不断的分解问题,直到解决方案的计算过程,机器可以理解执行。