程序的基本概念
程序和编程语言
自然语言和形式语言
程序的调试
第一个程序
程序的基本概念
程序和编程语言
1.程序
程序(Program )告诉计算机应如何完成一个计算任务,这里的计算可以是数学运算,比如解方 程,也可以是符号运算,比如查找和替换文档中的某个单词。从根本上说,计算机是由数字电路组 成的运算机器,只能对数字做运算,程序之所以能做符号运算,是因为符号在计算机内部也是用数字表示的。此外,程序还可以处理声音和图像,声音和图像在计算机内部必然也是用数字表示的,这些数字经过专门的硬件设备转换成人可以听到、看到的声音和图像。
程序由一系列指令组成, 指令是指示计算机做某种运算的命令,通常包含以下几类:
- 输入(Input):从键盘,文件或者其他设备获取数据.
- 输出(Output):把数据显示到屏幕,或者存入一个文件,或者发送到其他设备.
- 基本运算:执行最基本的数学运算和数据存取.
- 测试和分支:测试某个条件,然后根据不同的测试结果执行不同的后续指令.
- 循环:重复执行一系列操作
基本我们用过的任何一个程序,都是由这几类指令组成的,程序是虽然很复杂,但是编写程序能够用到的指令只有这简单的几种,这中间的东西需要我们来填充,所以编写程序就是一件很复杂的工作.
实质就是:把复杂的问题分解为子任务,把子任务再分解成更为简单的任务,层层分解,直到最后简单得可以用上面的指令来完成.
2.编程语言
编程语言( Programming Language )分为低级语言( Low-level Language )和高级语言( High-level Language)。机器语言( Machine Language)和汇编语言(Assembly Language )属于低级语言,直接用计算机指令编写程序。而C 、C++、Java、Python 等属于高级语言,用语句(Statement )编写程序,语句是计算机指令的抽象表示。举个例子,同样一个语句用 C 语言、汇编语言和机器语言分别表示如下:
编程语言 | 表示形式 |
C语言 | a=b+1 |
汇编语言 | mov 0x804a01c,%eax add $0x1,%eax mov %eax,0x804a18 |
机器语言 | a1 1c a0 04 08 83 c0 01 a3 18 a0 04 08 |
计算机中只能对数字做运算处理,符号,声音等在计算机内部都需要用数组来表示,指令也不例外,上述表中机器语言都是由十六进制数字组成的.最早一批的程序员就是使用机器语言进行编程的,但是是很麻烦的,需要查询大量的表格来确定每个数字代表的意思,编写出来的程序也不是很友好,于是再次基础上有了汇编语言,就是把机器语言中一组一组的数字用助记符表示,直接用这些助记符写出汇编程序,然后让汇编器去查表把助记符替换成数字,也就把汇编语言翻译成了机器语言.从上面的例子可以看出,汇编语言和机器语言的指令是一一对应的,汇编语言有三条指令,机器语言也有三条指令,汇编器就是做一个简单的替换工作,例如在第一条指令中,把mov ?,%eax这种格式的指令替换成机器码a1 ?,?表示一个地址,在汇编指令中是0x804a01c,转换成机器码之后是1c a0 04 08.(小端表示)
从上述例子我们还可以看出来,C语言的语句和低级语言的指令之间并不是简单都得一一对应关系,一语句a=b+1;语句要翻译成三条汇编或者机器指令,这个过程我们称为编译,由编译器来完成,显然编译器的功能比汇编器要复杂的多.用C语言编写的程序必须经过编译转成机器指令才能被计算机指令,编译需要花一些时间,这是用高级语言编译的一个缺点,然而更多的是优点.首先,用C语言编程更容易,写出来的代码更紧凑,可读性更强,出了错也更容易改正.其次,C语言是可移植的或者称为平台无关的.
首先来解释一下平台这个词的意思,可以指的是计算机体系结构,也可以指的是操作系统,也可以指开发平台(编译器,连接器等).不同的计算机体系结构有不同的指令集,可以识别的机器指令格式是不同的,直接用某种体系结构的汇编或机器指令写出来的程序只能在这种体系结构的计算机上运行,然后各种体系结构的计算机都有各自的C编译器,可以把C程序编译成各种不同体系结构的机器指令,这意味着用C语言写的程序只需要稍加修改甚至不用修改就可以在不用的计算机上编译运行.各种高级语言也都是具备C语言的这些优点,所以绝大部分程序都是用高级语言编写的,只有和硬件关系密切的少数程序(例如驱动程序)才会用到低级语言.还要注意一点,即使在相同的体系结构和操作系统下,用不同的C编译器编译同一个程序得到的结果也有可能是不同的,C语言有些语言特性在C标准种并没有明确规定,各编译器有不同的实现方式,编译出来的指令行为特性也会不同,应该尽量避免使用不可移植的语法特性.
总结一下编译执行的过程,首先你用文本编辑器写一个C程序,然后保存成一个文件,例如test.c.这称为源代码或源文件,然后运行编译器对它进行编译,编译的过程并不执行程序,而是把源代码全部翻译成机器指令,再加上一些描述信息,生成一个新的文件,例如a.out,这成为可执行文件,可执行文件可以被操作系统加载运行,计算机执行该文件中由编译器生成的指令.
有些高级语言以解释(Interpret)的方式执行,解释执行过程和C语言的编译执行过程很不一样。
#! /bin/sh
VAR=1
VAR=$(($VAR+1))
echo $VAR
定义Shell变量VAR的初始值是1,然后自增1,然后打印VAR的值。用Shell程序/bin/sh解释执行这个脚本,结果如下:
$ /bin/sh script.sh
2
这里的/bin/sh称为解释器(Interpreter),它把脚本中的每一行当作一条命令解释执行,而不需要
$ VAR=1
$ VAR=$(($VAR+1))
$ echo $VAR
2
编程语言仍在发展演化。以上介绍的机器语言称为第一代语言(1GL,1st Generation Programming Language),汇编语言称为第二代语言(2GL,2nd Generation Programming Language),C、C++、Java、Python等可以称为第三代语言(3GL,3rdGenerationProgramming Language)。目前已经有了4GL(4th Generation Programming Language)和5GL(5th Generation Programming Language)的概念。3GL的编程语言虽然是用 语句编程而不直接用指令编程,但语句也分为输入、输出、基本运算、测试分支和循环等几种,和 指令有直接的对应关系。而4GL以后的编程语言更多是描述要做什么(Declarative)而不描述具体 一步一步怎么做(Imperative),具体一步一步怎么做完全由编译器或解释器决定,例如SQL语言 (SQL,Structured Query Language,结构化查询语言)就是这样的例子。
问题一: 解释执行的语言相比编译执行的语言有什么优缺点?
1.主体不同:
编译执行:由编译程序将目标代码一次性编译成目标程序,再由机器运行目标程序
解释执行:将源语言直接作为源程序输入,解释执行解释一句后就提交计算机执行一句,并不形成目标程序。
2.优势不同
编译执行:相比解释执行编译执行效率高,占用资源小,适合复杂程序
解释执行:开发速度快,出现严重BUG的几率小。
3.缺点不同
编译执行:兼容性差,例如在windows平台上写的编译程序一般不可以在unix平台上运行。
解释执行:解析需要时间,不生成目标程序而是一句一句的执行的方式会造成计算机资源的浪费,即执行效率低。
各类语言分类:
- 编译执行:GO语言、C语言、C++
- 解释执行:python
- 半编译半解释型语言:java、C#
自然语言和形式语言
程序的调试
- 编译时错误:
编译器只能翻译语法正确的程序,否则将导致编译失败,无法生成可执行文件。对于自然语言来说,一点语法错误不是很严重的问题,因为我们仍然可以读懂句子。而编译器就没那么宽容了,只要有哪怕一个很小的语法错误,编译器就会输出一条错误提示信息然后罢工,你就得不到你想要的结果。虽然大部分情况下编译器给出的错误提示信息就是你出错的代码行,但也有个别时候编译器给出的错误提示信息帮助不大,甚至会误导你。在开始学习编程的前几个星期,你可能会花大量的时间来纠正语法错误。等到有了一些经验之后,还是会犯这样的错误,不过会少得多,而且你能更快地发现错误原因。等到经验更丰富之后你就会觉得,语法错误是最简单最低级的错误,编译器的错误提示也就那么几种,即使错误提示是有误导的也能够立刻找出真正的错误原因是什么。相比下面两种错误,语法错误解决起来要容易得多
-
运行时错误编译器检查不出这类错误,仍然可以生成可执行文件,但在运行时会出错而导致程序崩溃。对于我们接下来的几章将编写的简单程序来说,运行时错误很少见,到了后面的章节你会遇到越来越多的运行时错误。读者在以后的学习中要时刻注意区分编译时和运行时( Run-time )这两个概念,不仅在调试时需要区分这两个概念,在学习 C 语言的很多语法时都需要区分这两个概念,有些事情在编译时做,有些事情则在运行时做。
-
逻辑错误和语义错误第三类错误是逻辑错误和语义错误。如果程序里有逻辑错误,编译和运行都会很顺利,看上去也不产生任何错误信息,但是程序没有干它该干的事情,而是干了别的事情。当然不管怎么样,计算机只会按你写的程序去做,问题在于你写的程序不是你真正想要的,这意味着程序的意思(即语义)是错的。找到逻辑错误在哪需要十分清醒的头脑,要通过观察程序的输出回过头来判断它到底在做什么。
第一个程序
#include <stdio.h>
/* main: generate some simple output */
int main(void)
{
printf("Hello, world.\n");
return 0;
}
$ gcc main.c
$ ./a.out
Hello, world.
$ gcc main.c
main.c:1:19: error: stdoi.h: No such file or directory
$ gcc main.c
main.c: In function ‘main’:
main.c:7: warning: passing argument 1 of ‘printf’ makes pointer from
integer without a cast
$ ./a.out
Segmentation fault
$ gcc main.c
$ ./a.out
$ gcc -Wall main.c
main.c: In function ‘main’:
main.c:7: warning: null argument where non-null required (argument
1)