tceic.com
学霸学习网 这下你爽了
当前位置:首页 >> 理学 >>

第1章 C语言程序设计初步_图文

第一章 C语言程序设计的 概念

1.1程序与程序设计语言
1.1.1 常量 一提起计算机,人们就会联想到键盘、显示器和主机。 其实,应用要早得多、并一直流传至今的计算机工具是算 盘(见图1.1)。那么,现代电子计算机与算盘的最大区 别在哪里呢?关键在于现代计算机可以自动完成计算过程, 而算盘进行的计算过程是在人的拨动下才能进行。

图1.1 算盘

那么,为什么现代计算机可以自动完成计算过程呢?这 首先要从程序说起。程序实际上是一个非常普通的概念:按 照一定的顺序安排的工作步骤。可以说,做任何事情都有相 应的程序。做的事情不同,要求的效果不同,程序就不同。 例如,用同样的原料,采用不同的程序,会做出不同的菜肴 来。 一种工具能够自动工作,一是要有记忆功能,能够记住 程序;二是具有按照程序控制相关部件操作的能力。如果能 让算盘记住做某种计算的口诀和计算的数据,并且有能按照 口诀控制算珠自动运动的机制,则只要发出开始执行的命令, 算盘就会自动完成计算。 可惜这样的机制并没有在算盘中实现。但是,却有另外 一种机器却在这方面向前推进了一步。这就是明朝末年宋应 星在其《天工开物》中记载的中国古代提花机(见图1.2)。

图1.2 中国古代的提花机

中国提花机大约出现于西汉末年(公元前)。它采用用 丝线结成的“花本”(花版)控制经线起落,以织成要求的 图样。这是最早的程序控制思想。后来,提花机沿着丝绸之 路传到欧洲,历经改进,1805年法国人Joseph Jacquard制 造成功用穿孔卡片(见图1.3)控制连杆(横针),用有孔和 无孔进一步控制经线起落的提花机。

图1.3

穿孔卡片

穿孔卡片把程序控制技术向前推进了一步。这一技术被一 位英国数学家Charles Babbage(见图1.4)引入到了计算机中 机,用有孔和无孔的组合来表示数据和程序。

图1.4 英国数学家Charles Babbage

18世纪末,法国数学界调集大批数学家,组成了人工手 算的流水线,经过长期艰苦奋斗,终于完成了17卷《数学用 表》的编制,但是,手工计算出的数据出现了大量错误。这 件事情强烈刺激了Babbage。1812年20岁的Babbage开始 计算机的研制工作,他要把函数表的复杂算式转化为差分运 算,用简单的加法代替平方运算,快速编制不同函数的数学 用表,并将这种机器称为“差分机”。经过十年的努力,终 于于1822年完成了第一台差分机,可以处理3个不同的5位数, 计算精度达到6位小数。1833年他又开始投身于一种“会分 析的机器”——分析机的研制中。他把机器设计成三个部分, 一是用来储存数据信息的“仓库(The Store)”,二是进 行数据运算处理的“工场(The mill)”,三是使用穿孔卡 片来输入程序并用穿孔卡片输出数据。 这台机器虽然没有制造成功,但它的工作原理——程序 存储控制为今天的计算机奠定了基础: (1)任何工具的工作,都是由程序控制的; (2)只有工具具有了记忆程序的功能,并具有了按照程 序进行自我控制的功能,该工具才能自动工作。

1.1.2 计算机程序设计语言 程序要需要用某种形式(语言)来描述。例如,用算盘进行计算, 程序是用口诀描述的,珠算的语言是口诀。现代计算机的程序则是用 计算机程序设计语言来描述的。从计算机诞生到今天,程序设计语言 也在伴着计算机技术的进步不断升级换代。 1. 机器语言 一种CPU的指令系统,也称该CPU的机器语言,它是该CPU可以 识别的一组由0和1序列构成的指令码。下面是某CPU指令系统中的两 条指令: 1 0 0 0 0 0 0 0 (进行一次加法运算) 1 0 0 1 0 0 0 0 (进行一次减法运算) 用机器语言编程序,就是从所使用的CPU的指令系统中挑选合适 的指令,组成一个指令系列。这种程序虽然可以被机器直接理解和执 行,却由于它们不直观,难记、难认、难理解、不易查错,只能被少 数专业人员掌握,同时编写程序的效率很低,质量难以保证。这种繁 重的手工方式与高速、自动工作的计算机极不相称。这种方式仅使用 于计算机出现的初期(使用穿孔纸带的时期)的编程(用有孔、无孔, 分别代表1、0),现在已经不再使用

2. 汇编语言 为减轻人们在编程中的劳动强度,20世纪50年代中期人们开 始用一些“助记符号”来代替0,1码编程。如前面的两条机器指 令可以写为 A+B => A或ADD A,B A-B =>个样 A或SUB A,B 这种用助记符号描述的指令系统,称为符号语言或汇编语言。 用汇编语言编程,程序的生产效率及质量都有所提高。但是 汇编语言指令是机器不能直接识别、理解和执行的。用它编写的 程序经检查无误后,要先翻译成机器语言程序才能被机器理解、 执行。这个翻译转换过程称为“代真”。代真后得到的机器语言 程序称为目标程序(object program),代真以前的程序,称为源 程序(source program)。由于汇编语言指令与机器语言指令基本 上具有一一对应的关系,所以汇编语言源程序的代真可以由汇编 系统以查表的方式进行。 汇编语言与机器语言,都是依CPU的不同而异,它们都称为 面向机器的语言。用面向机器的语言编程,可以编出效率极高的 程序。但是程序员用它们编程时,不仅要考虑解题思路,还要熟 悉机器的内部结构,并且要“手工”地进行存储器分配。这种编 程的劳动强度仍然很大,给计算机的普及推广造成很大的障碍。

