当前位置:首页 >> 电脑基础知识 >>

C语言程序设计(第三版)-谭浩强[开始免费了]


第一章

?

本章要点
?

C语言的特点

?
?

C程序的结构
在计算机上运行C程序的方法

§1-1 C语言出现的历史背景
? C语言是国际上广泛流行的高级语言。 ? C语言是在B语言的基础上发展起来的。 ? B (BCPL)语言是1970年由美国贝尔实验 室设计的, 并用于编写了第一个UNIX操作 系统,在PDP 7上实现。优点:精练,接近硬 件,缺点:过于简单,数据无类型。 ? 1973年贝尔实验室的D.M.Ritchie 在B语言 的基础上设计出了C语言,对B取长补短, 并用之改写了原来用汇编编写的UNIX,(即 UNIX第5版),但仅在贝尔实验室使用。

§1-1C语言出现的历史背景
? 1975年UNIX第6版发布,C优点突出引起关注。 ? 1977年出现了《可移植C语言编译程序》 , 推动了UNIX在各种机器上实现 ,C语言也得 到推广,其发展相辅相成。 ? 1978年影响深远的名著《The C Programming Language》由 Brian W.Kernighan和Dennis M.Ritchie 合著,被称为标准C。 ? 之后,C语言先后移植到大、中、小、微型计 算机上,已独立于UNIX和PDP,风靡世界,成为 最广泛的几种计算机语言之一。

§1-1C语言出现的历史背景 ? 1983年,美国国家标准化协会(ANSI)根据C语 言各种版本对C的发展和扩充,制定了新的标 准ANSI C ,比标准C有了很大的发展。 ? 1988年K & R按照 ANSI C修改了他们的《The C Programming Language》。 ? 1987年,ANSI公布了新标准——87 ANSI C。 ? 1990年,国际标准化组织接受了87 ANSI C为 ISO C 的标准(ISO9899—1990)。 ? 1994年,ISO又修订了C语言标准。 ? 目前流行的C语言编译系统大多是以ANSI C为 基础进行开发的。

§1-1C语言出现的历史背景 说明:
不同版本的C编译系统所实现的语言 功能和语法规则又略有差别,因此读者 应了解所用的C语言编译系统的特点(可 以参阅有关手册)。本书的叙述基本上以 ANSI C 为基础。

§1-2 C语言的特点
(1)语言简洁、紧凑,使用方便、灵活。 32 个关键字、9种控制语句,程序形式自由 (2)运算符丰富。34种运算符 (3)数据类型丰富,具有现代语言的各种数据 结构。 (4)具有结构化的控制语句 ,是完全模块化 和结构化的语言。 (5)语法限制不太严格,程序设计自由度大。

§1-2 C语言的特点
(6)允许直接访问物理地址,能进行位操 作,能实现汇编语言的大部分功能,可直 接对硬件进行操作。兼有高级和低级语 言的特点 。 (7)目标代码质量高,程序执行效率高。 只比汇编程序生成的目标代码效率低10 %-20%。 (8)程序可移植性好(与汇编语言比)。 基本上不做修改就能用于各种型号的计 算机和各种操作系统。

§1-2 C语言的特点
问题:既然有了面向对象的C++语言,为 什么还要学习C语言? 解释1:C++是由于开发大型应用软件的需 要而产生的,并不是所有的人都要去编 写大型软件; 解释2:面向对象的基础是面向过程。C++ 是面向对象的语言,C是面向过程的,学 起来比C语言困难得多,所以不太适合程 序设计的初学者。

说明: 本程序的作用是输出一行信息:

§1-3 简单的C语言程序介绍 This is a C program. #include <stdio.h> /*文件包含*/ void main( ) /*主函数 */ { /*函数体开始*/ printf ("This is a C program.\n"); /*输出语句*/ } /*函数体结束*/
说明: main-主函数名, void-函数类型

? ? ? ?

每个C程序必须有一个主函数main { }是函数开始和结束的标志,不可省 每个C语句以分号结束 使用标准库函数时应在程序开头一行写: #include <stdio.h>

说明: 输出一行信息:sum is 579

例1.2 求两数之和 #include <stdio.h> void main( ) /*求两数之和*/ { int a,b,sum; /*声明,定义变量为整型*/ /*以下3行为C语句 */ a=123; b=456; sum=a+b; printf(″sum is %d\n″,sum); } 说明: /*??*/表示注释。注释只是给人 看的,对编译和运行不起作用。所以可以用 汉字或英文字符表示,可以出现在一行中 的最右侧,也可以单独成为一行。

? 程序运行情况如下: 例1.3 求3个数中较大者。 8,5 ↙(输入8和5赋给a和b) ? #include <stdio.h> ? max=8 (输出c的值) void main( ) /* 主函数*/ { int max(int x,int y); / 对被调用函数max的声明 */ int a, b, c; /*定义变量a、b、c */ scanf(″%d,%d″,&a,&b); /*输入变量a和b的值*/ max(a,b); c=max(a,b); /*调用max函数,将得到的值赋给c */x, inty); int max(int x,int y) printf(″max=%d\\n″,c); { /*输出c的值*/ } int z; 说明:本程序包括main和被调用 if (x>y) z=x; 函数max两个函数。max函数的 else z=y; 作用是将x和y中较大者的值赋 return (z); 给变量z。return语句将z的值 } 返回给主调函数main。

§1-3 简单的C语言程序介绍
C程序:
(1) C程序是由函数构成的。 这使得程序容易实现 模块化。 (2) 一个函数由两部分组成: 函数的首部:例1.3中的max函数首部 int max(int x,int y ) 函数体:花括号内的部分。若一个函数有多个花 括号,则最外层的一对花括号为函数体的范围。 函数体包括两部分 : 声明部分:int a,b,c; 可缺省 执行部分:由若干个语句组成。可缺省

§1-3 简单的C语言程序介绍
注意:
函数的声明部分和执行部分都可缺省,例如:

void dump ( ) { }
这是一个空函数,什么也不做,但是合法的函数。

§1-3 简单的C语言程序介绍
小结:
(3) C程序总是从main函数开始执行的,与main函数 的位臵无关。 (4) C程序书写格式自由,一行内可以写几个语句, 一个语句可以分写在多行上,C程序没有行号。 (5) 每个语句和数据声明的最后必须有一个分号。 (6) C语言本身没有输入输出语句。输入和输出的操 作是由库函数scanf和printf等函数来完成的。C对 输入输出实行?函数化?。

§1-4 运行C程序的步骤和方法 一、运行C程序的步骤 ?上机输入与编辑源程序 ?对源程序进行编译 ?与库函数连接 ?运行目标程序

§1-4 运行C程序的步骤和方法 二、上机运行C程序的方法
目前使用的大多数C编译系统都是集成环境(IDE)的。 可以用不同的编译系统对C程序进行操作
? ?

常用的有Turbo C 2.0、Turbo C++ 3.0、Visual C++等

Turbo C++ 3.0:是一个集成环境,它具有方便、直观 和易用的界面,虽然它也是DOS环境下的集成环境, 但是可以把启动Turbo C++ 3.0 集成环境的DOS执行文 件tc.exe生成快捷方式,也可以用鼠标操作。
?
?

Visual C++:也可以用Visual C++对C程序进行编译。

例:Turbo C++ 3.0的使用
将Turbo C++ 3.0编译程序装入磁盘某一目录下,例如: 放在C盘根目录下一级TC3.0子目录下。 (1) 进入Turbo C++ 3.0集成环境 ①在DOS环境下 C:\TC3.0>tc ↙?

② 在Windows环境下 找到可执行文件tc.exe,执行该文件。

主菜单:11个菜单项:? File Edit Search Run Compile Debug Project Options Window Help?

(2) 编辑源文件 新建:单击“File”菜单下 的“New”,

