1. 程序和编程语言
程序是由一系列指令(instruction)构成,指令包含:输入、输出、基本运算、测试和分支、循环。
编程语言分为低级语言(机器语言和汇编语言,用计算机指令编写程序)和高级语言(C、C++、Java、Python,用语句编写程序)。
表 1.1. 一个语句的三种表示
编程语言 | 表示形式 |
---|---|
C语言 | a=b+1; |
汇编语言 |
mov 0x804a01c,%eax |
机器语言 |
a1 1c a0 04 08 |
最早的程序员都是直接用机器语言编程,但是很麻烦,需要查大量的表格来确定每个数字表示什么意思,编写出来的程序很不直观,而且容易出错,于是有了汇编语言,把机器语言中一组一组的数字用助记符(
Mnemonic
)表示,直接用这些助记符写出汇编程序,然后让汇编器(
Assembler
)去查表把助记符替换成数字,也就把汇编语言翻译成了机器语言。从上面的例子可以看出,汇编语言和机器语言的指令是一一对应的,汇编语言有三条指令,机器语言也有三条指令,汇编器就是做一个简单的替换工作。
从上面的例子还可以看出,
C
语言的语句和低级语言的指令之间不是简单的一一对应关系,一条
a=b+1;
语句要翻译成三条汇编或机器指令,这个过程称为编译(
Compile
),由编译器(
Compiler
)来完成,显然编译器的功能比汇编器要复杂得多。用
C
语言编写的程序必须经过编译转成机器指令才能被计算机执行,编译需要花一些时间,这是用高级语言编程的一个缺点,
然而更多的是优点。首先,用
C
语言编程更容易,写出来的代码更紧凑,可读性更强,出了错也更容易改正。其次,
C
语言是可移植的(
Portable
)或者称为平台无关的(
Platform Independent
)。
图 1.1. 编译执行的过程

有些高级语言以解释(
Interpret
)的方式执行,解释执行过程和
C
语言的编译执行过程很不一样。
#! /bin/sh
VAR=1
VAR=$(($VAR+1))
echo $VAR
这里的
/bin/sh
称为解释器(
Interpreter
),它把脚本中的每一行当作一条命令解释执行,而不需要先生成包含机器指令的可执行文件再执行。
图 1.2. 解释执行的过程

编程语言仍在发展演化。以上介绍的机器语言称为第一代语言(
1GL
,
1st Generation
Programming Language
),汇编语言称为第二代语言(
2GL
,
2nd Generation Programming Language
),
C
、
C++
、
Java
、
Python
等可以称为第三代语言(
3GL
,
3rd GenerationProgramming Language
)。
而
4GL
以后的编程语言更多是描述要做什么(
Declarative
)而不描述具体一步一步怎么做(
Imperative
),具体一步一步怎么做完全由编译器或解释器决定,例如
SQL
语言(
SQL
,
Structured Query Language
,结构化查询语言)就是这样的例子。
习题1
、解释执行的语言相比编译执行的语言有什么优缺点?
根据编译和解释的不同原理,你能否在执行效率和平台无关性等方面做一下比较?
答:解释型语言需要在运行时由解释器翻译成指令,效率低。
编译型语言已在编译时生成指令(机器码),效率高。跨平台时需重新编译。
2. 自然语言和形式语言
自然语言(Natural Language
)就是人类讲的语言,比如汉语、英语和法语。这类语言不是人为设计(虽然有人试图强加一些规则)而是自然进化的。形式语言(
Formal Language
)是为了特定应用而人为设计的语言。例如数学家用的数字和运算符号、化学家用的分子式等。编程语言也是一种形式语言,是专门设计用来表达计算过程的形式语言。
形式语言有严格的语法(
Syntax
)规则,例如,
3+3=6
是一个语法正确的数学等式,而
3=+6$
则不是,
H
2
O
是一个正确的分子式,而
2
Zz
则不是。语法规则是由符号(
Token)和结构(Structure
)的规则所组成的。
Token
的概念相当于自然语言中的单词和标点、数学式中的数和运算符、化学分子式中的元素名和数字,例如
3=+6$
的问题之一在于
$
不是一个合法的数也不是一个事先定义好的运算符,而
2
Zz
的问题之一在于没有一种元素的缩写是
Zz
。结构是指
Token
的排列方式,
3=+6$
还有一个结构上的错误,虽然加号和等号都是合法的运算符,但是不能在等号之后紧跟加号,而
2
Zz
的另一个问题在于分子式中必须把下标写在化学元素名称之后而不是前面。关于
Token的规则称为词法(Lexical)规则,而关于结构的规则称为语法(Grammar)规则
。
3. 程序的调试
据说有这样一个典故:早期的计算机体积都很大,有一次一台计算机不能正常工作,工程师们找了半天原因最后发现是一只臭虫钻进计算机中造成的。从此以后,程序中的错误被叫做臭虫(Bug),而找到这些Bug并加以纠正的过程就叫做调试(Debug)。
4. 第一个程序
main.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 -o main
$ ./main
Hello, world.
一个好的习惯是打开gcc的-Wall选项,也就是让gcc提示所有的警告信息,不管是严重的还是不严重的,然后把这些问题从代码中全部消灭。
$ gcc -Wall main.c