1、C语言研究性学习的路线 现行的多数C语言教材有太多的误区,不仅不能给读者提供有效的学习线索,还常常“误导”读者,于是,“死记硬背”便成了学习C语言的唯一选择。本文以拙作《新编C语言程序设计教程》(清华大学出版社出版,博客 C语言的学习可分为四个阶段。 第一阶段 理解C语言与计算机的关系 理解计算机如何计算。 计算机由五大部件组成,C语言如何使用这五大组成部件? 计算机采用二进制,那么正负号,小数点、字符等如何在计算机中存储?采用不同的编码方式,计算机中的运算与数学上的运算有何异同? 在理解上述问题的基础上,能根据优先级、结合性、数据类型和序列点熟练地对C语言表达式求值,也就是能模
2、拟计算机进行计算。 第二阶段 掌握编程方法 计算机通过连续地对表达式求值(计算)解决实际问题。 掌握逻辑运算之后,理解计算机解决问题的方法,即有条件地重复。编程的关键在于找到利用“重复”解决问题的方法。算一次加法,再算一次加法,又算一次加法,“重复加”,如1+2+3+…+100。大僧人数为1时满足条件吗?大僧人数为2时满足条件吗?大僧人数为3时满足条件吗?“重复试”,如百僧百馍问题。 遇到问题一定要先用自己的超级计算机(大脑)分析问题找出重复,再用循环把重复表达出现,这就是编程。 第三阶段 用函数组织代码 当程序由多个函数组成时,可以借助对函数封闭性的研究来理解这一阶段遇到的问题。
3、 递归函数是个奇迹,自然而优雅地模拟了递归算法。递归算法是更高级的重复。 第四阶段 掌握指针的用法 指针提供了使用内存中相关存储单元的另一种途径。 由于只需某类型的地址,就可使用相关存储单元,指针变量可以提高函数的执行效率。由于void型指针变量的特点,程序中的某些函数可以超越类型的限制而具有了某种通用性。 理解了指针的作用,掌握指针也并非什么难事。 C语言的知识点有: 1. C语言与计算机的关系 2. 表达式的求值 3. 逻辑运算及选择结构 4. 算法及循环结构 5. 数组的作用及准确理解 6. 函数的作用及准确理解 7. 指针的作用及准确理解 8. 其它 这几
4、部分相辅相成,构成了一个有机的整体。分析如下: 一、C语言与计算机的关系 (-)C语言如何驱动计算机 人们利用C语言与计算机沟通,借助编译系统,计算机能够理解并执行C语言程序。C语言是使用计算机的接口。 使用计算机需了解计算机的三大基本原理,特别是二进制编码和五大组成部分。C语言与计算机的五大组成部分关系如图所示。 图1-6 C语言与计算机的联系(1-6为书中编号) 在此需特别注意“变量”这一概念。C语言中用变量标识计算机中的存储单元,使用变量就是使用计算机中的存储器。如果存储单元不能存入某类存储数据如5.5,则相关变量就不能赋值为5.5。这是变量与数学中未知数如x的本质区别。
5、以后在学习指针变量、数组变量时,主要通过分析它们标识存储单元的特点来探求其本质特征。 C语言用函数控制计算机的输入设备和输出设备,那么C语言中的函数与数学中的函数有何异同?本质上没有区别!都是从输入到输出的映射。 不仅应注意函数在C语言中的表现形式,更重要的是明白函数与程序的关系。main函数就是程序,程序的执行过程就是main函数的执行过程。没有main函数,函数再多也不能称为程序。程序中的其它函数只有通过main函数的调用(直接或间接)才有执行的机会。借助编译系统的调试功能,不仅可以直观地观察一个“复杂”程序的执行过程,而且应明白一个道理,程序是一条语句一条语句执行的,并不神秘。 通
6、过学习printf函数的简单使用,既能动手尝试编程,又能体会到编写程序时需要的严谨,尤其是程序的输出与代码的“绝对一致”。
重点:
1. 计算机的五大组成部分与C语言的关系。
2. 变量的概念。(存储单元的标识;分类型;先定义后使用;赋值)
3. 函数在C语言中的表现形式及重要作用。(写程序就是定义main函数)
4. 程序中#include
7、须以二进制形式编码。 虽然数值型数据可以方便地转化成二进制形式,但正负号和小数点位置的编码问题需要解决。同类型的数据在计算机中为何编码长度一致?固定的编码长度又会带来什么样的问题? 整数编码的难点在于为何采用补码形式,补码的符号位为何可以参与运算,且补码加法的运算规律。 小数编码的难点在于精度,如何估算float型和double型的精度呢? 理解了数值型数据的编码规律,则C语言(或其它编程语言)中的基本数据类型也就懂了。稍有疑问就是C语言中的整型为何有许多种类呢? 整型的知识点多而散。 1. 字面量前缀表进制(0八进制0x十六进制),后缀表示类型(l长整型u无符号整型)。 2.
8、输入输出时的格式字符,既有长短(l和h),又有进制(o和x)。 特别注意: 1. 用负数给无符号整型变量赋值,如unsigned long ui = -1;,或输入格式字符为无符号型时,用户却输入了一个负数,如scanf(“%uh”, &i); -1↙。 2. printf函数输出时以格式字符所指明的类型解码变量所标识存储单元的存储内容,而不考虑相关变量的实际类型,即“我的地盘我作主”。 浮点型的知识点。 1. 字面量有小数形式和指数形式两种。 2. 格式字符f,e,E用在printf函数中(输出数据时)没有区别,但是当利用scanf函数获得用户输入的double型数据时,格式字符
9、前面必须加附加格式说明符l。 难点: 1. 精度是指小数形式还是指数形式? 2. 计算机中0.1+0.1并不等于0.2,但是0.25+0.25却等于0.5!什么情况下有误差,什么情况没有误差? 字符型数据编码的原则就是统一标准,每个字符对应一个编号,计算机中存储这个编号(整数),因此C语言中字符型数据可以与整型数据通用。(当用整型解码时就输出编号printf("%d",ch);当用字符型解码时就输出字符如printf("%c",ch)。) 字符型的知识点。 1. ASCII码的一些特点,如小写字母和大写字母谁的编号大? 2. 回车键对应于C语言中哪个字符? 3. 字符型变量多样
10、的初始化方法。 4. 语句short i = 9; 与char ch = ‘9';的区别及联系。 5. putchar函数和getchar函数的使用。 思考与探究 1. 按下5回车后,用户输入的是整数5、小数5还是字符5? 2. 用户输入多个数据时,如zzj↙,下面的代码段如何执行? { char ca, cb; ca = getchar(); cb = getchar(); printf(“%c%c”, ca, cb); } 3. 用不同的数据类型作为求阶乘函数的返回值类型,阶乘函数最大能正确输出哪个整数的阶乘? 二、表达式的求值 (一)基础知识 在
11、后面加一个分号(;),C语言表达式就变成了C语句,此时计算机执行C语句的过程就是对表达式求值的过程,因此表达式求值实际上模拟了计算机的计算。 C语言表达式的重要特征是每个表达式都有一个确定的值及类型。求值时需根据操作符的优先级和结合性来确定运算顺序。能否顺利地求出表达式的值可作为判断C语言表达式是否合法的依据。 从表3-1可知,C语言操作符的种类繁多,但是数学上常见的一些运算在C语言中却并没有相关的操作符,如求平方根、求绝对值、幂运算等,不过C语言提供了相应功能的库函数,如sqrt函数、fabs函数、pow函数等。与printf函数类似,使用这些库函数需要在程序中包含math.h头
12、文件(#include
13、难点: 类型不匹配时的赋值操作。 1. 整型之间相互赋值 1.1 编码长度相同,但有无符号数和有符号数的区别,如unsigned short型和short型,此时只是简单地把被赋值变量的状态设置成赋值变量的存储状态。赋值后两者的值通常不同,如有unsigned uh; short h=-1; ,则uh=h;后uh的值为65535。 1.2 编码长度不同,如long型与short型或unsigned short型。以a=b为例,又分两种情况。当b的编码长度小于a时,赋值原则为赋值后两者的值相同。如有long l; unsigned short uh=65535; sh
14、ort h=-1;则l=uh后,l的值为65535;而l=h后,l的值为-1。由练习2.6可知,编码长度增加而值不变时编码的变化规律。当b的编码长度大于a时,赋值操作只能使a的状态与b的部分字节状态一致,舍弃了b中高位的状态,赋值后两者的值通常不同。 2. 整型与浮点型之间的相互赋值 整数可以看作是小数部分为0的浮点数,而浮点型变量向整型变量赋值时会舍弃小数部分。 注意: 1. 编程时尽量使用安全的赋值操作。(赋值后两者的值相等) 2. 应理解类型不匹配时的赋值原则。 (三)算术表达式 C语言中算术操作符的优先级和结合性虽然和数学上的一致。但由于计算机中不
15、同类型数据的编码格式不同,当类型不同的操作数混合运算时,得出与计算机一致的结果也并非易事。 先讨论整型间的算术运算。 表达式求值通常在运算器中进行,而运算器中专用存储单元的长度是固定的,因此,整型间运算时,当操作数的编码长度“不够”长时会被自动扩充成相应的长度。需注意两点: 1. 短变长是安全的“赋值操作”。 2. C语言中这个长度是“逻辑的”,即编译系统中int型的长度,而非计算机的实际长度。int型在TC中是2个字节,在VC6.0中是4个字节。这也就意味着同样的代码可能可能在TC中不需扩充而直接计算,但在VC6.0中则需要扩充后才能计算。当然,字符型进行算术运算时被
16、看作只有1个字节的整型,无论如何都会被扩充的。 整型间计算的难点是有符号数和无符号数混合运算时,结果也是无符号数,因此结果不会小于0。正如例3-4所示,辨析是否真正为混合运算却有难度。如有unsigned short ui=23;int j=-32;,则ui+j的值大于0吗? 在TC中,ui+j可直接计算,两者为不同类型混合运算,结果为无符号数,ui+j又不可能等于0,故它们的值大于0。 在VC中,ui只有2个字节需要扩充为4个字节的int型,扩充后值虽然没变,但参与运算的“新操作数”却变成了int型,为有符号数了,两个int型相加结果为-9小于0。 注意: 当编
17、码长度扩充为int型后,参与运算的“新操作数”就变成了有符号数。 特别提醒: 不能用printf函数输出结果的方式查看ui+j的结果是否大于0!现阶段查看ui+j的结果是正是负是个难题。〈可以借助算术操作符%来查看结果为正还是为负〉 有浮点型参与的算术运算 由于编码的原因,浮点型运算要比整型的复杂得多。为提高运算精度,操作数有浮点型时,操作数如为整型或float型则自动转化为double型,结果也为double型。注意,正如例3-5所示,表达式的最终结果为double型,并非开始求值时就将所有操作数的类型转化为double型。(5/2+0.3) 算术表达式求值涉及
18、的类型转换与不同类型变量相互赋值类似,原变量的值与类型并不会改变。上述的类型转换均是“自动”进行的,除此之外,类型不匹配时就会出错,如有float f=2.3;,则表达式f%3就是非法的,因为%操作符只能用于整型,求值时浮点型操作数不会自动转换成整型!只有强制改变操作数的类型为整型,相关表达式才能合法,即(int)f%3。 强制类型转换操作的重点在于理解操作过程。 自增自减操作符的重点在于: 3. 作用。(本质为i=i+1的最简洁形式,故3++是非法的) 4. 前置与后置的区别在于表达式的值不同。(int i=2;则表达式i++的值为2,表达式++i的值为3。当然两者都可使i自
19、增1变为3) (四)其它 1. 逗号操作符的作用为将多条C语句连接成一条C语句,因此,它的优先级最低且通常不关心表达式的值与类型。(此处有伏笔。逗号表达式i=3,++i按优先级应先计算自增操作,但实际上并非如此。) 2. C语言并没有严格规定表达式求值的方方面面,因此,特殊情况下某些表达式的值可能与编译系统相关,如(i++)+(i++)+(i++)。这不是C语言学习的重点,只需了解并编程时不使用此类表达式即可。由于可读性的重要性,过于复杂的表达式都不提倡在程序中出现,更别说此类表达式了。 3. 学习表达式有两方面的要求:一方面能根据优先级、结合性、类型转换等求值原则求出复杂表达式的值,
20、像计算机那样“计算”;另一方面在编码时尽量选用简洁易懂无歧义的表达式,以提高程序的可读性。 4. 学习3.6典型例题需注意: 4.1 体会表达式的作用及目的,如学习例3-10时,不仅会对每条表达式求值,而且要体会程序最终的目的。 4.2 会用表达式编程解决问题。如例3-11如何输出用户输入的三位正整数的数字和;例3-12怎样交换两个字符型变量的值;例3-13如何求一元二次方程的根。 关键在于体会程序的执行过程,即程序执行时,其状态(每个变量的值)如何变化,每条语句是如何影响程序状态的。 三、逻辑运算及选择结构 (一) 逻辑运算简介 逻辑结算是计算机支持的一种运算,计算机中运算器的
21、重要组成部分除了支持运算的专用存储单元,就是进行算术运算和逻辑运算的算术逻辑单元。逻辑运算使得编写功能强大的程序成为可能。 逻辑运算是指对结论进行判断并得出一个或为真或为假的值的过程。它的最大特点是运算结果只有两个值:真(对)或假(错)。 最常见的逻辑运算就是“比较”操作,如2<2,2<=2,2==2,2>2等,C语言中称之为“关系”运算。 逻辑量真假的编码需注意: 5. C语言中没有逻辑型变量,必要时基本数据类型变量会被认为是逻辑型变量,此时0为假,非0为真。为假的0可能是整数0,小数0.0,或编号为0的字符(并非字符0,’0’的编号为48)。 6. 逻辑运算的结果为真时用1表示,
22、为假时,用0表示。
特别强调这种编码的不对称性,参与运算时非0为真,表示运算结果时真为1。
关系表达式的重点:
1. “关系“运算就是数学上的比较操作,但需注意计算机中计算的特点,如’a’>’A’为真(字符的值为其编号);3-5u>0为真。
2. 能分析表达式的实际含义,如a%2!=0与“整型变量a是奇数”这一结论等价。(与整型变量a是偶数等价吗?)
3. 关系操作符的优先级为什么低于算术操作符的?(先求值再比较)
4. 能证明表达式a>b==2恒假,而99 23、关系表达式99 24、
重点:
1. 逻辑表达式的特点(与关系表达式的不同之处)。
2. 逻辑操作符的语义(与生活中什么词语接近)。
3. 逻辑操作符为什么可以用真值表来说明其运算规则?写出相关操作符的真值表。(注意,操作数非0为真,结果真为1)
4. 逻辑操作符的优先级有何特点?(1.!为单目操作符优先级很高;2.&&和||低于关系操作符〈先求值再计算〉;3. &&高于||)
5. 能写出与较复杂结论等价的逻辑表达式,如整型变量x,y,z中x,y至少有一个小于z。〈x,y只有一个小于z〉能说出与逻辑表达式等价的结论,如X%4==0 && x%100!=0。
难点:
逻辑表达式的求值。
在C语言中 25、逻辑与&&和逻辑或||采用“短路计算”,其实就是“简便运算”。以a&&b为例,当子表达式a的值为假时,根据规则无论子表达式b为真或假,表达式a&&b的值已经为假,因此,求值时会直接得出表达式a&&b的值为假而不再对子表达式b求值,这就是所谓的“短路计算”。只有子表达式a的值为真时才会对子表达式b求值。如有int i=2;,则表达式 2>3&&++i的值为假(0),且求值后,整型变量i的值仍然为2,因为在求值时子表达式++i由于“短路计算”的缘故没有求值(执行自增)。
与之类似,当子表达式a的值为真时,表达式a||b的值为真,且不对子表达式b求值。
仔细分析表达式2>3&&++i会发现问题 26、自增操作符的优先级最高,可为何不先执行自增操作呢?因为逻辑与&&有一个序列点。简单地说,表达式求值时先考虑序列点的影响,要保证序列点左边的子表达式先于其右边的子表达式求值。由于序列点的作用,2>3先于++i求值,之后又由于“短路计算”,使得子表达式++i没有了执行的机会!
逻辑或||和逗号操作符,也各有一个序列点。&&和||的序列点保证了“短路计算”,逗号操作符的序列点保证了自左向右依次求值子表达式。
下面看看例4-5中“号称”C语言中最难的两个表达式。
#include 27、", 'a' || (a = 1) && (a += 2));
printf("a = %d\n", a);
printf("%d\n", (a = 0) && (a = 5) || (a += 1));
printf("a = %d\n", a);
}
分析时既要考虑序列点的影响,又要考虑优先级的影响,求值顺序如下:
(('a') || ((a = 1) && (a += 2)))
(((a = 0) && (a = 5)) || (a += 1))
注意:赋值表达式“客串”逻辑量只为直观地分析求值过程,实际编程时绝不允许出现类似的用法!
总结 28、
1. C语言中表达式求值需注意四点:优先级,结合性、数据类型和序列点。
2. 复杂变量的定义也可根据“求值”顺序判断其类型。如int *p(int, int);中先括号则变量p为一个函数名称,整个语句为函数声明;而int (*p)(int, int);中两个括号,左结合,先(*p),则变量p为一个指针变量,指向一类函数。
(三) if结构
从上一章的思考题“输出用户输入数的绝对值”可知,此问题虽然简单,但实际上却难于写出程序。如果用户输入一个非负数(用变量f存储用户输入的数据),则直接输出即可;如果用户输入一个负数,则需输出其相反数,可用语句f=-f;表示。难于写出程序的原因在于程 29、序中语句f=-f;是否执行与用户的输入有关或者说与变量f是否为负数有关,而到目前为止,我们所写的程序都是只要语句出现在程序中就肯定会被执行。
判断变量f是非否为负数是逻辑运算,在C语言中处理此类问题时都是先假设一个结论,如变量f为负数,然后用等价的逻辑表达式表示此结论,如f<0,在对表达式求值时会根据变量f的实际值给出一个或为真或为假的结果,从而回答了变量f是否为负数的问题。
求绝对值的问题可总结为:如果f<0为真则执行f=-f;;如果f<0为假则不执行f=-f;。
C语言中if结构可以实现这种操作,相应的语句为:
if(f<0)
f=-f;
学完if结构后写求绝对值的程序,简 30、单得不能再简单!不学if结构写这个程序,即使写不出来,但能分析出问题何在其实也已经成功了,因为找到问题的症结往往就意味着解决了问题。
一点启示:
1. 编写复杂程序需要“逻辑运算”能力,即把条件转化为相应的逻辑表达式的能力。
2. 编程就是分析问题,给出解决问题的步骤,再把步骤“翻译”成相应的C语句。关于步骤:不限形式,初学者最好用自然语言;要根据计算机解决问题的特点〈只会循环〉,因此有些所谓数学上的简便运算,计算机可不一定认为那样做简便,如35×16=35×2×8=560;步骤到语句是编程能力,语句到步骤是“阅读”能力,对于初学者,一定要思考程序中每条语句的作用,争取“读懂”程序,提高 31、阅读”能力,以促进编程能力;步骤就是所谓的“算法”。
3. 常听人说“一出校门所学知识就过时了!”。知识是问题的答案,学会知识并非就是记住答案,更重要是学习知识中蕴含的分析解决问题的方法与能力,从而培养自己分析解决问题的能力。与其记住结论,不如理解证明过程。
重点:
1. if结构的作用。(视条件确定是否执行语句)
2. if结构对程序的影响。(程序中有了多条可执行路径;“正确的程序”需保证每条可执行路径都要正确)
3. if结构的执行流程图。
4. if结构的语法。(分写两行,但是一个整体算一条C语句,只有一个分号。如何理解if(f<0);f=-f;。复合语句在if结构中的作用 32、
5. 以例4-8为例,探讨如何看待程序中的逻辑错误。
6. 以例4-9为例,体会return语句对if结构的影响。
(四) if-else结构
重点:
1. if-else结构的语法及其与if结构的区别和联系。(通过执行流程图分析两者的区别。if-else结构虽然有两个分号但也是一个整体算一条C语句。if-else语句可以用两条if语句改写,反之可以吗?)
2. 把例4-10按升序输出用户输入的两个数的程序改写成用两条if语句实现。体会if-else结构中else分支其实也有条件制约;体会两种实现在执行效率上的差异。
3. 对比例4-11和例4-9,分析return语句对if 33、结构的影响。(含return语句的if结构相当于if-else结构)
难点:
选择结构的嵌套。
1. 画出一个if结构嵌套一个if-else结构的流程图;分析每条可执行路径的条件。 结合例4-12再次讨论分析。
2. 画出一个if-else结构嵌套一个if-else结构的流程图;分析每条可执行路径的条件。结合例4-13再次讨论分析。
3. 讨论例4-13的另一种算法。此算法是一个if-else结构嵌套一个if结构,编程实现这个算法。
图4-8 例4-13的另一种算法
测试程序至少需用3组数据,每组数据检测一条可执行路径。
测试时许多同学会发现逻辑错误!通过调试单步执行程序可 34、找到出错的原因:else的配对!总结C语言中else的配对原则。最后使用复合语句影响else的配对。
通过编程通过调试,“发现”else的配对原则。重要的不是掌握了知识,而是培养了解决问题的能力。
(五) 其它
条件操作符:唯一的三目操作符。
重点:
1. 作用。(用于改写简单的if-else结构)
2. 显然?处有一个序列点,保证了执行过程与if-else结构的完全一致。
3. 总结C语言中操作符的4个序列点的作用。
switch结构
重点:
1. 理解switch结构的语法。(常量表达式,default子句的次序)
2. 分析switch结构的执行过程。(体会case 35、标号的作用。为什么常量表达式的值不能为浮点数?)
3. break语句对switch结构的影响。
4. 有break语句的switch结构的作用。(改写为if结构时如何确定default子句的条件)
(六) 典型例题
关键在于学习如何分析解决问题,培养锻炼自己的编程能力。不要在意你能否做出这些题,如果能,一定要对比分析;如果不能,一张白纸也并非什么坏事。
例4-18升序排列输入的3个整数a,b,c。(注意处理后应让a的值最小,c的值最大)
步骤:
1. 第一步先把b插入到前面有序的子序列中使a,b有序;第二步再把c插入到前面有序的子序列中使a,b,c有序,任务完成。
这一步似乎 36、解决了问题,但又好像没有“解决”问题。体会这种分析方法的特点。(从宏观上研究问题,由于忽略了次要因素从而易于把握关键点。又好像在做可行性分析。)
2. 再讨论每一步的细节,究竟该如何做。
如何使a,b有序?(这一步没有难度,大部分同学都可以写出伪代码)
如何使a,b,c有序?(部分同学忽视了前提条件此时a,b已经有序。可直接思考也可举例分析,但要考虑不同的情况)
分b不小于c和b大于c两种情况,当b>c时需要排序,否则已经有序。显然这是个if结构。
在这里b>c为真意味着什么?
b最大,需要互换b,c的值。换完后a,b,c有序了吗?处于什么状态?(c的值最大,但a的值是否最小还不一 37、定)
注意:
1. 体会从分析问题到给出解决问题的步骤再到写出程序的编码过程。体会程序中每行代码究竟意味着什么样的处理过程。两者对照,体会“编程”。
2. 本题采用的是“自顶向下,逐步求精”的分析方法,体会其特点。
3. 练习4.20的算法也解决了这个问题,体会两者的区别。再通过练习4.21排序5个整数,分析两者的区别。
4. 练习4.21不仅让大家通过实践体会了这种排序方法“先进性”,更重要的是引导大家发现“重复性”,为用“循环”解决这个问题埋下伏笔。
如果学完了例4-18,只学会了对三个数排序而没有体会到什么是编程,什么是“自顶向下,逐步求精”,不能不说是个遗憾。
例4-19 38、判断闰年。
算法:
如果不是4的倍数,则不是闰年。
否则〈是4的倍数〉
如果不是100的倍数,则是闰年。
否则〈是100的倍数〉
如果不是400的倍数,则不是闰年。
否则〈是400的倍数〉
是闰年。
重点:
1. 体会编程时需要的条理性、逻辑性。
2. else if的用法。(仅仅是简写形式)
例4-20百分制成绩转换成A-E五级成绩。
重点:
1. 先分析出 39、关键处理的流程图。再次体会思路的逻辑性,条理性。(也可直接用五条if语句,如if(grade<=100 && grade>=90) putchar(‘A’);,但要改成if-else结构,因为效率问题。改写时注意提醒else也是条件再判断时就没必要用&&了。)
2. 根据流程图写出程序。
3. 对比练习4.23的流程图,同样的问题不同处理过程,分析两者的区别。
例4-21用switch结构改写例4-20。
结合switch结构的作用,针对问题,找出矛盾。转变思路,解决问题。(问题的解法有多种,结合要求,具体问题具体分析。)
例4-22判断用户输入的含+-*/的等式如3.11+3.12 40、6.23是否成立。
算法:
1. 获得用户的输入数据
2. 根据等式中的运算符(+-*/)计算出正确的结果。
3. 比较运算结果与用户的输入结果判断用户输入的等式是否成立。
重点:
1. 用户的输入分为浮点型和字符型。
2. 用switch结构实现“相等关系”的多分支,而非if-else结构。
3. 浮点型数据通常不直接比较是否相等,需要比较时常用的方法是如果两数之差绝对值小于某数就认为两者相等。
所谓“误差累积”是指3.11赋值给变量fa时有误差3.12赋值给变量fb时也有误差,fa与fb之和的误差由于累积跟6.23赋值给变量fc时产生的误差不同了,因此fa+fb不等于f 41、c。(如果fa=6.23;fb=6.23;,则fa和fb当然相等。如果fa=0.3;fb=0.5;fc=0.8;则fa+fb与fc相等吗?)
再回顾问题:switch结构中为何通常不用浮点型做标号?
四、算法及循环结构
/*前言
这注定又是……的一章。
算法的精髓是“循环”,而构造循环的能力只能在一次次的分析、编码、调试、对比、思考中培养。除了一道道的演示,我实在想不出其它的办法可以“速成”,毕竟如表达式求值似的总结个一二三四的速成法仅适用于“知识”。关于编程,有的人似乎“天生”就会,有的人可能“上手”较慢,所幸正如武侠书中的演绎,并非“上手”越快就一定走得越远。
*/ 42、
(一)再谈算法
多次指出,编写程序的过程为:分析问题,得到解决问题的步骤,最后把步骤“翻译”成相应的语句。解决问题的步骤就是算法。问题的解法可能有多种,行之有效的方法通常与欲使用的工具相关,也就是说,必须结合工具的特点思考问题的解法。
计算机解决问题主要靠循环!运用循环的熟练程度直接反映了一个程序员的编程水平。毫不夸张地说,思考问题的解法就是考虑“如何构造循环”。
循环是有条件的重复,是“螺旋式上升”,是“波浪式前进”。
(二)while循环结构
重点:
1. while循环结构的形式。
其与if结构有何异同?(从形式、执行过程、流程图)
2. while循环结构的特点 43、
while循环结构的循环体执行多少次?(再次强调由循环控制表达式决定)
如何使用while循环结构输出五个*号?(由具体示例展开,总结while循环结构使用的3个特点:设置循环变量的初值、决定循环控制表达式、调整循环变量的值)
由例5-1熟悉不同while循环结构的执行情况。(循环次数固定的,输出’A’至‘Z’;循环次数不定的,(1.0/i>1.23e-3),体会作用“求出了倒数小于0.00123的最小整数”,注意循环执行完毕后循环变量的值;复杂的循环,含有switch结构。可结合循环结构使用特点)
难点:
while循环结构的用法。(关键在于如何得到“重复”的过程)
例5-2 44、求1+2+3+…+100的和。
1. 先计算1+2+3+4+5,总结规律:“重复算加法”,加数为“上一次的和”与“一个新的加数”。如何使用while循环结构来模拟这个过程呢?加到何时为止?新的加数大于5为止,因此循环控制表达式为i<=5(i代表新的加数),循环体为sum=sum+i(sum为上一次的和)。
2. 尝试写程序。
3. 分析执行过程,并与列式计算过程对比,体会两者的共同之处。
例5-3输出一个正整数各位上的数字和。
1. 以2325为例,2+3+2+5=12,本质上也是“重复算加法”,但此例与例5-2有何不同?新的加数为“各位上的数字”,没有规律,需要在加之前求出它。(考 45、虑是从左向右算呢,还是从右向左算?)加到何时为止?“加完”为止,即新的加数为0时为止,因此“新的加数为0”可以作为循环控制的条件。当然此时就可写出程序了。(不过程序可能出现错误,如1002时。)再想“什么情况下新的加数为‘标志结束的’0?”,则经过深入分析可以得到正确的程序。
2. 尝试写出程序。
3. 分析执行过程,体会程序中每条语句的作用。体会如何让循环“螺旋式上升”,“波浪式前进”。(在下次循环之前先做好准备,“恢复状态”,此例为“去掉”加过的数字。)
例5-4 百僧分百馍
1. 总共有多少种可能?对于每种情况,处理方式相同吗?(本质也是重复)
2. 尝试写出并分析程序的执行情 46、况。
3. 体会用计算机解决问题的特点。(一个一个地试。美其名曰“穷举法”)
4. 这个程序在理论上有问题。(应确保小僧的人数为3的倍数,即需同时保证(100-i)%3==0为真)
(三)for循环结构
为了使循环结构更紧凑,更优雅,C语言提供了for循环结构。
重点:
1. for循环结构的一般形式。(典型循环结构的3个特点与for循环结构的对应关系。理解了对应关系自然也就弄懂了for循环结构。)
2. for循环结构的特点。(表达式1和表达式2可以为逗号表达式。表达式省略后应如何理解)
难点:
虽然同样为构造循环,同样为“找重复”或“穷举”,但是此处示例的难度有所增加。
47、
例5-6输出100以内奇数之和与偶数之和。
1. 不同的理解可能有不同的算法。1+3+5+…+99 2+4+6+…+100,可以用两个循环结构实现。也可合并为一个从1至100的循环,循环体内用if-else结构。
2. 最终归结为for(i=1;i<=99;i+=2){oddsum+=i;evensum+=i+1;}。
3. 对循环过程分析得越透彻算法的效率越高。
例5-7求正整数m和n的最小公倍数。
1. 公倍数i可能的取值为m,2*m,3*m,…n*m,如果i也为n的倍数,则i就是所要求的公倍数。显然是穷举法。
2. 写出程序。运行时会发现错误。分析出错的原因。(程序在什么情 48、况下出错,什么情况下不会出错?)
3. 如何修改?(修改循环控制的条件。应为i不为n的倍数,而非i<=n*m)
4. 循环体竟然是传说中的空语句,呵呵。
例5-8输出斐波那契数列的前30项。
1. 分析从第3项开始的计算过程,手工模拟计算直到熟悉计算过程,找出计算第i项时的“标准化”步骤。
2. 注意输出结果的安排。
总结:
循环结构的关键在于“第i次循环”有什么作用?循环是如何模拟手工过程的。为了循环必须把手工过程“标准化”。
(四)break语句和continue语句
重点:
1. break语句与continue语句的区别和联系。(常用于if结构中,在某些条件下均可影 49、响循环结构的执行。break语句终止循环结构的执行,而continue语句终止循环结构的一次执行。)
2. break语句的作用为终止所属结构的执行。(注意其是属于哪个循环结构,有时可能是哪个switch结构)
3. continue语句如何用选择结构替换?
难点:
例5-9判断正整数n是否为质数。
分析:如果从2至n-1均不为n的因数,则n就是质数。处理过程为:2是因数吗?3是吗?循环(穷举)。
循环变量i从2至n-1,对于第i次循环,作用为判断i是否为n的因数。如果i为n的因数,则不用再判断其它数,可以立即终止循环了。否则什么也不用做,显然循环体为一个if结构。
循环执行完毕,如何判断n是否为质数?或者说,如何判断2至n-1中是否有n的因数?
假设其中有n的因数,则循环如何执行?没有的话,循环又会如何执行?
循环结束后循环变量i的值反映了循环的执行情况,并最终决定了n是否为质数。
注意:
1. 体会分析过程。
2. 用特殊数据1测试程序时应输出不为质数。
(五)循环的嵌套
重点:复杂循环结构的构造。仍然为“找重复”或“穷举”,不过要有一点“自顶向下”的思路。
例5-11输出如下图形
*
**
***
****
*****
1. 如