修改:选择“File”→“Open”(即单击“File” 的下拉菜单 中的“Open”项,修改已有的源程序。

在编辑(EDIT) 状态下光标表示当前进行编辑的位置,在此位 置可以进行插入、删除或修改,直到自已满意为止。

保存:在编辑(EDIT) 状态下光标表示当前进行编辑的位置, 在此位置可以进行插入、删除或修改,直到自已满意为止。

(3) 对源程序进行编译 选择“Compile”(或“Alt+F9”)对源程序进行编译。

c1.cpp源程序,出现1个错误(error) ,0个警告 (warming)。

(4) 将目标程序进行连接

选择菜单“Compile” →“Link” ,如果不出现 错误,会得到一个后缀为.exe的可执行文件。 (5) 执行程序。 选菜单“Run” →“Run”( 或按“Ctrl+F9” 键)。 (6) 退出Turbo C++ 3.0环境 选择“File”→“Quit” 。

第二章

?

本章要点
?

算法的概念

?
?

算法的表示
结构化程序设计方法

?

主要内容
2.1 算法的概念
2.2 简单算法举例

2.3 算法的特性
2.4 怎样表示一个算法 2.5 化程序设计方法

一个程序应包括两个方面的内容:
? 对数据的描述:数据结构(data structure) ? 对操作的描述:算法(algorithm) 著名计算机科学家沃思提出一个公式: ?? 数据结构 + 算法 = 程序?

完整的程序设计应该是:
数据结构+算法+程序设计方法+语言工具

§2.1 算法的概念
广义地说,为解决一个问题而采取的方 法和步骤,就称为?算法?。 对同一个问题,可有不同的解题方法和步骤 例: 求

?n
n ?1

100

? 方法1:1+2,+3,+4,一直加到100 加99次 ? 方法2:100+(1+99)+(2+98)+…+(49 +51)+50 = 100 + 49×100 +50 加51次

§2.1 算法的概念
为了有效地进行解题,不仅需要保证 算法正确,还要考虑算法的质量,选择合 适的算法。希望方法简单,运算步骤少。 计算机算法可分为两大类别: ? 数值运算算法:求数值解,例如求方程的 根、求函数的定积分等。 ? 非数值运算:包括的面十分广泛,最常见 的是用于事务管理领域,例如图书检索、 人事管理、行车调度管理等。

§2.2 简单算法举例
例2.1: 求1×2×3×4×5
步骤1:先求1×2,得到结果2? 步骤2:将步骤1得到的乘积2再乘以3,得到结果6 步骤3:将6再乘以4,得24? 步骤4:将24再乘以5,得120

如果要求1×2×…×1000,则要写999个步骤

可以设两个变量:一个变量代表被乘数,一 个变量代表乘数。不另设变量存放乘积结 果,而直接将每一步骤的乘积放在被乘数 变量中。设p为被乘数,i为乘数。用循环 算法来求结果, 算法可改写:
S1:使p=1? S2:使i=2? S3:使p×i,乘积仍放在变量p中,可表示为:p×ip S4:使i的值加1,即i+1i。? S5:如果i不大于5,返回重新执行步骤S3以及其后 的步骤S4和S5;否则,算法结束。最后得到p的值就 是5!的值。

如果题目改为:求1×3×5×……×1000 算法只需作很少的改动: S1:1p? S2:3i? S3:p×ip? S4:i+2p? S5:若i≤11,返回S3。否则,结束。

用这种方法表示的算法具有通用性、 灵活性。S3到S5组成一个循环,在实现 算法时 要反复多次执行S3,S4,S5等步 骤,直到某一时刻,执行S5步骤时经过 判断,乘数i已超过规定的数值而不返回 S3步骤为止。此时算法结束,变量p的值 就是所求结果。

例2.2 有50个学生,要求将他们之中成绩在80 分以上者打印出来。设n表示学号, n1代表 第一个学生学号, 代表第i个学生学号。用G 代表学生成绩 , gi代表第i个学生成绩,算法 表示如下:
S1:1i? S2:如果≥80,则打印和,否则不打印。? S3:i+1i? S4:如果i≤50,返回S2,继续执行。否则算法结束

变量i作为下标,用来控制序号(第几个学 生,第几个成绩)。当i超过50时,表示 已对 50个学生的成绩处理完毕,算法结束。

例2.3 判定2000~2500年中的每一年是否闰年, 将结果输出。 分析:闰年的条件是:(1)能被4整除,但不能被 100整除的年份都是闰年,如1996,2004年是闰 年;(2)能被100整除,又能被400整除的年份是 闰年。如1600,2000年是闰年。不符合这两个条 件的年份不是闰年。 变量i作为下标,用来控制序号(第几个学 生,第几个成绩)。当i超过50时,表示 已对 50个学生的成绩处理完毕,算法结束。

设y为被检测的年份,算法可表示如下 :? S1:2000y? S2:若y不能被4整除,则输出y “不是闰年?。然后转 到S6。? S3:若y能被4整除,不能被100整除,则输出y “是闰 年?。然后转到S6。? S4:若y能被100整除,又能被400整除,输出y“是闰年 ?,否则输出?不是闰年?。 然后转到S6。 S5: 输出y “不是闰年?。 S6:y+1y? S7:当y≤2500时,转S2继续执行,如y>2500,算法
停止。?

以上算法中每做一 步都分别分离出一 些范围(巳能判定为 闰年或非闰年),逐 步缩小范围,直至 执行S5时,只可能 是非闰年。 ?其它? 包括能被4 整除,又能被100整 除,而不能被400整 除的那些年份(如 1990) 是非闰年。

例2.4 求1 ? 1 ? 1 ? 1 ? ...... ? 1 ? 1 算法如下 :
S1:sign=1??
2 3 4 99 100

单词作变量名,以使算 S2:sum=1? 法更易于理解: S3:deno=2? sum表示累加和,deno是 S4:sign=(-1)×sign? 英文分母(denom inator) 缩写,sign代表数值的符 S5:term=sign×(1/deno)? 号,term代表某一项。 S6:sum=sum+term? S7:deno=deno+1? S8:若deno≤100返回S4,否则算法结束。?
反复执行S4到S8步骤,直到分母大于100为止。 一共执行了99次循环,向sum累加入了99个分数。 sum最后的值就是多项式的值。

例2.5 对一个大于或等于3的正整数,判断 它是不是一个素数。

概念:所谓素数,是指除了1和该数本身之外, 不能被其它任何整数整除的数。例如,13是 素数。因为它不能被2,3,4,…,12整除。
分析:判断一个数n(n≥3)是否素数的方法: 将n作为被除数,将2到(n-1)各个整数轮流作 为除数,如果都不能被整除,则n为素数。

算法如下 :
S1:输入n的值? S2:i=2 ? (i作为除数) S3:n被i除,得余数r? S4:如果r=0,表示n能被i整除,则打印n“不是 素数?,算法结束。否则执行S5? S5:i+1?i? S6:如果i≤n-1,返回S3。否则打印 n “是素数 ?。然后结束。?

实际上,n不必被2到(n-1)的整数除,只 需被2到n/2间整数除,甚至只需被2到 n 之间 的整数除即可。

§2.3 算法的特性
一个算法应该具有以下特点: ? 有穷性:包含有限的操作步骤 ? 确定性:算法中的每一个步骤都应当是确 定的 ? 有零个或多个输入:输入是指在执行算法 时需要从外界取得必要的信息 ? 有一个或多个输出:算法的目的是为了求 解,?解? 就是输出 ? 有效性:算法中的每一个步骤都应当能有 效地执行,并得到确定的结果 。

§2.4 算法的表示
可以用不同的方法表示算法,常用的有: –自然语言 –传统流程图 –结构化流程图 –伪代码 –PAD图

§2.4.1 用自然语言表示算法 自然语言就是人们日常使用的语言,可 以是汉语或英语或其它语言。用自然语言 表示通俗易懂,但文字冗长,容易出现? 歧义性?。自然语言表示的含义往往不大 严格,要根据上下文才能判断其正确含义 ,描述包含分支和循环的算法时也不很方 便。因此,除了那些很简单的问题外,一 般不用自然语言描述算法。

§2.4.2 用流程图表示算法 美国国家标准化协会ANSI(American National Standard Institute)规定了一 些常用的流程图符号:
起止框 判断框 处理框 输入/输出框

注释框

流向线

连接点

例2.6 将求5!的算法用流程图表示 如果需要将最后结 果打印出来,可在 菱形框的下面加一 个输出框。

例2.7 将例2.2的算 法用流程图表示。打 印50名 学生中成绩在 80分以上者的学号和 成绩。

如果如果包括 这个输入数据 的部分,流程 图为

例2.8 将例 2.3判定闰 年的算法用 流程图表示

用流程图表示算法要比 用文字描述算法逻辑清 晰、易于理解。

例2.9 将例2.4的算法用流程图表示 1 1 1 1 1 1 ? ? ? ? ...... ? ? 2 3 4 99 100

例2.10 将例2.5判断素数 的算法用流程图表示

小结:
? 流程图是表示算法的较好的工具。 一个流程图包括以下几部分 : (1)表示相应操作的框; (2)带箭头的流程线; (3)框内外必要的文字说明。

§2.4.3 三种基本结构和改进的流程图 1、传统流程图的弊端 传统流程图用流程线指出各框的执行 顺序,对流程线的使用没有严格限制。因 此,使用者可以毫不受限制地使流程随意 地转向,使流程图变得毫无规律,阅读者 要花很大精力去追踪流程,使人难以理解 算法的逻辑。如图:

缺点:难以阅读、修改,使算法的 传统流程图的流程可以是: 可靠性和可维护性难以保证。 解决办法:必须限制箭头的滥用, 即不允许无规律地使流程随意转向, 只能顺序地进行下去。

这种如同乱麻一样的算法称为BS型算 法,意为一碗面条(A Bowl of Spaghetti),乱无头绪。

2、三种基本结构 Bohra和Jacopini提出了以下三种基本 结构: 顺序结构、选择结构、循环结构 用这三种基本结构作为表示一个良好算 法的基本单元。

三种基本结构的图示:

顺序结构

选择结构

循环结构的图示:

当型(While型)循环结构

直到型(Until型)循环

三种基本结构的共同特点:? (1)只有一个入口; (2)只有一个出口;(请注意:一个菱形判断 框有两个出口,而一个选择结构只有一个出 口。不要将菱形框的出口和选择结构的出口 混淆。)? (3)结构内的每一部分都有机会被执行到; (4)结构内不存在?死循环?(无终止的循环) 。

不正确的流程表示:

图中没有一条 从入口到出口的 路径通过A框。

流程内的死循环

小结: ? 由三种基本结构顺序组成的算法结构 ,可以解决任何复杂的问题。由基本 结构所构成的算法属于?结构化?的 算法,它不存在无规律的转向,只在 本基本结构内才允许存在分支和向前 或向后的跳转。

扩展: ? 只要具有上述四 个特点的都可以 作为基本结构。 可以自己定义基 本结构,并由这 些基本结构组成 结构化程序。

此图符合基本结构的特点

这是一个多分 支选择结构,根据 表达式的值决定执 行路线。虚线框内 的结构是一个入口 一个出口,并且有 上述全部的四个特 点。由此构成的算 法结构也是结构化 的算法。可以认为 这是由三种基本结 构所派生出来的。

§2.4.4 用N--S流程图表示算法
1973年美国学者I.Nassi和B.Shneiderman 提出了一种新的流程图形式。在这种流程图 中,完全去掉了带箭头的流程线。全部算法 写在一个矩形框内,在该框内还可以包含其 它的从属于它的框,或者说,由一些基本的 框组成一个大的框。这种流程图又称N--S结 构化流程图 。

N--S流程图用以下的流程图符号:

(1)顺序结构? (2)选择结构?

(3)循环结构?

用三种N--S流程图中的基本框,可以组成复 杂的N--S流程图。图中的A框或B框,可以是一 个简单的操作,也可以是三个基本结构之一。
A框可以是一个选择结构

B框可以是一个循环结构

例2.11 将例2.1 的求5!算法用 N--S图表示

例2.12 将例2.2 的算法用N--S 图表示。( 打印50名学 生中成绩高 于80分的学 号和成绩)

没有输入数据

例2.12 将例2.2 的算法用N--S 图表示。( 打印50名学 生中成绩高 于80分的学 号和成绩)

有输入数据

例2.13 将例2.3 判定闰 年的算 法用N-S图表示

例2.14 将例2.4的算 法用N--S图表示
1 1 1 1 1 1 ? ? ? ? ...... ? ? 2 3 4 99 100

例2.15 将例2.5判别 素数的算法用N--S 流程图表示。 传统流程图分析:
此图不符合基本结构特点! 由于不能分解为三种基本结 构,就无法直接用N--S流程 图的三种基本结构的符号来 表示。因此,应当先作必要 的变换。 出口1

出口2

例2.15 将例2.5判别 素数的算法用N--S 流程图表示。 传统流程图变换为:

一个出口

用 流 程 图 表 示 :

N--S

N--S图表示算法的优点 ? 比文字描述直观、形象、 易于理解; 比传统流程图紧凑易画。尤其是它废除 了流程线,整个算法结构是由各个基本 结构按顺序组成的,N--S流程图中的上 下顺序就是执行时的顺序。用N--S图表 示的算法都是结构化的算法,因为它不 可能出现流程无规律的跳转,而只能自 上而下地顺序执行。

小结: ? 一个结构化的算法是由一些基本结构顺序 组成的。在基本结构之间不存在向前或向 后的跳转,流程的转移只存在于一个基本 结构范围之内(如循环中流程的跳转);一 个非结构化的算法可以用一个等价的结构 化算法代替,其功能不变 。如果一个算 法不能分解为若干个基本结构,则它必然 不是一个结构化的算法。

§2.4.5 用位代码表示算法
? 概念:伪代码是用介于自然语言和计算机 语言之间的文字和符号来描述算法。 ? 特点:它如同一篇文章一样 ,自上而下地 写下来。每一行(或几行)表示一个基本操 作。它不用图形符号,因此书写方便 、格 式紧凑,也比较好懂,也便于向计算机语 言算法(即程序)过渡。 ? 用处:适用于设计过程中需要反复修改时 的流程描述。?

例: ?打印x的绝对值 ?的算法可以用伪代 码表示为:

IF x is positive THEN? print x? ELSE? print -x

也可以用汉字伪代码表示:
若 x为正 打印 x? 否则? 打印 -x?

也可以中英文混用,如:?
IF x 为正? print x? ELSE? print -x?

例2.16 求5!。用伪代 码表示算法:
也可以写成以下形式:
BEGIN{算法开始}? 1?t 2 ? i? while i≤5 {t×i ?t ? i+1 ? i?} print t? END{算法结束}

开始

置t的初值为1
置i的初值为2? 当i<=5,执行下面操作: 使t=t×i? 使i=i+1? {循环体到此结束}? 输出t的值? 结束

例2.17 输出50个学生中成绩高于80 分者的学号和成绩。? BEGIN{算法开始}? 用伪代码表示算法: 1 ? i?
while i≤50? {input and ? i+1 ? i} 1 ? i? while i≤50? {if ≥80 print and i+1 ? i} END{算法结束}

§2.4.6 用计算机语言表示算法 ? 概念:用计算机实现算法。计算机是无法 识别流程图和伪代码的。只有用计算机语 言编写的程序才能被计算机执行。因此在 用流程图或伪代码描述出一个算法后,还 要将它转换成计算机语言程序。 ? 特点:用计算机语言表示算法必须严格遵 循所用的语言的语法规则,这是和伪代码 不同的。 ? 用处:要完成一件工作,包括设计算法和 实现算法两个部分。设计算法的目的是为 了实现算法。

例 2.20 将例2.16表示 的算法(求5!)用 C语言表示。

#include <stdio.h> void main( ) {int i,t; t=1; i=2; while(i<=5) {t=t*I; i=i+1; } printf(“%d\n”,t); }

? 应当强调说明:写出了C程序,仍然 只是描述了算法,并未实现算法。只 有运行程序才是实现算法。应该说, 用计算机语言表示的算法是计算机能 够执行的算法。

§ 2.5

结构化程序设计方法

? 一个结构化程序 就是用高级语言表示的结构 化算法。用三种基本结构组成的程序必然是 结构化的程序,这种程序便于编写、便于阅 读、便于修改和维护。 ? 结构化程序设计强调程序设计风格和程序结 构的规范化,提倡清晰的结构。 ? 结构化程序设计方法的基本思路是:把一个 复杂问题的求解过程 分阶段进行,每个阶段 处理的问题都控制在人们容易理解和处理的 范围内。

§ 2.5

结构化程序设计方法

采取以下方法来保证得到结构化的程序:? ? 自顶向下; ? 逐步细化; ? 模块化设计; ? 结构化编码。? 两种不同的方法: ? 自顶向下,逐步细化; ? 自下而上,逐步积累。 ?

用这种方法逐步分解,直到作者认为可以直接将 各小段表达为文字语句为止。这种方法就叫 做?自顶 向下,逐步细化?。?

自顶向下,逐步细化方法的优点: 考虑周全,结构清晰,层次分明,作 者容易写,读者容易看。如果发现某一部 分中有一段内容不妥,需要修改,只需找 出该部分修改有关段落即可,与其它部分 无关。我们提倡用这种方法设计程序。这 就是用工程的方法设计程序。?

模块设计的方法: ?模块化设计的思想实际上是一种?分而治之? 的思想,把一个大任务分为若干个子任务, 每一个子任务就相对简单了。 ?在拿到一个程序模块以后,根据程序模块的 功能将它划分为若干个子模块,如果这些子 模块的规模还嫌大,还再可以划分为更小的 模块。这个过程采用自顶向下方法来实现。 ?子模块一般不超过50行 ?划分子模块时应注意模块的独立性,即:使 一个模块完成一项功能,耦合性愈少愈好。

第三章

?

本章要点
?

数据的描述规则

?

数据的操作规则

?

主要内容 3.1 C的数据类型 3.2 常量与变量

3.3 整型数据
3.4 浮点型数据运行

3.5 字符型数据

?

主要内容
3.6变量赋初值 3.7 各类数值型数据间的混合运算 3.8 算术运算符和算术表达式 3.9 赋值运算符和赋值表达式 3.10 逗号运算符和逗号表达式

§ 3.1

C的数据类型
整型 int char 单精度实型 float 双精度实型 double

C语言提供了以下一些数据类型。 基本类型
字符型

实型(浮点型)

数组类型

数据类型 构造类型 指针类型

结构类型 联合类型 枚举类型

struct union enum

空类型(无值类型) void

§3.2 常量与变量
3.2.1 常量和符号常量
? 在程序运行过程中,其值不能被改变的量称为常量 ? 常量区分为不同的类型:

整型 100,125,-100,0 实型 3.14 , 0.125,-3.789 字符型 ‘a?, ?b?,‘2? 字符串 ‘a?, ?ab?,‘1232?

运行结果: total=300 例3.1 符号常量的使用 #define PRICE 30? #include <stdio.h> void main ( )? {? int num, total;? num=10;? total=num * PRICE;? printf(″total=%d\n″,total);? ? 说明:如再用赋值语句给PRICE赋值是错误 说明:用一个标识符代表一个常量的,称为符 }? 说明: 程序中用#define命令行定义PRICE 的。? 号常量,即以标识符形式出现的常量。符号 ? 代表常量30,此后凡在本文件中出现的 PRICE=40;? /* 错误,不能给符 常量的值在其作用域(在本例中为主函数) PRICE都代表30,可以和常量一样进行运算 号常量赋值。 内不能改变,也不能再被赋值。

§3.2 常量与变量
3.2.2 变量 ? 变量代表内存中具有特定属性的一个存储单 元,它用来存放数据,这就是变量的值,在 程序运行期间,这些值是可以改变的。 ? 变量名实际上是一个以一个名字对应代表一 个地址,在对程序编译连接时由编译系统给 每一个变量名分配对应的内存地址。从变量 中取值,实际上是通过变量名找到相应的内 存地址,从该存储单元中读取数据。


§3.2 常量与变量
变量命名的规定:C语言规定标识符只能由 字母、数字和下划线三种字符组成,且第一 个字符必须为字母或下划线。 例:sum,_total, month, Student_name,
lotus_1_2_3,BASIC, li_ling M.D.John, ¥123,3D64,a>b

?

?

§3.2 常量与变量
注意:

? 编译系统将大写字母和小写字母认为是两 个不同的字符。 ? 建议变量名的长度最好不要超过8个字符。 ? 在选择变量名和其它标识符时,应注意做 到?见名知意?,即选有含意的英文单词 (或其缩写)作标识符。 ? 要求对所有用到的变量作强制定义,也就 是?先定义,后使用? 。

§3.3

整型数据

3.3.1整型常量的表示方法 整型常量即整常数。在C语言中,整常数 可用以下三种形式表示:
(1)十进制整数。 如:123, -456.4。 (2)八进制整数。以0头的数是八进制数。 如:0123表示八进制数123,等于十进制数 83,-011表示八进制数-11,即十进制数-9。

§3.3

整型数据

(3)十六进制整数。以0x开头的数是16进制数。 如:0x123,代表16进制数123,等于十进制数 291。 -0x12等于十进制数-10。

3.3.2 整型变量 (1)整型数据在内存中的存放形式
数据在内存中是以二进制形式存放的。

如: int i;
i=10;

/* 定义为整型变量 */ /* 给i赋以整数10 图 */

§3.3

整型数据

注意:
? 十进制数10的二进制形式为1010,Turbo C 2.0和Turbo C++ 3.0为一个整型变量在 内存中分配2个字节的存储单元(不同的编 译系统为整型数据分配的字节数是不相同 的,VC++ 6.0则分配4个字节)。 ? 数值是以补码(complement) 表示的。

§3.3

整型数据
有符号基本整型 有符号短整型 有符号长整型 无符号基本整型 无符号短整型 无符号长整型 (signed)int (signed)short (int ) (signed) long (int) unsigned int unsigned short (int) unsigned long (int)

(2)整型变量的分类:

共六种

注意:括号表示其中的内容是可选的.

§3.3
类型
基本型 短整型

整型数据
类型说明符
int short

? 整数类型的有关数据:
长度
2字节 2字节 2字节 4字节

数的范围
-32768~32767 -215~215-1 0~65535 0~65535 0~(232-1)

长整型
无符号整型

long
unsigned

4字节 -231~231-1

无符号短整型 unsigned short 2字节 无符号长整型 unsigned long

§3.3

整型数据

(3)整型变量的定义: C规定在程序中所有用到的变量都必须在 程序中定义,即?强制类型定义?。 例如: int a,b(指定变量a、b为整型) unsigned short c,d;(指定变量c、d为
无符号短整型)

long e,f;(指定变量e、f为长整型)

例3.2 整型变量的定义与使用 运行结果: a+u=22 #include <stdio.h> ,b+u=-14 void main() {int a,b,c,d; /*?指定a、b、c、d为整 型变量?*/ unsigned u; /*?指定u为无符号整型变量 ?*/ a=12;b=-24;u=10; c=a+u;d=b+u; 说明: 可以看到不同种类的整型数据可以 printf(″a+u=%d,b+u=%d\n″ ,c,d); 进行算术运算 }

例3.3 整型数据的溢出 #include <stdio.h> void main() {int a,b; a=32767; b=a+1; printf(“%d,%d\n”,a,b); 说明:数值是以补码表示的。一个整型变量 } 只能容纳-32768~32767范围内的数,无法
表示大于32767或小于-32768的数。遇此情 况就发生“溢出”。

运行结果: 32767,-32768

§3.3

整型数据

3.3.3 整型常量的类型 (1)一个整数,如果其值在-32768~+32767范 围内,认为它是int型,它可以赋值给int型 和long int型变量。 (2) 一个整数,如果其值超过了上述范围, 而在-2147483637~+2147483647范围内,则 认为它是为长整型。可以将它赋值给一个 long int型变量。

§3.3

整型数据

(3) 如果所用的C版本(如Turbo C)分配给 short int与int型数据在内存中占据的长度 相同,则它的表数范围与int型相同。因此 一个int型的常量同时也是一个short int型 常量,可以赋给int型或short int型变量。

§3.3

整型数据

(4) 一个整常量后面加一个字母u或U,认 为是unsigned int型,如12345u,在内存 中按unsigned int规定的方式存放(存储 单元中最高位不作为符号位,而用来存储 数据)。如果写成-12345u,则先将-12345 转换成其补码53191,然后按无符号数存 储。

§3.3

整型数据

(5) 在一个整常量后面加一个字母l或L,则 认为是long int型常量。例如123l.432L.0L 等。这往往用于函数调用中。如果函数的形 参为long int型,则要求实参也为long int 型。

§3.4 浮点型数据
3.4.1浮点型常量的表示方法 两种表 示形式

小数 0.123
指数 3e-3

注意:字母e(或E)之前必须有数字,且e后面的 指数必须为整数

?

?

1e3、1.8e-3、-123e-6、-.1e-3 e3、2.1e3.5、.e3、e

§3.4 浮点型数据
规范化的指数形式:
在字母e(或E)之前的小数部分中,小数点左边 应有一位(且只能有一位)非零的数字.

例如: 123.456可以表示为:
123.456e0, 12.3456e1, 1.23456e2, 0.123456e3, 0.0123456e4, 0.00123456e 其中的1.23456e3称为?规范化的指数形式 ?。

§3.4 浮点型数据
3.4.2 浮点型变量 (1)浮点型数据在内存中的存放形式 一个浮点型数据一般在内存中占4个字 节(32位)。与整型数据的存储方式不同, 浮点型数据是按照指数形式存储的。系统 把一个浮点型数据分成小数部分和指数部 分,分别存放。指数部分采用规范化的指 数形式。


§3.4 浮点型数据
(2) 浮点型变量的分类
浮点型变量分为单精度(float型)、双精度 (double型)和长双精度型(long double)三类 形式。
类型 float double型 long double 位数 32 64 128 数的范围 10-37 ~ 1038 10-307~10308 10-4931~104932 有效数字 6~7 位 15~16位 18~19位

例3.4 浮点型数据的舍入误差 #include <stdio.h> void main() {float a,b; a = 123456.789e5; b = a + 20 ; printf(“%f\n”,b); 说明:一个浮点型变量只能保证的有效数字是7位 } 有效数字,后面的数字是无意义的,并不准确地
表示该数。应当避免将一个很大的数和一个很小 的数直接相加或相减,否则就会“丢失”小的数

运行结果: 123456.789e5

§3.4 浮点型数据
3.4.3 浮点型常量的类型 C编译系统将浮点型常量作为双精度来处理。 例如:f = 2.45678 * 4523.65
系统先把2.45678和4523.65作为双精度数,然后 进行相乘的运算,得到的乘也是一个双精度数。最 后取其前7位赋给浮点型变量f。如是在数的后面加 字母f或F(如1.65f, 654.87F),这样编译系统就 会把它们按单精度(32位)处理。

§3.5字符型数据
3.5.1 字符常量
(1)用单引号包含的一个字符是字符型常量 (2)只能包含一个字符

?a?,?A?, ?1? ?abc?、“a”

?

§3.5字符型数据
有些以?\”开头的特殊字符称为转义字符

\n \t \r \\ \ddd \xhh

换行 横向跳格 回车 反斜杠 ddd表示1到3位八进制数字 hh表示1到2位十六进制数字

? 打印机上的显示结果: 显示屏上的运行结果:

例3.5 转义字符的使用 fab c gde gde f h jik #include <stdio.h> h j k void main() {printf(″ ab c\t de\rf\tg\n″); printf(″h\ti\b\bj k\n″); }

§3.5字符型数据
3.5.2字符变量
? 字符型变量用来存放字符常量,注意只能放 一个字符。 ? 字符变量的定义形式如下:char c1,c2; ? 在本函数中可以用下面语句对c1,c2赋值: c1=‘a?;c2= ‘b? ; ? 一个字符变量在内存中占一个字节。

§3.5字符型数据
3.5.3字符数据在内存中的存储形式及其使用方法 ? 将一个字符常量放到一个字符变量中,实际上并 不是把该字符本身放到内存单元中去,而是将该 字符的相应的ASCII代码放到存储单元中。 这样使字符型数据和整型数据 之间可以通用。一个字符数据 既可以以字符形式输出,也可 以以整数形式输出。 图

例3.6 向字符变量赋以整数。 a b #include <stdio.h> 97 98 void main() {char c1,c2; c1=97; c2=98; printf(“%c %c\n”,c1,c2); ? 说明:在第3和第4行中,将整数97和98分 printf(“%d %d\n”,c1,c2); 别赋给c1和c2,它的作用相当于以下两个赋值语 } 句:
c1=′a′;c2=′b′; 因为’a’和’b’的ASCII码为97和98

? 运行结果:

例3.7 大小写字母的转换 #include <stdio.h> void main() {char c1,c2; c1=’a’; c2=’b’; c1=c1-32; ? 说明:程序的作用是将两个小写字母a和b转换成 c2=c2-32; 大写字母A和B。从ASCII代码表中可以看到 printf(“%c %c″,c1,c2); 每一个小写字母比它相应的大写字母的ASCI I码大32。C语言允许字符数据与整数直接进 }
行算术运算。

? 运行结果:A B

§3.5字符型数据
说明:
有些系统(如Turbo C)将字符变量定义为signed char型。其存储单元中的最高位作为符号位,它的取 值范围是-128~127。如果在字符变量中存放一个 ASCII码为0~127间的字符,由于字节中最高位为0, 因此用%d输出字符变量时,输出的是一个正整数。如 果在字符变量中存放一个ASCII码为128~255间的字 符,由于在字节中最高位为1,用%d格式符输出时, 就会得到一个负整数。

§3.5字符型数据
3.5.4字符串常量
? 字符串常量是一对双撇号括起来的字符序列. ? 合法的字符串常量: ?How do you do.”, “CHINA”, ?a” , ?$123.45” ? 可以输出一个字符串,如 printf(“How do you do.”);

§3.5字符型数据
?a’是字符常量,?a?是字符串常量,二 者不 同。

如:假设C被指定为字符变量 :char c ? c=’a’;
结论:不能把一个字符串常量赋给一个字符 变量。

?

c=”a”;c=”CHINA”;

§3.5字符型数据
? C规定:在每一个字符串常量的结尾加一个 ?字 符 串结束标志?,以便系统据此判断字符串是否结束。 C规定以字符’\0’作为字符串结束标志。 ,实 如:如果有一个字符串常量”CHINA” 际上在内存中是:
C H I N A \0

它占内存单元不是5个字符,而是6个字符, 最后一个字符为’\0’。但在输出时不输出’ \0’。

§3.6变量赋初值
字符串常量 (1)C语言允许在定义变量的同时使变量初始化 如: int a=3; // 指定a为整型变量,初值为3
float f=3.56; // 指定f为浮点型变量,初值 为3.56 char c= ‘a’; // 指定c为字符变量,初值 为‘a’

§3.6变量赋初值
(2)可以使被定义的变量的一部分赋初值。

如: int a,b,c=5; 表示指定a、b、c为整型
变量,但只对c初始化,c的初值为5 (3)如果对几个变量赋以同一个初值, 应写成:int a=3,b=3,c=3; 表示a、b、c 的初值都是3。 不能写成∶ int a=b=c3; 注意:初始化不是在编译阶段完成的而是在程序运 行时执行本函数时赋初值的,相当于有一个赋值语 句。

§3.7各类数值型数据间的混合运算
整型(包括int,short,long)、浮点型(包括 float,double)可以混合运算。在进行运算时 ,不同类型的数据要先转换成同一类型,然后 进行运算. 上述的类型转换是由 系统自动进行的


§3.8

算术运算符和算术表达式

3.8.1 C运算符简介
C的运算符有以下几类: (1)算术运算符 (+ - * / %) (2)关系运算符 (><==>=<=!=) (3)逻辑运算符 (!&&||) (4)位运算符 (<< >> ~ |∧&) (5)赋值运算符 (=及其扩展赋值运算符) (6)条件运算符 (?:) (7)逗号运算符 (,)

§3.8

算术运算符和算术表达式

(8)指针运算符 (*和&) (9)求字节数运算符(sizeof) (10)强制类型转换运算符( (类型) ) (11)分量运算符(.->) (12)下标运算符([ ]) (13)其他 (如函数调用运算符())

§3.8
? ? ? ? ? + - * / %

算术运算符和算术表达式
(加法运算符,或正值运算符。如:3+5、+3) (减法运算符,或负值运算符。如:5-2、-3)
(乘法运算符。如:3*5)

3.8.2 算术运算符和算术表达式 (1)基本的算术运算符:

(除法运算符。如:5/3) (模运算符,或称求余运算符,%两侧均应为整型

数据,如:7%4的值为3)。

§3.8

算术运算符和算术表达式

(2) 算术表达式和运算符的优先级与结合性基 本的算术运算符:
用算术运算符和括号将运算对象(也称操作数) 连接起来的、符合C语法规则的式子,称为C算 术表达式。运算对象包括常量、变量、函数等。

例如: a*b/c-1.5+′a′
是一个合法的表达式

§3.2 常量与变量
? C语言规定了运算符的优先级和结合性。
在表达式求值时,先按运算符的优先级别高低
次序执行,例如先乘除后加减。

? C规定了各种运算符的结合方向(结合性)
算术运算符的结合方向为“自左至右”,即先 左后右 。

§3.8

算术运算符和算术表达式

(3)强制类型转换运算符
可以利用强制类型转换运算符将一个表达式转换成 所需类型。 一般形式:(类型名)(表达式) 例如: ? (double)a 将a转换成double类型 ? (int)(x+y) 将x+y的值转换成整型 ? (float)(5%3) 将5%3的值转换成float型

例3.8 强制类型转换。 ? 运行结果: x=3.600000, i=3 #include <stdio.h> voidmain() {float x; int i; x=3.6; i=(int)x; ? 说明:有两种类型转换,一种是在运算时不必用 printf("x=%f, i=%d\n",x,i); } 户指定,系统自动进行的类型转换,如3+6.5。
第二种是强制类型转换。当自动类型转换不能实 现目的时,可以用强制类型转换。

§3.8

算术运算符和算术表达式

(4) 自增、自减运算符
作用是使变量的值增1或减1

如:
? ? ++i,--i(在使用i之前,先使i的值加 (减)1) i++,i--(在使用i之后,使i的值加( 减)1)

§3.8

算术运算符和算术表达式

i++与++i的区别:
++i是先执行i=i+1后,再使用i的值; i++是先使用i的值后,再执行i=i+1。

例如:
①j=++i; i的值先变成4, 再赋给j,j的值均为4 ②j=i++; 先将 i的值3赋给j,j的值为3,然后i变为4

§3.8
注意:

算术运算符和算术表达式

(1)自增运算符(++),自减运算符(--), 只能用于变量,而不能用于常量或表达式, (2)++和--的结合方向是?自右至左?。 自增(减)运算符常用于循环语句中使循环变量 自动加1。也用于指针变量,使指针指向下一个地址

§3.8

算术运算符和算术表达式

(5) 有关表达式使用中的问题说明 ①ANSI C并没有具体规定表达式中的子表达式的求 值顺序,允许各编译系统自己安排。 a = f1( )+f2( ) 并不是所有的编译系统都先调用f1( ), 然后 调用f2( )。在有的情况下结果可能不同。有时会出 现一些令人容易搞混的问题,因此务必要小心谨慎。

例如:对表达式

§3.8

算术运算符和算术表达式

②C语言中有的运算符为一个字符,有的运算符由 两个字符组成 ,为避免误解,最好采取大家都能理 解的写法。

例如:不要写成i+++j的形式,而应写成
(i++)+j的形式

§3.8

算术运算符和算术表达式

③在调用函数时,实参数的求值顺序,C标准并无统 一规定。 例如:i的初值为3,如果有下面的函数调用: printf(″%d,%d″,i,i++) 在有的系统 中,从左至右求值,输出“3,3”。在多数系 统中对函数参数的求值顺序是自右而左,pri ntf函数输出的是“4,3”。以上这种写法 不宜提倡, 最好改写成 不要写出别人看不懂的也 j = i++; 不知道系统会怎样执行程 printf("%d, %d", 序 j,i)

§3.9 赋值运算符和赋值表达式
(1)赋值运算符
赋值符号“=”就是赋值运算符,它的作用是 将一个数据赋给一个变量。如“a=3”的作用 是执行一次赋值操作(或称赋值运算)。把常 量3赋给变量a。也可以将一个表达式的值赋 给一个变量.

§3.9 赋值运算符和赋值表达式
(2)类型转换 如果赋值运算符两侧的类型不一致,但都 是数值型或字符型时,在赋值时要进行类型 转换。
①将浮点型数据(包括单、双精度)赋给整 型变量时,舍弃浮点数的小数部分。

如:i为整型变量,执行?i=3.56”的结果是使
i的值为3,以整数形式存储在整型变量中.

§3.9 赋值运算符和赋值表达式
②将整型数据赋给单、双精度变量时,数值不变, 但以浮点数形式存储到变量中

如: 将23赋给float变量f,即执行f=23,先
将23转换成23.00000,再存储在f中。 将23赋给double型变量d,即执行d= 23,则将23补足有效位数字为23.00000 000000000,然后以双精度浮点数形式存储 到变量d中。

§3.9 赋值运算符和赋值表达式
③将一个double型数据赋给float变量时,截取其前 面7位有效数字,存放到float变量的存储单元(4个 字节)中。但应注意数值范围不能溢出。 如:float f;double d=123.456789e100; f=d; 就出现溢出的错误。 如果将一个float型数据赋给double变量时,数值不 变,有效位数扩展到16位,在内存中以8个字节存储

§3.9 赋值运算符和赋值表达式
④字符型数据赋给整型变量时,由于字符只占1个字 节,而整型变量为2个字节,因此将字符数据(8个 二进位)放到整型变量存储单元的低8位中。 第一种情况: 如果所用系统将字符处理为无符号的字符类型,或程 序已将字符变量定义为unsigned char 型,则将字符的8位放到整型变量低8位,高8位补 零 图 例如:将字符‘\376’赋给int型变量i

§3.9 赋值运算符和赋值表达式
第二种情况:

如果所用系统(如Turbo C++)将字符处理为带符号
的(即signed char),若字符最高位为0,则整型 变量高8位补0;若字符最高位为1,则高8位全补 1。这称为?符号扩展?,这样做的目的是使数值保 持 不变,如变量c(字符‘\376’)以整数形式输 出 图 为-2,i的值也是-2。

§3.9 赋值运算符和赋值表达式
⑤将一个int、short、long型数据赋给一个char型变 量时,只将其低8位原封不动地送到char型变量(即 截断)。

例如:int i=289;char c=′a′;c=i; 赋值情况 :
c的值为33, 如果用“%c”输 出c,将得到字符“!” (其 ASCII码为33)。



§3.9 赋值运算符和赋值表达式
⑥ 将带符号的整型数据(int型)赋给long型变 量时,要进行符号扩展,将整型数的16位送到 long型低16位中: 如果int型数据为正值(符号位为0),则long 型变量的高16位补0; 如果int型变量为负值(符号位为1),则long 型变量的高16位补1,以保持数值不改变。 反之,若将一个long型数据赋给一个int型变量 ,只将long型数据中低16位原封不动地送到整型 变量(即截断)。

? ?

§3.9 赋值运算符和赋值表达式
例如:int a; long b=8;a=b 赋值情况如图 :
如果b=65536(八进制 数0200000),则 赋值后a值为0。见图 3.14

图 图3.14

§3.9 赋值运算符和赋值表达式
⑦将unsigned int型数据赋给long int型变量时,不 存在符号扩展问题,只需将高位补0即可。将一个 unsigned类型数据赋给一个占字节数相同的非 unsigned型整型变量(例如:unsigned int ->int,unsigned long->long,unsigned short ->short),将unsigned型变量的内容原样送到非 unsigned型变量中,但如果数据范围超过相应整型的 范围,则会出现数据错误。

§3.9 赋值运算符和赋值表达式
例如:unsigned int a=65535; int b;b=a;
将a整个送到b中,由于b是int, 第1位是符号位,因此b成了 负数。根据补码知识可知,b的 值为-1,可以用printf(″%d″, b);来验证。 ⑧将非unsigned型数据赋给长度 相同的unsigned型变量,也 是原样照赋(连原有的符号 位也作为数值一起传送)。



例3.9 有符号数据传送给无符号变量。 65535 #include <stdio.h> void main() {unsigned a; int b=-1; a=b; printf(″%u\n″,a) ; ? 说明:“%u”是输出无符号数 } 时所用的格式符。如果b为正
值,且在0~32767之间,则赋
值后数值不变。赋值情况见图 图

?运行结果:

§3.9 赋值运算符和赋值表达式
(3) 复合的赋值运算符
在赋值符“=”之前加上其他运算符,可以构成复 以“a+=3”为例来说明, 它相当于使a进行一次自加 合的运算符。
(3)的操作。即先使a加 3,再赋给a。

例如:
? ? ?

a+=3 等价于 a=a+3 x*=y+8 等价于 x=x*(y+ 8) x%=3 等价于 x=x%3

§3.9 赋值运算符和赋值表达式
为便于记忆,可以这样理解: ① a += b (其中a为变量,b为表达式) ② a += b(将有下划线的?a+”移到?=”右侧 ) |___↑ ③ a = a + b (在?=”左侧补上变量名a)

§3.9 赋值运算符和赋值表达式
注意:如果b是包含若干项的表达式,则相当于它有
括号。
凡是二元(二目)运 算符,都可以与赋值 符一起组合成复合赋 值符。

如: ① x %= y+3 ② x %= (y+3) |__↑ ③ x = x %(y+3)(不要错写成x=x%y+3)
C语言规定可以使用10种复合赋值运算符: +=,-=,*=,/=,%=,<<=,>>=,&= ,∧=,|=

§3.9 赋值运算符和赋值表达式
(4) 赋值表达式 由赋值运算符将一个变量和一个表达式连接 起来的式子称为“赋值表达式”。 一般形式为: <变量><赋值运算符><表达式>

例如:

“a=5”是一个赋值表达式

§3.9 赋值运算符和赋值表达式
对赋值表达式求解的过程是: ①求赋值运算符右侧的?表达式?的值; ②赋给赋值运算符左侧的变量。 例如: 赋值表达式?a=3*5”的值为15,执行表 达式后,变量a的值也是15。 注意: 一个表达式应该有一个值

§3.9 赋值运算符和赋值表达式
左值 ? ? ? (lvalue) : 赋值运算符左侧的标识符 变量可以作为左值; 而表达式就不能作为左值(如a+b); 常变量也不能作为左值,

右值 (lvalue) :出现在赋值运算符右侧的表达式 左值也可以出现在赋值运算符右侧,因而左值 都可以作为右值。

§3.9 赋值运算符和赋值表达式
赋值表达式中的“表达式”,又可以是一个赋值表

达式.例如: a=(b=5)

分析:括弧内的?b=5”是一个赋值表达式,它
的值等于5。执行表达式?a=(b=5)”相当于执 行 ?b=5”和?a=b”两个赋值表达式。 赋值运算 符 按照?自右而左?的结合顺序,因此,?(b= 5)” 外面的括弧可以不要,即?a=(b=5)”和 ?a=b=5”

§3.9 赋值运算符和赋值表达式
请分析下面的赋值表达式∶(a=3*5)=4*3

分析:先执行括弧内的运算,将15赋给a,然后执行
4*3的运算,得12,再把12赋给a。最后a的值为12, 整个表达式的值为12。可以看到∶(a=3*5)出现在赋 不能写成: 值运算符的左侧,因此赋值表达式(a=3*5)是左值 a=3*5=4*3

注意:在对赋值表达式(a=3*5)求解后,变量a得到值
15执行(a=3*5)=4*3时,实际上是将4*3的积12赋给变 量a,而不是赋给3*5。

§3.9 赋值运算符和赋值表达式
赋值表达式也可以包含复合的赋值运算符。 如:a+=a-=a*a 分析:此赋值表达式的求解步骤如下∶
① 先进行?a-=a*a?的运算, 它相当 于a=a-a*a,a的值为12-144 =-132。 ②再进行?a+=-132?的运算,相当 于a=a+(-132),a的值为-132-132=264。

§3.9 赋值运算符和赋值表达式
将赋值表达式作为表达式的一种,使赋值操作不 仅可以出现在赋值语句中,而且可以以表达式形 式出现在其他语句(如输出语句、循环语句等) 中.

如:printf("%d",a=b);
分析:如果b的值为3, 则输出a的值(也是表
达式a=b的值)为3。在一个语句中完成 了赋值和输出双重功能。

§3.10 逗号运算符和逗号表达式
逗号表达式 的 逗号运算符:将两个表达式连接起来,又称为?顺序求 值为14 值运算符?

如:3+5,6+8
一般形式:

表达式1,表达式2

求解过程: 先求解表达式1,再求解表达式2。整个逗号 表达式的值是表达式2的值。

§3.10 逗号运算符和逗号表达式 a的值为15, 例:逗号表达式a=3*5,a*4然后求解a*4,
得60。整个 分析:赋值运算符的优先级别高于逗号运算符, 因 逗号表达式的 此应先求解a=3*5. 值为60。

一个逗号表达式又可以与另一个表达式组成一 个新的逗号表达式 如:(a=3*5,a*4) a+5先计算出a的值等于15,再进行a*4的 运算得60(但a值未变,仍为15),再进行a +5得20,即整个表达式的值为20。

§3.10 逗号运算符和逗号表达式
逗号表达式的一般形式可以扩展为
赋值表达式, 它的值为表达式n的值。 将一个逗号表 逗号运算符是所有运算符中级别最低的 达式的值赋给 x,x的值等 例: ① x=(a=3,6*3) 于18

表达式1,表达式2,表达式3,……,表达式n

② x=a=3,6*3

逗号表达式,包括 一个赋值表达式和 一个算术表达式, x的值为3,整个 逗号表达式的值为 18。

§3.10 逗号运算符和逗号表达式 “a,b,c”
并不是一个逗 注意:并不是任何地方出现的逗号都是作为逗 号表达式,它 是printf函数的3 号运算符。例如函数参数也是用逗号来间 个参数 隔的。

如:

printf(“%d,%d,%d”,a,b,c); printf(“%d,%d,%d”,(a,b,c),b,c) “(a,b,
c)”是一个 逗号表达式, 它的值等于c 的值。

第四章

?

主要内容
4.1 C语句概述

4.2 赋值语句
4.3 数据输入输出的概念及在c语言中的实现

4.4 字符数据的输入输出
4.5 格式输入与输出 4.6 顺序结构程序设计举例

§ 4.1

C语句概述
C程序

源程序文件1

源程序文件2

源程序文件n

预处理命令

数据声明

函数1

函数n

函数首部

函数体

数据声明

执行语句

§ 4.1

C语句概述(续)

? 一个c程序可以有若干个源程序文件组成 ? 一个源文件可以有若干个函数和预处理命令 以及全局变量声明部分组成 ? 一个函数有函数首部和函数体组成 ? 函数体由数据声明和执行语句组成 控制语句 ? C语句分为 5类 函数调用语句
表达式语句 空语句 复合语句

§ 4.1

C语句概述(续)
完成一定的控制功能
6 break 间断语句 7 switch() 开关语句 8 goto 转向语句 9 return 返回语句

(一)控制语句
1 if() ~else 条件语句 2 for()~ 循环语句 3 while()~循环语句 4 do ~while();循环语句 5 continue 继续语句

§ 4.1

C语句概述(续)

(二)函数调用语句 有一个函数调用加一个分号构成一个语句

Printf(“This is a C statement.”);

§ 4.1

C语句概述(续)

(三)表达式语句 有一个表达式加一个分号构成一个语句

赋值表达式

分号

表达式语句

a = 3 ;

§ 4.1

C语句概述(续)
只有一个分号的语句

(四)空语句

(什么也不做)

? 用来做流程的转向点 ? 用来作为循环语句中的循环体

§ 4.1

C语句概述(续)
用一对{}括起来的语句

(五)复合语句

{

z=x+y; t=z/100; printf(“%f”,t);

}

§ 4.2

赋值语句

?赋值语句是由赋值表达式加上一个分号构成 例:a=100 赋值表达式 a=100; 赋值语句

?条件中不能含有赋值符号,但是赋值表达式可以 包含于条件表达式中 例:if(a=b) t=a; 错误 if((a=b)>0) t=a; 正确

§ 4.2

赋值语句(续)

问题:c语言中的赋值语句于其他高级语言 的赋值语句有什么不同点? 1:C语言中的赋值号“=”是一个运算符, 在其他大多数语言中赋值号不是运算符. 2:其他大多数高级语言没有“赋值表达式 ”这一概念.

§ 4.3

数据输入输出的概念及在C语言中的实现

(一).所谓输入输出是以计算机主机为主体而言的
输出:从计算机向外部输出设备(显示器,打印机) 输出数据 输入:从输入设备(键盘,鼠标,扫描仪)向计算机 输入数据.

§ 4.3

数据输入输出的概念及在C语言中的实现

(二).C语言本身不提供输入输出语句,输入和输出 操作是由C函数库中的函数来实现的 例如: 字符输入函数: getchar 字符输出函数:putchar 格式输入函数: scanf 格式输出函数: printf 字符串输入函数:gets 字数穿输出函数:puts

§ 4.3

数据输入输出的概念及在C语言中的实现

(三).在使用系统库函数时,要用预编译命令 ?#include”将有关的?头文件?包括到用户源 文件中. 例如:在调用标准输入输出库函数时,文件开头应 头文 该有: 件 #include “stdio.h” 或: #include <stdio.h>

§ 4.4

字符数据的输入输出

(一).字符输出函数 ? 一般形式:putchar(c)
字符型变量 整型变量

? 函数作用:向终端输出一个字符

§ 4.4

字符数据的输入输出(续)

例4.1 输出单个字符。 #include<stdio.h> 运行结果:B void main() O { Y char a,b,c; a=‘B’;b=‘O’;c=‘Y’; putchar(a);putchar(b);putchar(c);putchar(‘\n’); putchar(a);putchar(?\n?);putchar(b);putchar(?\n?);putchar(c);putchar(?\n?); }
运行结果:BOY

§ 4.4

字符数据的输入输出(续)

(二).字符输入函数
? 一般形式:getchar() ? 函数作用:从终端(或系统隐含指定的输入设备 )输入一个字符。 ? 函数值: 从输入设备得到的字符。

§ 4.4

字符数据的输入输出(续)
运行程序:
从键盘输入字符‘a’ 按Enter键 屏幕上将显示输出的字符‘a’
a a

例4.2 输入单个字符。 #include<stdio.h> void main() { char c; c=getchar(); putchar(c); putchar(‘\n’); }

§ 4.5

格式输入与输出

(一).格式输出函数 ? 函数作用:向终端(或系统隐含指定的输出设备 )输出若干个任意类型的数据。 ? 一般格式:printf(格式控制,输出表列)
%d:以带符号的十进制形式输出整数 %o:以八进制无符号形式输出整数 %x:以十六进制无符号形式输出整数 To be continued……

§ 4.5

格式输入与输出(续)

%u:以无符号十进制形式输出整数 %c:以字符形式输出,只输出一个字符 %s:输出字符串 %f:以小数形式输出单,双精度数,隐含输出六位小数 %e:以指数形式输出实数 %g:选用%f或%e格式中输出宽度较短的一种格式,不输 出无意义的0

§ 4.5

格式输入与输出(续)

几种常见的格式符的修饰符:

L:用于长整型整数,可加在格式符d,o,x,u前面 M(代表一个正整数):数据最小宽度 N(代表一个正整数):对实数,表示输出n位小数; 对字符串,表示截取的字符个数 —:输出的数字或字符在域内向左靠

§ 4.5

格式输入与输出(续)

(1) d格式符。用来输出十进制整数。
几种用法: ① %d:按十进制整型数据的实际长度输出。 ② %md:m为指定的输出字段的宽度。如果数据的位数小于m, 则左端补以空格,若大于m,则按实际位数输出。 例: printf(″%4d,%4d″,a,b); 若a=123,d=12345,则输出结果为 123,12345 ③ %ld:输出长整型数据。 例: long a=135790;/* 定义a为长整型变量*/ printf(″%ld″,a);

§ 4.5

格式输入与输出(续)

(2) o格式符。以八进制整数形式输出。
输出的数值不带符号,符号位也一起作为八进制数的一部分输出。 例:int a=-1; printf("%d,%o",a,a); -1在内存单元中的存放形式(以补码形式存放)如下: 1111111111111111 输出为: -1,177777 不会输出带负号的八进制整数。对长整数(long型)可以 用?%lo?格式输出。还可以指定字段宽度, 例:printf("%8o",a); 输出为: 177777。 (数字前有2个空格)

§ 4.5

格式输入与输出(续)

(3)x格式符。以十六进制数形式输出整数。同样不会出 现负的十六进制数。 例: int a=-1;
printf(″%x,%o,%d″,a,a,a); 输出结果为: ffff,177777,-1 可以用?%lx?输出长整型数,也可以指定输出字段的宽度 例: “%12x?

§ 4.5

格式输入与输出(续)

(4)u格式符,用来输出unsigned型数据.
一个有符号整数(int型)也可以用%u格式输出; 一个unsigned型数据也可以用%d格式输出。 unsigned型数据也可用%o或%x格式输出。

(5)c格式符,用来输出一个字符。
如:char d=′a′; printf(″%c″,d); 输出字符′a′. 一个整数,只要它的值在0~255范围内,可以用 ?%c?使之按字符形式输出,在输出前,系统会将该整数 作为ASCII码转换成相应的字符;一个字符数据也可以用 整数形式输出。

§ 4.5

格式输入与输出(续)

例4.3 无符号数据的输出。 #include<stdio.h> void main() { unsigned int a=65535;int b=-2; printf(“a=%d,%o,%x,%u\n”,a,a,a,a); printf(“b=%d,%o,%x,%u\n”,b,b,b,b); } 运行结果: a=-1,177777,ffff,65535 b=-2,177776,fffe,65534

§ 4.5

格式输入与输出(续)

例4.4 字符数据的输出。 运行结果: #include<stdio.h> a,97 指定输出字数的宽度, void main() a,97 printf(“%3c”,c); { 则输出: char c=‘a’; a int i=97; printf(“%c,%d\n”,c,c); printf(“%c,%d\n”,i,i); }

§ 4.5
(6)s格式符

格式输入与输出(续)
输出字符串.

① %s。例如: printf(″%s″,″CHINA″) 输出字符串?CHINA?(不包括双引号)。 ② %ms,输出的字符串占m列,若串长大于m,则全部输出,若串长 小于m,则左补空格。 ③ %-ms,若串长小于m,字符串向左靠,右补空格。 ④ %m. ns,输出占m列,只取字符串中左端n个字符,输出在m列的 右侧,左补空格。 ⑤ %-m.ns,n个字符输出在m列的左侧,右补空格,若n〉m,m自 动取n值。

§ 4.5

格式输入与输出(续)

例4.5字符串的输出。 #include<stdio.h> void main() { printf(“%3s,%7.2s,%.4s,%-5.3s\n”, “CHINA”, “CHINA”, “CHINA”, “CHINA”); }
运行结果: CHINA, CH ,CHIN,CHI

§ 4.5

格式输入与输出(续)

(7)f格式符。用来以小数形式输出实数(包括单双精度)
有以下几种用法: ① %f。不指定字段宽度,由系统自动指定字段宽度,使整数 部分全部输出,并输出6位小数。应当注意,在输出的数字中 并非全部数字都是有效数字。单精度实数的有效位数一般为7位。 ②%m.nf。指定输出的数据共占m列,其中有n位小数。如果 数值长度小于m,则左端补空格。 ③%-m.nf与%m.nf基本相同,只是使输出的数值向左端 靠,右端补空格。

§ 4.5

格式输入与输出(续)

例4.6 输出实数时的有效位数。
#include <stdio.h> void main() { float x,y; x=111111.111;y=222222.222; printf(″%f″,x+y); }

运行结果:
333333.328125

§ 4.5

格式输入与输出(续)

例4.7输出双精度数时的有效位数。
#include <stdio.h> void main() {double x,y; x=1111111111111.111111111; y=2222222222222.222222222; printf(“%f”,x+y); }

运行结果:
3333333333333.333010

§ 4.5

格式输入与输出(续)

例4.8 输出实数时指定小数位数。
#include <stdio.h> void main() { float f=123.456; printf(“%f%10f%10.2f%.2f%-10.2f\n”,f,f,f,f,f); }

运行结果:
123.455994 123.455994 123.46 123.46 123.46

§ 4.5

格式输入与输出(续)

(8)e格式符,以指数形式输出实数。
可用以下形式: ① %e。不指定输出数据所占的宽度和数字部分的小数位数. 例: printf(″%e″,123.456); 输出: 1.234560 e+002 6列 5列 所输出的实数共占13列宽度。(注:不同系统的规定略有不同)

§ 4.5

格式输入与输出(续)

② %m.ne和%-m.ne。 m、n和?-?字符的含义与前相同。 此处n指拟输出的数据的小数部分(又称尾数)的小数位数。 若f=123.456,则: printf("%e %10e %10.2e %.2e %-10.2e",f,f,f,f,f); 输出如下: 说明: 1.234560e+002 1.234560e+002 1.23e+002 1.23e+002 未指定n,自动使n=6. 13列 13列 10列 9列 超过给定的10列,乃突破10列的限制,按实际长度输出。 1.23e+002 第3个数据共占10列,小数部分占2列。 10列 只指定n=2,未指定m,自动使m等于数据应占的长度。 第5个数据应占10列,数值只有9列,由于是?%-10.2e? 数值向左靠,右补一个空格。 (注:有的C系统的输出格式与此略有不同)

§ 4.5

格式输入与输出(续)

(9)g格式符,用来输出实数.
它根据数值的大小,自动选f格式或e格式(选择输出时占宽度 较小的一种),且不输出无意义的零。 例:若f=123.468,则 printf(″%f %e %g″,f,f,f); 输出如下: 说明: 123.468000 1.234680e+002 123.468 用%f格式输出占10列,用%e格式输出占13列,用%g 10列 13列 10列 格式时,自动从上面两种格式中选择短者(今以%f格式为短) 故占10列,并按%f格式用小数形式输出,最后3个小数位为 无意义的0,不输出,因此输出123.468,然后右补3个空格。 %g格式用得较少。

§ 4.5
说明:

格式输入与输出(续)

? 除了X,E,G外,其他各式字符必须用小写。 ? 可以在printf函数中的“格式控制”字符串中包含 转义字符。 ? 一个格式说明必须以“%”开头,以9个格式字符之 一为结束,中间可以插入附加格式字符。 ? 想输出%,则应该在格式控制字符串中用连续两 个%表示

§ 4.5

格式输入与输出(续)

(一).格式输入函数 ? 函数作用:按照变量在内存的地址将变量值存 进去。 ? 一般格式:scanf(格式控制,地址表列)
同printf函数 是由若干个地址组成的表列,可以是变量的 地址,或字符串的首地址

§ 4.5

格式输入与输出(续)

例4.9 用scanf函数输入数据。 #include<stdio.h> a在内存中的地 void main() 址 { &是地址运算符 int a,b,c; scanf(“%d%d%d”,&a,&b,&c); printf(“%d,%d,%d\n”,a,b,c); } 运行情况: 3 4 5 (输入a,b,c的值) 3,4,5 (输出a,b,c的值)

§ 4.5

格式输入与输出(续)

说明:
? 对unsigned型变量所需要的数据,可以用%u,%d 或%o,%x格式输入。 ? 可以指定输入数据所占的列数,系统自动按它截 取所需数据。 ? 如果在%后有一个“*”附加说明符,表示跳过它 指定的列数。 ? 输入数据时不能规定精度。

§ 4.5

格式输入与输出(续)

使用scanf函数时应注意的问题 :

(1)scanf函数中的?格式控制?后面应当是变量地址,而不应 是变量名。 (2) 如果在?格式控制?字符串中除了格式说明以外还有其他字符, 则在输入数据时在对应位臵应输入与这些字符相同的字符。 (3) 在用?%c?格式输入字符时,空格字符和?转义字符?都作为 有效字符输入 (4) 在输入数据时,遇以下情况时认为该数据结束。 ① 遇空格,或按?回车?或?跳格?(Tab)键; ② 按指定的宽度结束,如?%3d?,只取3列; ③ 遇非法输入。

§ 4.6 顺序结构程序设计举例
例4.10 输入三角形的三边 长,求三角形面积。 假设:三个边长a,b,c能构 成三角形。 已知面积公式: area= s(s ? a)(s ? b)(s ? c) s=(a+b+c)*0.5
开始
输入三边长 计算s 计算面积 结束

§ 4.6 顺序结构程序设计举例(续)
#include<stdio.h> 数学函数库 #include<math.h> 因为要用到 void main() 其中的sqrt {float a,b,c,s,area; 函数 scanf(“%f,%f,%f,&a,&b,&c); s=1.0/2*(a+b+c); area=sqrt(s*(s-a)*(s-b)*(s-c)); printf(“a=%7.2f, b=%7.2f, c=%7.2f, 运行情况: s=%7.2f\n”,a,b,c,s); 3,4,6 printf(“area=%7.2f\n”,area);} s= 6.50 a= 3.00, b= 4.00, c= 6.00,
area= 5.33

§ 4.6 顺序结构程序设计举例(续)
例4.11 从键盘输入一个大写字母,要求改用小写字母输 出。 运行情况:
#include <stdio.h> A↙? void main() A,65 { char cl,c2; cl=getchar(); a,97 printf(″%c,%d\n″,cl,cl); c2=cl+32; printf(″%c,%d\n″,c2,c2); }

§ 4.6 顺序结构程序设计举例(续)
例4.12 求ax2+bx+c=0方程的根。
a,b,c由键盘输入,设 b 2 ? 4ac >0。 众所周知,一元二次方程式的根为
? b ? b 2 ? 4ac x1= ? b ? b ? 4ac x2= 2a 2a
2

可以将上面的分式分为两项:

?b p= , q= 2a

b 2 ? 4ac 2a

x1=p+q,

x2=p-q

§ 4.6 顺序结构程序设计举例(续)
#include <stdio.h> #include <math.h> void main ( ) { float a,b,c,disc,x1,x2,p,q; scanf("a=%f,b=%f,c=%f",&a,&b,&c); disc=b*b-4*a*c; p=-b/(2*a); 运行情况: q=sqrt(disc)/(2*a); a=1,b=3,c=2↙? x1=p+q;x2=p-q; x1=-1.00 printf("\n\nx1=%5.2f\nx2=%5.2f\n",x1,x2); x2=-2.00 }

第五章

?

本章要点
?
?

关系表达式 逻辑表达式

?

选择结构程序设计

?

主要内容
5.1 关系运算符和关系表达式

5.2 逻辑运算符和逻辑表达式
5.3 if语句

5.4 switch语句
5.5 程序举例

§5.1 关系运算符和关系表达式
(一).关系运算符及其优先次序 1. < (小于) 2. <= (小于或等于) 优先级相同(高) 3. > (大于) 4. >= (大于或等于) 5. == (等于) 优先级相同(低) 6. 说明: != (不等于) 关系运算符的优先级低于算术运算符
关系运算符的优先级高于赋值运算符

§5.1 关系运算符和关系表达式(续)
(二).关系表达式 ? 用关系运算符将两个表达式(可以是算术表达式或 关系表达式,逻辑表达式,赋值表达式,字符表达式) C语言中没有专用 接起来的式子,称关系表达式
的逻辑值,1代表 真,0代表假 例:a>b,a+b>b+c,(a=3)>(b=5),?a?<?b?,(a>b)>(b<c)

? 关系表达式的值是一个逻辑值,即?真?或?假?。 例:关系表达式?a>b”的值为?真?,表达式的值为1。

§5.2 逻辑运算符和逻辑表达式
(一).逻辑运算符及其优先次序 1. && (逻辑与) 相当于其他语言中的AND 2. || (逻辑或) 相当于其他语言中的OR 3. ! (逻辑非) 相当于其他语言中的NOT 例:a&&b 若a,b为真,则a&&b为真。 a||b 若a,b之一为真,则a||b为真。 !a 若a为真,则!a为假。 优先次序:
?!(非)->&&()->||() ?逻辑运算符中的?&&”和?||”低于关系运算符,?!”高于算 术运算符

§5.2 逻辑运算符和逻辑表达式(续)
(二)逻辑表达式 ?用逻辑运算符将关系表达式或逻辑量连接起来的式子就 是逻辑表达式 ?逻辑表达式的值应该是一个逻辑量?真?或?假?。
任何非零的数值被认作?真?

例:设a=4,b=5: !a的值为0 a||b的值为1 4&&0||2的值为1

a&&b的值为1 !a||b的值为1

§5.2 逻辑运算符和逻辑表达式(续)
例:5>3&&8<4-!0
自左向右运算
5>3逻辑值为1 !0逻辑值为1 4-1值为3

表达式值为0

1&&0逻辑值为0

8<3逻辑值为0

§5.2 逻辑运算符和逻辑表达式(续)

在逻辑表达式的求解中,并不是所有的逻辑运算符都要被执行。 (1)a&&b&&c 只有a为真时,才需要判断b的值,只有a和b都为真时, 才需要判断c的值。 (2)a||b||c 只要a为真,就不必判断b和c的值,只有a为假,才 判断b。a和b都为假才判断c

例:(m=a>b)&&(n=c>d) 当a=1,b=2,c=3,d=4,m和n的原值为1时,由于?a>b”的值 为0,因此m=0,而?n=c>d”不被执行,因此n的值不是0而 仍保持原值1。

§5.2 逻辑运算符和逻辑表达式(续)
用逻辑表达式来表示闰年的条件
?能被4整除,但不能被100整除。 ?能被4整除,又能被400整除

答 案

(year%4==0&&year%100!=0)||year%400==0

值为真(1)是闰年,否则为非闰年。

§5.3 if语句
一.If语句的三种基本形式
(1)if (表达式) 语句 ch=getchar(); if(ch>=?a?&&ch<=?z?) ch=ch-?a?+?A?; putchar();

表达式 真(非0)
语句

假 (0)

ch=getchar(); if(ch>=?a?&&ch<=?z?) {ch=ch-?a?+?A?; putchar();}

§5.3 if语句(续)
(2)if(表达式) else 语句1 语句2

非0 条件 语句1

0 语句2

例:if(x>y) printf(“%d”,x); else printf(“%d”,y);

§5.3 if语句(续)
(3)if(表达式1)语句1 else if(表达式2)语句2 else if(表达式3)语句3 …… else if(表达式m)语句m else 语句n

§5.3 if语句(续)
例:
if (number>500)cost=0.15; else if(number>300)cost=0.10; else if(number>100)cost=0.075; else if(number>50)cost=0.05; else cost=0;

§5.3 if语句(续)
说明:
(1).3种形式的if语句中在if后面都有表达式, 一般为逻辑表达式或关系表达式。 (2).第二,第三种形式的if语句中,在每个 else前面有一个分号,整个语句结束处有一 个分号。 (3).在if和else后面可以只含有一个内嵌的操 作语句,也可以有多个操作语句,此时用花 括号将几个语句括起来成为一个复合语句。

§5.3 if语句(续)
例5.1 输入两个实数,按代数值由小到大的顺序输出这两 个数。 #include<stdio.h> a>b void main() {float a,b,t; y scanf(“%f,%f”,&a,&b); n T=a if(a>b) A=b {t=a; B=t a=b; b=t;} printf(“%5.2f,%5.2f\n”,a,b);}

§5.3 if语句(续)
例5.2 输入三个数a,b,c,要求按由小到大的顺序输出。
a>b n a>c y y n

If a>b If a>c If b>c

将a和b对换 将a和c对换 将b和c对换

y

b>c

a和b交换

a和c交换

c和b交换

§5.3 if语句(续)
#include <stdio.h> void main ( ) { float a,b,c,t; scanf(?%f,%f,%f?,&a,&b,&c); if(a>b) {t=a;a=b;b=t;} if(a>c) {t=a;a=c;c=t;} if(b>c) {t=b;b=c;c=t;} printf("%5.2f,%5.2f,%5.2f\n",a,b,c); }

§5.3 if语句(续)
二.If语句的嵌套
在if语句中又包含一个或多个if语句称为if语句的嵌套。 形式: If() if() 语句1 else 语句2 内嵌if Else if() 语句3 else 语句4

§5.3 if语句(续)
匹配规则: Else总是与它上面的,最近的,同一复合语句中的,未配 对的if语句配对。

例:
If() if() 语句1

例:
If() {if() 语句1}

else
if() 语句2 else 语句3

else
if() 语句2 else 语句3

当if和else数目不同时,可以加花括号来确定配对关系。

§5.3 if语句(续)
?-1 (x<0) 例 5.3 有一个函数 y= ? 0 (x=0),编一程序,输入一个x 值,输出y值。 ? 1 (x>0)
算法1: 输入x 若x<0,则y=-1 若x=0,则y=0 若x>0,则y=1 输出y 算法1: 输入x 若x<0,则y=-1 否则: 若x=0,则y=0 若x>0,则y=1 输出y

§5.3 if语句(续)
#include<stdio.h> void main() { int x,y; scanf(“%d”,&x); { 程序段 } printf(“x=%d,y=%d\n”,x,y); }

? -1 (x<0) y= ? 0 (x=0) §5.3 if语句(续) ? 1 (x>0) 上例中的程序段有四个,请判断哪个是正确的?
程序1: if(x<0) 正 Y=-1; 确 else if(x==0) y=0; else y=1; 程序3: y=-1; if(x!=0) If(x>0) y=1; else y=0; 程序2:

if(x>=0) if(x>0) y=1; 正 else y=0; 确 else y=-1;
程序4: y=0; if(x>=0) if(x>0) y=1; else y=-1;

§5.3 if语句(续)
三.条件运算符
?格式: ?功能:

表达式1?表达式2∶表达式3 判断表达式1的值,如果成立就执行表 达式2,否则就执行表达式3 ?使用场合:若在if语句中,当被判别的表达式的 值为?真?或?假? 时,都执行一个赋值 句且向同一个变量赋值时,可以用一个 条件运算符来处理。

§5.3 if语句(续)
例:
if (a>b) max=a; else max=b; 当a>b时将a的值赋给max,当a≤b时将b的值赋给max, 可以看到无论a>b是否满足,都是向同一个变量赋值。 可以用下面的条件运算符来处理:

max=(a>b)?a∶b;

§5.3 if语句(续)
说明:
(1).条件运算符的执行顺序:先求解表达式1,若为非0(真) 则求解表达式2,此时表达式2的值就作为整个条件表达式 的值。若表达式1的值为0(假),则求解表达式3,表达 式3的值就是整个条件表达式的值。 (2).条件运算符优先级高于赋值运算符 ,低于关系运算符 和算术运算符。 (3).条件运算符的结合方向为?自右至左?。 (4).“表达式2”和?表达式3”不仅可以是数值表达式,还可以 是赋值表达式或函数表达式。 (5).条件表达式中,表达式1的类型可以与表达式2和表达 式3的类型不同。

§5.3 if语句(续)
例5.4输入一个字符,判别它是否大写字母,如果是,将 它转换成小写字母;如果不是,不转换。然后输出最后 得到的字符。

#include <stdio.h> void main ( ) { char ch; scanf("%c",& ch); ch=(ch>='A'&& ch<='Z')?(ch+32):ch; printf("%c\n",ch); }

如果字符变量ch的值为大写字母, 则条件表达式的值为(ch+3 2),即相应的小写字母。如果ch 的值不是大写字母,则条件表达式 的值为ch,即不进行转换。

§5.4 switch语句
switch语句的格式:
switch(表达式) {case 常量表达式1:语句1 case 常量表达式2:语句2 … case 常量表达式n:语句n default :语句n+1 }

§5.4 switch语句(续)
例:
要求按照考试成绩的等级输出百分制分数段,用 switch语句实现:
switch(grade) {case ′A′∶printf(″85~100\n″); case ′B′∶printf(″70~84\n″); case ′C′∶printf(″60~69\n″); case ′D′∶printf(″<60\n″); default∶printf(″error\n″); }

§5.4 switch语句(续)
说明:
(1)switch后面括弧内的?表达式?, ANSI标准允许 它为任何类型。 (2) 当表达式的值与某一个case后面的常量表达式的值相 等时,就执行此case后面的语句,若所有的case 中的常量表达式的值都没有与表达式的值匹配的,就执行 default后面的语句。 (3) 每一个case的常量表达式的值必须互不相同,否则就 会出现互相矛盾的现象(对表达式的同一个值,有两种或 多种执行方案)。

§5.4 switch语句(续)

(4) 各个case和default的出现次序不影响执行结 果。例如,可以先出现?default:…”,再出现 ?case ′D′:…”,然后是?case ′A′:…”。 (5) 执行完一个case后面的语句后,流程控制转移到下一 个case继续执行。?case常量表达式?只是起语句 标号作用,并不是在该处进行条件判断。在执行 switch语句时,根据switch后面表达式的值 找到匹配的入口标号,就从此标号开始执行下去,不再进 行判断。应该在执行一个case分支后,可以用一个 break语句来终止switch语句的执行。 (6) 多个case可以共用一组执行语句。

§5.5 程序举例
例5.5 写程序,判断某一年是否闰年。

§5.5 程序举例(续)
#include <stdio.h> void main() {int year, leap; scanf("%d",&year); if (year%4==0) if(year%4!=0) leap=0; {if (year%100==0) else if(year%100!=0) {if (year%400==0) leap=1; leap=1; else leap=0;} else if(year%400!=0) else leap=1;} leap=0; 运行情况: else leap=0; else leap=1; 1989↙? 1989 is not a leap if (leap) printf("%d is ",year); year. 2000↙? else printf("%d is not ",year); 2000 is a leap year. printf("a leap year.\n");}

§5.5 程序举例(续)
例5.7 运输公司对用户计算运费。
路程(s)越远,每公里运费越低。标准如下: s<250km 没有折扣 250≤s<500 2%折扣 500≤s<1000 5%折扣 1000≤s<2000 8%折扣 2000≤s<3000 10%折扣 3000≤s 15%折扣 设每公里每吨货物的基本运费为p,货物重为w,距离为s, 折扣为d,则总运费f的计算公式为: f=p*w*s*(1-d)

§5.5 程序举例(续)
分析折扣变化的规律性:
折扣的?变化点?都是250的倍数 在横轴上加一种坐标c,c的值为s/250。c代表250的倍数。

c<1,无折扣; 1≤c<2,折扣d=2%; 2≤c<4,d=5%; 4≤c<8,d=8%; 8≤c<12,d=10%; c≥12,d=15%。

#include <stdio.h> void main ( ) §5.5 程序举例(续) {int c,s; float p,w,d,f; scanf("%f,%f,%d",&p,&w,&s); if(s>=3000) c=12; else c=s/250; switch(c){ case 0:d=0;break; case 1:d=2;break; case 2:case 3:d=5;break; case 4:case 5:case 6:case 7:d=8;break; case 8:case 9:case 10: case 11:d=10;break; case 12:d=15;break; } f=p*w*s*(1-d/100.0); printf("freight=%15.4f\n",f);}

第六章

?

本章要点
?

循环的基本概念

?
?

不同形式的循环控制
多重循环问题

?

主要内容
6.1 概述 6.2 goto语句以及用goto语句构成循环 6.3 用while语句实现循环 6.4 用do-while语句实现循环 6.5 用for 语句实现循环 6.6 循环的嵌套 6.7 几种循环的比较 6.8 break语句continue和语句 6.9 程 序 举 例

§6.1 概述
问题1: ? ? n y
n ?1 100

什么是循环? 为什么要使用循环?

问题2:求学生平均成绩

分数相加后除以课数

在许多问题中需要用到循环控制。循环结构 是结构化程序设计的基本结构之一,它和顺序 结构、选择结构共同作为各种复杂程序的基本 构造单元。

§6.2

goto语句以及用goto语句构成循环1

? goto语句为无条件转向语句,它的一般形式 为 goto 语句标号; ? 语句标号用标识符表示,它的定名规则与变 量名相同,即由字母、数字和下划线组成, 其第一个字符必须为字母或下划线。 例如:goto label_1; 合法; goto 123; 不合法.

§6.2

goto语句以及用goto语句构成循环

结构化程序设计方法主张限制使用goto语句,因 为滥用goto语句将使程序流程无规律、可读性差. 一般来说,可以有两种用途:

(1) 与if语句一起构成循环结构; (2) 从循环体中跳转到循环体外。
但是这种用法不符合结构化原则,一般不宜采 用,只有在不得已时(例如能大大提高效率)才使 用.

?n
n ?1

100

运行结果:5050

例6.1 用if语句和goto语句构成循环,求1到100的和 void main( ) { int i, sum=0; i=1; loop: if(i<=100) { sum=sum+i; i++; goto loop; } 说明:这里用的是“当型”循环 printf("%d\\n″,sum); 结构,当满足“i<=100” 时 } 执行花括弧内的循环体。

§6.3 用while语句实现循环 while语句用来实现?当型?循环结构。
一般形式:

while (表达式) 语句 当表达式为非0值时,执行while语 句中的内嵌语句。其特点是:先判断表 达式,后执行语句。



?n
n ?1

100

例6.2 求1到100的和 #include <stdio.h> void main() {int i,sum=0; i=1; while (i<=100) { sum=sum+i; i++; 说明:(1)循环体如果包含一个以上 } 的语句,应该用花括弧括起来,以 printf(″%d\\n″,sum); } 复合语句形式出现.(2)在循环体中 应有使循环趋向于结束的语句


运行结果:5050

§6.3

用while语句实现循环

注意:
(1) 循环体如果包含一个以上的语句,应该用 花括弧括起来,以复合语句形式出现。 (2) 在循环体中应有使循环趋向于结束的语句 。如果无此语句,则i的值始终不改变, 循环永不结束。

§6.4 用do-while语句实现循环
do-while语句的特点:先执行循环体,然后判断循环条件 是否成立。 一般形式:

do 循环体语句 while (表达式);

执行过程:先执行一次指定的循环体语句,然后判 别表达式,当表达式的值为非零(“真?) 时,返 回重新执行循环体语句,如此反复,直到表达式 的值等于0为止,此时循环结束。



?n
n ?1

100

运行结果:5050 例6.3 求1到100的和 #include <stdio.h> void main() { int i,sum=0; i=1; do {sum=sum+i; i++; } while(i<=100); printf("%d\\n″,sum); }

§6.4 用do-while语句实现循环
while语句和用do-while语句的比较: 在一般情况下,用while语句和用do-while语 句处理同一问题时,若二者的循环体部分是一 样的,它们的结果也一样。但是如果while后面 的表达式一开始就为假(0值)时,两种循环的结 果是不同的。

?n
n ?1

100

例6.4 while和do-while循环的比较 运行结果: (1) #include <stdio.h> (2) #include <stdio.h> 运行结果: void main ( ) void main( ) 1↙? 1↙? {int sum=0,i; {int sum=0,i; sum=55 sum=55 scanf(“%d″,&i); scanf(”%d″,&i); while (i<=10) do再运行一次: 再运行一次: {sum=sum+I; { sum=sum+i; 11↙? 11↙? i++; i++; sum=11 sum=0 } while (i<=10); 说明:(1)当while后面的表达式的 printf(“sum=%d\\n″ ,sum); 第一次的值为“真”时,两种循 } printf(“sum=%d\\n”,sum); 环得到的结果相同。否则,二者 } 结果不相同。

§ 6.5 用for 语句实现循环
? C语言中的for语句使用最为灵活,不仅可以用于 循环次数已经确定的情况,而且可以用于循环次 数不确定而只给出循环结束条件的情况,它完全 可以代替while语句。 ? 一般形式: for(表达式1;表达式2;表达式3) 语句

§ 6.5 用for 语句实现循环
for语句的执行过程:
(1) 先求解表达式1。 (2) 求解表达式2,若其值为真(值为非0),则执 行for语句中指定的内嵌语句,然后执行下 面第(3)步。若为假(值为0),则结束循环, 转到第(5)步。 (3) 求解表达式3。 (4) 转回上面第(2)步骤继续执行。 (5) 循环结束,执行for语句下面的一个语句

§ 6.5 用for 语句实现循环
执行表达式1 表达式2?
成立 不成立

循环初始条件 循环控制条件

执行语句
执行表达式3

循环体 ? for语句等价于下列语句: 表达式1; while (表达式2) { 语句; 表达式3; }

执行for循环之后的语句

§ 6.5 用for 语句实现循环
? for语句最简单的应用形式也就是最易理解的如 下形式:
for(循环变量赋初值;循环条件;循环变量增值)

例如: for(i=1;i<=100;i++) sum=sum+i;
它相当于以下语句: i=1; 显 然 , 用 for 语 句 简单、方便。 while(i<=100) {sum=sum+i;i++;}

§ 6.5 用for 语句实现循环

说明:
(1) for语句的一般形式中的“表达式1”可以省略,此 时应在for语句之前给循环变量赋初值。注意省略表 达式1时,其后的分号不能省略。如 for(;i<=100;i++) sum=sum+i; 执行时,跳过“求解表达式1”这一步,其他不变。

§ 6.5 用for 语句实现循环 说明:
(2) 如果表达式2省略,即不判断循环条件,循环无终 止地进行下去。也就是认为表达式2始终为真。 例如:for(i=1; ;i++) sum=sum+i; 表达式1是一个赋值表达式,表达式2空缺。它相当于: i=1; while(1) 图6.7 {sum=sum+1;i++;}

§ 6.5 用for 语句实现循环 说明:
(3) 表达式3也可以省略,但此时程序设计者应另外设 法保证循环能正常结束。如: for(i=1;i<=100;) {sum=sum+i;i++;} 在上面的for语句中只有表达式1和表达式2,而没有 表达式3。i++的操作不放在for语句的表达式3的位置 处,而作为循环体的一部分,效果是一样的,都能使 循环正常结束。

§ 6.5 用for 语句实现循环 说明:
(4) 可以省略表达式1和表达式3,只有表达式2,即只 给循环条件。如: for(;i<=100;) while(i<=100) {sum=sum+i; 相当于 {sum=sum+i; i++;} i++;} 在这种情况下,完全等同于while语句。可见for语句 比while语句功能强,除了可以给出循环条件外,还可 以赋初值,使循环变量自动增值等。

§ 6.5 用for 语句实现循环 说明:
(5) 3个表达式都可省略,如: for(; ;) 语句 相当于 while(1) 语句 即不设初值,不判断条件(认为表达式2为真值), 循环变量不增值。无终止地执行循环体。

§ 6.5 用for 语句实现循环 说明:
(6) 表达式1可以是设置循环变量初值的赋值表达式, 也可以是与循环变量无关的其他表达式。如: for (sum=0;i<=100;i++) sum=sum+i; 表达式3也可以是与循环控制无关的任意表达式。

§ 6.5 用for 语句实现循环 说明:
表达式1和表达式3可以是一个简单的表达式,也可以 是逗号表达式,即包含一个以上的简单表达式,中间用 逗号间隔。如: for(sum=0,i=1;i<=100;i++) sum=sum+i; 或 for(i=0,j=100;i<=j;i++,j--) k=i+j; 表达式1和表达式3都是逗号表达式,各包含两个赋值 图6.8 表达式,即同时设两个初值,使两个变量增值.

§ 6.5 用for 语句实现循环 说明:
在逗号表达式内按自左至右顺序求解,整个逗号表达 式的值为其中最右边的表达式的值。如: for(i=1;i<=100;i++,i++) sum=sum+i; 相当于 for(i=1;i<=100;i=i+2) sum=sum+i;

§ 6.5 用for 语句实现循环 说明:
(7) 表达式一般是关系表达式(如i<=100)或逻辑表达式 (如a<b && x<y),但也可以是数值表达式或字符表达 式,只要其值为非零,就执行循环体。

§ 6.5 用for 语句实现循环 说明:
① for(i=0;(c=getchar())!=′\n′;i+=c); 在表达式2中先从终端接收一个字符赋给c,然后判断 此赋值表达式的值是否不等于′\n′(换行符),如果 不等于′\n′,就执行循环体。

注意:此for语句的循环体为空语句,把本来要在循环
体内处理的内容放在表达式3中,作用是一样的。可见 图6.9 for语句功能强,可以在表达式中完成本来应在循环体 内完成的操作。

§ 6.5 用for 语句实现循环 说明:
运行情况: Computer↙? (输入) ② for( ;(c=getchar())!=′\n′;) Computer (输出) printf(″%c″,c); 而不是 for语句中只有表达式2,而无表达式1和表达式3。 Ccoommppuutteerr 其作用是每读入一个字符后立即输出该字符,直到输入 一个“换行”为止。请注意,从终端键盘向计算机输入 时,是在按Enter键以后才将一批数据一起送到内存缓 冲区中去的。

§ 6.5 用for 语句实现循环
注意: C语言中的for语句比其他语言(如BASIC, PASCAL)中的FOR语句功能强得多。可以把循环体 和一些与循环控制无关的操作也作为表达式1或 表达式3出现,这样程序可以短小简洁。但过分 地利用这一特点会使for语句显得杂乱,可读性 降低,最好不要把与循环控制无关的内容放到 for语句中。

§6.6循环的嵌套
? 一个循环体内又包含另一个完整的循环结构 称为循环的嵌套。内嵌的循环中还可以嵌套 循环,这就是多层循环。 ? 三种循环(while循环、do-while循环和for循 环)可以互相嵌套。

§6.6循环的嵌套
? 下面几种都是合法的形式:
(1) while( ) {… while( ) {…} } (2) do (3) for(;;) {… { do for(;;) {… } {… } while( ); } } while( );

§6.6循环的嵌套
(4) while( ) (5) for(;;) {… {… do{…} while( ) while( ) { } {…} … } } (6) do {…
for(;;){ } … } while( )

§6.7几种循环的比较
(1)四种循环都可以用来处理同一问题,一般情 况下它们可以互相代替。但一般不提倡用 goto型循环。 (2)在while循环和do-while循环中,只在while 后面的括号内指定循环条件,因此为了使循 环能正常结束,应在循环体中包含使循环趋 于结束的语句(如i++,或i=i+1等)。

§6.7几种循环的比较
for循环可以在表达式3中包含使循环趋于 结束的操作,甚至可以将循环体中的操作全 部放到表达式3中。因此for语句的功能更强 ,凡用while循环能完成的,用for循环都能
实现。

(3)用while和do-while循环时,循环变量初始 化的操作应在while和do-while语句之前完成 。而for语句可以在表达式1中实现循环变量 的初始化。

§6.7几种循环的比较
(4)while循环、do-while循环和for循环,可以 用break语句跳出循环,用continue语句结束本 次循环(break语句和continue语句见下节)。而 对用goto语句和if语句构成的循环,不能用 break语句和continue语句进行控制.

§6.8 break语句和continue语句
6.8.1 break语句

break语句可以用来从循环体内跳出循环体, 即提前结束循环,接着执行循环下面的语句 一般形式: break; 注意:break语句不能用于循环语句和switch语 句之外的任何其他语句中。

§6.8 break语句和continue语句
例: float pi=3.14159;
for(r=1;r<=10;r++) { area=pi*r*r; if(area>100) break; printf(″r=%f,area=%f\n″,r,area); } 程序的作用是计算r=1到r=10时的圆面积,直到 面积area大于100为止。从上面的for循环可以 看到:当area>100时,执行break语句,提前结 束循环,即不再继续执行其余的几次循环。

§6.8 break语句和continue语句
6.8.2 continue语句

作用为结束本次循环,即跳过循环体中下面 尚未执行的语句,接着进行下一次是否执行 循环的判定. 一般形式: continue;

§6.8 break语句和continue语句
continue语句和break语句的区别 continue语句只结束本次循环,而不是终止 整个循环的执行。 while(表达式1) for
{…

if(表达式2) continue;
… }0



§6.8 break语句和continue语句
continue语句和break语句的区别 break语句则是结束整个循环过程,不再判 断执行循环的条件是否成立。
while(表达式1) for {… if(表达式2) break; … }



?n
n ?1

100

例6.5 把100~200之间的不能被3整除的数输出。 #include <stdio.h> void main() {int n; for (n=100;n<=200;n++) {if (n%3==0) continue; 说明:当n能被3整除时,执行continue printf("%d ″,n); 语句,结束本次循环(即跳过printf函 } 数语句),只有n不能被3整除时才执行 } printf函数。

§6.9 程 序 举 例
例 6.6 用 π/4≈1-1/3+1/5-1/7+… 公 式 求 π 的 近似值,直到某一项的绝对值小于为止。
N-S结构化流程图表示算法



?n
n ?1

100

例6.6 求pi的近似值 pi= 3.141594 #include <stdio.h> #include<math.h> void main() { int s;float n,t,pi; t=1;pi=0;n=1.0;s=1; while(fabs(t)>1e-6) {pi=pi+t;n=n+2;s=-s;t=s/n;} pi=pi*4; printf(″pi=%10.6f\n″,pi); }

运行结果:

F1

§6.9 程 序 举 例
例6.7 求Fibonacci数列前40个数。这个数列有如下
特点:第1,2两个数为1,1。从第3个数开始,该数 是其前面两个数之和。即: ? F(1)=1 (n=1) ? F(2)=1 (n=2) ? F(n)=F(n-1)+F(n-2) (n≥3) 算法如图所示:



?n
n ?1

100

运行结果: 例6.7求Fibonacci数列前40个数。 1 2 3 #include <stdio.h> 1 8 13 21 void main() 5 { long int f1,f2; 34 55 89 144 int i; 233 377 610 987 f1=1;f2=1; 1597 2584 4181 6765 for(i=1; i<=20; i++) 28657 10946 17711 46368 { printf(″%12ld %12ld ″,f1,f2); 75025 121393 196418 317811 if(i%2==0) printf(″\n″); 514229 832040 1346269 2178309 f1=f1+f2; 3524578 57022887 9227465 14930352 f2=f2+f1;}

}

24157817

39088169

63245986

102334155

F1

§6.9 程 序 举 例
例6.8 判断m是否素数。
算法思想:让m被2到除,如果m能被2~之中任何一个整 数整除,则提前结束循环,此时i必然小于或等于k( 即);如果m不能被2~k(即)之间的任一整数整除, 则在完成最后一次循环后,i还要加1,因此i=k+1, 然后才终止循环。在循环之后判别i的值是否大于或 等于k+1,若是,则表明未曾被2~k之间任一整数整 除过,因此输出?是素数?。 如图所示:



?n
n ?1

100

运行结果: 例6.8判断m是否素数。 17↙? #include <stdio.h> #include <math.h> 17 is a prime number void main() {int m,i,k; scanf(″%d″,&m);k=sqrt(m); for (i=2;i<=k;i++) if(m%i==0) break; if(i>k) printf("%d is a prime number\n″,m); else printf("%d is not a prime number\n″,m);
}

?n
n ?1

100

例6.9 求100~200间的全部素数。 运行结果: 101 103 107 109 113 127 #include <stdio.h> 131 137 139 149 151 157 163 167 173 # include <math.h> 179 181 191 193 197 199 void main() {int m,k,i,n=0; for(m=101;m<=200;m=m+2) { k=sqrt(m); for (i=2;i<=k;i++) if (m%i==0) break; if (i>=k+1){printf("%d ″,m);n=n+1;} if(n%10==0) printf(″\n″); } printf ("\n");}

F1

§6.9 程 序 举 例
例6.10 译密码。为使电文保密,往往按一定规律将
其转换成密码,收报人再按约定的规律将其译回原 文。 例如:可以按以下规律将电文变成密码: 将字母A变成字母E,a变成e,即变成其后的第4个字 母,W变成A,X变成B,Y变成C,Z变成D。素数。 如图所示



?n
n ?1

100

例6.10输入一行字符,要求输出其相应的密码 ? 运行结果: include <stdio.h> China!↙ ? void main() Glmre! {char c; while((c=getchar())!=′\n′) {if((c>=′a′ && c<=′z′) || (c>=′A′ && c<=′Z′)) { c=c+4; if(c>′Z′ && c<=′Z′+4 || c>′z′) c=c-26; } printf(″%c\n″,c); } }

第七章

第七章 数组
问题:给一组数排序,这组 数该 如何存放呢 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 8 2 9 4 5 6 3 7 1 6 8 8 8 8 8 8 8 8 8 88 8 8 8 8 8 88 8 ??? 这些数据如何存放才便于排序

这便是本章所要解决的问题

第七章 数组
?

本章要点
掌握一维、二维数组的定义和引用方 法、存储结构和初始化方法。
?

?
?

掌握有关一维数组的有关算法。
掌握数组的运算。

第七章 数组
?

主要内容
7.1 一维数组的定义和引用

7.2 二维数组的定义和引用
7.3 字符数组

§7-1 一维数组的定义和引用
问题:有如下几组数据,它们分别该如何存储呢? ? 一个班学生的学习成绩 ? 一行文字 这些数据的特点是: ? 一个矩阵 1、具有相同的数据类型 2、使用过程中需要保留原始数据 C语言为这些数据,提供了一种 构造数据类型:数组。

数组

是一组具有相同数据类型的数据的有序集合。

§ 7.1.1一维数组的定义
1、一维数组的定义格式为:
类型说明符
例如:

数组名[常量表达式];

int a[10];

它表示定义了一个整形数组,数组名为a,此数组 有10个元素。

2、说明:
(1)数组名定名规则和变量名相同,遵循标识符定 名规则。

(2)在定义数组时,需要指定数组中元素的个数, 方括弧中的常量表达式用来表示元素的个数,即数组 长度。例如,指定a[10],表示a数组有10个元素, 注意下标是从0开始的,这10个元素是,a[0],a[ 1],a[2],a[3],a[4],a[5],a[6],a [7],a[8],a[9]。请持别注意,按上面的定 义,不存在数组元素a[10]。 (3)常量表达式中可以包括常量和符号常量,但不 能包含变量。也就是说,C语言不允许对数组的大小 作动态定义,即数组的大小不依赖于程序运行过程中 变量的值。例如,下面这样定义数组是不行的:

举例: int n;
scanf(“%d″,&n); int a[n]; /*在程序中临时输入数

组的大小 */

数组说明中其他常见的错误
① float a[0]; /* 数组大小为0没有意义 */ ② int b(2)(3); /* 不能使用圆括号 */ ③ int k, a[k]; /* 不能用变量说明数组大小*/

3、一维数组在内存中的存放
一维数组: float mark[100]; 低地址 每个数据元素占用 的字节数,就是基 类型的字节数 一个元素占4个字节
高地址

86.5 92.0 77.5 52.0
. . .
94.0

mark[0] mark[1] mark[2] mark[3]
. . .
mark[99]

§ 7.1.2一维数组元素的引用
1、数组元素的引用方式:

数组名[下标]
下标可以是整型常量或整型表达式。例如: a[0]=a[5]+a[7]-a[2*3]

注意
定义数组时用到的“数组名[常量表达式]” 和引用 数组元素时用到的“数组名[下标]” 是有区别的。 例如∶ int a[10]; /* 定义数组长度为10 */ t=a[6]; /* 引用a数组中序号为6的元 素。此时6不代表数组长度 */

2、一维数组元素引用的程序实例
#include <stdio.h> void main() { int i,a[10]; for (i=0; i<=9;i++) a[i]=i; for(i=9;i>=0; i--) printf("%d ″,a[i]); printf("\n″); }

运行结果如下: 9 8 7 6 5 4 3 2 1 0 程序使a[0]到 a[9]的值为0~9, 然后按逆序输出。

§ 7.1.3一维数组的初始化
1、对数组元素初始化的实现方法: (1)在定义数组时对数组元素赋以初值。例如: int a[10]={0,1,2,3,4,5,6,7,8,9};

将数组元素的初值依次放在一对花括弧内。经过上面 的定义和初始化之后,a[0]=0,a[1]=1,a[2] =2,a[3]=3,a[4]=4,a[5]=5,a[6]=6,a [7]=7,a[8]=8,a[9]=9。

2)可以只给一部分元素赋值。例如: int a[10]={0,1,2,3,4}; 定义a数组有10个元素,但花括弧内只提供5个初值, 这表示只给前面5个元素赋初值,后5个元素值为0。 (3)如果想使一个数组中全部元素值为0,