2. 高级语言 汇编语言和机器语言是面向机器的,不同类型的计算 机所用的汇编语言和机器语言是不同的。1954年出现的 FORTRAN语言以及随后相继出现的其它高级语言,开始 使用接近人类自然语言的、但又消除了自然语言中的二义 性的语言来描述程序。这些高级语言使人们开始摆脱进行 程序设计必须先熟悉机器的桎梏,把精力集中于解题思路 和方法上。 第一种高级语言是1954年问世的FORTRAN语言。此 后不久,不同风格、不同用途、不同规模、不同版本的面 向过程的高级语言便风涌而起。据统计,全世界已有2500 种以上的计算机语言,其中使用较多的有近百种。图1.5为 几种广泛流行的高级语言的发展变迁情况。

1952

1956

1960

1964

1968

1972

1976

1980

1984

1988

1992

1996

2000

2004

Ada

PASCAL
ALGOL60 ALGOL68 CPL—BCPL—B— C Smalltal k 80 BASIC FORTRAN 77 PL/1 QBASIC Modula-2 C89 C++ Java C# Visual BASIC FORTRAN 90 C99

Simula 67 FORTRAN

COBOL
LISP PROLOG

图1.5

几种广泛流行的高级语言的发展变迁情况

1.1.3 高级语言程序的开发过程 一般来说,程序开发的一般过程有如图1.6所示几个 步骤。

图1.6 高级语言程序的开发过程

1. 分析问题,建立模型 一般来说,一个具体的问题要涉及许许多多的方面, 这是问题的复杂性所在。为了便于求解,往往要忽略一 些次要方面。这种通过忽略次要方面,而找出解题规律, 就称为建立模型。 2. 表现模型 表现模型就是用一种符号-语言系统来描述模型。一 般来说,模型的表现会随着对问题抽象程度的加深和细 化,不断由领域特色向计算机可解释、执行靠近,中间 也可能采用一些其他的符号系统,如流程图等,直到最 后用一种计算机程序设计语言描述出来。 3. 源程序的编辑 源程序的编辑就是在某种字处理环境下,用具体的 程序设计语言书写并修改的过程。为此就要掌握一种计 算机程序设计语言。还要应用一种专用程序编辑器或通 用的文字编辑器进行。

4. 程序的编译(或解释)与链接 写出一个高级语言程序后,并不是就可以立即拿来执行。 要让机器直接执行,还要将它翻译成由机器可以直接辨认并 可以执行的机器语言程序。为区别它们,把用高级语言写的 程序(文件)称为源程序(文件),把机器可以直接辨认并 执行的程序(文件)称为可执行程序(文件)。这一过程一 般分为两步: 第1步:在程序编辑过程中输入到源文件中的是一些字 符码,但是机器可以直接处理的是0、1信息。为此,首先要 将源程序文件翻译成0、1码表示的信息,并用相应的文件保 存。这种保存0、1码信息的文件称为目标程序文件。由源文 件翻译成目标文件的过程称为编译。在编译过程中,还要对 源程序中的语法和逻辑结构进行检查。编译任务是由称做编 译器(compiler)的软件完成的。目标程序文件还不能被执 行,它们只是一些目标程序模块。

第2步:将目标程序模块以及程序所需的系统中固有的 目标程序模块(如执行输入输出操作的模块)链接成一个完 整的程序。经正确链接所生成的文件才是可执行文件。完成 链接过程的软件称为链接器(linker)。 图1.7为编译和链接过程的示意图。 程序在编译、链接过程中,也可能发现错误。这时要重 新进入编辑器进行编辑。
目标文件1 源文件1
# include <stdio.h> int add (int ,int ); int main (void) { int s; 01011001 01110101 10101000 10100010 00110010 10111101 0001

s = add(2,3); printf(“The sum is:%d”, s); return 0;
}

编 译 器

可执行文件 目标文件2
00010100 01000110 01010111 10100010 01110111 01

链 接 程 序

1100011010 1110001010 0010001100 1010111101 0001001110 1110110010 0100100000 00000

源文件2
int add (int a,int b) { int sum; sum = a + b; return sum; }

其他目标文件
10001000 10100010 00110010 10111101 00010011 10111011 00110011

图1.7

编译和链接过程的示意图

5. 程序的测试与调试 经编译、链接的程序文件,生成可执行文件,就可以让 计算机执行了。但是,并不是就可以得到预期的结果而交付 用户使用了,因为程序仍然会存在某些错误。因此,每一个 人编写出一个程序后,在正式交付使用前,总要试通一下。 “试通”就是试运行程序,也就是对程序进行测试。 测试是以程序通过编译、没有语法和链接上的错误为前 提,目的是找出程序中可能存在的错误并加以改正。因此, 应该测试程序在不同情况下运行的情况,输入不同的数据可 以检测出程序在不同情况下运行的情况。测试的数据应是以 “程序是会有错误的”为前提精心设计出来的,而不是随心 所欲地乱凑而成的。它不仅应含有被测程序的输入数据,而 且还应包括程序执行它们后预期的结果。每次测试都要把实 际的结果与预期的结果相比较,以观察程序是否出错。

6. 编写程序文档 经过了问题分析、设计、程序编码、测试后,程序开发的 工作基本上结束了。但是,这时还不能交付使用。因为,随着 程序规模的增大和日益复杂化,程序的使用和运行也越来越不 那么直接,用户要运行程序,还需要知道许多信息,如: 程序的功能 需要输入的数据类型、格式和取值范围 需要使用的文件数量、名称、内容以及存放位置等 程序运行需要的软、硬件环境 程序的装入、启动方法以及交互方式等。 为此,程序开发者需要向用户提供这些资料——称为程序 使用说明书或用户文档。需要说明的是,在许多软件中,这些 内容已经部分或全部地以“readme‖或“help‖的形式提供。 目前,程序文档已经成为软件开发产品的必要部分。文档 在程序使用和维护中的重要性也改变了软件的概念,使之由早 期的“软件是计算机程序的总称”演化为“软件是计算机的程 序连同计算机化的文档的总称。”

