C语言概述
C语言称得上是一种历史悠久的高级程序设计语言,也往往是初学者最先接触的程序设计语言,它对近代的程序设计领域有着非凡的贡献。
C语言的历史
C语言是贝尔实验室的Ken Thompson、Dennis Ritchie等人开发的UNIX操作系统的“副产品”。Thompson独自编写出了UNIX操作系统的最初版本,这套系统运行在DEC PDP-7计算机上。这款早期的小型计算机仅有16KB内存(毕竟那是在1969年)。
最初,他将新开发的语言命名为NB语言(意为“New B”),但是后来新语言越来越偏离B语言,于是他将其改名为C语言。到了1973年,C语言已经足够稳定,可以用来重新编写UNIX系统了。改用C语言编写程序有一个非常重要的好处:可移植性。
C语言在20世纪70年代(特别是1977年到1979年之间)持续发展。这一时期出现了第一本有关C语言的书。Brian Kernighan和Dennis Ritchie合作编写的The C Programming Language 一书于1978年出版,并迅速成为C程序员必读的“圣经”。 由于当时没有C语言的正式标准,所以这本书就成为了事实上的标准,编程爱好者把它称为“K&R”或者“白皮书”。
1983年,在美国国家标准协会(ANSI)的推动下,美国开始制订本国的C语言标准。经过多次修订,C语言标准于1988年完成并在1989年12月正式通过,成为ANSI标准X3.159-1989。1990年,国际标准化组织(ISO)通过了此项标准,将其作为ISO/IEC 9899:1990国际标准。我们把这一C语言版本称为C89或C90,以区别于原始的C语言版本(经典C)。
基于C的语言
C语言对现代编程语言有着巨大的影响,许多现代编程语言都借鉴了大量C语言的特性。在众多基于C的语言中,以下几种非常具有代表性。
- C++:包括了所有C特性,但增加了类和其他特性以支持面向对象编程。
- Java:是基于C++的,所以也继承了C的许多特性。
- C#:是由C++和Java发展起来的一种较新的语言。
- Perl:最初是一种非常简单的脚本语言,在发展过程中采用了C的许多特性。 考虑到这些新语言的普及程度,人们自然会问:“C语言还值得学习吗?”我想答案是肯定的,原因如下:第一,学习C有助于更好地理解C++、Java、C#、Perl以及其他基于C的语言的特性,一开始就学习其他语言的程序员往往不能很好地掌握继承自C语言的基本特性;第二,目前仍有许多C程序,我们需要读懂并维护这些代码;第三,C语言仍然广泛用于新软件开发,特别是在内存或处理能力受限的情况下以及需要使用C语言简单特性的地方
C语言的优缺点
与其他任何编程语言一样,C语言也有自己的优缺点。这些优缺点都源于该语言的最初用途(编写操作系统和其他系统软件)和它自身的基础理论体系。
- C语言是一种底层语言 为了适应系统编程的需要,C语言提供了对机器级概念(例如,字节和地址)的访问,而这些是其他编程语言试图隐藏的内容。此外,C语言还提供了与计算机内置指令紧密协调的操作,使得程序可以快速执行。应用程序的输入/输出、存储管理以及其他众多服务都依赖于操作系统,因此操作系统一定不能运行得太慢。
- C语言是一种小型语言 与其他许多编程语言相比,C语言提供了一套更有限的特性集合。(在K&R第2版的参考手册中仅用49页就描述了整个C语言。)为了使特性较少,C语言在很大程度上依赖一个标准函数的“库”(“函数”类似于其他编程语言中描述的“过程”、“子例程”或“方法”)。
- C语言是一种包容性语言 C语言假设用户知道自己在做什么,因此它提供了比其他许多语言更广阔的自由度。此外,C语言不像其他语言那样强制进行详细的错误检查。
C语言的优点
C语言的众多优点有助于解释为什么这种语言如此流行。
- 高效 高效性是C语言与生俱来的优点之一。发明C语言就是为了编写那些以往由汇编语言编写的应用程序,所以对C语言来说,能够在有限的内存空间里快速运行就显得至关重要了。
- 可移植 虽然程序的可移植性并不是C语言的主要目标,但它还是成为了C语言的优点之一。当程序必须在多种机型(从个人计算机到超级计算机)上运行时,常常会用C语言来编写。C程序具有可移植性的一个原因是该语言没有分裂成不兼容的多种分支(这要归功于C语言早期与UNIX系统的结合以及后来的ANSI/ISO标准)。另一个原因是C语言编译器规模小且容易编写,这使得它们得以广泛应用。最后,C语言自身的特性也支持可移植性(尽管它没有阻止程序员编写不可移植的程序)。
- 功能强大 C语言拥有一个庞大的数据类型和运算符集合,这个集合使得C语言具有强大的表达能力,往往寥寥几行代码就可以实现许多功能。
- 灵活 虽然C语言最初设计是为了系统编程,但是没有固有的约束将它限制在此范围内。C语言现在可以用于编写从嵌入式系统到商业数据处理的各种应用程序。此外,C语言在其特性使用上的限制非常少。在其他语言中认定为非法的操作在C语言中往往是允许的。例如,C语言允许一个字符与一个整数值相加(或者是与一个浮点数相加)。虽然灵活性可能会让某些错误溜掉,但是它却使编程变得更加轻松。
- 标准库 C语言的一个突出优点就是它具有标准库,该标准库包含了数百个可以用于输入/输出、字符串处理、存储分配以及其他实用操作的函数。
- 与UNIX系统的集成 C语言在与UNIX系统(包括广为人知的Linux)结合方面特别强大。事实上,一些UNIX工具甚至假定用户是了解C语言的。
C语言的缺点
C语言的缺点和它的许多优点是同源的,均来自C语言与机器的紧密结合。下面是众所周知的几个问题。
- C程序更容易隐藏错误 C语言的灵活性使得用它编程出错的概率较高。在用其他语言编程时可以发现的错误,C语言编译器却无法检查到。从这方面来说,C语言与汇编语言极为相似,后者直到程序运行时才能检查到大多数错误。更糟的是,C语言还包含大量不易觉察的隐患。在后续的章节中我们将会看到,一个额外的分号可能会导致无限循环,遗漏一个& 可能会引发程序崩溃。
- C程序可能会难以理解 虽然根据大多数衡量标准C语言是一种小型语言,但是它也有许多其他通用语言没有的特性(并且常常被误解)。这些特性可以用多种方式结合使用,其中的一些结合方式尽管编程者心知肚明,但是其他人恐怕难以理解。另一个问题就是C程序简明扼要的特性。C语言产生的时候正是人机交互最为单调乏味的时期,因此设计者特意使C语言简明以便将录入和编辑程序的用时减到最少。C语言的灵活性也可能是一个负面因素,过于聪明的程序员甚至可以编写出除了他们自己几乎没人可以读得懂的程序。
- C程序可能会难以修改 如果在设计中没有考虑到维护的问题,那么用C语言编写的大规模程序将很难修改。现代的编程语言通常都会提供“类”和“包”之类的语言特性,这样的特性可以把大的程序分解成许多更容易管理的模块。遗憾的是,C语言恰恰缺少这样的特性。
高效地使用C语言
高效地使用C语言要求在利用C语言优点的同时要避免它的缺点。下面是一些建议。
- 学习如何规避C语言的缺陷 规避缺陷的提示遍布全书,寻找 符号即可发现。如果想看到更详尽的缺陷列表,可以参考Andrew Koenig的《C陷阱与缺陷》一书。现代编译器可以检查到常见的缺陷并且发出警告,但是没有一个编译器可以侦察出全部缺陷。
- 使用软件工具使程序更加可靠 C程序员是众多软件工具的制造者(和使用者)。 lint 是最著名的C语言工具之一,一般由UNIX系统提供。与大多数C语言编译器相比,lint 可以对程序进行更加广泛的错误分析。如果可以得到lint (或某个类似的程序),那么使用它应该是个好主意。另一个有益的工具是调试工具。由于C语言的本性,许多错误无法被C编译器查出。这些错误会以运行时错误或不正确输出的形式表现出来。因此,在实践中C程序员都必须能够很好地使用调试工具。
- 利用现有的代码库 使用C语言的一个好处是其他许多人也在使用C。把别人编写好的代码用于自己的程序是一个非常好的主意。C代码经常被打包成库(函数的集合)。获取适当的库既可以大大减少错误,也可以节省相当多的编程工作。用于常见任务(包括用户界面开发、图形学、通信、数据库管理以及网络等)的库很容易获得。有些库是公用的,有些是开源的,而有些则是作为商品销售的。
- 采用一套切合实际的编码规范 编码规范是一套设计风格准则,即使语言本身没有强制要求,程序员也决定遵守。精心选择的规范可以使程序更加统一,并且易于阅读和修改。使用任何一种编程语言时,规范都很重要,对C语言来说尤其如此。正如前面所说的,C语言本身具有高度的灵活性,这使得程序员编写的代码可能会难以理解。本书的编程示例只遵循一套编码规范,但是,还有其他一些同样有效的规范可以使用。(本书将穿插讨论一些可供选择的方法。)选用哪套编码规范并不重要,重要的是必须采纳某些 规范并且坚持使用它们。
- 避免“投机取巧”和极度复杂的代码 C语言鼓励使用编程技巧。通常用C语言完成某项指定任务时会有多种解决途径,程序员经常会尝试选择最简洁的方式。但是,千万不要没有节制,因为最简略的解决方式往往也是最难以理解的。本书将给出一种相当简洁但仍然易于理解的编码风格。
- 紧贴标准 大多数C编译器都提供不属于C89或C99标准的特性和库函数。为了程序的可移植性,若非确有必要,最好避免使用这些特性和库函数。
我的第一个C程序
让我们看一段简单的代码,可以输出单词 “Hello World”:
#include <stdio.h>
int main()
{
/* 我的第一个 C 程序 */
printf("Hello, World! \n");
return 0;
}
接下来我们讲解一下上面这段程序:
- 程序的第一行 #include
是预处理器指令,告诉 C 编译器在实际编译之前要包含 stdio.h 文件。 - 下一行 int main() 是主函数,程序从这里开始执行。
- 下一行 /…/ 将会被编译器忽略,这里放置程序的注释内容。它们被称为程序的注释。
- 下一行 printf(…) 是 C 中另一个可用的函数,会在屏幕上显示消息 “Hello, World!”。
- 下一行 return 0; 终止 main() 函数,并返回值 0。
编译 & 执行 C 程序
接下来让我们看看如何把源代码保存在一个文件中,以及如何编译并运行它。下面是简单的步骤:
- 打开一个文本编辑器,添加上述代码。
- 保存文件为 hello.c。
- 打开命令提示符,进入到保存文件所在的目录。
- 键入 gcc hello.c,输入回车,编译代码。
- 如果代码中没有错误,命令提示符会跳到下一行,并生成 a.out 可执行文件。
- 现在,键入 a.out 来执行程序。
- 您可以看到屏幕上显示 “Hello World”。
$ gcc hello.c
$ ./a.out
Hello, World!
请确保您的路径中已包含 gcc 编译器,并确保在包含源文件 hello.c 的目录中运行它。
如果是多个 c 代码的源码文件,编译方法如下:
$ gcc test1.c test2.c -o main.out
$ ./main.out
test1.c 与 test2.c 是两个源代码文件。
因编译器的原因,生成的 .exe 文件打开时会一闪而过,从而观察不到其运行的结果,这是因为 main() 函数结束时,DOS 窗口会自动关闭。为了避免这个问题可在 return 0; 前加入 system(“pause”); 语句。
#include <stdio.h>
#include <stdlib.h>
int main()
{
/* 我的第一个 C 程序 */
printf("Hello, World! \n");
system("pause"); //暂停函数,请按任意键继续...
return 0;
}
头文件的作用
C语言是一种符合模块化(module)设计精神的语言,模块化最大的好处是内建了许多标准函数(function)供程序设计者使用。这些函数被分门别类地放置在扩展名为“.h”的不同头文件(header files)中。我们只要通过“#include”语句就可以将相关的头文件“包含”(include)到我们的程序中使用,而不用从头到尾自行编写:
#include <stdio.h>
#include <stdlib.h>
#include
“#include”语句的作用就是告诉编译程序要加入哪些C语言中所定义的头文件或指令。在C语言中,“#include”语句是一种预处理指令,并不是C语言的正式语句,所以不需要在语句最后加上“;”作为结束标志。当使用C语言所提供的内建头文件时,还必须用“<>”将其括住。如果大家使用的是自定义的头文件,就必须换成以“”“”符号来括住。
方式1:#include <内建头文件的名称>
方式2:#include "自定义头文件的名称"
方式1用来加载内建头文件,而方式2用来加载程序设计者自行编写的头文件。例如,在A文件中要引用B文件时,就可以在A文件中加入自定义的头文件#include”B.c”。
大家可能会好奇这两种加载方式的不同。事实上,两者之间的差异就在于头文件的搜索路径不同。如果采用方式1的加载方式,编辑器就会去寻找系统默认的函数库目录,方式2则会先在当前的工作目录下寻找,找不到才会寻找系统默认的函数库目录。
main()函数简介
首先大家要清楚一点,C程序本身就是由各种函数所组成的。所谓函数(function),就是执行特定功能的程序语句的集合。我们可以自行建立函数,或者直接使用C语言中内建的标准函数库,例如main()函数和printf()函数都是C语言所提供的标准函数。
main()函数是C语言中一个相当特殊的函数,代表所有C程序的开始进入点,也是唯一且必须使用main作为函数名称的函数。C程序开始运行时,不管是在程序代码中的任何位置,一定会先从main()函数开始执行,编译程序都会找到它,然后开始编译程序内容,因此main()函数又称为C语言的“主函数”。
一般来说,函数主体是以“{}”(一对大括号)定义的。在函数主体的程序段中,可以包含多行程序语句(statement),而每一条语句都要以“;”结尾。另外,程序段结束后必须以“}”(右大括号)告知编译程序,而且在“}”后无须加上“;”作为结尾。以main()函数来说,最简单的C程序可以如下定义:
int main(){
}
C语言函数前的类型声明用来表示函数执行完成后的返回值类型,例如int main()表示返回值为整数类型。如果函数不返回值,就可以把数据类型设置为“void”。括号中使用void时代表这个函数没有任何需要传入或传出函数体的参数,也可以直接以“()”(空白括号)来表示。例如,可以声明成以下两种方式:
void main(void);
void main();
system()函数的作用
system()函数是C语言的一种内建函数,其功能相当有趣。我们不妨把第11行“system(“PAUSE”);”语句拿掉,再重新编译与运行一次,会发现运行界面一闪即逝,根本来不及看清楚运行的结果,原因是当程序在Windows系统中正常运行结束后,Windows会直接关掉C/C++的运行窗口。
在程序设计中要解决这种现象有两种方法,一种是直接使用命令行操作界面(DOS界面)来查看运行结果,另一种是加上“system(“PAUSE”);”。因为system()函数会调用系统参数PAUSE,并让程序运行到这条语句时先暂停,同时在运行窗口中显示“请按任意键继续…”等文字,当我们按任意键后程序才会继续往下执行。
注释与缩排
注释(comment)既可以帮助其他程序员了解程序的内容,也能够在日后进行程序维护与修订时省下不少时间成本。 在C语言中,只要是在“/”与“/”之间的文字都属于注释内容。另外,注释也能够跨行使用。例如:
/*
跨行注释
*/
C程序是由一个或数个程序区块(block)所构成的。程序区块由“{}”组成,包含多行或单行语句,就像我们一般编写文章时的段落。除了加上注释外,编写程序和写作文一样,最后都希望能够段落分明,适当的缩排就可以达到这样的效果,区分出程序区块的层次。例如,主程序中包含子区段,或者子区段中又包含其他子区段时,就可以通过缩排来区分出程序代码的层次,让程序可读性更强。