可以写成
int a[10]={0,0,0,0,0,0,0,0,

0,0}; 或inta[10]={0};
不能写成:int a[10]={0*10}; 这是与FORTRAN语言不同的,不能给数组整体赋初值。

4)在对全部数组元素赋初值时,由于数据的个数已 经确定,因此可以不指定数组长度。
例如:int a[5]={1,2,3,4,5}; 也可以写成 int a[]={1,2,3,4,5}; 在第二种写法中,花括弧中有5个数,系统就会据此 自动定义a数组的长度为5。但若数组长度与提供初值 的个数不相同,则数组长度不能省略。例如,想定义 数组长度为10,就不能省略数组长度的定义,而必须 写成 int a[10]={1,2,3,4,5}; 只初始化 前5个元素,后5个元素为0。

§ 7.1.4一维数组程序举例
程序举例1:用数组来处理,求解Fibonacci数列。

Fibonacci数列公式:已知: a1=a2=1an=an-1+an-2 即:1,1,2,3,5,8,13
程序实例: #include <stdio.h> void main() { int i; int f[20]={1,1};

for(i=2;i<20;i++) f[i]=f[i-2]+f[i-1]; for(i=0;i<20;i++) { if(i%5==0) printf(″\ n″); printf(″%12d″,f[i]) } /*For循环结束*/ } /*程序结束*/ 运行结果如下: 1 1 2 8 13 21 89 144 233 987 1597 2584

