第1篇 C语言基础
第1章 C语言概述
本章简要介绍C语言的产生、发展,C语言的标准,C语言的特点,以及C语言编程开发过程等内容。最后还将通过一个小例子演示C语言开发过程中程序编写、编译及调试的过程。
1.1 C语言发展历史
C语言是一种典型的从实用中产生出来的语言,它走过了一个独特的发展历程。下面首先简单介绍C语言的发展过程,接着介绍C语言的两个标准。
1.1.1 C语言发展过程
20世纪40年代,计算机问世,这个发明给世界带来巨大的变化。然而随之而来的是相当复杂的计算机程序的开发。早期程序开发使用的是汇编语言,汇编语言和机器语言一样能够直接对计算机硬件操作,但是比机器语言更容易记忆和理解。由于计算机程序规模越来越大,汇编语言已经不能胜任程序开发,这个时候产生了高级语言。高级语言比汇编语言简练,容易阅读,而且可以做到模块化。这就大大降低了计算机程序开发的难度,缩短了程序开发时间,常见的如Basic、Fortran、Pascal和C都是高级语言。
20世纪60年代,ALGOL语言问世。ALGOL是第一个结构化程序设计语言。在此后的几年内产生了许多新的程序设计语言,如广泛使用的Basic语言就是1965年由Thomas E. Kurtz和John Kemeny开发完成的。1963年,剑桥大学将ALGOL 60语言发展成为CPL (Combined Programming Language)语言。1967年,剑桥大学的Matin Richards 对CPL语言进行了简化,形成了BCPL语言。受BCPL语言的影响,美国贝尔实验室的Ken Thompson于1970年将BCPL进行了修改,形成了“B语言”,并用B语言写了第一个UNIX操作系统。
1973年,美国贝尔实验室的Dennis Ritchie在B语言的基础上,最终设计出了一种新的语言,并以BCPL的第二个字母作为这种语言的名字——这就是C语言。
随着计算机技术的日益发展,C语言出现了多种版本。由于C语言没有一个统一的标准,各个版本之间存在着差异,各版本之间不兼容给程序开发带来了各种各样的问题。为了改变这种情况,1983 年初美国国家标准研究所(ANSI)成立一个委员会,该委员会制定了C语言的标准。ANSI C标准于1989年被完全采用,国际标准化组织ISO也采纳了ANSI C标准,并对ANSI C做了部分修改。鉴于ISO的权威性,ANSI于1990年初重新采纳ISO C,合称为ANSI/ISO Standard C。1989年的C标准称为C89。
20世纪末,随着计算机技术的飞速发展,新的程序设计语言和技术不断产生。此时出现了结合C语言的优点和面向对象编程技术的程序设计语言——C++。但是C语言并没有因此退出历史舞台,而是结合了新的计算机技术的特点,不断改进,最终于 1999 年形成新的C语言标准,称为C99。
1.1.2 C语言标准
随着C语言的发展和广泛使用,出现了多个版本的C编译器,程序设计人员意识到需要一个全面、系统的标准。为满足这一要求,美国国家标准化组织(ANSI)在1983年设置了一个专门委员会,制作C语言的标准。该标准于1989年被正式采用,称为ANSI C(又称C89)标准。
在推出ANSI C标准化后,在相当长一段时间内该标准都保持不变。从1994年起, ANSI 开始了修订标准的工作,这就是后来推出的 ISO9899:1999(1999 年出版)。这个版本就是通常提及的C99。它被ANSI于2000年3月采用。
C99和C89的大多数特性差别很小。总的来说,C99和C89之间主要有以下区别:
C99修改了C89的一些语法。
C99在C89的基础上增加一些关键字。
提示
初学者只需要知道C语言有C89和C99两个标准即可,可在学习本书后面的内容后再了解具体的修改或增加特性。为了知识结构的完整性,在此处简单列出C99修改或增加的特性。
1.C99修改的特性
在C99中增加的大多数特性,都是标准委员会做的创新。其中多数是以各种C工具提供的语言扩展为基础,还有一些是从C++中借用的。C99中修改的特性主要包括以下内容:
增加单行注释。这种注释以“//”为开头,“//”以后的同一行内所有字符都是注释内容。
长度可变数组。在C89中数组的长度必须是固定的;在C99中数组的长度可以用变量或表达式表示,根据变量或表达式的值确定数组的长度。
C89中所有变量必须在第一条表达式之前声明;C99取消了这个限制,可以在使用该变量之前任何位置声明此变量。
函数必须有返回值。C89中如果函数没有指定类型,则默认为int类型;C99取消了这个特性,并且必须明确指定返回类型。
预处理中宏可以设定参数,宏定义中用(…)表示,预处理标识符__VA_ARGS__表示在什么情况下替换参数,如#define ClassAVG(…) avg(__VA_ARGS__)。
C99中增加了__func__预定义标识符,此标识符用来表示它所在的函数的名字。
2.C99增加的关键字
与C89标准相比,C99标准新增加以下关键字:
_Bool:C99增加了_Bool类型。C++中有bool类型,用来保存布尔类型,该类型有两个常量:true和false。但是,C99中的_Bool和C++中的bool是不同的。C99标准中增加了头文件stdbool.h,并在该文件中定义了宏bool、true和false。如果在C99的程序中使用这些宏就需要把头文件stdbool.h包含到程序中。
_Complex:由于C89标准不支持复数类型,在进行复数运算时需要自定义数据类型。在C99标准中可以使用_Complex和_Imaginary关键字,进行复数运算。在C99中增加了头文件complex.h,在该文件中增加了两个宏complex和imaginary,使用时需要在程序中包含头文件complex.h。
inline:在C99和C++中都有这个关键字,都是用在函数之前,表示该函数为内联函数,程序在编译的时候直接扩展为函数的代码而不是调用函数。
restrict:restrict 关键字只用来修饰指针。最初由 restrict 指针所指向的对象,必须由第二个指针基于restrict指针时才能进行对象的存取动作。
1.2 C语言的特点
从 C 语言产生到现在,C 语言已经成为最重要和最流行的编程语言之一。在最近 10多年中,虽然有许多人从C语言转向使用功能更强大的C++语言,但C语言有其自身的优势,仍然是一种重要的语言。例如,C++、C#、Java等现在流行的程序设计语言都是以C语言的语法为基础的,因此,精通C语言是通向这些C++、C#、Java的必由之路。
在学习C语言的过程中,读者将了解到C语言的许多特点,下面先简单列出几个优点。
1.2.1 简洁紧凑
C语言一共有32个关键字,9种控制语句。程序书写自由,主要用小写字母表示,它把高级语言的基本结构和语句与低级语言的实用性结合起来。
1.2.2 灵活高效
C语言是一种高效的语言,其代码很紧凑且运行效率高。C语言是“中级语言”,但并不是指C语言比高级语言低级,而是指C语言吸收了其他高级语言的许多优点,又结合汇编语言的特点。因此C语言比其他任何一种高级语言强大、灵活,又比汇编语言更适合程序开发。
高效不仅仅是指程序开发上的高效,更重要的是其执行效率高。C语言和汇编语言一样可以对硬件进行编程,所以大部分系统都用C语言做底层开发。
C语言程序生成代码质量高,程序执行效率高,一般只比汇编程序生成的目标代码效率低10%~20%。
1.2.3 强大的控制结构
C语言是结构化程序设计语言。结构化程序设计可以对代码和数据进行封装,既能保证代码和数据的安全,又可以实现模块化程序设计,实现代码的可重用,从而既可以保证程序安全,又能提供程序开发效率。
C语言功能强大,包含多种循环语句和条件分支语句。通过这些语句开发人员可以轻松实现各种复杂的功能,开发人员根据实际情况选择最合适的语句。C语言还支持自定义数据类型,通过自定义数据类型,开发人员可以进行复杂的运算。另外,C语言还支持指针和逻辑运算。
1.2.4 可移植性
C语言是可移植的语言。C语言适合多种操作系统,包括DOS、Windows、UNIX和Linux等。在一个操作系统中编写的C程序可不修改(或进行少量修改)就在其他系统上运行。
除了在不同操作系统间进行移植外,C语言还可使用在不同架构的计算机系统。C编译器可在约40种系统上使用,从PC机(个人计算机)、小型机、直至大型超级计算机。在这些不同的系统中移植代码时,只需修改指针对特殊硬件操作的代码即可。
提示
C编译器是指将C语言源代码转换为计算机内部机器码的程序。在UNIX和Linux中都包含C编译器。在DOS、Windows等操作系统中,可有多种C编译器供选择使用,需另外安装。
1.2.5 C语言的缺点
C语言在提供上述优点的同时,也有自身的一些缺点。常见的缺点有以下几种:
C语言的语法限制不太严格,对变量的类型约束不严格,影响程序的安全性,对数组下标越界不作检查等。从应用的角度,C语言比其他高级语言较难掌握。
指针是C语言的一大特色,可以说C语言优于其他高级语言的一个重要原因就是因为它有指针。通过指针可以直接对硬件进行操作。但是,C的指针操作也给它带来了很多不安全因素。C++在这方面做了很好的改进,在保留指针操作的同时又增强了安全性。Java取消了指针操作,提高了安全性。
由于 C 语言的简洁性,可能经常会在代码中见到多个运算符重叠结合的情况。有时候,这种表示方式将导致 C 程序代码极难理解。解决该缺点的方法是,程序员要养成良好的编码习惯,尽量将代码写得简单易懂(多增加代码行数是一个可行的方法)。
1.2.6 C与C++的关系
C++是由Bjarne Stroustrup设计并实现、建立在C基础上的面向对象的编程语言。可以说C是C++的子集,C++是C的超集,C++包含了全部的C语言特性。C++和C最大的不同就是引入了“类”的概念,同时对 C++又增强了一些 C 语言的特性(这里所说的 C是以C89为标准)。
现在大部分的C++编译器能编译C语言代码,但是由于C++是以C89标准为基础的,而C99对C89做了一些修改,在使用C++编译器编译C程序时注意代码要符合相应的标准。只有编译器支持C99标准,才能在程序中包含符合C99特性的代码。
1.3 C语言程序开发过程
如果读者在学习 C 语言之前使用过其他程序设计语言,应该对程序开发过程有一定的了解。使用C语言进行程序开发一般的过程可分为7个步骤,如图1-1所示。
图1-1 C语言程序的开发过程
1.3.1 定义程序目标
在程序开发过程中,首先要明确目标,如果没有了目标,程序开发就成了空谈。所谓程序目标,就是希望程序做什么。然后围绕这一目标,考虑需要向计算机输入哪些信息,再通过什么样的操作,最后输出需要的信息。例如,编制程序用来计算圆的面积,需要用户输入圆的半径或直径,通过程序计算后输出圆的面积。
程序开发的目标还要具有可行性。在明确目标的时候要对目标的可行性进行详细分析,如果目标不可行,那么就要及时更改目标。只有开发目标可行,才能制订详细的开发计划,进行程序开发。
1.3.2 设计程序
程序设计在整个程序开发过程中至关重要。程序设计分为概要设计和详细设计两个阶段。
概要设计要对整个程序设计形成一个完整的说明书。该说明书包含程序设计的基本流程、功能划分、模块设计、数据结构和异常处理等内容。
设计完程序的基本结构之后,需要对程序的各个部分进行详细设计,最终形成详细设计说明书。详细设计是对程序的流程控制、功能的实现、算法和异常处理等进行详细设计。对于简单的程序,可直接进入详细设计。
注意
在这一步中并不进行详细的代码设计,主要侧重于怎么表示数据,用什么方法处理数据,程序的逻辑流程等方面的构思。
1.3.3 编写源代码
在将程序的逻辑结构设计好之后,就可以使用C语言来进行编码了。从这一步开始,就需要使用C的相关知识了。
源代码由一系列的C语句组成,用于指示计算机执行设定的任务。
一般的C开发环境都提供编写代码的环境,如图1-2所示为Dev-C++开发环境中输入代码的界面。
图1-2 在C开发环境中输入代码
也可在其他文本编辑器(如DOS中的Edit、Windows中的“记事本”、Linux中的ed或vi等)中编写C语言代码,然后再使用C编译器对源代码进行处理。如图1-3为使用UltraEdit编辑器编写C代码。
图1-3 在UltraEdit中输入代码
提示
UltraEdit是一个功能强大的编辑器,可以编辑文本、十六进制、ASCII码。使用该编辑器将输入的C、C++、VB、HTML等代码关键字突出显示,可同时编辑多个文件。
C源代码文件必须为纯文件格式,即不能包含设置文档格式的特殊代码。如在Windows中使用“写字板”或Word等程序编写的文档可对文字设置特殊的格式,若要使用这类编辑程序编写C源代码,在保存文件时需将其保存为“纯文本”格式。编写代码是一个非常烦琐、复杂的工作,代码的编写不但要符合上一步的设计要求,还要尽量在格式上保持一致。良好的编码格式不但便于阅读理解,更有利于检查调试。保存源代码时,必须为每个源文件取一个文件名。C语言程序的源文件以.c为扩展名,头文件以.h为扩展名。
1.3.4 编译和链接
C源代码可供程序员进行阅读,了解程序的执行过程。但计算机并不能识别源代码中的字符,计算机只能识别称为机器语言的二进制指令。C编译器就是将C源代码转换为对应的机器代码的程序。
C的核心是编译器。编译器将上一步编写的源代码(文本格式)转换为可执行的代码。可执行代码是用计算机机器语言表示的代码,是控制计算机执行不同操作的指令。不同的硬件系统具有不同的机器语言,C编译器用来将C源代码转换为特定计算机的指令。
C编译器还检查每一行源代码是否正确,通过编译可以及时发现代码编写中的错误,并将错误信息显示出来,方便用户根据错误修改源代码。根据错误级别也可能不生成二进制文件。
编译顺利完成后,将生成二进制的中间文件,该文件以.obj或.o为扩展名。
编译完整后还需要进行链接,链接时把编译产生的.obj文件或.o文件和其他库文件链接到一起,生成一个可执行文件——扩展名为.exe。库中包含许多标准函数供用户调用,这些函数不需要用户额外编码即可直接调用,可提高程序的开发效率。
1.3.5 测试
C程序通过编译链接后,将生成一个可执行文件。在DOS和Windows中通过扩展名.exe来标志可执行文件。运行生成的可执行文件,检查程序是否能顺利执行、界面是否和程序设计要求一致。若程序不能运行,或界面不符合设计要求,还需返回到设计或编码阶段,检查设计和编码的问题,再重新编译链接生成新的可执行文件。
编译后的程序能运行并不表示其运行正确。程序在交给用户使用之前还必须进行多种测试。测试不需要知道程序的具体实现方式,只需检查给定的输入能不能产生设计要求的输出。通过测试可以及时发现程序设计和代码编写存在的问题。
如果程序运行中出现异常、某些方面未能达到设计要求,就要进行调试。利用调试工具对程序运行过程中出现的异常进行跟踪,确定程序出现问题的位置,进行修改,并重新编译运行,直到程序能稳定运行并达到设计要求为止。
1.3.6 维护和修改
程序交付使用后,可能在使用过程中发现新的问题,使用者有可能会对程序提出新的要求,这就需要对程序重新设计、对程序代码进行修改、编译运行,直到测试通过。
1.3.7 总结
程序开发是一个周期性的过程,每个程序的开发经过以上各个阶段最终才能成为一个成熟的作品。
程序的开发,从提出目标,经过设计开发、使用维护、程序修改,最终会因为不再适用使用者的需求而停止使用。这个时候就需要设计开发另一种程序,这就是程序的生命周期。
1.4 创建第一个C程序
学习程序设计最好的方法就是编写程序代码,并上机调试。本章最后一节介绍一个最简单的C程序,让读者了解C程序代码输入、编译链接、测试的过程。
提示
因为还未介绍 C 的任何语法成分,读者可能无法理解本例中的所有内容。在这里,读者只需按步骤操作即可。
本例的C程序用来在计算机屏幕上显示文字“这是第一个C程序!”。程序清单如下,在向编辑器中输入程序时,不要输入最左边的行号和冒号。在程序清单中添加行号,主要方便后面对程序各语句的讲解。
【程序1-1】第一个C语言程序
1: #include <stdio.h> 2: 3: int main() 4: { 5: printf("这是第1个C程序\n"); /* 输出字符串。 */ 6: return 0; /* 程序返回0。*/ 7: }
1.4.1 C程序结构
每一种程序设计语言都有其特定的语法规则,按照规则编写程序不仅仅便于阅读,更有利于减少错误的发生。
C语言程序一般由两大部分组成:程序头部、完成程序功能的一个或多个函数。
(1)程序头部:在程序开头部分一般是一个或多个#include 语句,用来导入头文件程序中将使用库函数的声明部分;在程序开头部分还可能包括宏定义、变量的定义语句。
(2)函数:C语言完成具体功能的代码必须编写在函数中。一个C程序可以有一个或多个函数,但必须有一个(且只能有一个)函数的名称为main。C语言程序运行时总是先从main()函数开始的。
1.4.2 输入程序代码
本例中的程序代码很短,可以直接在开发环境中编写输入。下面演示在Dev-C++中输入本例代码的过程。
1 启动Dev-C++开发环境。
2 执行菜单“文件/新建/源代码”命令(或按快捷键“Ctrl+N”)新建一个空白代码文件。
3 在新打开的窗体中依次输入本节开头部分给出的C源代码,按Enter键换行。行号不需要输入,左侧的行号是开发环境生成的,并不是源代码的一部分。最后得到如图1-4所示的代码。
图1-4 输入源代码
输入代码后,可根据需要随时对每行代码进行检查修改。在Dev-C++开发环境中,语句的不同部分将显示不同的颜色,非常醒目。
注意
C程序对大小写敏感,即代码中要区分大小写的,在输入代码时要特别注意。使用过Basic语言的读者要特别注意这点。
4 代码输入完毕后,执行菜单“文件/保存”命令保存源代码,将打开如图1-5所示对话框,选择保存源文件的位置,在“保存类型”中选择保存为扩展名为“.c”的文件(C++源文件保存的扩展名为“.cpp”),输入源文件名称(如“1-1”),单击“保存”按钮完成保存操作。
图1-5 保存源代码
1.4.3 编译链接程序
在Dev-C++开发环境中,编译链接程序的操作可直接在开发环境中进行,非常简单,具体步骤如下:
1 执行菜单“运行/编译”命令,将对当前源代码进行编译操作,同时显示如图1-6所示编译进度对话框。
图1-6 编译
2 完成编译后,该对话框的“Status”将显示为“Done”。单击“关闭”按钮退出对话框。
3 在Dev-C++中执行编译命令时,将同时将编译生成的目标文件链接成可执行文件。因此编译后就可执行菜单“运行/运行”命令查看程序的运行结果。
编译生成的程序在DOS环境中运行,因此,执行菜单“运行/运行”命令后,会看到Windows XP的命令窗口闪一下就消失了。这时,可转到命令窗口模型去执行编译生成的程序。
例如:本例编译链接后将生成一个名为“1-1.exe”的文件。进入命令窗口,通过DOS命令切换到编译生成文件的目录,在命令提示符中输入文件名“1-1”,按 Enter 键将运行该程序。在屏幕上输出一个字符串,如图1-7所示。
图1-7 在命令窗口运行程序
除了在集成开发环境中,通过菜单方式对源代码进行编译链接外,许多编译器也提供了通过命令方式编译链接。
1.4.4 调试程序
对于初学者,在输入代码时可能会出现一些错误,如关键字的大小写、少输入语句结束符等。这些错误都可以在编译时被编译器发现,并将错误信息显示在开发环境的下方。
例如,在如图1-8所示的代码中,第5行代码printf后面未输入分号。
图1-8 编译错误
执行菜单“运行/编译”命令时会发现错误,并显示在下方,如图1-8所示。在下方的错误信息分为两行:第1行提示错误是由main函数中的语句产生的;第2行提示错误在第6行,错误原因是return的语法错误。
这里发现一个问题,明明是第5行错误,为什么编译器报错在第6行,而且是语法错误呢?在 C 代码中,每条语句结束都要用分号“;”隔开。在第 5 行中少输入分号,编译器将第5行和第6行作为一条语句,当然就出现语法错误了。编译到第6行时发现语法错误,所以报错时显示的是第6行。所以,如果在指定行找不到编译器指出的错误,在上一行代码中一般都能找到。
这只是一个常见错误,在程序中还可能出现各种错误,通过错误信息提示,一般都能快速找到出错的行。
1.4.5 程序语句的含义
因还未正式学习C语言的相关语法,对本节的程序代码无法理解。下面对【程序1-1】中各语句的作用进行简要说明:
第1行语句中,#include是一个预处理标识符,用来导入头文件stdio.h。stdio.h中包含标准输入/输出函数的定义(如,在本例中使用的printf函数的定义,若不包含该头文件,就不能使用printf函数向屏幕输出信息)。
第2行为一个空行,在C源程序文件中,常用空行来分隔程序段,提高源代码的可读性。在C编译器中将忽略这些空行(也就是说,在代码行之间可插入多个空行,而不影响最终生成的程序)。
第3行定义一个名为main的函数。C程序是由一个或多个函数组成的,其中必须有一个(且只能有一个)名为main的函数,该函数是程序的入口。该行中的int表示main()函数返回数据的类型。
第4行和第7行为一对大括号,大括号中是main函数的代码。在C语言中可使用大括号将一条或多条语句括起来作为一个代码块。在C语言中,代码块的作用很多,在本书后面将会介绍。
第5行代码调用printf函数,向屏幕上输出一个字符串。printf是最常用的输出函数,其声明包含在头文件“stdio.h”中,因此,在要使用printf函数的C源代码中,必须要使用#include 包含该头文件。在 printf 函数中,用(半角)双引号包括的字符串将输出到屏幕上。在双引号包含的字符串中有两个特殊的字符“\n”,在 C 语言中这两个符号合在一起表示一个换行,即在屏幕上输出字符串后,将光标换到下一行。语句的结尾是一个半角的分号“;”,在 C 语言中每条语句都必须以“;”为结尾,如果两条语句之间没有“;”,C语言在编译的时候会认为是一条语句。在最后以“/* */”包含的部分为程序的注释,在程序中添加注释可提高程序的可读性,C编译器将忽略注释的内容。
第6行语句用来给函数返回值。在C程序中,每个函数都可以有一个返回值。程序在执行完return语句后将退出return语句所在的函数。这条语句表示main函数的返回值为0。