7. 程序的维护 程序交付用户使用之后,并不是万事大吉了。由于 多种原因,还可能要对程序进行修改。交付之后对程序 的修改称为程序的维护。维护程序的原因主要有: 原来的程序没有完全满足用户要求; 用户要求的改变; 程序中遗留有错误,在运行中被发现。 程序的维护可以由开发者进行,也可能是由别人进 行。为能便于程序的维护,开发者应当提供必要的技术 资料,并且要保证程序的可读性好——能让人看懂。

1.2 C语言及其标准
1.2.1 C语言的出现 C语言是目前程序设计领域中最有影响力的一种程 序设计语言。可是,它却是“漫不经心”地开发出 来的。 20世纪60年代,Bell实验室的Ken Thompson (见图 1.8)着手开发后来对计算机产生了巨大影响的UNIX操作系 统。为了描述UNIX,Thompson首先将当时的一种专门用 来描述系统程序的BCPL语言改进为他称为B的语言。1970 年Thompson发表了用汇编语言和B语言写成的PDP-7上实 现UNIX的初版。

1971年,Dennis Ritchie(见图1.8)开始协助 Thompson开发UNIX。他对B语言做了进一步的充实和完善, 加入数据类型和新的句法,于1972年推出了一种新型程序设 计语言——C语言(取BCPL的第2个字母)。为了使UNIX操 作系统推广,1977年Dennis M.Ritchie 发表了不依赖于具 体机器系统的C语言编译文本《可移植的C语言编译程序》。 于是,C语言是借助UNIX操作系统的翅膀而起飞的,UNIX操 作系统也由于C而得已快速移植落地生根,两者相辅相承, 成就了软件开发史上历时30年的时代。

图1.8

Thompson(左)和Ritchie(中)于1999 年接受当时美国总统克林顿授予的国家技术勋章

1971年,Dennis Ritchie(见图1.8)开始协助 Thompson开发UNIX。他对B语言做了进一步的充实和完善, 加入数据类型和新的句法,于1972年推出了一种新型程序设 计语言——C语言(取BCPL的第2个字母)。为了使UNIX操 作系统推广,1977年Dennis M.Ritchie 发表了不依赖于具 体机器系统的C语言编译文本《可移植的C语言编译程序》。 于是,C语言是借助UNIX操作系统的翅膀而起飞的,UNIX操 作系统也由于C而得已快速移植落地生根,两者相辅相承, 成就了软件开发史上历时30年的时代。

图1.8

Thompson(左)和Ritchie(中)于1999 年接受当时美国总统克林顿授予的国家技术勋章

1978年Brian W.Kernighian和Dennis M.Ritchie出 版了名著《The C Programming Language》,从而 使C语言成为目前世界上流行最广泛的高级程序设计语 言。以后,又有多种程序设计语言在C语言的基础上产 生,如C++、Visual C++、Java、C#等。

1.2.2 C语言的标准
C语言的灵活性、丰富性、可移植性很快得到了普遍的承认, 接着适合于各种不同操作系统(UNIX,MS-DOS,CP/M-80,86等) 和不同机种(字长为8bit~32bit)的C语言编译系统相继出现。1982年 美国国家标准局(ANSI)语言标准化委员会成立了一个委员会开始着 手进行C语言的标准化工作,并于1983年公布了第一个C语言标准草 案(83 ANSI C)。1989年,ASNI又发布了一个完整的C语言标准— —ANSI X3.159-1989,通常称做“ANSI C‖,简称“C89‖。1990 年,国际标准组织ISO/JEC JTC1/SC22/WG14采纳了C89,做了少 编辑性修改后,以国际标准ISO/IEC 9899:1990发布,通常称其为 “C90‖,它同C89基本相同。 1995年,WG14对C89做了两处技术修订和一个扩充。人们将 其称为“C89增补1‖或“C95‖。同时,WG14开始着手对C标准做 全面修订,并于1999年完成获得通过,形成正式的C语言标准,命 名为ISO/IEC 9899:1999,简称“C99‖。 本书将基于C99介绍C 语言程序设计的基本方法。目前各厂家 所提供的所有C编译系统都还未实现C99所建议的功能。为了读者能 实际运行C程序,本书所介绍的程序都是符合ASNI C标准并能在大 多数C编译系统通过和运行的程序。但在文字叙述中,会介绍C99所 增加的新功能,以使读者今后能顺利地过渡到用C99编程。

1.3 C语言概要
1.3.1 函数 任何一部机器都是用部件组装而成的。计算机程序和机器 一样,也是由一些部件构建起来的。C语言程序部件是函数。 也就是说,设计C语言程序就是设计它的构成函数。 下面举例说明C语言程序中的函数是什么样的。 例1.1 一个输出一串字符的C程序。
/* 文件名:ex010101.c */ # include <stdio.h> int main(void) { printf((″Programming is fun.″); return 0; }

/* 输出一串字符 /* 向操作系统返回一个数字0

*/ */

这是一个非常简单的C语言程序,它的执行结果 是显示一行字符: Programming is fun. 说明: (1)这里
int main (void) { …

}

是一个函数。这个函数的名字为“main‖。这个 名字是专用的,表示这个函数是“主函数”。所谓 主函数,就是执行这个程序时,由操作系统直接调 用的函数。每一个C语言程序必须也只能有一个主函 数。

(2)函数名后面的圆括号用于表示参数。一般说来,用 函数进行计算,需要给定参数。但是广义的计算也可以没有参 数而只执行一个过程。在C语言程序中,参数部分写为 “void‖,表示该函数没有参数,只执行一个过程。“void‖ 可以省写,如程序第一行可写为: int main() 在许多教材和程序中,可以常常见到这种形式的主函数 首行。但是,C标准建议写上void,使含义清晰。在本书的程 序中都是写成main(void) 形式的。 (3)再后面的一对花括号中的部分称为函数体,用来表 明该函数的功能是如何实现的。通常,函数体用一些语句表述。 C语言规定语句必须用分号结束。先分析下面的语句: printf(″Programming is fun.″); 它的功能是调用编译系统提供的函数库中的一个函数 printf(),来输出后面的一串字符。函数printf的使用比较 复杂,后面将陆续介绍。