if语句用来控 制换行,每行 输出5个数据。

3 34 377 4181

5 55 610 6765

程序举例2:用起泡法对10个数排序(由小到大)。 起泡法的思路是:将相邻两个数比较,将小的调到前头。

第 一 趟 比 较
注 经过第一趟(共5次比较与交换)后,最大的数9已“沉 底” 。然后进行对余下的前面5个数第二趟比较,

第 二 趟 比 较



经过第二趟(共4次比较与交换)后,得到次大的数8。

如果有n个数,则要进行n-1趟比较。在第1趟比较中 要进行n-1次两两比较,在第j趟比较中要进行n-j次两 两比较。

程序流程图如下:

程序实例7.3: #include <stdio.h> void main() { int a[10]; int i,j,t; printf(″input 10 numbers :\n″); for (i=0;i<10;i++) scanf("%d",&a[i]); printf("\n");

for(j=0;j<9;j++) for(i=0;i<9-j;i++) if (a[i]>a[i+1]) { t=a[i];a[i]=a[i+1]; a[i+1]=t; } 程序运行结果如下: printf(″the sortednumbers: :\n″); input 10 numbers for(i=0;i<10;i++) 12 65 -76 100 -45 123↙? 1 0 4 8 printf(″%d ″,a[i]); the sorted numbers: printf(″\n″); -76 -45 0 1 4 8 12 65 100 123 }/*程序结束*/

