文章目录
本章介绍了计算机语言发展历史上重要的语言类型,C语言的历史和优缺点,以及具体的、入门的“Hello,World程序”和编译运行程序的过程。本章重难点是“Hello,World程序”和“编译运行过程”这两节。理解“计算机语言”这一节的内容对理解编译运行过程有一定帮助。
1.1 计算机语言
计算机语言有很多种,各种语言想办法在一定的场景下方便程序员编程。最终,所有的编程语言要转换为计算机可以执行的指令。
-
机器语言
机器语言特指计算机本身的语言(native language),不同的计算机的本身的语言是不同的,它们是一组内建的指令。这组指令是二进制形式的,比如,为了加两个数,需要这样写出指令:
1101101010011010
显然,这样编写程序是非常繁琐的。而且非常难以阅读和修改。 -
汇编语言
汇编语言用来代替机器语言。汇编语言用称为助记符的描述性单词来代表机器指令。比如,助记符add表示加两个数,sub表示减两个数。2和3相加并把结果放到result里的汇编指令为:
add 2, 3, result
计算机不能直接执行汇编指令,一种称为汇编器(assembler)的程序将汇编语言程序翻译成机器语言程序。
显然,相比机器语言,汇编语言是一大进步, 但是写汇编程序仍然比较麻烦,汇编语言的指令一般与机器指令对应,写汇编程序要求了解CPU如何工作。汇编语言被称为低级语言,因为本质上来说汇编语言与机器语言相近,是机器相关的。(不同的计算机上指令是不同的)
-
高级语言
上世纪50年代,新一代的编程语言出现了,它们被称为高级语言。它们是平台无关的,即用高级语言写的同一段程序可以运行在不同的机器上。高级语言与英语很像,容易读写。高级语言的指令称为语句。比如,下面给出一条语句,用来计算圆面积area = 5 * 5 * 3.1415;
高级语言有很多,比如,C、C++、Java、Python等等,语法上它们会有一些不同,因为它们都是为不同环境下开发而发明出来的。
用高级语言写出来的程序称为源程序/源代码。源程序需要通过解释程序/编译程序转换为机器语言才能被机器执行。
解释程序从源程序中取出一条语句,将它翻译为机器码或者虚拟机码,然后立即执行它。注意,源程序的一条语句可能被翻译成多条机器指令。如很多脚本语言,如Python。
编译程序将整个源代码文件翻译成机器指令文件,然后机器指令文件再
执行。如C语言。
很多语言是解释执行还是编译执行界限不是那么清楚,不需要过于深究。了解解释执行和编译执行这两个概念即可。
1.2 C语言的历史
C语言是贝尔实验室的Ken Thompson、Dennis Ritchie等人开发的UNIX操作系统的“副产品”。Thompson独自编写出了UNIX操作系统的最初版本,这套系统运行在DEC PDP-7计算机上。这款早期的小型计算机仅有16KB内存(毕竟那是在1969年)。
与同时代的其它操作系统一样,UNIX系统最初也是用汇编语言编写的。用汇编语言编写的程序往往难以调试和改进,UNIX系统也不例外。Thompson意识到需要用一种更加高级的编程语言来完成UNIX系统未来的开发,于是他设计了一种小型的B语言。(基于BCPL语言的基础上)后来,Ritchie也加入到UNIX项目中,并且开始着手用B语言编写程序。1970年,贝尔实验室为UNIX项目争取到一台PDP-11计算机。当B语言经过改进并能够在PDP-11计算机上成功运行后,Thompson用B语言重新编写了部分UNIX代码。到了1971年,B语言已经明显不适合PDP-11计算机了,于是Ritchie着手开发B语言的升级版。最初,他将新开发的语言命名为NB语言(意为“New B”),但是后来新语言越来越偏离B语言,于是他将其改名为C语言。到了1973年,C语言已经足够稳定,可以用来重新编写UNIX系统了。改用C语言编写程序有一个非常重要的好处:可移植性。只要为贝尔实验室的其它计算机编写C语言编译器,他们的团队就能让UNIX系统也运行在那些机器上。
C语言在20世纪70年代持续发展。这一时期出现了第一本有关C语言的书。Brian Kernighan和Dennis Ritchie合作编写的The C Programming Language一书于1978年出版,并迅速成为C程序员必读的“圣经”。由于当时没有C语言的正式标准,所以这本书就成了事实上的标准,编程爱好者把它称为“K&R”或者“白皮书”。
在20世纪70年代,C程序员相对较少,而且他们中的大多数人都是UNIX系统的用户。然而,到了20世纪80年代,C语言已不再局限于UNIX领域。运行在不同操作系统下的多种类型的计算机都开始使用C语言编译器。特别是迅速壮大的IBM PC平台也开始使用C语言。
随着C语言的迅速普及,一系列问题也接踵而至。编写新的C语言编译器的程序员都用"K&R"作为参考,但是遗憾的是,"K&R"对一些语言特性的描述非常模糊,以至于不同的编译器常常hui对这些特性做出不同的处理。而且,"K&R"也没有对属于C语言的特性和属于UNIX系统的特性进行明确的区分。更糟糕的是,"K&R"出版以后C语言仍在不断变化,增加了新特性并去除了一些旧的特性。很快,C语言需要一个全面、准确的最新描述开始成为共识。如果没有这样一种标准,就会出现各种“方言”,这势必威胁到C语言的主要优势-程序的可移植性。
1983年,在美国国家标准协会(ANSI)的推动下,美国开始制定本国的C语言标准。经过多次修订,C语言标准于1988年完成并在1989年12月正式通过,成为ANSI标准X.159-1989。1990年,国际标准化组织(ISO)通过了此项标准,将其作为ISO/IEC9899:1990国际标准。我们把这一C语言版本称为C89或C90,以区别于原始的C语言版本(经典C)。
1995年,C语言发生了一些改变。1999年通过的ISO/IEC9899:1999新标准中包含了一些更重要的改变,这一标准所描述的语言通常称为C99。
尽管从经典C、C89到C99,C语言存在这样几个重大的版本,但是目前国内大多数教材还是基于C89的,因此我们也以C89为基础,但是C99在某些地方的修改确实方便了C语言的使用,因此我们也会提到C99的改变。
1.3 C语言的优缺点
1.3.1 C语言的优点
C语言在产业和高校中使用广泛,在语言排行榜中似乎从未跌出前三,其众多优点有助于解释为什么这种语言如此流行。
- 高效。
- 可移植性。
- 功能强大。
- 灵活。
- 标准库。
- 与UNIX系统的集成。
1.3.2 C语言的缺点
C语言毕竟是一门有几十年的历史了,在它的基础上诞生了C++、Java、Python等一系列的语言,这些语言各自有自己的特色,换句话说,它们在某些地方改进了C语言。C语言的缺点和它的许多优点是同源的,均来自C语言与机器的紧密结合。下面是众所周知的几个问题。
- C程序更容易隐藏错误。
- C程序可能会难以理解。
- C程序可能会难以修改。
1.3.3 C语言的优缺点个人感受
-
语法简单明了,易于学习。(是很多语言的基石,学好C语言,有利于学习其它语言和理解编程。)
-
语言不够抽象,标准库不够强大,不易于编写应用程序。
1.4 Hello,World程序
作为第一个C语言的程序,下面的程序的功能是向控制台窗口输出一段文字,文字的内容是“Hello, World!”
#include <stdio.h>
int main(void)
{
// 输出文字Hello, World!
printf("Hello, World!\n");
return 0;
}
下面来分析下这个程序,从大的角度来看,整个程序可以分成这几块:
预处理指令
int main(void) // 定义main函数
{
语句
return 0;
}
- 语句。语句是程序执行时运行的代码,一个程序由哪些语句组成是与程序要完成的功能密切相关的。
printf("Hello, World!\n");
语句实际上调用标准库中的函数printf来输出文字Hello, World!和一个换行符’\n’。输出一个换行符意味着接下来的输出会另起一行。 - 函数定义。程序整体是在定义函数main:
函数是由多条语句组成,完成一个逻辑功能的“小程序”,通俗的说就是一段代码。定义函数就是在“编写代码”(写出若干条语句告知计算机如何完成一个功能),而定义好的函数就可以被别的函数来使用(调用)。比如printf就是一个已经写好(定义好)的函数,其功能是输出文字,在上面的程序中,被main函数使用(调用)。int main(void) { ... return 0; }
int
和return 0;
有关联,都是组成函数的语句。作用是在程序运行结束后向操作系统返回一个整数值0。
printf和main都是函数,但是区别在printf函数是定义好的函数,其名称是固定的printf,而main函数是我们自己定义的,可以叫其它的名字。但是,C语言规定,一个程序中必须有而且只能有一个main函数,程序以main函数中的代码的运行而运行,以main函数中代码运行的结束为结束。换句话说,main函数是整个程序的入口和出口函数。
- 预处理指令。在编译程序之前,预处理器首先对源文件进行编辑。
#include <stdio.h>
的作用是,在编译之前把文件<stdio.h>
中的信息“包含”到程序中 。为什么要包含stdio.h文件呢?因为printf函数的使用信息在这个文件中。而C语言要求使用函数时,需要将函数的使用信息包含到源文件中。
1.5 程序注释
为了增加程序的可读性(一段时候后程序员本人也可能忘记代码的含义),可以在源文件中添加文本注释。注释作为源文件的一部分文字起到解释代码、添加作者名、编写日期等等作用。但是不会作为代码被编译/解释。有一种说法是程序是写给人看的,而不光是给计算机运行的。为程序添加必要的注释是非常好的编程习惯。
具体来看,C语言的注释有两种:
-
多行注释
多行注释是C89标准中就引入的注释方式,用/*和*/将含有连续多行的文字包含起来,这些文字就不会被看成代码。比如,/* 文件名:area.c 功能:计算圆的面积 作者:张三 */ #include <stdio.h> ...
注意,多行注释不支持嵌套。
-
单行注释
//是C99标准引入的注释形式,用来为单行做注释。#include <stdio.h> int main(void) { printf("Hello, World!\n"); // 输出文字Hello, World! return 0; }
显然,多行注释也可以只用来注释一行文字,单行注释连续使用也可以用来注释多行文字。
1.6 其它若干程序的例子
下面的程序输出多行文字
#include <stdio.h>
int main(void)
{
printf("Hello, World!\n");
printf("Programming is fun!\n");
printf("Practice!\n");
return 0;
}
下面的程序显示9的平方根
#include <stdio.h>
#include <math.h>
int main(void)
{
// sqrt是标准库中计算平方根的函数,其使用信息在头文件math.h中
printf("9的平方根是%f\n", sqrt(9));
return 0;
}
下面的程序自定义了一个函数print_hello
#include <stdio.h>
void print_hello(void)
{
printf("Hello, World!\n");
}
int main(void)
{
print_hello();
printf("Programming is fun!\n");
printf("Practice!\n");
print_hello();
return 0;
}
1.7 编译运行过程
相应的,C语言的错误有:
1.生成期错误:
生成期错误指将源码生成为可执行代码过程中的错误,主要是指编译错误,也包括链接错误。
1.1 编译错误
编译器发现的语法错误,比如一句话结束没有写分号,小括号或者大括号没有匹配等等。
#include <stdio.h>
int main()
{
printf("Hello, World!\n") // 缺少分号
return 0;
}
1.2 链接错误
将源代码的目标文件(二进制文件)和相关的系统库函数的目标文件(二进制文件),链接为可执行程序过程中出现的错误,比如程序中没有定义main函数,或者使用了不存在的函数。
#include <stdio.h>
int mian() // 没有定义main函数
{
printf("Hello, World!\n");
return 0;
}
2.执行(运行)期错误
将源码生成为可执行代码后,程序可以运行起来了,但是仍然可能有错误,这种错误称为运行期错误,主要是指逻辑错误,也包括运行时错误(runtime error)。
2.1 逻辑错误
这个错误比较好理解,就是程序可以正常运行,但是由于代码逻辑错误,导致运行结果不符合预期。比如计算两个数和的程序,加号写成了减号。
#include <stdio.h>
int main()
{
printf("2 + 3 = %d\n", 2 - 3); // 加好错写成了减号
return 0;
}
2.2 运行时错误
运行时错误(runtime error)是一类在运行时导致程序突然中止的错误的总称。比如除法除数为0,或者应该输入整数,但是输入了字符串。
#include <stdio.h>
int main()
{
int x, y, z;
x = 10;
y = 0;
z = x / y; // 除数为0,运行时出错
printf("%d\n", z);
return 0;
}
1.8 使用VS2010
在 VS2010 下开发程序首先要创建项目,不同类型的程序对应不同类型的项目,初学者应该从控制台程序学起。
1) 创建项目
打开 VS2010,在上方菜单栏中选择“文件 --> 新建 --> 项目”:
或者按下Ctrl+Shift+N组合键,都会弹出下面的对话框:
选择“Win32控制台应用程序”,填写好项目名称,选择好存储路径,点击“确定”按钮即可。
点击“确定”按钮后会弹出向导对话框:
点击“下一步”按钮,弹出新的对话框:
然后点击“完成”按钮就创建了一个新的项目。
2) 添加和编辑源文件
或者按下Ctrl+Shift+A组合键,都会弹出添加源文件的对话框。如下图所示:
注意,文件名后缀要主动以.c结束,标志该文件为C语言源代码文件,否则,默认生成.cpp文件,而.cpp文件是C++语言文件后缀名。
3) 编译源文件
打开 hello.c,将本节开头的代码输入到 hello.c 中,上图是输入完成以后的效果。
注意:虽然可以将整段代码复制到编辑器,但是我还是强烈建议你手动输入,我敢保证你第一次输入代码会有各种各样的错误,只有把这些错误都纠正了,你才会进步。本教程后续章节还会给出很多示例代码,这些代码一定要手动输入,不要复制后运行成功了就万事大吉。
编译(Compile)
在上方菜单栏中点击“生成”按钮,会弹出一个子菜单,再点击“编译”按钮,就完成了 hello.c 源文件的编译工作。
或者直接按下Ctrl+F7组合键,也能够完成编译工作,这样更加便捷。
如果代码没有错误,会在下方的“输出窗口”中看到编译成功的提示:
编译完成后,打开项目目录下的 Debug 文件夹,会看到一个名为hello.obj的文件,这就是经过编译产生的中间文件,这种中间文件的专业称呼是目标文件(Object File)。在 VS 和 VC 下,目标文件的后缀都是.obj。
链接(Link)
在菜单栏中选择“项目 --> 仅用于项目 --> 仅链接 cDemo”,就完成了 hello.obj 的链接工作,如下图所示:
如果代码没有错误,会在下方的“输出窗口”中看到链接成功的提示:
本项目中只有一个目标文件,链接的作用是将 hello.obj 和系统组件(专业讲是静态链接库)结合起来,形成可执行文件。如果有多个目标文件,这些目标文件之间还要相互结合。
再次打开解决方案目录下的 Debug 文件夹,会看到一个名为cDemo.exe的文件,这就是最终生成的可执行文件,就是我们想要的结果。
双击 cDemo.exe 运行,并没有输出“Hello, World!”几个字,而是会看到一个黑色窗口一闪而过。这是因为,程序输出“Hello, World!”后就运行结束了,窗口会自动关闭,时间非常短暂,所以看不到输出结果,只能看到一个“黑影”。
对上面的代码稍作修改,让程序输出“Hello, World!\n”后暂停下来:
#include <stdio.h>
int main(void)
{
// 输出文字Hello, World!
printf("Hello, World!\n");
getchar();
return 0;
}
按下键盘上的任意一个键,程序就会关闭。
VS 提供了一种更加快捷的方式,可以一键完成编译、链接、运行三个动作,点击菜单栏中的“运行”按钮,或者按下F5键就能做到这一点。