(4)函数名前面的“int‖表明函数的返回值是一个整数。 有的操作系统(如Unix)要求在执行一个程序后应向系统返回一 个整数值,如程序正常执行和结束,应返回0,否则返回一个 非0值。因此,需要将main函数指定为int(整型),同时在函数 体的最后写一返回语句: return 0; 它的功能是向调用者(操作系统)返回0值,表示主函数正 常结束(也就是程序正常结束)。此语句必须写在函数体的最后 一行才有意义,因为只要执行到这条语句,就表达程序正常结 束,向操作系统返回一个0,如果程序未执行到这个返回语句 就非正常结束了,就不会向操作系统返回0。操作系统会据此 作出相应的处理。

有的操作系统(如DOS,Windows)并无程序必须返回整 数的要求,因此,可以不指定main函数为整型。这时可在 main函数的前面加上void,如∶ void main(void) 或 void main() 表示main函数是无类型的,不返回任何类型的值。显然 在main函数的最后也不必写返回语句“return 0;‖。读者可以 在其他教材或程序中看到这种形式的main函数。 以上两种用法都是是合法的、有效的,编程者可以根据情 况决定。为了使程序具有一般性,采用以下形式∶ int main(void) 并在函数体最后有“return 0;‖语句。

(5)程序最前面的 # include <stdio.h> 是一种在程序编译之前要处理的内容,称为编译预处理命 令。编译预处理命令还有一些,它们都用“#‖开头,并且不 用分号结束,所以不是C语言的语句。这里的编译预处理命令 称为文件包含命令,它的作用是在编译之前把程序中需要使用 关于系统定义的函数printf()的一些信息文件stdio.h包含进 来。用“.h‖作为后缀的文件称为头文件。 (6)“/* … */‖中的文字用于做一些说明——注释,让读 程序的人容易读懂。例如,注释 /* 文件名:ex1_01.c */ 是告诉读程序的人,这个程序的源代码用文件ex1_01保 存。而其他两个注释是对其左面两条语句功能的说明。 上面的程序只由一个函数组成(在主函数中又调用了库函数 printf()。在例1.2中将介绍由两个函数组成的程序。

例1.2

计算两个整数(2,3)相加的结果

/* 文件名:ex010201.c */ # include <stdio.h> int add (int ,int ); int main (void) { int s; s = add(2,3); printf(―The sum is:%d‖, s); return 0; } int add (int a,int b) { int sum; sum = a + b; return sum; }

/* 声明后面将要使用函数add()

*/

/* 声明后面使用的变量s是整型的 */ /* 调用add()进行计算,并用s接收*/ /* 输出s的值 */

/* 函数add()的定义

*/

/* 定义一个整数sum,用于存放和 */ /* 将a和b求和,并把结果送sum */ /* 返回sum的值到调用者 */

说明 (1)图1.9表明了该程序的执行过程。为了清晰,仅列出 了执行语句。
将2和3传递给a和b 操作系统
add(int a, int b) { 返回sum的值sum = a + b; 计算a+b ① 到调用处 操作系统 return sum; 调用main() printf(“The sum is %d”, s); } main () { ② s = add(2,3);

ex1_02.ex e


返回操作系统

③ return 0; }
编译系统提供库函 数printf 进行计算 并输出结果

图1.9 程序ex1_02的执行过程

① 经过编译、链接后的C语言程序就成为一个可执行文件。 例如,程序的ex1_02的默认可执行文件名为“ex1_02.exe‖。 若要执行这个程序,只要在操作系统的命令执行环境中打入这 个文件名,系统就会开始执行这个程序。对于C语言程序而言, 首先从调用主函数开始。 ② 在主函数main中,第一个语句是 s = add(2,3); 但是,这个语句的执行要分如下步骤才能完成。 调用函数add(),同时将数据2和3分别传送给函数add ()中的变量a和b; 使用表达式a + b进行加法计算。 将和用“=‖)送给函数add()中的变量sun中。注意 “=‖是赋值操作符,不是等号。C语言中的等号是“==‖。 用return语句将sum的值返回的函数add()的调用处。 将函数add()的返回值送给主函数中的变量s。

③ 执行函数printf(),输出下面的内容: The sun is 5 这个语句的执行也需要如下多个步骤才能实现: 圆括号中的引号中的“The sum is:‖要求原样输出。 圆括号中的引号中的“%‖表示后面的字符“d‖是一个格 式字符,要求将双引号后面的表达式的值,按照整型数据输出。 函数printf()将流程返回到调用处。printf()也有返 回值(成功返回输出的字符个数;失败时,返回一个负整数), 但是一般不用。 ④ 执行main()中的返回语句return,用“0‖向操作系 统送回“平安”信号。

(2)变量及其类型 本例中的s和sum都称为变量。变量是程序中被命名的数据实 体,并且它的值是可以改变的。同时,为了便于计算与存储, C语言中程序中所使用的每个数据都被规范化了。这种数据的 规范称为数据类型。 本例中使用语句 int s 和 int sum; 的作用就是声明了两个变量s和sum名字和类型(用“int‖表 明它们是整型数据)。变量在使用之前都要先行声明。