§7-2 二维数组的定义和引用
§ 7.2.1二维数组的定义

二维数组定义的一般形式为
类型说明符 数组名[常量表达式][常量表达式]; 例如:定义a为3×4(3行4列)的数组,b为5×10(5行10 列)的数组。如下: float a[3][4],b[5][10]; 不能写成 float a[3,4],b[5,10];

注意
我们可以把二维数组看作是一种特殊的一维数组: 它的元素又是一个一维数组。 例如,可以把a看作是一个一维数组,它有3个元素: a[0]、a[1]、a[2],每个元素又是一个包含4

个元素的一维数组。

§ 7.2.1二维数组的定义
一维数组在内存中的存放 二维数组中的元素在 内存中的排列顺序是:按 行存放,即先顺序存放第 一行的元素,再存放第二 行的元素…

下图表示对a[3][4] 数组存放的顺序

例如:整型数组 b[3][3]={ {1,2,3}, {4,5,6}, {7,8,9} };

地址
3000H

3002H
3004H 3006H 3008H 300AH

值 1 2 3
4 5 6 7 8 9

数组元素
b[0][0]
b[0][1] b[0][2] b[1][0] b[1][1]

b[1][2]
b[2][0] b[2][1] b[2][2]

300CH
300EH 3010H

§ 7.2.1二维数组的定义
问题:有了二维数组的基础,那么多维数组如何定义呢? 定义三维数组: float a[2][3][4]; 多维数组元素在内存中的排列顺序:第一维的下标变化 最慢,最右边的下标变化最快。
三 维 数 组 的 元 素 排 列 顺 序

a[0][0][0]→a[0][0][1]→a[0][0][2]→a[0][0][3]→

a[0][1][0]→a[0][1][1]→a[0][1][2]→a[0][1][3]→

a[0][2][0]→a[0][2][1]→a[0][2][2]→a[0][2][3]→

a[1][0][0]→a[1][0][1]→a[1][0][2]→a[1][0][3]→

a[1][1][0]→a[1][1][1]→a[1][1][2]→a[1][1][3]→

a[1][2][0]→a[1][2][1]→a[1][2][2]→a[1][2][3]→

§ 7.2.2二维数组的引用
二维数组元素的表示形式为:数组名[下标][下标] 例如: a[2][3] 下标可以是整型表达式,如 a[2-1][2*2-1] 注意 不要写成 a[2,3],a[2-1,2*2-1]形式

数组元素可以出现在表达式中,也可以被赋值,例如: b[1][2]=a[2][3]/2

在使用数组元素时,应该注意下标值应在已定义 的数组大小的范围内。 常出现的错误有:

int a[3][4];


/* 定义a为3×4的数组 */

a[3][4]=3;

§ 7.2.3二维数组的引用
数据类型
数组名 [常量表达式1][常量表达式2]={ 初始化数据 };

可以用下面4种方法对二维数组初始化
(1) 分行给二维数组赋初值。如: int a[3][4]={{1,2,3,4},{5,6,7,8}, {9,10,11,12}}; (2) 可以将所有数据写在一个花括弧内,按数组排列 的顺序对各元素赋初值。如: int a[3][4]={1,2,3,4,5,6,7,8,9,10, 11,12};

(3) 可以对部分元素赋初值。如 int a[3][4]={{1},{5},{9}};

也可以对各行中的某一元素赋初值,如
int a[3][4]={{1},{0,6},{0,0,11}}; 1 0 0 0 5 6 0 0 0 0 0 0 1 0 0 0 6 0 0 0 0 0 0 11 1 0 0 0 5 0 0 0 9 0 0 0

也可以只对某几行元素赋初值。如: int a[3][4]={{1},{5,6}};

§ 7.2.3二维数组的引用
(4)如果对全部元素都赋初值,则定义数组时对第一维 的长度可以不指定,但第二维的长度不能省。如: int a[3][4]={1,2,3,4,5,6,7,8,9,10, 11,12};它等价于:int a[][4]={1,2,3,4,5,

6,7,8,9,10,11,12};
在定义时也可以只对部分元素赋初值而省略第一维的 长度,但应分行赋初值。如:int a[][4]={{0, 0,3},{},{0,10}}; 0 0 3 0 0 0 0 0 0 10 0 0

§ 7.2.4二维数组程序举例
例7.4 将一个二维数组行和列元素互换,存到另一个 二维数组中。

例如:a= 1 2 3 1 4 #include <stdio.h> 4 5 6 b= 2 5 void main() 3 6 { int a[2][3]={{1,2,3},{4,5,6}}; int b[3][2],i,j; printf(″array a:\n″); for (i=0;i<=1;i++) { for (j=0;j<=2;j++) {