(3)函数的声明 本例中的 int add(int,int ); 称为函数声明。函数声明的作用是让编译器知道 该函数的原型(包括返回类型、参数个数和类型,以 便对调用语句进行语法检查。如果定义在调用前,从 定义可以直接获得这些信息,就可以不写声明;如果 调用在定义之前,则需要一个原型声明说明这些信息。 对于编译系统提供的库函数,它们的定义不在程 序中,因此需要给出相应的原型声明。为了方便使用, 系统把某些类型的库函数的原型声明写在某个头文件 中,程序员只要把要求的头文件用文件包含语句写在 程序中函数调用之前,就等于把原型声明写在了函数 调用之前。这就是使用函数printf(),必须在其前写 一条#include <stdio.h>的原因。

(4)关于printf()函数的参数 printf()函数的参数有两部分:前面的用双引号引起 的部分称为“控制串”。控制串由一些字符组成,这些字 符可以分成两类:第一类字符可以直接显示出来,第二类 字符作为格式说明符使用。或者说,除了格式说明符之外 的字符,都是可以直接显示的。格式说明符是由“%‖开 头,后面跟着的是格式码。本例中的“d‖就是格式码,它 后面输出的数据按照带符号十进制输出。其他格式说明符 将陆续介绍。

(5)关于赋值运算 在C语言中,符号“=‖称为赋值运算符,它的作用是把后 面(右面)的值,送到其前(左面)的变量(左值)中。一定 不要将其当作是等号。在C语言中,等号是“==‖。 例1.3 计算一个数的正弦值的C语言程序。

/* 文件名:ex010301.c */ # include <stdio.h> #include <math.h> int main(void) { float x; x = sin(0.19199); printf(″%f\n″,x); }

/*定义x为实型变量*/ /*调用sin函数 */ /*调用printf函数,输出x的值*/

程序的执行结果如下:
0.190813

说明: (1)“float x;‖是声明:x是一个实型变量。 (2)“x = sin(0.19199)‖可执行一次函数调用,求出 0.19199弧度的正弦值,并赋给实型变量x。sin()是一 个库函数,math.h是其要求的头文件。 (3)printf()中的格式说明符“%f‖,指定一个实型格式输 出(前面介绍的%d是整型数据格式符)。通常输出的数 据在小数后有6位数字;小数点前的数字位数不指定,根 据实际值的位数输出。格式说明符的类型要与后面要输 出的数据类型相一致。 (4)printf()中的“\n‖称为转义字符序列,前面加了反 斜杆后,“n‖不再作为字符,而是作为一条换行命令使 用。转义字符还有一些,以后陆续介绍。

从这一小节可以得出如下结论: C语言程序是由函数组成的。 设计C语言程序时,一个必须设计的函数是主函数。C语 言的执行是从系统调用主函数开始的。 主函数的部分功能也可以通过其他子函数补充实现。子函 数应当首选从函数库中的函数;函数库中没有时。可以考虑自 行设计。 使用库函数时,要用文件包含命令将需要的头文件包含到 程序中调用该库函数之前。

1.3.2 C语言的标准 由前一小节中的例子可以看出,在C语言程序中,函数下 面的组成单位是语句。在C99中,基本的语句有表达式语句、 流程控制语句和块语句。 1. 表达式语句 C语言程序的具体计算过程是由表达式完成的。表达式是 由运算符(如上述+,=等)、变量(如上述s,sum,a,b, x等)和常量(如上述2,3,0.19199等)组成。前面使用过 的 s = add(2,3) sum = a + b x=sin(0.19199) 都是表达式。表达式加上语句结束符(分号)就构成表达 式语句。学习C语言程序设计,必须掌握正确地使用变量、常 量和运算符的表示方法和使用规则。 变量和常量的使用涉及它们的数据类型、表示(命名)规 则等,后面要专门介绍。

C语言中的运算符种类很多,正确地使用这些运算符,有 三点需要注意: 它们的含义。特别要区分一个运算符符号在C语言中和在 普通数学中的意义的不同。如“=‖。 优先级,即在一个表达式中存在多个运算符时,进行运算 的先后顺序。 结合性,即在一个表达式中有多个优先级别相同的运算符 时,先进行哪个运算符的运算。例如,在表达式2*3/5(在C 语言中,“*”为乘运算符。“/‖为除运算符)中,先进行除 呢,还是先进行乘。在这个表达式中,好像对运算结果没有影 响,但有时是有影响的。 关于这些问题,后面将专门介绍。

2. 流程控制语句 一般说来,程序中的语句是按照书写顺序执行的。但是, 有些情况下,需要改变默认的执行顺序,例如像图1.10(a) 那样要从两个或多个语句中挑选一个语句执行,或者像图1.10 (b)那样要重复执行某一个语句或语句块。前者称为选择控 制,后者称为重复控制。
是 否 条件 是 语句

条件



语句1

语句2

(a)选择结构 (b)重复结构 图1.10 两种基本的流程控制结构

下面给出两个实例。 例1.4 由键盘输入两个数,输出其中的大数。 本例中函数max2()的执行过程如图1.11所示。



x >= y



max = x

max = y

输出max

图1.11 函数max2()的执行过程

程序如下:
/* 文件名:ex010401.c */ #include <stdio.h> float max2(float x,float y) { float max; if(x >= y) max = x; else max = y; printf(―The max is:%f‖,max); } int main(void) { float a,b; printf(―Input two real numbers:\n‖); scanf(―%f%f‖,&a,&b); max2(a,b); return 0; }

/* 选择判断 */ /* 条件满足进行的运算*/ /* 条件不符进行的运算*/

/* 输入提示 */ /* 输入数据 */ /* 调用函数max2()*/

说明: (1)本例的函数max2()中有一个选择结构, 条件是“x >= y‖。满足该条件,则执行运算max = x;不满足,则执行运算max = y。这样,就在max 中保存了x和y中的大者。 (2)在主函数中,函数scanf(―%f %f‖,&a,&b) 的功能是从键盘上输入两个实数,分别存放到地址&a 和&b中。地址&a和&b是变量a和b地址,“&‖是一 个运算符。用于计算其后面变量的地址。 (3)在函数scanf(―%f%f‖,&a,&b)中,“%f‖ 表示要输入的数据是实型数据。也就是说,格式说 明符的类型,要与输入数据的类型一致。另外,键 入的两个数据之间应当以空格、制表符(按Tab键) 或回车分隔。 程序运行时的情形如下:
Input two real numbers: 1.235 2.345 The max is: 2.345

例1.5 求累加和的值。 程序如下:
/* 文件名:ex010501.c */ #include <stdio.h> int sigma(int n) { int i = 0,sum = 0; while(i <= n) { i ++; sum = sum + i; } renturn sum; } int main(void) { int m,total; scanf(―%d‖,&m); total = sigma(m); printf(―total = %d\n‖,total); return 0; }

说明: (1)声明
int i = 0,sum = 0;

有两个功能:一是定义了两个整型变量;二是对两个整 型变量设定了初值。这称为变量的初始化。变量在没有初始化 时并且也没有执行赋值操作之前,其值是不确定的。为了避免 使用这些不确定的值,应当尽量在变量定义的同时对其进行初 始化。 一般说来,存放和的变量的初始值应当为0,而存放积的 变量的初始值应当为1。

(2)在本例中,函数 sigma()用来计算。计算的方 法 是: 先设置sum的初值为0、 i的初值为0。 用表达式“i ++‖将自增 1,相当于执行操作:i = i + 1。 每执行一次i的自增1, 执行一次sum = sum + i。 用流程图表示如图 1.12所示。

i=0,sum=0 否

i <= n 是 i ++ sum = sum + i

return sum

图1.12 函数sigma()的执行过程

除了上述两种控制语句外,C语言还提供了其他一些控制 语句,以后会专门介绍。

3. 块语句 块语句也称为复合语句,就是用一对花括号将一组语句 括起来。在一个块语句中可以包括若干声明和若干语句。在 例1.8的函数sigma()中,while下面的用花括号括起的两个语 句,就组成一个块语句。块语句在语法上相当于一条语句。 因此,当语法上需要一个语句,而一个语句又不能满足需要 时,就必须使用块语句。

1.3.3 名字与声明 1. 标识符与关键字 标识符也称为名称。变量的名字、函数的名字、文件的 名字等,都是关键字。在C语言程序中,所使用的标识符应当 符合如下的词法规则: (1)标识符是大小写字母、数字和下划线组成的序列,但 不能以数字开头。例如,下面是合法的C标识符: a A Ab _Ax _aX A_x abcd 但是下列不是合法的C标识符: 5_A(数字打头) A-3(含非法字符) (2)C语言区别同一字母的大小写,如abc与abC被看作 识不同的名标识符。 (3)C89要求C编译器能识别的标识符长度为不少于31个 有效字符,C99要求C编译器能识别的标识符长度为不少于63 个有效字符。一个标识符中超过了这个长度的字符可能会不被 辨认。例如在符合C99的系统中,当两个标识符的前63个字符 都相同时,不管后面的有效字符是否相同,都可能被当作同一 个标识符。

(4)普通标识符不能使用对于系统有特殊意义的名称。 这些对系统有特殊意义的名称称为关键字。表1.1为C99关键 字。
表1.1 C99关键字
auto _Complex _Bool const break continue case default char do

double
for int short switch volatile

else
goto long signed typedef while

enum
if register sizeof union

extern
_Imaginary restrict static unsigned

float
inline return struct void

在一个程序中,往往要使用大 量的名字。大量的名字的使用,可 能会造成名字的冲突和使用错误。 为此,除了上述词法规则外,人们 还总结了在程序中使用“好”名字 的一些原则: (1)尽量做到“见名知义”, 以增加程序的可读性。 (2)尽量避免使用容易混淆的 字符,例如 0(数字)-O(大写字母) -o(小写字母) 1(数字)-I(大写字母) -i(小写字母) 2(数字)-Z(大写字母) -z(小写字母) (3)名字不要太短,一般函数 名尽量使用动宾结构,如 PrintCalendar、IsPrime等。 (4)许多国外的Windows程序 员还采用匈牙利人Charles Simonyi 提出的将类型作为变量名前缀的命 名方法——通常称为匈牙利命名法。 表1.2为部分常用匈牙利前缀。

表1.2
匈牙利前缀 a或ar b by c或ch d或dbl dw或w f或fl fn h L或l m n或i p s sz x,y

部分常用匈牙利前缀
数据类型 数组 BULL(布尔值) BYTE(无符号字 符) char double 无符号整数 float 函数 句柄 long 类的数据成员 int 指针 字符串 “0”结束的字符串 无符号整型或坐标 n,i,nCount,iCou nt pInt,pWnd sName sz,szMystring 变量名举例 arAge bDone,b byCount,by c,ch d,dbl,dCost,dbl Cost wNumber f,fl,fCost,flCo st fnFun1 hWnd L,l,lCount

2. 声明 在程序中,有许多东西是需要系统为其开辟存储空间的,例如变量、 函数类型定义等。它们都有自己的名字,并且要在内存中独立存储,为 此可以将它们称做程序实体。那么,如何建立它们的名字与实体之间的 关联呢?这就是声明的作用。 声明也称为说明,它的作用非常重要,包括如下一些: 告诉编译器,一个名字是与哪个实体联系,不能张冠李戴。 告诉编译器,也要程序员明白这个实体的类型。 告诉编译器,这个实体什么时候建立?在什么范围内可以使用? 前面已经使用过了变量和函数的声明。所以,例1.2中的 int s; 就是建立变量名s与它的实体之间的关联。 在一个语句块中关于声明的进一步用法,后面还要陆续介绍。目前 要牢记的是,在使用一个程序实体之前,一定要让编译器知道该程序实 体的属性。 在C99之前,对变量和函数的声明不作为语句(尽管它们也是用分号 结尾),它们必须出现在C语句的前面(声明的位置必须集中写在语句之 前)。C99改变了这一做法,它吸取了C++的做法,声明不必集中放在执 语句之前,可以出现在程序中的任意行。这样,C语句就有执行语句和 非执行语句之分。声明是非执行语句,表达式语句和流程控制语句是执 行语句。

1.3.4 变量及其赋值 1. 变量 变量(variable)是一种程序实体。它具有一个值,并且 这个值是可以通过程序操作改变的。 2. 变量的赋值运算 在C语言中,符号“=‖称为赋值运算符,它连接了左右两 个操作数(即运算量):右操作数也称右值,可以是一个表达式, 左操作数也称左值(lvalue),只能是变量。赋值操作的过程 是把右操作数的值先转换成左操作数(变量)的类型,并把该 值存放到左操作数(变量)中。例如
int a; a = 2.6; printf(―%d‖,a);

结果为
2

这是因为计算机在执行上述语句时,首先将2.6舍去小数部 分截尾(truncation)成整型,赋值给变量a。

应当注意,赋值运算符是“=‖。这个符号不是等号。例如
int a = 2, b = 3; a = a + b;

的操作是把表达式a + b的值(2 + 3)送到(赋值给)变量a。即经上 述操作后,变量的a的值由2变为5。图1.13表明这一操作过程:先 计算a + b的值,然后把这个结果送到变量a中。于是,变量a的值 由2变为5。
5
a 2 a + b 2 b 3 3

运算器

图1.13 a = a + b的操作过程

赋值运算符具有“自右至左” 的结合性,例如
int a = 0,b = 0,c = 0; a = b = c = 5 + 3;

相当于
int a,b,c; a = (b =(c = (5 + 3)));

即先计算把5 + 3 的值,得8,赋值给变量c;再把变量c的值 (8)赋值给变量b;最后把变量b的值(8)赋值给变量a。图 1.14表明这一操作过程。执行的结果,a、b、c三个变量中的 值均为8。也就是说,从一个变量向另一个变量赋值后,原来 变量中的值并不会消失或改变。所以,赋值操作相当于拷贝, 而不是移动。

a

0

b

0

c

0

5

3

运算 器

a + b

(a) 操作前变量a、b、c的值
a 0 b 0 c 8 5 运算 器 a + b 3

(b)执行操作c = 5 + 3后变量a、b、c的值
a 0 b 8 c 8 运算 器

(c) 执行操作b = c = 5 + 3后变量a、b、c 的值
a 8 b 8 c 8 运算 器

(d)执行操作a = b = c = 5 + 3后变量a、b、c的值

图1.14 a = b = c = 5 + 3的操作过程

1.3.5 算术运算 算术运算是一切计算的基础,也是大家都认为非常熟悉的。 但是对于高级程序设计语言尤其是C语言中的算术运算符,还需 要有一个再学习的过程。C语言中的算术运算符与普通数学中的 算术运算符有如下一些不同。 ·运算符符号有所不同。 ·种类有所不同。 ·结合性可能会破坏交换率。 1. C语言的基本算术运算符 表1.3为C语言中的基本算术运算符及其说明。
表1.3 C语言中的基本算术运算符及其说明
运算符 名称 运算对象 功 能 示例表达 式 示例值

*
/ % + -


除 模 加 减

任何两个实数或整数
任何实数或整数,但右操作数不可为0 两个整数,但右操作数不可为0 任何两个实数或整数 任何两个实数或整数

求两数之积
求两数之商 求整除的余数 求两数之和 求两数之差

5.5 * 4.0
4.5 / 5 13 % 8 8 + 3.5 10 - 4.6

22.000000
0.900000 5 11.500000 5.400000

说明: (1)这几个算术运算符的运算对象有两个,所以也称为 双目算术运算符。 (2)这几个算术运算符的优先级别为:*、/、%高于+、-。 并且它们都比赋值运算符的优先级别高。所以,在一个表达式 中有赋值运算符,也有算术运算符时,不使用圆括号,可以先 进行算术运算,后进行赋值运算。 (3)这几个算术运算符均为“自左至右”。 (4)需要特别注意的是整数除和模运算的结果都是整 数。 例1.6 分析下面的程序的执行结果。
/* 文件名:ex010601.c */ #include <stdio.h> int main(void) { printf ("300 * 2 / 3 = %d\n",300 * 2 / 3); printf ("2 / 3 * 300 = %d\n",2 / 3 * 300); return 0; }

观察上面的程序,按照交换率,似乎它们的计算结果应该 相同。但是,非常遗憾!结果完全不同:
300 * 2 / 3 = 200 2 / 3 * 300 = 0

原因在于算术运算符具有自左至右的结合性,即对于第1个表 达式语句,执行的顺序为: 300 * 2 = 600,600 / 3 = 200 对于第2个表达式语句,执行的顺序则为: 2 / 3 = 0(注意是整数相除),0 * 300 = 0 因此,使用整数除,应当特别小心。

2. 自反算术赋值运算符 前面介绍过这样的赋值表达式: a=a+b 它的作用是将变量a的值加上变量b的值,再送回到变量a中。或者说是 将变量a的值增加一个变量b的值。这样类似的运算很多。为此C语言为 这种运算提供了一种简洁形式: a+=b 这样,就可以用一个复合运算符代替原来的两个运算符。这种复合运算 符称为自反算术赋值运算符。除自反加以外,还有下列一些: - = (自反减赋值) * = (自反乘赋值) / = (自反除赋值) % = (自反模 赋值) 自反算术赋值的结合方向与赋值运算符一样,为自右向左。另外, 它的优先级别相当低,与赋值是同一级别。例如表达式语句:

c = b * = a + 2;

相当于如下两个表达式语句:
b = b * (a + 2); c = b;

3. 自加和自减运算 自反算术赋值运算中有两种更特殊的情况,即: i = i + 1 即 i += 1 和 i = i - 1即 i -= 1 这是两种极为常用的操作。把i称为计数器,用来记录完成某 一工作的次数。C语言为它们专门提供了两个更简洁的运算符: i ++或++ i 和 i --或-- i 前一种(i ++和i --)称为后缀形式;后一种(++ i和-- i)称为前缀 形式,都称为自加或自减运算符。

例如: int i = 5; i ++; y = i; 与 int i = 5; ++ i; y = i; 两段程序执行的结果i值都为6,y的值也都为6。但是把它们引用在表达 式中就表现出区别了。例如: int i = 5; x = i ++; /* 相当于 x = i; i = i + 1;*/ y = i; 的执行结果为:x为5,y为6。即后缀方式是“先引用后增值”。而
int i = 5; x = ++i; y = i; /* 相当于 x = i = i + 1; */

的执行结果为:x为6,y为6。即前缀方式是“先增值后引用”。 自加和自减运算符的结合方向是“自右至左”,它的运算对象只能 是整型变量而不能是表达式或常数。例如:5 ++或(x + y) ++是错误的。

4. 正负号运算符 正负号运算符为+(正号)和-(负号),它们是一元运算符。 例如,-5和+6?5。它们的优先级别高于*、/运算符。例如: -a * b 先使a变符号再乘以b。其实正负号运算相当于一次算术赋值 运算,例如: -a 相当于a = 0 – a -a * b 相当于(0 - a) * b 它的结合方向为自右至左。综上所述,凡赋值运算符及其变种 (包括自反算术赋值运算符、自加自减运算符和正负号运算符) 的结合方向都是自右至左的。

1.3.6 赋值类运算符的副作用及限制 在程序设计中,效率与易读性是一对主要矛盾。人们为了提 高程序的效率,往往要使用技巧把程序写得尽可能简洁一些。但 这样就降低了程序的可读性和可理解性。可读性差的后果是易于 隐藏错误,难于纠正错误,不易维护,降低了程序的可靠性。鉴 于“软件危机”的教训,现代人们进行程序设计时要遵守的基本 规范是:可靠性第一,效率第二。为保证可靠性第一,就要清晰 易读第一。这就要求进行程序设计时,把程序写得清晰易懂一些。 初学者从一开始就应当培养这个良好的程序设计风格。 C语言允许在一个表达式中使用一个以上的赋值类运算符(包括赋 值符,自反算术赋值符,自加、自减运算符等)。这种灵活性在 给程序带来简洁性的同时也会引起副作用。这种副作用表现在两 个方面. 1. 费解、易于误解——对人的副作用 下面是容易引起误解的两个例子。 ① c = b* = a + 2;容易误解为 b *= a; c = b + 2; ② x = i ++ +j;应该理解为x =(i + +) + j呢?还是x = i +(+ + j) 呢?实际上C编译器总是从左至右尽量多地将若干个字符组成一 个运算符(对标识符也如此),因此i + + + j被处理成(i ++) + j而不 是i + (++ j)。

克服这类副作用的方法是:尽量把程序写得易懂一些。为了使表 达式清晰易懂,可以采用这样一些措施: (1) 将费解处分解。 例如将上面第①个表达式语句分写为
a = a + 2; b = b * a; c = b;或c = a + 2; c = b*c;

对第②个表达式语句,可分写为:
x = i + j; i ++;

(2)加一些“冗余”括号 C语言的运算符号很丰富,但运算规则复杂、难记。为了避 免出现差错,可以在易于误解的地方加一些“冗余”括号,例如, c=b*=a+2可以改写为 c=b*=(a+2);或c=(b*=(a+2)); 便清晰些了。 所以称之为“冗余”是指不加这些括号计算机也不会错, 但为了让人不理解错而多加的括号。 (3) 加注释说明。 注释是提高程序清晰性的有力工具。对C语言来说,注释不 会影响程序的效率。所以,一方面想保证程序的效率,一方面又 怕别人误解(当然自己不能误解)的情况下,可以加注释说明。如 对c = b* = a + 2可以写为:c=b*=a+2; /* c=a+2;c=b*c */

2. 不定解——对机器的副作用
先看一个例子:
j = 3; i =(k = j + 1)+(j = 5);

执行这段程序时,不同机器上得到i的值可能是不同的。有的 机器先执行(k=j+1)然后再执行(j=5),i值得9。而有的机器是先执 行(j=5)后执行(k=j+1),i的值就成了11。 在数学中,a+b和b+a是一样的,因为符合交换律,所以 (a+b)+(c+d)也可以写成(c+d)+(a+b)。换言之,(a+b)+(c+d)的求 值顺序不影响结果(可以先求a+b,也可以先求c+d)。但在C表达 式中含有一个以上的赋值类运算符时,交换律不再适用。由于C 语言对表达式的求值顺序(方向)无统一规定,而是由各个C编译系 统自己决定,这就造成了同一程序在不同计算机系统运行时会得 到不同的结果。 为了提高程序的可移植性,应当使表达式分解,使之在任何机 器上运行都能得到同一结果。因此上面语句可改为:
j = 3; k = j + 1; j = 5; i =k + j;

习题
1.1 请编写一个程序,能显示出以下两行文字: I am a student. I love China. 1.2 编一程序,从键盘输入两个整数,输出此二数之和。 1.3 调用库函数,求以下的函数值: (1) cos 3.5678(角度单位为弧度) (2) log 90 (3) e2.567 1.4 编写一个求三个整数之和的程序。 (1) 只用main函数 (2) 用求两个整数之和的函数实现(自己设计函数)。 1.5 编写一个求a+|b|的程序,a,b为两个整数。 1.6 编写一个求0~100之间全部偶数之和的程序。 1.7 编写一个程序从5个整数中找出最小的数。 1.8 在下列符号中,你可以选用哪些作变量名?哪些不可以?为什么? a3B 3aB π +a *x $a b5_ if next_ day e_2 OK? MAXNUMBER i*j Main

1.9 写出下面程序段执行后变量a,b,c的值。

char a =′2′,b =′a′; int c; c = a + b; a = c;
1.10 写出下面程序的输出结果。 #include <stdio.h> int main(void) { int x; x = -3 + 4 * 5 - 6; printf (″%d\n″,x); x = 3 + 4 % 5 - 6; printf (″%d\n″,x); x = -3 * 4 % -6 / 5; printf (″%d\n″,x); x =(7 + 6) % 5/2;printf (″%d\n″,x); return 0; } 1.11 写出下面程序的输出结果 #include <stdio.h> int main(void) { int x,y,z; x = y = 2;z = 3; y = x ++ -1; printf (″%d\t %d\t″,x,y); y = ++ x - 1; printf (″%d\t%d\t″,x,y); y = z - - +1; printf (″%d\t%d\t″,z,y); y = - - z + 1; printf (″%d\t%d\n″,z,y); return 0; }

1.12 分析下面的程序段,指出其中的错误以及错误原因,再将其改正。 (1) int x = 12.345; printf(―%d‖,x); (2) int a,b; scanf(―%d,%d‖,a,b); (3) float x,y; scanf(―%d,%d‖,&x,&y);


网站首页 | 网站地图 | 学霸百科 | 新词新语
All rights reserved Powered by 学霸学习网 www.tceic.com
copyright ©right 2010-2021。
文档资料库内容来自网络,如有侵犯请联系客服。zhit325@126.com