printf(″%5d″,a[i][j]); b[j][i]=a[i][j];
运行结果如下: array a: } 1 2 printf(″array b:\n″); 4 5 for (i=0;i<=2;i++) array b: { 1 4 for(j=0;j<=1;j++) 2 5 3 6 printf("%5d″,b[i][j]); printf(″\n″); } } /*程序结束*/ } printf(″\n″);

3 6

§ 7.2.4二维数组程序举例
例7.5: 有一个3×4的矩阵,要求编程序求出其中值最大 的那个元素的值,以及其所在的行号和列号。

先用N-S流程图表示算法 ,如下:

程序如下: #include <stdio.h> void main() { int i,j,row=0,colum=0,max; int a[3][4]={{1,2,3,4},{9,8,7,6}, {-10,10,-5,2}}; max=a[0][0];

for (i=0;i<=2;i++) for (j=0;j<=3;j++) if (a[i][j]>max) { max=a[i][j]; row=i; colum=j; } printf(″max=%d,row=%d,colum=%d\n″, max,row,colum); } /*程序结束*/

§7-3 字符数组
§ 7.3.1字符数组的定义 用来存放字符数据 的数组是字符数组。 定义方法与前面介绍的类似。例如: 字符数组中的一个 char c[10]; 元素存放一个字符。 c[0]=′I′;c[1]=′ ′;c[2]=′a′;

c[3]=′m′;c[4]=′ ′;c[5]=′h′;c[6]=′a′;
c[7]=′p′;c[8]=′p′;c[9]=′y′;

§ 7.3.2字符数组的初始化
对字符数组初始化,最容易理解的方式是逐个字符 赋给数组中各元素。如: char c[10]={ ?I′, ? ′,?a′,’m′,??,′h′,′a′, ′p′,′p′,′y′}; 如果在定义字符数组时不进行初始化,则数组中各元 素的值是不可预料的。 如果花括弧中提供的初值个数(即字符个数)大于数组 长度,则按语法错误处理。

如果初值个数小于数组长度,则只将这些字符赋给数 组中前面那些元素,其余的元素自动定为空字符 (即′\0′)。例如:

char c[10]={′c′,′ ′,′p′,′r′,′o′, ′g′,′r′,′a′,′m′};

如果提供的初值个数与预定的数组长度相同,在定义 时可以省略数组长度,系统会自动根据初值个数确定 数组长度。例如:
char c[]={′I′,′ ′,′a′,′m′,′ ′,′h′, ′a′,′p′,′p′,′y′};数组c的长度自动定为10。

也可以定义和初始化一个二维字符数组。例如:

char diamond[5][5]={{′ ′,′ ′,*′}, {′ ′,′*′,′ ′,′*′},{′*′,′ ′,′ ′,′ ′,′*′},{′ ′,′*′,′ ′,′*′}, {′ ′,′ ′,′*′}}

§ 7.3.3字符数组的引用
例7.6 输出一个字符串。 程序如下: #include <stdio.h> void main() { char c[10]={’I’,’ ’,’a’,’m’,’ ’,’a’,’ ’, ’b’,’o’,′y′}; int i; for(i=0;i<10;i++) printf(″%c″,c[i]); printf(″\n″); 运行结果:I am a boy }

§ 7.3.3字符数组的引用
例7.7 输出一个钻石图形

#include <stdio.h> void main() { char diamond[][5]={{′ ′,′ ′,′*′}, {′′,′*′,′ ′,′*′},{′*′,′ ′,′ ′,′ ′,′*′},{′ ′,′*′,′ ′,′*′}, {′ ′,′ ′,′*′}}; int i,j; for (i=0;i<5;i++) 运行结果 * { for (j=0;j<5;j++) * * printf(″%c″,diamond[i][j]); * * * * printf(″\n″); * } }

§ 7.3.4字符串和字符串结束标志
为了测定字符串的实际长度,C语言规定了一个“字符 串结束标志”,以字符′\0′作为标志。如果有一 个字符串,前面9个字符都不是空字符(即′\0′) , 而第10个字符是′\0′,则此字符串的有效字符为9 个。系统对字符串常量也自动加一个′\0′作为结 束符。 ′\0′代表ASCII码为0的字符,从ASCII码表中可以 查到,ASCII码为0的字符不是一个可以显示的字符, 而是一个?空操作符?,即它什么也不干。用它来作 为字符串结束标志不会产生附加的操作或增加有效字 符,只起一个供辨别的标志。

§ 7.3.4字符串和字符串结束标志
可以用字符串常量来使字符数组初始化。 注意 例如 char c[]={″I am happy″}; 也可以省略花括弧,直接写成 char c[]=“I am happy″; 它与下面的数组初始化等价 char c[] ={′I′,′ ′,′a′,′m′,′ ′, ′h′,′a′,′p′,′p′,′y′,′\0′} 再比如 char c[10]={"China″}; 数组c的前5个元素为′C′,′h′,′i′,′n′, ′a′,第6个元素为′\0′,后4个元素也设定为空字 符。

需要说明的是:字符数组并不要求它的最后一个字符为 ′\0′,甚至可以不包含′\0′。 例如:char c[5]={′C′,′h′,′i′,′n′, ′a′};这样写完全是合法的。但是由于系统对字符串 常量自动加一个′\0′。因此,人们为了使处理方法 一致,在字符数组中也常人为地加上一个′\0′。如: char c[6]={′C′,′h′,′i′,′n′,′a′, ′\0′};这样做,这样做是为了便于引用字符数组中 的字符串。

例如
定义了以下的字符数组∶

char c[]={”Pascal program”};
现在,想用一个新的字符串代替原有的字符 串”Pascal program” ,从键盘向字符数组输

入∶Hello
如果不加′\0′的话,字符数组中的字符如下∶

Hellol program

§7-3 字符数组
§ 7.3.5字符数组的输入输出

字符数组的输入输出可以有两种方法:
? 逐个字符输入输出。用格式符“%c”输入或输出 一个字符。 ? 将整个字符串一次输入或输出。用“%s”格式符, 意思是对字符串的输入输出。

例如 char c[]={″China″}; printf(″%s″,c);

在内存中数组c的状态

说明
(1)用“%s”格式符输出字符串时,printf函数中的输 出项是字符数组名,而不是数组元素名。如:写成 下面这样是不对的:printf(″%s″,c[0]); (2)如果数组长度大于字符串实际长度,也只输出到遇 ′\0′结束。 (3)输出字符不包括结束符′\0′。
如:char c[10]={″China″}; 长度为5,连’\0’共占6个字节 */ /* 字符串

printf(″%s″,c);只输出字符串的有效字符 “China”,而不是输出10个字符。这就是用字符串 结束标志的好处。

说明
(4)如果一个字符数组中包含一个以上′\0′,则遇 第一个′\0′时输出就结束。 (5)可以用scanf函数输入一个字符串。

例如 scanf(″%s″,c);
scanf函数中的输入项c是已定义的字符数组名,输 入的字符串应短于已定义的字符数组的长度。例如, 定义 char c[6]; 从键盘输入:

China↙?系统自动在China后面加一个′\0′结束 符。

如果利用一个scanf函数输入多个字符串,则 在输入时以空格分隔。例如:
char strl[5],str2[5],str3[5]; scanf(″%s%s%s″,str1,str2,str3); 输入数据: How are you? ? 数组中未被赋值的元素的值自动置′\0′。

若改为

char str[13];
scanf(″%s″,str); How are you? ?

如果输入以下12个字符

大家思考一下str[13]数组中存放的结果会是什么呢?

由于系统把空格字符作为输入的字符串之间的分隔符, 因此只将空格前的字符“How”送到str中。由于把“How”

作为一个字符串处理,故在其后加′\0′。

需要注意:scanf函数中的输入项如果字符数组名。 不要再加地址符&,因为在C语言中数组名代表该数 组的起始地址。下面写法不对:

scanf(″%s″,&str);
分析图中所示的字符数组 用8进制形式输出数组c的起始地址

printf(″%o″,c); 输出数组c的起始地址2000。
printf(″%s″,c);按字符数组名c找到其数组起始 地址,然后逐个输出其中的字符,直到遇′\0′为 止。

§ 7.3.6字符串处理函数
在C的函数库中提供了一些 1. puts函数 用来处理字符串的函数,使 其一般形式为: puts (字符数组) 用方便。几乎所有版本的C ?其作用是将一个字符串(以′\0′结束的字 编译系统都提供这些函数。 下面介绍几种常用的函数。 符序列)输出到终端。假如已定义str是一个字 符数组名,且该数组已被初始化为"China"。 则执行puts(str);其结果是在终端上输出 China。 由 于 可 以 用 printf 函 数 输 出 字 符 串 , 因 此 puts函数用的不多。

用puts函数输出的字符串中可以包含转义字 符。例如:

char str[]={″China\nBeijing″};
puts(str);

输出结果:
China

Beijing

在输出时,将字符串 结束标志′\0′ 转换成′\n′, 即输出完字符串后换行。

§ 7.3.6字符串处理函数
2. gets函数
其一般形式为:gets(字符数组)

其作用是从终端输入一个字符串到字符数组, 并且得到一个函数值。该函数值是字符数组 的起始地址。如执行下面的函数:
gets(str) 从键盘输入: Computer↙?

将输入的字符串"Computer"送给字符数组str (请注意送给数组的共有9个字符,而不是8个 字符),函数值为字符数组str的起始地址。 一般利用gets函数的目的是向字符数组输入一 个字符串,而不大关心其函数值。 注意:用puts和gets函数只能输入或输出一个 字符串,不能写成 puts(str1,str2)
或 gets(str1,str2)

§ 7.3.6字符串处理函数
3. strcat函数 其一般形式为:strcat(字符数组1,字符数组2) Strcat的作用是连接两个字符数组中的字符串,

把字符串2接到字符串1的后面,结果放在字符数
组1中,函数调用后得到一个函数值——字符数组1

的地址。

例如: char str1[30]={″People′s Republic of ″}; char str2[]={″China″}; print(″%s″,strcat(str1,str2)); 输出: People′s Republic of China

§ 7.3.6字符串处理函数
4. strcpy函数 其一般形式为:strcpy(字符数组1,字符串2) strcpy是“字符串复制函数”。作用是将字 符串2复制到字符数组1中去。例如:

char str1[10],str2[]={″China″};
strcpy(str1,str2);

1.字符数组1必须定义得足够大,以便容纳被复制的 字符串。字符数组1的长度不应小于字符串2的长度。 2.“字符数组1”必须写成数组名形式(如str1),“字 符串2”可以是字符数组名,也可以是一个字符串常 量。如strcpy(str1,″China″);

3.复制时连同字符串后面的′\0′一起复制到字符
数组1中。

4.可以用strcpy函数将字符串2中前面若干个字符复 制到字符数组1中去。例如:strcpy(str1,str2,2);
作用是将str2中前面2个字符复制到str1中去,然后 再加一个‘\0?。

5.不能用赋值语句将一个字符串常量或字符数组直接 给一个字符数组。如下面两行都是不合法的: str1=″China″; str1=str2; 而只能用strcpy函数将一个字符串复制到另一个字符 数组中去。用赋值语句只能将一个字符赋给一
个字符型变量或字符数组元素。如下面是合法的: char a[5],c1,c2; c1=′A′; c2=′B′; a[0]=′C′; a[1]=′h′; a[2]=′i′; a[3]=′n′; a[4]=′a′;

§ 7.3.6字符串处理函数
5. strcmp函数

其一般形式为:strcmp(字符串1,字符串2)
strcmp的作用是比较字符串1和字符串2。

例如:strcmp(str1,str2);
strcmp(″China″,″Korea″);

strcmp(str1,″Beijing″);

字符串比较的规则与其他语言中的规则相同,即对两 个字符串自左至右逐个字符相比(按ASCII码值大小比 较),直到出现不同的字符或遇到′\0′为止。如全 部字符相同,则认为相等;若出现不相同的字符,则 以第一个不相同的字符的比较结果为准。

"A"<"B", 例如 "a">"A", "computer">"compare", "36+54">"!$&#", "CHINA">"CANADA", "DOG"<"cat"

比较的结果由函数值带回 (1) 如果字符串1=字符串2,函数值为0。 (2) 如果字符串1>字符串2,函数值为一正整数。 (3) 如果字符串1<字符串2,函数值为一负整数。
注意:对两个字符串比较,不能用以下形式: if(str1>str2) printf(″yes″); 而只能用 if(strcmp(str1,str2)>0) printf(″yes″);

§ 7.3.6字符串处理函数
6. strlen函数
其一般形式为:strlen (字符数组)

strlen是测试字符串长度的函数。函数的值为字 符串中的实际长度(不包括′\0′在内)。 如:char str[10]={″China″};
printf(″%d″,strlen(str));

输出结果不是10,也不是6,而是5。也可以直接 测试字符串常量的长度,如strlen(″China″);

§ 7.3.6字符串处理函数
7. strlwr函数

其一般形式为:strlwr (字符串)
strlwr函数的作用是将字符串中大写字母换成小写字母。 8. strupr函数 其一般形式为:strupr (字符串) strupr函数的作用是将字符串中小写字母换成大写字母。

以上介绍了常用的8种字符串处理函数,应当再次强 调:库函数并非C语言本身的组成部分,而是C编译 系统为方便用户使用而提供的公共函数。不同的编 译系统提供的函数数量和函数名、函数功能都不尽 相同,使用时要小心,必要时查一下库函数手册。

§ 7.3.7字符数组应用举例
例7 .8 输入一行字符,统计其中有多少个单词,单 词之间用空格分隔开。

程序如下:

#include <stdio.h> void main() { char string[81]; int i,num=0,word=0; char c; gets(string); for (i=0;(c=string[i])!=′\ 0′;i++)

if(c==′ ′) word=0; else if(word==0) { word=1; num++; }
运行情况如下: I am a boy.↙ There are 4 words in the line.

printf(″There are %d words in the

line.\n″,num);
}

例7.9

有3个字符串,要求找出其中最大者

程序如下: #include<stdio.h> #include<string.h> void main ( ) { char string[20]; char str[3][20]; int i; for (i=0;i<3;i++) gets (str[i]);

if (strcmp(str[0],str[1])>0)

strcpy(string,str[0])
else strcpy(string,str[1]);

if (strcmp(str[2],string)>0)
strcpy(string,str[2]);

printf(″\nthe largest string is∶
\n%s\n″,string);

}

运行结果如下: CHINA↙? HOLLAND↙? AMERICA↙ the largest string is∶ HOLLAND

第八章

?

本章要点
? ? ? ? ? 函数的概念 函数的定义与调用 函数的递归调用 变量的作用域 函数的作用域

?

主要内容
§ 8.1 概述 § 8.2函数定义的一般形式 § 8.3函数参数和函数的值 § 8.4 函数的调用 § 8.5 函数的嵌套调用 § 8.6函数的递归调用 § 8.7数组作为函数参数 § 8.8 局部变量和全局变量 § 8.9变量的存储类别 § 8.10 内部函数和外部函数

§8.1概述

一个较大的程序可分为 若干个程序模块,每一 个模块用来实现一个特 定的功能。在高级语言 中用子程序实现模块的 功能。子程序由函数来 完成。一个C程序可由 一个主函数和若干个其 他函数构成。 函数间的调用关系

由主函数调用其他函数,其他函数也可以互相调用。 同一个函数可以被一个或多个函数调用任意多次。

例8.1先举一个函数调用的简单例子
# include <stdio.h>
void main()

{
void printstar(); void print_message(); /*对print_message函数声明*/ printstar(); print_message(); printstar(); /*调用printstar函数*/ /*调用print_message函数*/ /*调用printstar函数*/ /*对printstar函数声明*/

}

void printstar() /*定义printstar函数*/ { printf("* * * * * * * * * * * * * * * *\n"); }
void print_message() /*定义print_message函数*/ { printf("How do you do!\n"); }

运行情况如下: * * * * * * * * * * * * * * * * How do you do! * * * * * * * * * * * * * * * *

说明:
(1) 一个C程序由一个或多个程序模块组成,

每一个程序模块作为一个源程序文件。对较 大的程序,一般不希望把所有内容全放在一 个文件中,而是将他们分别放在若干个源文 件中,再由若干源程序文件组成一个C程序。 这样便于分别编写、分别编译,提高调试效 率。一个源程序文件可以为多个C程序公用。

(2) 一个源程序文件由一个或多个函数以及 其他有关内容(如命令行、数据定义等)组成 。一个源程序文件是一个编译单位,在程序编 译时是以源程序文件为单位进行编译的,而不 是以函数为单位进行编译的。 (3)C程序的执行是从main函数开始的 ,如是在main函数中调用其他函数,在调 用后流程返回到main函数,在main函 数中结束整个程序的运行。

(4) 所有函数都是平行的,即在定义函数时 是分别进行的,是互相独立的。一个函数并不 从属于另一函数,即函数不能嵌套定义。函数 间可以互相调用,但不能调用main函数。 main函数是系统调用的。

(5)从用户使用的角度看,函数有两种:

① 标准函数,即库函数。这是由系统提供的 ,用户不必自己定义这些函数,可以直接使 用它们。应该说明,不同的C系统提供的库函 数的数量和功能会有一些不同,当然许多基 本的函数是共同的。
② 用户自己定义的函数。用以解决用户的专 门需要。

(6) 从函数的形式看,函数分两类: ① 无 参 函 数 。 如 例 8.1 中 的 printstar 和 print_message就是无参函数。在调用无参函数 时,主调函数不向被调用函数传递数据。无参 函数一般用来执行指定的一组操作。例如,例8 .1程序中的printstar函数。 ②有参函数。在调用函数时,主调函数在调用 被调用函数时,通过参数向被调用函数传递数 据,一般情况下,执行被调用函数时会得到一 个函数值,供主调函数使用。

§8.2函数定义的一般形式 §8.2.1. 无参函数的定义一般形式
定义无参函数的一般形式为:
类型标识符 函数名()


声明部分 语句部分 }

在定义函数时要 用“类型标识符” 指定函数值的类 型,即函数带回 来的值的类型。 例8.1中的 printstar和 print_message函 数为void类型, 表示不需要带回 函数值。

§8.2.2. 有参函数定义的一般形式
定义有参函数的一般形式为:

类型标识符 函数名(形式参数表列)


声明部分 语句部分 } 例如:
int max(int x,int y) {int z;/ *函数体中的声明部分*/ z=x>y?x∶y; return(z); }

§8.2.3 空函数
定义空函数的一般形式为:

类型标识符 函数名()
{ }

例如:
dummy() {}

调用此函数时,什么 工作也不做,没有任 何实际作用。在主调 函数中写上“dum my();”表明 “这里要调用一个函 数”,而现在这个函 数没有起作用,等以 后扩充函数功能时补 充上。

§8.3函数参数和函数的值 §8.3.1形式参数和实际参数
在前面提到的有参函数中,在定义函数时函 数名后面括弧中的变量名称为“形式参数” (简称“形参”),在主调函数中调用一个 函数时,函数名后面括弧中的参数(可以是一 个表达式)称为“实际参数”(简称“实参” )。return后面的括弧中的值()作为函数带 回的值(称函数返回值)。

大多数情况下,主调函数和被调用函数之间有 数据传递的关系。 在不同的函数之间传递数据,可以使用的方法: ◆ 参数:通过形式参数和实际参数 ◆ 返回值:用return语句返回计算结果 ◆ 全局变量:外部变量

例8.2调用函数时的数据传递 #include <stdio.h> void main() { int max(int x,int y); /* 对max函数的声明 */ int a,b,c; scanf("%d,%d",&a,&b); c=max(a,b); printf("Max is %d",c); }

int max(int x,int y)/*定义有参函数max */ { int z; z=x>y?x∶y; return(z); }

运行情况如下: 7,8↙? Max is 8

通过函数调用,使两个函数中的数据发生联系

关于形参与实参的说明: (1) 在定义函数中指定的形参,在未出现函数 调用时,它们并不占内存中的存储单元。只 有在发生函数调用时,函数max中的形参 才被分配内存单元。在调用结束后,形参所 占的内存单元也被释放。 (2) 实参可以是常量、变量或表达式,如: max(3,a+b); 但要求它们有确定的值。在调用时将实参的 值赋给形参。

(3) 在被定义的函数中,必须指定形参的类 型(见例8.2程序中的 ?c=max(a, b);? )。
(4) 实参与形参的类型应相同或赋值兼容。 例8.2中实参和形参都是整型。如果实参为 整型而形参x为实型,或者相反,则按第3章 介绍的不同类型数值的赋值规则进行转换。 例如实参值a为3.5,而形参x为整型,则将实 数3.5转换成整数3,然后送到形参b。字符型 与整型可以互相通用。

(5) 在C语言中,实参向对形参的数据传递 是?值传递?,单向传递,只由实参传给形 参,而不能由形参传回来给实参。在内存中, 实参单元与形参单元是不同的单元。

在调用函数时,给形参分配存储单元,并将实 参对应的值传递给形参,调用结束后,形参单 元被释放,实参单元仍保留并维持原值。因此, 在执行一个被调用函数时,形参的值如果发生 改变,并不会改变主调函数的实参的值。例如, 若在执行函数过程中x和y的值变为10和1 5,而a和b仍为2和3。

§8.3.2

函数的返回值

通常,希望通过函数调用使主调函数能得到一个确 定的值,这就是函数的返回值。例如,例8.2中,

max(2,3)的值是3,max(5,2)的
值是5。赋值语句将这个函数值赋给变量c。

关于函数返回值的一些说明:

(1)函数的返回值是通过函数中的return语句获
得的。

return语句将被调用函数中的一个确定值带回主调函 数中去。见图8.2中从return语句返回的箭头。 如果需要从被调用函数带回一个函数值供主调函数使 用,被调用函数中必须包含return语句。如果不需要从 被调用函数带回函数值可以不要return语句。 一个函数中可以有一个以上的return语句,执行到哪一 个return语句,哪一个语句起作用。return语句后面的括 弧也可以不要, 如: “return z;” 等价于 ?return (z);?

return后面的值可以是一个表达式。

例如,例8.2中的函数max可以改写成:

max(int x,int y) { return(x>y?x∶y); }

(2) 函数的返回值应当属于某一个确定的类型,
在定义函数时指定函数返回值的类型。

例如:下面是3个函数的首行:
int max(float x,float y) /* 函数值为整型 */ char letter(char c1,char c2) /* 函数值为字符型 */ double min(int x,int y) /* 函数值为双精度型 */ 在C语言中,凡不加类型说明的函数,自动按整型处 理。例8.2中的max函数首行的函数类型int可以省 写,用Turbo C 2.0编译程序时能通过,但用Turbo C ++ 3.0编译程序时不能通过,因为C++要求所有函数 都必须指定函数类型。因此,建议在定义时对所有函数 都指定函数类型。

(3)在定义函数时指定的函数类型一般应该和 return语句中的表达式类型一致。
如果函数值的类型和return语句中表达式的值不一致, 则以函数类型为准。对数值型数据,可以自动进行类 型转换。即函数类型决定返回值的类型。

(4)对于不带回值的函数,应当用?void”定 义函数为?无类型?(或称?空类型?)。这 样,系统就保证不使函数带回任何值,即禁止 在调用函数中使用被调用函数的返回值。此时 在函数体中不得出现return语句。

例 8.3 返回值类型与函数类型不同 运行情况如下:
# include <stdio.h> 1.5, 2.5↙? void main() Max is 2 { int max(float x,float y); float a,b; int c; scanf("%f,%f,",&a,&b); c=max(a,b); printf("Max is %d\n",c); } int max(float x,float y) { float z; /* z为实型变量 */ z=x>y?x∶y; return(z); }

§8.4 函数的调用 §8.4.1 函数调用的一般形式
函数调用的一般形式为: 函数名(实参表列) 如果是调用无参函数,则“实参表列” 可以没有,但括弧不能省略。 如果实参表列包含多个实参,则各参数 间用逗号隔开。实参与形参的个数应相 等,类型应匹配。实参与形参按顺序对 应,一一传递数据。

如果实参表列包括多个实参,对实参求值 的顺序并不是确定的,有的系统按自左至 右顺序求实参的值,有的系统则按自右至

左顺序。许多C版本是按自右而左的顺序
求值,例如Tubro C++。

例 8.4 实参求值的顺序

#include <stdio.h>
void main()

{
int f(int a,int b); /* 函数声明 */

int i=2,p;
p=f(i,++i); } /* 函数调用 */

printf("%d\n",p);

int f(int a,int b) { int c; if(a>b) c=1; else if(a==b) c=0; else c=-1; return(c); }

/* 函数定义 */

对于函数调用 int i=2,p; p=f(i,++i);
如果按自右至左顺序求实 参的值,则函数调用相当 于f(3,3)

如果按自左至右顺序求实 参的值,则函数调用相当 于f(2,3)

§8.4.2函数调用的方式

1.函数语句

把函数调用作为一个语句。如例8.1中的printstar(),这
时不要求函数带回值,只要求函数完成一定的操作。

2.函数表达式 函数出现在一个表达式中,这种表达式称为函数表达 式。这时要求函数带回一个确定的值以参加表达式的

运算。例如:c=2*max(a,b);

3.函数参数

函数调用作为一个函数的实参。例如:

m = max (a , max ( b , c ) ) ;
其中max ( b , c )是一次函数调用,它的值作为max另
一次调用的实参。m的值是a、b、c三者中的最大者。 又如: printf ("%d", max (a,b));也是把max ( a , b ) 作为printf函数的一个参数。 函数调用作为函数的参数,实质上也是函数表达

式形式调用的一种,因为函数的参数本来就要求是表
达式形式。

§8.4.3对被调用函数的声明和函数原型

(1) 首先被调用的函数必须是已经存在的

函数(是库函数或用户自己定义的函数)。

但光有这一条件还不够。

(2) 如果使用库函数,还应该在本文件开 头用#include命令将调用有关库 函数时所需用到的信息“包含”到本文件 中来。 (3) 如果使用用户自己定义的函数,而该 的后面(在同一个文件中),应该在主调 函数中对被调用的函数作声明。

函数的位置在调用它的函数(即主调函数)

函数原型的一般形式为

(1) 函数类型 函数名(参数类型1,参数
类型2??); (2) 函数类型 函数名(参数类型1,参数

名1,参数类型2,参数名2??);

“声明”一词的原文是delaration,过去
在许多书中把它译为“说明”。声明的作

用是把函数名、函数参数的个数和参数类
型等信息通知编译系统,以便在遇到函数

调用时,编译系统能正确识别函数并检查
调用是否合法。 (例如函数名是否正确,

实参与形参的类型和个数是否一致)。

注意:函数的“定义”和“声明”不是一
回事。函数的定义是指对函数功能的确立,
包括指定函数名,函数值类型、形参及其类

型、函数体等,它是一个完整的、独立的函
数单位。而函数的声明的作用则是把函数的

名字、函数类型以及形参的类型、个数和顺
序通知编译系统,以便在调用该函数时系统

按此进行对照检查。

例8.5 对被调用的函数作声明
# include <stdio.h> void main() { float add(float x, float y); /*对被调用函数add的声明*/ float a,b,c; scanf("%f,%f",&a,&b); c=add(a,b); printf("sum is %f \n",c); } float add(float x,float y) /*函数首部*/ { float z; /* 函数体 */ z=x+y; return(z); }

如果 被调用函数的定义出现在主调函数
之前,可以不必加以声明。因为编译系
统已经先知道了已定义函数的有关情况, 会根据函数首部提供的信息对函数的调

用作正确性检查。

改写例 8.5
# include <stdio.h> float add(float x,float y) /*函数首部*/ { float z; /* 函数体 */ z=x+y; return(z); } void main() { float a,b,c; scanf("%f,%f",&a,&b); c=add(a,b); printf("sum is %f \n",c); }

§8.5 函数的嵌套调用
嵌套定义就是在定义一个函数时,其函数体 内又包含另一个函数的完整定义 。 C语言不能嵌套定义函数,但可以嵌套调用 函数,也就是说,在调用一个函数的过程中, 又调用另一个函数。

例 8.6 用弦截法求方程 f(x)=x3-5x2+16x-80=0 的根

方法:
(1) 取两个不同点x1,x2,如果f(x1)和f(x2)符号相
反,则(x1,x2)区间内必有一个根。如果f(x1)与

f(x2)同符号,则应改变x1,x2,直到f(x1)、f(x2)异
号为止。注意x1、x2的值不应差太大,以保证 (x1,x2)区间内只有一个根。

(2) 连接(x1,f(x1))和(x2,f(x2))两点,此线(即
弦)交x轴于x。

(3) 若f(x)与f(x1)同符号,则根必在(x,x2)区 间内,此时将x作为新的x1。如果f(x)与f(x2)同

符号,则表示根在(x1,x)区间内,将x作为新的x2。

(4) 重复步骤 (2) 和 (3) , 直到 |f(x)|< ε 为止, ε 为一个很小的数, 例如 10-6\. 此 时认为 f(x)≈0

N-S流程图

分别用几个函数来实现各部分功能: (1) 用函数f(x)代表x的函数:x3-5x2+16x-80. (2) 用函数调用xpoint (x1,x2)来求(x1,f(x1))和 (x2,f(x2))的连线与x轴的交点x的坐标。 (3) 用函数调用root (x1,x2)来求(x1,x2)区间的

那个实根。显然,执行root函数过程中要用
到函数xpoint,而执行xpoint函数过程中要用

到f函数。

#include <stdio.h> #include <math.h> float f(float x) /* 定义f函数,以实 现f(x) =x3-5x2+16x-80 */ {float y; y=((x-5.0)*x+16.0)*x-80.0; return(y); }

float xpoint(float x1,float x2) /*定义xpoint函数,求出弦与x轴交点 */ {float y; y=(x1*f(x2)-x2*f(x1)) /f(x2)-f(x1)); return(y); }

float root(float x1,float x2) /* 定义root函数,求近似根 */ {float x,y,y1; y1=f(x1); do { x=xpoint(x1,x2); y=f(x); if(y*y1>0) /*f(x)与f(x1)同符号 */ { y1=y; x1=x;} else x2=x; } while(fabs(y)>=0.0001); return(x); }

void main() /?主函数?/ {float x1,x2,f1,f2,x; do {printf("input x1,x2:\n"); scanf("%f,%f",&x1,&x2); f1=f(x1); f2=f(x2); }while(f1*f2>=0); x=root(x1,x2); printf("A root of equation is %8.4f\n",x); 运行情况如下: }

input x1,x2: 2,6 A root of equation is 5.0000

§8.6函数的递归调用
在调用一个函数的过程中又出现直接或间接地 调用该函数本身,称为函数的递归调用。C语 言的特点之一就在于允许函数的递归调用。例

如:

int f(int x) { int y,z; z=f(y); return(2*z); }

例 8.7 有5个人坐在一起,问第5个人多少岁? 他说比第4个人大2岁。问第4个人岁数,他说比第 3个人大2岁。问第3个人,又说比第2个人大2岁。 问第2个人,说比第1个人大2岁。最后问第1个人, 他说是10岁。请问第5个人多大。

age(5)=age(4)+2 age(4)=age(3)+2 age(3)=age(2)+2 age(2)=age(1)+2 age(1)=10 可以用数学公式表述如下: age(n)=10 (n=1) age(n-1)+2 (n>1)

可以用一个函数来描述上述递归过程: int age(int n) /*?求年龄的递归函数?*/ { int c; /* c用作存放函数的返回值的变量 */ if(n==1) c=10; else c=age(n-1)+2; return(c); } 用一个主函数调用age函数,求得第5人的年龄。 #include <stdio.h> 运行结果如下: void main() 18 { printf(″%d″,age(5)); }



8.8用递归方法求n!

求n!也可以用递归方法,即5!等于 4!×5,而4!=3!×4…1!= 1。可用下面的递归公式表示:

n!=1
n· (n-1)!

(n=0,1)
(n>1)



8.9 Hanoi(汉诺)塔问题。这

是一个古典的数学问题,是一个用递归方法解 题的典型例子。问题是这样的:古代有一个梵 塔,塔内有3个座A、B、C,开始时A座上有6 4个盘子,盘子大小不等,大的在下,小的在 上(见图8.13)。有一个老和尚想把这6 4个盘子从A座移到C座,但每次只允许移动

一个盘,且在移动过程中在3个座上都始终保
持大盘在下,小盘在上。在移动过程中可以利

用B座,要求编程序打印出移动的步骤。

为便于理解,我们先分析将A座上3个盘子移到 将以上综合起来,可得到移动3个盘子的步骤为 C座上的过程: A→C,A→B,C→B,A→C,B→A,B→ C,A→C。 (1) 将A座上2个盘子移到B座上(借助C); (2) 将A座上1个盘子移到C座上; (3) 将B座上2个盘子移到C座上(借助A)。 其中第(2)步可以直接实现。第1步又可用递归方 法分解为: 1.1 将A上1个盘子从A移到C; 1.2 将A上1个盘子从A移到B; 1.3 将C上1个盘子从C移到B。 第(3)步可以分解为: 3.1 将B上1个盘子从B移到A上; 3.2 将B上1个盘子从B移到C上; 3.3 将A上1个盘子从A移到C上。

由上面的分析可知:将n个盘子从A座移 到C座可以分解为以下3个步骤: (1) 将A上n-1个盘借助C座先移到B座 上。 (2) 把A座上剩下的一个盘移到C座上。 (3) 将n-1个盘从B座借助于A座移到C 座上。

程序如下: #include <stdio.h> void main() { void hanoi(int n,char one,char two,char three); /* 对hanoi函数的声明 */ int m; printf("input the number of diskes:"); scanf(“%d”,&m); printf("The step to moveing %d diskes:\n",m); hanoi(m,'A','B','C'); }

void hanoi(int n,char one,char two,char three) /* 定义hanoi函数,将n个盘从one座借助two座,移到 three座 */ { void move(char x,char y); /* 对move函数的声明 */ if(n==1) move(one,three); else { hanoi(n-1,one,three,two); move(one,three); hanoi(n-1,two,one,three); } } void move(char x,char y) /* 定义move函数 */ { printf(“%c-->%c\n",x,y); }

运行情况如下: input the number of diskes:3↙? The steps to noving 3 diskes: A-->C A-->B C-->B A-->C B-->A B-->C A-->C

§8.7数组作为函数参数
§8.7.1 数组元素作函数实参
由于实参可以是表达式,而数组元素可以是表达式 例8.10 有两个数组a和b,各有10个元素,将 的组成部分,因此数组元素当然可以作为函数的实 它们对应地逐个相比(即a[0]与b[0]比, 参,与用变量作实参一样,是单向传递,即“值传 a[1]与b[1]比……)。如果a数组中的元 送”方式。 素大于b数组中的相应元素的数目多于b数组中元素 大于a数组中相应元素的数目(例如,a[i]>b[i]6 次,b[i]>a[i]3次,其中i每次为不同的值),则 认为a数组大于b数组,并分别统计出两个数组相应

元素大于、等于、小于的次数。

#include <stdio.h> void main() { int large(int x,int y); /* 函数声明 */ int a[10],b[10],i,n=0,m=0,k=0; printf(″enter array a∶\n″); for(i=0;i<10;i++=) scanf(″%d″,&a[i]); printf(″\n″); printf(″ enter arrayb∶\n″); for(i=0;i<10;i++=) scanf (″%d″,&b[i]); printf(″\n″); for(i=0;i<10;i++) { if(large (a[i],b[i] )== 1) n=n+1; else if( large (a[i],b[i] )==0) m=m+1; else k=k+1;}

printf("a[i]>b[i] %d times\na[i]=b[i] %d times\na[i]<b[i] %d times\n",n,m,k); if(n>k) printf("array a is larger than array b\n"); else if (n<k) printf("array a is smaller than array b\n"); else printf("array a is equal to array b\n"); } large(int x,int y) { int flag; if(x>y)flag=1; else if(x<y)flag=-1; else flag=0; return(flag); }

运行情况如下: enter array a: 1 3 5 7 9 8 6 4 2 0↙? enter array b∶ 5 3 8 9 –1 –3 5 6 0 4↙? a[i]>b[i] 4 times a[i]=b[i] 1 times a[i]<b[i] 5 times array a is smaller thann array b

§8.7.2 数组名作函数参数
可以用数组名作函数参数,此时形参应当用数组名
或用指针变量 。

例8.11 有一个一维数组score,内放 10个学生成绩,求平均成绩。

#include <stdio.h> void main() { float average(float array[10]); /* 函数声明 */ float score[10] , aver; int i; printf(″input 10 scores:\n″); for(i=0;i<10;i++= scanf(″%f″,&score[i]); printf(″\n″); aver=average( score ); printf (″ average score is %5.2f\n″, aver); }

float average (float array[10]) { int i; float aver,sum=array[0]; for (i=1;i<10;i++=) sum=sum+array[i]; aver=sum/10; return(aver); }
运行情况如下: input 10 scores: 100 56 78 98.5 76 87 99 67.5 7 5 97↙? average score is 83.40

例 8.12形参数组不定义长度
#include <stdio.h> void main() { float average(float array[ ],int n) float score_1[5] ={98.5,97,9 1.5,60,55}; float score_2[10]={ 67.5,89.5,99,69.5, 77,89.5,76.5,54,60,99.5}; printf(“the average of class A is %6.2f\n”, average(score_1,5)); printf(“the average of class B is %6.2f\n”, average(score_2,10)); }

float average(float array[ ],int n) { int i; float aver,sum=array[0]; for(i=1;i<n;i++= sum=sum+array[i]; aver=sum/n; return(aver); }

运行结果如下: the average of class A is 80.40 The average of class B is 78.20

例 8.13 用选择法对数组中10个整数按由 小到大排序。所谓选择法就是先将10个 数中最小的数与a[0]对换;再将a[1] 到a[9]中最小的数与a[1]对换…… 每比较一轮,找出一个未经排序的数中最 小的一个。共比较9轮。

a[0] a[1] a[2] a[3] a[4] 3 6 1 9 4 未排序时的情况 1 6 3 9 4 将5个数中最小的数1与a[0]对换 1 3 6 9 4 将余下的4个数中最小的数3与a[1]对换 1 3 4 9 6 将余下的3个数中最小的数4与a[2]对换 1 3 4 6 9 将余下的2个数中最小的数6与a[3]对 换,至此完成排序

程序实例 #include <stdio.h> void main() { void sort(int array[],int n); int a[10],i; printf(″enter the array\n″); for(i=0;i<10;i++= scanf(″%d″,&a[i]); sort(a,10); printf(″the sorted array∶\n″); for(i=0;i<10;i++= printf(″%d″,a[i]); printf(″\n″); }

void sort(int array[],int n) { int i,j,k,t; for(i=0;i<n-1;i++) { k=i; for(j=i+1;j<n;j++) if(array[j] < array[k]=k=j; t=array[k]; array[k]=array[i];array[i]=t } }

§8.7.3. 多维数组名作函数参数
程序如下: #include <stdio.h> void main() { max_value ( int array[ ][4]); int [3][4]={{1,3,5,7},{2,4,6,8},{15,17,34,12}}; printf(″max value is %d\n″, max_value(a) ); }

max_value ( int array[ ][4]) { int i,j,k,max; max=array[0][0]; for(i=0;i<3;i++) for(j=0;j<4;j++= if(array[i][j]>max) max= array [i][j]; return(max); }

运行结果如下: Max value is 34

§8.8局部变量和全局变量
§8.8.1局部变量
在一个函数内部定义的变量是内部变量,它只在本 函数范围内有效,也就是说只有在本函数内才能使 用它们,在此函数以外是不能使用这些变量的。这 称为“局部变量”。

float f1( int a) /*?函数f1? */ {int b,c; … a、b、c有效 } char f2(int x,int y) /*?函数f2 */ {int i,j; x、y、i、j有效 } void main( ) /*?主函数?*/ {int m,n; … m、n有效 }

(1) 主函数中定义的变量(m,n)也只在主函数中有效,而 说 不因为在主函数中定义而在整个文件或程序中有效。 明 主函数也不能使用其他函数中定义的变量。 (2) 不同函数中可以使用相同名字的变量,它们代表不 同的对象,互不干扰。例如, 上面在f1函数中定义了变 量b和c,倘若在f2函数中也定义变量b和c,它们在内存 中占不同的单元,互不混淆。 (3) 形式参数也是局部变量。例如上面f1函数中的形 参a,也只在f1函数中有效。其他函数可以调用f1函数, 但不能引用f1函数的形参a。 (4) 在一个函数内部,可以在复合语句中定义变量,这些 变量只在本复合语句中有效,这种复合语句也称为 “分程序”或“程序块”。

void main ( ) {int a,b; … {int c; c=a+b; c在此范围内有效 a,b在此范围内有效 … } … }

§8.8.2 全局变量
在函数内定义的变量是局部变量,而在函数之
外定义的变量称为外部变量,外部变量是全局

变量(也称全程变量)。全局变量可以为本文件
中其他函数所共用。它的有效范围为从定义变

量的位置开始到本源文件结束。

int p=1,q=5; /* 外部变量 */ float f1(int a) /* 定义函数f1 */ {int b,c; … } char c1,c2; /* 外部变量?*/ char f2 (int x, int y) /* 定义函数f2 */ {int i,j; 全局变量p,q的作用范围 … 全局变量c1,c2的作用范围 } void main ( ) /*?主函数?*/ {int m,n; … }

例 8.15 有一个一维数组,内放10个学生成绩,写 一个函数,求出平均分、最高分和最低分。
#include <stdio.h> float Max=0,Min=0; /*?全局变量?*/ void main() { float average(float array[ ],int n); float ave,score[10]; int i; for(i=0;i<10;i++) scanf(″%f″,&score[i]); ave= average(score,10); printf(“max=%6.2f\nmin=%6.2f\n average=%6.2f\n“,Max,Min,ave); }

float average(float array[ ],int n) /* 定义函数,形参为数组 */ { int i; float aver,sum=array[0]; Max=Min=array[0]; for(i=1;i<n;i++) { if(array[i]>Max)Max=array[i]; else if(array[i]<Min)Min= array[i]; sum=sum+array[i]; 运行情况如下: } 99 45 aver=sum/n; 78 97 100 67.5 8 9 92 66 return(aver); 43↙? max=100.00 } min=43.00 average=77.65

建议不在必要时不要使用全局变量,原因如下:

① 全局变量在程序的全部执行过程中都占用存储单 元,而不是仅在需要时才开辟单元。 ② 使用全局变量过多,会降低程序的清晰性,人们

往往难以清楚地判断出每个瞬时各个外部变量的值。
在各个函数执行时都可能改变外部变量的值,程序

容易出错。因此,要限制使用全局变量。

③它使函数的通用性降低了,因为函数在执行时要
依赖于其所在的外部变量。如果将一个函数移到另

一个文件中,还要将有关的外部变量及其值一起移
过去。但若该外部变量与其他文件的变量同名时, 就会出现问题,降低了程序的可靠性和通用性。一 般要求把C程序中的函数做成一个封闭体,除了可 以通过“实参——形参”的渠道与外界发生联系外, 没有其他渠道。



8.16 外部变量与局部变量同名

#include <stdio.h> int a=3,b=5; /* a,b为外部变量*/ a,b作用范围 void main ( ) { int a=8; /*a为局部变量 */ 局部变量a作用范围 printf (″%d″, max (a,b)); 全局变量b的作用范围 } max (int a, int b) /*a,b为局部变量 */ { int c; c=a>b?a∶b; 形参a、b作用范围 return (c); } 运行结果为 8

§8.9 变量的存储类别
§8.9.1 动态存储方式与静态存储方式
前面已介绍了从变量的作用域(即从空间)角度来 分,可以分为全局变量和局部变量。那么从变量值 存在的时间(即生存期)角度来分,又可以分为静 态存储方式和动态存储方式。 所谓静态存储方式是指在程序运行期间由系统分 配固定的存储空间的方式。而动态存储方式则是在 程序运行期间根据需要进行动态的分配存储空间的 方式。这个存储空间可以分为三部分: ?程序区 ?静态存储区 ?动态存储区

在C语言中每一个变量和函数有两个属性:数 据类型和数据的存储类别。对数据类型,读者 已熟悉(如整型、字符型等)。存储类别指的 是数据在内存中存储的方式。存储方式分为两 大类:静态存储类和动态存储类。具体包含四 种:自动的(auto),静态的(stat ic),寄存器的(register),外 部的(extern)。根据变量的存储类别, 可以知道变量的作用域和生存期。

§8.9.2 auto变量 函数中的局部变量,如不专门声明为static存储类别,都 是动态地分配存储空间的,数据存储在动态存储区中。

函数中的形参和在函数中定义的变量(包括在复合语句中
定义的变量),都属此类,在调用该函数时系统会给它们 分配存储空间,在函数调用结束时就自动释放这些存储 空间。因此这类局部变量称为自动变量。自动变量用关 键字auto作存储类别的声明。例如:

int f(int a) /*定义f函数,a为形参 */
{auto int b,c=3; /*定义b、c为自动变量 */




8.9.3用static声明局部变量

有时希望函数中的局部变量的值在函数调用结束后
不消失而保留原值,即其占用的存储单元不释放,

在下一次该函数调用时,该变量已有值,就是上一
次函数调用结束时的值。这时就应该指定该局部变 量为“静态局部变量”,用关键字static进

行声明。通过下面简单的例子可以了解它的特点。

例8.17 考察静态局部变量的值。 #include <stdio.h> void main() {int f(int); int a=2,i; for(i=0;i<3;i++= printf(″%d ″,f(a)); } int f(int a) {auto int b=0; static c=3; b=b+1; c=c+1; return(a+b+c); }

对静态局部变量的说明: (1) 静态局部变量属于静态存储类别,在静态存储区内 分配存储单元。在程序整个运行期间都不释放。而自 动变量(即动态局部变量)属于动态存储类别,占动 态存储区空间而不占静态存储区空间,函数调用结束 后即释放。 (2) 对静态局部变量是在编译时赋初值的,即只赋初值 一次,在程序运行时它已有初值。以后每次调用函数 时不再重新赋初值而只是保留上次函数调用结束时的 值。而对自动变量赋初值,不是在编译时进行的,而 是在函数调用时进行,每调用一次函数重新给一次初 值,相当于执行一次赋值语句。

(3)如在定义局部变量时不赋初值的话,则对静态局 部变量来说,编译时自动赋初值0(对数值型变量) 或空字符(对字符变量)。而对自动变量来说,如 果不赋初值则它的值是一个不确定的值。这是由于 每次函数调用结束后存储单元已释放,下次调用时 又重新另分配存储单元,而所分配的单元中的值是 不确定的。 (4) 虽然静态局部变量在函数调用结束后仍然存在, 但其他函数是不能引用它的。

例8.18 输出1到5的阶乘值。 #include <stdio.h> void main() {int fac(int n); int i; for(i=1;i<=5;i++) printf(″%d!=%d\n″,i,fac(i)); } Int fac(int n) {static int f=1; f=f*n; return(f); }

8.9.4 register变量 一般情况下,变量(包括静态存储方式和动态存储 方式)的值是存放在内存中的。当程序中用到哪一 个变量的值时,由控制器发出指令将内存中该变量 的值送到运算器中。 经过运算器进行运算,如果 需要存数,再从运算器将数据送到内存存放。

如果有一些变量使用频繁(例如在一个函数中执行 10000次循环,每次循环中都要引用某局部变 量),则为存取变量的值要花费不少时间。为提高 执行效率,C语言允许将局部变量的值放在CPU中 的寄存器中,需要用时直接从寄存器取出参加运算, 不必再到内存中去存取。由于对寄存器的存取速度 远高于对内存的存取速度,因此这样做可以提高执 行效率。这种变量叫做寄存器变量,用关键字re gister作声明。例如,例8.19中的程序是 输出1到n的阶乘的值。

例8.19使用寄存器变量 #include <stdio.h> void main ( ) {long fac(long); long i,n; scanf("%ld",&n); for(i=1;i<=n;i++) printf("%ld!=%ld\n",i,fac(i)); } long fac(long n) {register long i,f=1; /*?定义寄存器变量?*/ for (i=1;i<=n;i++) f=f*i; return (f); }

8.9.5用extern声明外部变量

外部变量是在函数的外部定义的全局变量,它的作 用域是从变量的定义处开始,到本程序文件的末尾。 在此作用域内,全局变量可以为程序中各个函数所 引用。编译时将外部变量分配在静态存储区。 有时需要用extern来声明外部变量,以扩展外部变 量的作用城。

1. 在一个文件内声明外部变量 例8.20 用extern声明外部变量,扩展它在程 序文件中的作用域。 #include <stdio.h> void main() { int max(int,int); /*外部变量声明*/ extern A,B; printf("%d\n",max(A,B)); } int A=13,B=-8; /*定义外部变量*/ int max(int x,int y) /*定义max函数 */ { int z; z=x>y?x:y; return(z); }

2. 在多文件的程序中声明外部变量 例8.21 用extern将外部变量的作用域扩展到其他 文件。 本程序的作用是给定b的值,输入a和m,求 a×b和am的值。文件file1.c中的内容为:
#include <stdio.h> int A; /*定义外部变量*/ void main() {int power(int); /*函数声明*/ int b=3,c,d,m; printf(″enter the number a and its power m:\n″); scanf(″%d,%d″,&A,&m); c=A*b; printf(″%d*%d=%d\n″,A,b,c); d=power(m); printf(″%d**%d=%d\n″,A,m,d); }

文件file2.c中的内容为: extern A; /*声明A为一个已定义的外部变量*/ int power(int n); {int i,y=1; for(i=1;i<=n;i++) y*=A; return(y); }

8.9.6用static声明外部变量
有时在程序设计中希望某些外部变量只限于被本 文件引用,而不能被其他文件引用。这时可以在 定义外部变量时加一个static声明。 例如: file1.c file2.c static int A; extern int A; void main ( ) void fun (int n) { {… … A=A*n; }

8.9.7关于变量的声明和定义 对变量而言,声明与定义的关系稍微复杂一些。在声 明部分出现的变量有两种情况:一种是需要建立存储 空间的(如:int a; ),另一种是不需要建立存储空间的 (如:extern a;)。前者称为“定义性声 明”(defining declaration) ,或简称定义(definition)。 后者称为“引用性声明”(referencing declaration)。广 义地说,声明包括定义,但并非所有的声明都是定义。 对“int a;” 而言,它既是声明,又是定义。而对 “extern a;” 而言,它是声明而不是定义。

一般为了叙述方便,把建立存储空间的声明称定 义,而把不需要建立存储空间的声明称为声明。 显然这里指的声明是狭义的,即非定义性声明。 例如: void main() {extern A; /*是声明不是定义。声明A是一 个已定义的外部变量*/ … } int A;

§8.9.8存储类别小结
(1) 从作用域角度分,有局部变量和全局变量。它们 采用的存储类别如下: 局部变量 |自动变量,即动态局部变量 (离开函数,值就消失) |静态局部变量(离开函数,值仍保留) |寄存器变量(离开函数,值就消失) |(形式参数可以定义为自动变量或寄存 器变量) 全局变量 |静态外部变量(只限本文件引用) |外部变量 (即非静态的外部变量,允许其他文件引用)

(2) 从变量存在的时间(生存期)来区分,有动态 存储和静态存储两种类型。静态存储是程序整个运 行时间都存在,而动态存储则是在调用函数时临时 分配单元。 动态存储 |自动变量(本函数内有效) |寄存器变量(本函数内有效) |形式参数(本函数内有效) 静态存储 |静态局部变量(函数内有效) |静态外部变量(本文件内有效) |外部变量(其他文件可引用)

(3) 从变量值存放的位置来区分,可分为: 内存中静态存储区 |静态局部变量 |静态外部变量(函数外部静态变量) |外部变量(可为其他文件引用) 内存中动态存储区:自动变量和形式参数 CPU中的寄存器:寄存器变量

(4) 关于作用域和生存期的概念。从前面叙述可以知 道,对一个变量的性质可以从两个方面分析,一是 变量的作用域,一是变量值存在时间的长短,即生 存期。前者是从空间的角度,后者是从时间的角度。 二者有联系但不是同一回事。 ?(5) static对局部变量和全局变量的作用不同。对局部 变量来说,它使变量由动态存储方式改变为静态存储方 式。而对全局变量来说,它使变量局部化(局部于本文 件),但仍为静态存储方式。从作用域角度看,凡有static 声明的,其作用域都是局限的,或者是局限于本函数内 (静态局部变量),或者局限于本文件内(静态外部变量)。

§8.10 内部函数和外部函数
函数本质上是全局的,因为一个函数要被另外的函数 调用,但是,也可以指定函数不能被其他文件调用。根 据函数能否被其他源文件调用,将函数区分为内部函 数和外部函数。

§8.10.1内部函数
如果一个函数只能被本文件中其他函数所调用,它称 为内部函数。在定义内部函数时,在函数名和函数类 型的前面加static。即 static 类型标识符 函数名(形参表) 如 static int fun ( int a , int b )

§8.10.2外部函数
(1) 在定义函数时,如果在函数首部的最左端加关键字 extern,则表示此函数是外部函数,可供其他文件调 用。如函数首部可以写为extern int fun (int a, int b) 这样,函数fun就可以为其他文件调用。C语言规定, 如果在定义函数时省略extern,则隐含为外部函数。 本书前面所用的函数都是外部函数。 (2) 在需要调用此函数的文件中,用extern对函数作声 明,表示该函数是在其他文件中定义的外部函数

例 8.22 有一个字符串,内有若干个字符,今输入一个字 符,要求程序将字符串中该字符删去。用外部函数实现 File.c(文件1) #include <stdio.h> void main() { extern void enter_string(char str[]); extern void detele_string(char str[],char ch); extern void print_string(char str[]);/*以上3行声明在本
函数中将要调用的在其他文件中定义的3个函数*/

char c; char str[80]; scanf("%c",&c); detele_string(str,c); print_string(str);

}

file2.c(文件2) #include <stdio.h> void enter_string(char str[80]) /* 定义外部函数 enter-string*/ { gets(str); /*向字符数组输入字符串*/ } file3.c(文件3) void delete_string(char str[],char ch) /*定义外部函数 delete_string */ { int i,j; for(i=j=0;str[i]!='\0';i++) if(str[i]!=ch) str[j++]=str[i]; str[i]='\0'; }

file4.c(文件4) #include <stdio.h> void print_string(char str[]) { printf("%s\n",str); }
运行情况如下: abcdefgc↙? (输入str) c↙ ? (输入要删去的字符) abdefg (输出已删去指定字符的字符串)

第九章

?

本章要点
?预处理的概念 ?C语言处理系统的预处理功能 ?预处理命令的使用

?

主要内容
9.1 宏定义 9.2“文件包含”处理 9.3 条件编译

基本概念
? ANSI C标准规定可以在C源程序中加入一些? 预处理命令? ,以改进程序设计环境,提高 编程效率。 ? 这些预处理命令是由ANSI C统一规定的,但是 它不是C语言本身的组成部分,不能直接对它 们进行编译(因为编译程序不能识别它们)。 必须在对程序进行通常的编译之前,先对程序 中这些特殊的命令进行?预处理? ? 经过预处理后程序可由编译程序对预处理后的 源程序进行通常的编译处理,得到可供执行的 目标代码。

基本概念
? C语言与其他高级语言的一个重要区别是可以 使用预处理命令和具有预处理的功能。 C提供的预处理功能主要有以下三种: 1.宏定义 2.文件包含 3.条件编译 这些功能分别用宏定义命令、文件包含命 令、条件编译命令来实现。为了与一般C语句 相区别,这些命令以符号?#?开头。例如: #define #include

§9.1 宏定义
9.1.1 不带参数的宏定义 宏定义一 #define 标识符 字符串 般形式为: 例如:# define PI 3.1415926
? 宏定义的作用是在本程序文件中用指定的标识符PI 来代替?3.1415926”这个字符串,在编译预处理时 ,将程序中在该命令以后出现的所有的PI都用 ?3.1415926”代替。这种方法使用户能以一个简单 的名字代替一个长的字符串. ? 这个标识符(名字)称为?宏名? ? 在预编译时将宏名替换成字符串的过程称为?宏展 开?。#define是宏定义命令。

例9.1 使用不带参数的宏定义
#include <stdio.h> #define PI 3.1415926 void main() {float l,s,r,v; printf("input radius:"); scanf("%f",&r); l=2.0*PI*r; s=PI*r*r; v=4.0/3*PI*r*r*r; printf("l=%10.4f\ns=%10.4f\nv=%10.4f\n",l,s,v); }

运行情况如下:
input radius: 4↙

说明:

1=25.1328 s=50.2655 v=150.7966

(1) 宏名一般习惯用大写字母表示,以便与变量名 相区别。但这并非规定,也可用小写字母。 (2) 使用宏名代替一个字符串,可以减少程序中重 复书写某些字符串的工作量。 (3) 宏定义是用宏名代替一个字符串,只作简单臵 换,不作正确性检查。只有在编译已被宏展开后 的源程序时才会发现语法错误并报错。

说明:
(4) 宏定义不是C语句,不必在行末加分号。如果 加了分号则会连分号一起进行臵换。 (5) #define命令出现在程序中函数的外面,宏名 的有效范围为定义命令之后到本源文件结束。通 常,#define命令写在文件开头,函数之前,作 为文件一部分,在此文件范围内有效。 (6) 可以用#undef命令终止宏定义的作用域。 例如:

#define G 9.8 _______ void main() ↑ { G的有效范围 … } -----↓---#undef G f1() 在f1函数中,G不再代表 { 9.8。这样可以灵活控制宏 … 定义的作用范围。 }

说明:
(7) 在进行宏定义时,可以引用已定义的宏名,可 以层层臵换。

例9.2 在宏定义中引用已定义的宏名
#include <stdio.h> #define R 3.0 #define PI 3.1415926 #define L 2*PI*R #define S PI*R*R void main() { printf("L=%f\nS=%f\n",L,S); }

运行情况如下:
L=18.849556 S=28.274333

经过宏展开后,printf函数中的输出项L被展开为: 2*3.1415926*3.0 S展开为 3.1415926*3.0*3.0 printf函数调用语句展开为:

printf(“L=%F\NS=%f\n”,

2*3.1415926*3.0,3.1415926*3.0*3.0);

说明:
(8) 对程序中用双撇号括起来的字符串内的字符, 即使与宏名相同,也不进行臵换。 (9) 宏定义是专门用于预处理命令的一个专用名词 ,它与定义变量的含义不同,只作字符替换,不 分配内存空间。

9.1.2 带参数的宏定义

作用:不是进行简单的字符串替换,还 要进行参数替换。 带参数的宏定义一般形式为:
#define 宏名(参数表) 字符串 字符串中包含在括弧中所指定的参数

例:

? 程序中用3和2分别代替宏 #define S(a,b) a*b 定义中的形式参数a和b, 用3*2代替S(3,2) 。因此 ? 赋值语句展开为: area=S(3,2); area=3*2

对带参的宏定义是这样展开置换的:
? 对带实参的宏(如S(3, 2),则按#define命令 行中指定的字符串从左到 右进行臵换。若串中包含 宏中的形参(如a、b), 则将程序中相应的实参( 可以是常量、变量或表达 式)代替形参。如果宏定 义中的字符串中的字符不 是参数字符(如a*b中 的*号),则保留。这样 就形成了臵换的字符串。

例9.3 使用带参的宏

运行情况如下:

#include <stdio.h> r=3.600000 #define PI 3.1415926 area=40.715038 #define S(r) PI*r*r void main() {float a,area; a=3.6; area=S(a); printf("r=%f\narea=%f\n",a,area); }

赋值语句?area=S(a); ” 经宏展开后为: area=3.1415926*a*a;

说明:
(1)对带参数的宏展开只是将语句中的宏名后面 括号内的实参字符串代替#define 命令行 中的形参。 (2) 在宏定义时,在宏名与带参数的括弧之间不 应加空格,否则将空格以后的字符都作为替 代字符串的一部分。

带参数的宏和函数的区别:
(1) 函数调用时,先求出实参表达式的值,然后代入形 参。而使用带参的宏只是进行简单的字符替换。 (2) 函数调用是在程序运行时处理的,为形参分配临时 的内存单元。而宏展开则是在编译前进行的,在展 开时并不分配内存单元,不进行值的传递处理,也 没有?返回值?的概念。 (3) 对函数中的实参和形参类型要求一致。而宏名无类 型,它的参数也无类型,只是一个符号代表,展开 时代入指定的字符串即可。宏定义时,字符串可以 是任何类型的数据。 (4) 调用函数只可得到一个返回值,而用宏可以设法得 到几个结果。

例9.4 通过宏展开得到若干个结果
#include <stdio.h> #define PI 3.1415926 #define CIRCLE(R,L,S,V) L=2*PI*R;S=PI*R*R;V=4.0/3.0*PI*R*R*R void main() {float r,l,s,v; scanf("%f",&r); CIRCLE(r,l,s,v); printf("r=%6.2f,l=%6.2f,s=%6.2f,v=%6.2f\n" ,r,l,s,v); }

对宏进行预编译,展开后的main函数如下:
void main() { float r,l,s,v; scanf("%f",&r); l=2*3.1415926*r; s=3.1515926*r*r; v=4.0/3/0*3.1415926*r*r*r; printf(”r=%6.2f,l=%6.2f,s=%6.2f,v=%6.2f\n” ,r,l,s,v); 运行情况如下: } 3.5↙? r=3.50,l=21.99,s=38.48,v=179.59

带参数的宏和函数的区别:
(5) 使用宏次数多时,宏展开后源程序长,因为每 展开一次都使程序增长,而函数调用不会使源 程序变长。 (6) 宏替换不占运行时间,只占编译时间。而函数 调用则占运行时间(分配单元、保留现场、值 传递、返回)。 如果善于利用宏定义,可以实现程序的 简化,如事先将程序中的?输出格式?定 义好,以减少在输出语句中每次都要写出 具体的输出格式的麻烦。

例9.5 通过宏展开得到若干个结果
#include <stdio.h> void main() #define PR printf { int a,b,c,d; #define NL "\n" char string[]="CHINA"; #define D "%d" a=1;b=2;c=3;d=4; #define D1 D NL PR(D1,a); #define D2 D D NL PR(D2,a,b); #define D3 D D D NL PR(D3,a,b,c); #define D4 D D D D NL PR(D4,a,b,c,d); #define S "%s" PR(S,string); } 运行时输出结果:
1 12 123 1234 CHINA

§9.2 “文件包含”处理
? 所谓?文件包含?处理是指一个源文件可以将另 外一个源文件的全部内容包含进来。C语言提供 了#include命令用来实现?文件包含?的操作。 其一般形式为: #include "文件名" 或 #include <文件名>

例9.6 将例9.5时格式宏做成头文件,把它 包含在用户程序中。 (2)主文件file1.c (1)将格式宏做成头文件 #include <stdio.h> #include "format.h" format.h void main() #include <stdio.h> { int a,b,c,d; #define PR printf char string[]="CHINA"; #define NL "\n" a=1;b=2;c=3;d=4; #define D "%d" PR(D1,a); #define D1 D NL PR(D2,a,b); #define D2 D D NL PR(D3,a,b,c); #define D3 D D D NL PR(D4,a,b,c,d); #define D4 D D D D NL PR(S,string); #define S "%s" }

注意: 在编译时并不是分别对两个文件分别进行编 译,然后再将它们的目标程序连接的,而是 在经过编译预处理后将头文件format.h包含 到主文件中,得到一个新的源程序,然后对 这个文件进行编译,得到一个目标(.obj) 文件。被包含的文件成为新的源文件的一部 分,而单独生成目标文件。

说明:
(1) 一个#include命令只能指定一个被包含文件, 如果要包含n个文件,要用n个#include命令。 (2) 如果文件1包含文件2,而在文件2中要用到 文件3的内容,则可在文件1中用两个include命 令分别包含文件2和文件3,而且文件3应出现 在文件2之前,即在file1.c中定义。 (3) 在一个被包含文件中又可以包含另一个被包含 文件,即文件包含是可以嵌套的。

说明:
(4) 在#include命令中,文件名可以用双撇号或尖 括号括起来。 (5) 被包含文件(file2.h)与其所在的文件(即用 #include命令的源文件file2.c),在预编译后已 成为同一个文件(而不是两个文件)。因此,如 果file2.h中有全局静态变量,它也在file1.h文 件中有效,不必用extern声明。

§9.3 条件编译

(3) #if 表达式 程序段1 概念:所谓?条件编译?,是对部分内容 #else 指定编译的条件,使其只在满足一定条 程序段2 件才进行编译。 条件编译命令的几种形式: #endif

(1)#ifdef 标识符 程序段1 #else 程序段2 #endif

(2)#ifndef 标识符 程序段1 #else 程序段2 #endif

#include <stdio.h> #define LETTER 1 例9.7 输入一行字母字符,根据需要设置条件 void main() 编译,使之能将字母全改为大写输出,或全改为 {char str[20]="C Language",c; 小写字母输出。 int i; i=0; while((c=str[i])!='\0') 运行结果为: { i++; C LANGUAGE #if LETTER if(c>='a' && c<='z') c=c-32; #else if(c>='A' && c<='Z') c=c+32; #endif printf("%c",c); } }

第十章

§10.1地址和指针的概念
为了说清楚什么是指针,必须弄清楚数据在内存中是 如何存储的,又是如何读取的。 内存区的每一个字节有一个编号,这就是“地址” 。 如果在程序中定义了一个变量,在对程序进行编译时, 系统就会给这个变量分配内存单元。 1、按变量地址存取变量值的方式称为“直接访问”方式 例如: printf(″%d″,i); scanf(″%d″,&i); k=i+j;

另一种存取变量值的方式称为“间接访问”的方式。 即,将变量i的地址存放在另一个变量中。

在C语言中,指针是一种特殊的变量,它是存放地址的。 假设我们定义了一个指针变量i_pointer用来存放整型变 量的地址,它被分配地址为(3010)、(3011)的两个字节。 可以通过语句:i_pointer =&i;

将i的地址(2000)存放到i_pointer中。这时, i_pointer 的值就是(2000) ,即变量i所占用单元的起始地址。要 存取变量i的值,可以采用间接方式:先找到存放“i 的地址”的变量i_pointer ,从中取出i的地址(2000), 然后到2000 、 2001字节取出i的值(3)。

指针和指针变量的定义: 一个变量的地址称为该变量的“指针”。 例如,地址2000是变量i的指针。如果有一个变量专 门用来存放另一变量的地址(即指针),则它称为

“指针变量”。上述的i_pointer就是一个指针变量。
指针变量的值(即指针变量中存放的值)是地 址(即指针)。请区分“指针”和“指针变量” 这两个概念。

§10.2 变量的指针和指向变量的指针变量

10.2.1 定义一个指针变量

定义指针变量的一般形式为
基类型 *指针变量名;

下面都是合法的定义:

float *pointer_3;

// pointer_3是指向float型变量的

指针变量 char *pointer_4; //pointer_4是指向字符型变量的指 针变量 可以用赋值语句使一个

指针变量得到另一个变
量的地址,从而使它指 向一个该变量。如: pointer_1=&i; pointer_2=&j;

在定义指针变量时要注意两点:

(1) 指针变量前面的“*”,表示该变量的类型为指针型 变量。
例: float *pointer_1; 指针变量名是pointer_1 ,而不是* pointer_1 。 (2) 在定义指针变量时必须指定基类型。

需要特别注意的是,只有整型变量的地址才能放到指向 整型变量的指针变量中。下面的赋值是错误的∶ float a; int * pointer_1; pointer_1=&a; /* 将float型变量的地址放到指 向整型变量的指针变量中,错误 */

10.2.2 指针变量的引用
请牢记,指针变量中只能存放地址(指针), 不要将一个整数(或任何其他非地址类型的数据) 赋给一个指针变量。 例10.1 通过指针变量访问整型变量 #include <stdio.h> void main ( ) { int a,b; int?*pointer_1, *pointer_2; a=100;b=10; pointer_1=&a; /*把变量a的地址赋给 pointer_1 */

pointer_2=&b; /*把变量b的地址赋给 pointer_2 */ printf(″%d,%d\n″,a,b); printf(″%d,%d\n″,*pointer_1, *pointer_2); }

对“&”和“*”运算符说明:

如果已执行了语句 pointer_1=&a;
(1)&* pointer_1的含义是什么?“&”和“*”两个 运算符的优先级别相同,但按自右而左方向结合, 因此先进行* pointer_1的运算,它就是变量a,再 执行&运算。因此,&* pointer_1与&a相同,即

变量a的地址。如果有pointer_2 =&* pointer_1 ;
它的作用是将&a(a的地址)赋给pointer_2 ,如

果pointer_2原来指向b,经过重新赋值后它已不再
指向b了,而指向了a。

(2) *&a的含义是什么?先进行&a运算,得a的地 址,再进行*运算。即&a所指向的变量,也就是变量 a。*&a和*pointer_1的作用是一样的,它们都等价 于变量a。即*&a与a等价。 (3) (*pointer_1)++相当于a++。注意括号是必 要的,如果没有括号,就成为了*pointer_1++,从

附录可知:++和*为同一优先级别,而结合方向为自
右而左,因此它相当于*(pointer_1++)。由于++在

pointer_1的右侧,是“后加”,因此先对pointer_1的
原值进行*运算,得到a的值,然后使pointer_1的值 改变,这样pointer_1不再指向a了。

例10 . 2

输入a和b两个整数,按先大后小的顺序输出 a和b。

#include <stdio.h> void main() { int *p1,*p2,*p,a,b; scanf(″%d,%d″,&a,&b); p1=&a;p2=&b; if(a<b) {p=p1;p1=p2;p2=p;} printf(″a=%d,b=%d\n\n″,a,b); printf(″max=%d,min=%d\n″,*p1,*p2); }

运行情况如下: 5,9↙? a=5,b=9 max=9,min=5 当输入a=5,b=9时,由于a<b, 将p1和p2交换。交换前的情况见图 (a),交换后见图(b)。

10.2.3 指针变量作为函数参数 例10 . 3 对输入的两个整数按大小顺序输出 #include <stdio.h> void main() {void swap(int *p1,int *p2); int a,b; int *pointer_1,*pointer_2; scanf(″%d,%d″,&a,&b); pointer_1 =&a; pointer_2 =&b; if(a<b= swap( pointer_1 , pointer_2 ); printf(″\n%d,%d\n″,a,b); }

void swap(int *p1,int *p2) { int temp; temp=*p1; *p1=*p2; *p2=temp; }

例10.4 输入a、b、c 3个整数,按大小顺序输出 #include <stdio.h> void main() { void exchange(int *q1, int *q2, int *q3); int a,b,c,*p1,*p2,*p3; scanf(″%d,%d,%d″,&a, &b, &c); p1=&a;p2=&b;p3=&c; exchange (p1,p2,p3); printf(″\n%d,%d,%d\n″,a,b,c); }

void exchange(int *q1, int *q2, int *q3) { void swap(int *pt1, int *pt2); if(*q1<*q2) swap(q1,q2); if(*q1<*q3) swap(q1,q3); if(*q2<*q3= swap(q2,q3); } void swap(int *pt1, int *pt2) {int temp; temp=*pt1; *pt1=*pt2; *pt2=temp; }

§10.3 数组与指针
一个变量有地址,一个数组包含若干元素,每个 数组元素都在内存中占用存储单元,它们都有相

应的地址。指针变量既然可以指向变量,当然也
可以指向数组元素(把某一元素的地址放到一个

指针变量中)。所谓数组元素的指针就是数组元
素的地址。

10.3.1 指向数组元素的指针
定义一个指向数组元素的指针变量的方法,与以 前介绍的指向变量的指针变量相同。例如: int a[10]; (定义a为包含10个整型数据的数组) int?*p; (定义p为指向整型变量的指针变量) 应当注意,如果数组为int型,则指针变量的 基类型亦应为int型。

对该指针变量赋值: p=&a[0]; 把a[0]元素的地址赋给指针变量p。也就是使p 指向a数组的第0号元素,如图:

10.3.2通过指针引用数组元素
引用一个数组元素,可以用: (1) 下标法,如a[i]形式; (2) 指针法,如*(a+i)或*(p+i)。 其中a是数组名,p是指向数组元素的指针变量,其初 值p=a。 例10.5 输出数组中的全部元素 假设有一个a数组,整型,有10个元素。要输出 各元素的值有三种方法:

(1)下标法 #include <stdio.h> void main() { int a[10]; int i; for(i=0;i<10;i++) scanf(″%d″,&a[i]); printf(″\n″); for(i=0;i<10;i++) printf(″%d″,a[i]); }

(2) 通过数组名计算数组元素地址,找出元素的值。 #include <stdio.h> void main() { int a[10]; int i; for(i=0;i<10;i++ ) scanf(″%d″,&a[i]); printf(″\n″); for(i=0;i<10;i++) printf(″%d″,*(a+i)); }

(3) 用指针变量指向数组元素。 #include <stdio.h> void main() { int a[10]; int *p,i; for(i=0;i<10;i++) scanf(″%d″,&a[i]); printf(″\n″); for(p=a;p<(a+10);p++) printf(″%d ″,*p); }

例10.6 通过指针变量输出a数组的10个元素。

有人编写出以下程序:
#include <stdio.h> void main() { int?*p,i,a[10]; p=a; for(i=0;i<10;i++ ) scanf(″%d″,p++); printf(″\n″); for(i=0;i<10;i++,p++ ) printf(″%d″,*p); }

这个程序乍

相关文章:
C语言程序设计(第三版)课后习题答案
C语言程序设计第三版课... 34页 免费 谭浩强C程序设计第三版课后... 129页 2财富值如要投诉违规内容,请到百度文库投诉中心;如要提出功能问题或意见建议,...
C语言程序设计(第三版)笔记-谭浩强_免费下载
C语言程序设计(第三版)笔记-谭浩强C语言程序设计(第三版)笔记-谭浩强隐藏>> 第一章 概述 1. C 语言的特点 ①语言简洁、紧凑,使用方便、灵活。共有32个关键字...
C语言程序设计(第三版)-谭浩强_笔记
C语言程序设计(第三版)-谭浩强_笔记_其它考试_资格考试/认证_教育专区。C语言程序设计(第三版) 谭浩强 笔记今日推荐 89份文档 爆笑...
谭浩强_C语言程序设计(第三版)课后习题答案
谭浩强_C语言程序设计(第三版)课后习题答案_工学_高等教育_教育专区。程序是在 VC6.0 下编译的,所以头文件和 TC 的不太一样! 4-8 #include <iostream> #de...
《C语言程序设计》谭浩强版-教学教案k3
《C语言程序设计》谭浩强版-教学教案k3_工学_高等教育_教育专区。今日...C语言程序设计(第三版)-... 8页 免费 c语言程序设计谭浩强版 1页 免费 c...
C语言程序设计+谭浩强+第三版+课后习题答案
C语言程序设计+谭浩强+第三版+课后习题答案_电脑基础知识_IT/计算机_专业资料。1.5 请参照本章例题,编写一个 C 程序,输出以下信息: *** Very Good! *** 解...
C语言程序设计 第三版 部分习题 (谭浩强 著) 清华大学...
C语言程序设计 第三版 部分习题 (谭浩强 著) 清华大学出版社 课后答案 C语言程序设计 第三版 部分习题 (谭浩强 著) 清华大学出版社 课后答案C语言程序设计 第...
C语言程序设计第三版谭浩强课后习题答案完整版
C语言程序设计第三版谭浩强课后习题答案完整版_IT认证_资格考试/认证_教育专区。...main() {char c1=’a’,c2=’b’,c3=’c’,c4=’\101’,c5=’\116...
C语言程序设计第三版谭浩强课后习题答案完整版
C语言程序设计第三版谭浩强课后习题答案完整版_计算机软件及应用_IT/计算机_专业资料。答案 1.5 #include <stdio.h> void main() { printf("* * * * * * ...
谭浩强C程序设计(第三版)上机实验1--实验17上机步骤
C语言程序设计》第三版课... 34页 免费 C++程序设计题解与上机指导... ...60 谭浩强 C 程序设计(第三版)---上机实验 实验 1 C 程序的运行环境和运行...
更多相关标签: