C语言研究性学习的路线(3)

本文探讨了C语言的研究性学习,重点在于逻辑运算及选择结构。介绍了逻辑运算的背景、关系运算、逻辑表达式、if结构以及相关难点。文章通过实例分析帮助读者理解逻辑运算在C语言中的应用,并强调了编程中逻辑思维的重要性。
 

C语言研究性学习的路线

现行的多数C语言教材有太多的误区,不仅不能给读者提供有效的学习线索,还常常“误导”读者,于是,“死记硬背”便成了学习C语言的唯一选择。本文以拙作《新编C语言程序设计教程》(清华大学出版社出版,配套视频zeq126.56.com)为基础,探讨了C语言的研究性学习。

C语言的知识点有:

1.         C语言与计算机的关系

2.         表达式的求值

3.         逻辑运算及选择结构

4.         算法及循环结构

5.         数组的作用及准确理解

6.         函数的作用及准确理解

7.         指针的作用及准确理解

8.         自定义数据类型及文件

这几部分相辅相成,构成了一个有机的整体。分析如下:

三、逻辑运算及选择结构  

(一)   逻辑运算简介

逻辑结算是计算机支持的一种运算,计算机中运算器的重要组成部分除了支持运算的专用存储单元,就是进行算术运算和逻辑运算的算术逻辑单元。逻辑运算使得编写功能强大的程序成为可能。

逻辑运算是指对结论进行判断并得出一个或为真或为假的值的过程。它的最大特点是运算结果只有两个值:真(对)或假(错)。

最常见的逻辑运算就是“比较”操作,如2<2,2<=2,2==2,2>2等,C语言中称之为“关系”运算。

逻辑量真假的编码需注意:

1.        C语言中没有逻辑型变量,必要时基本数据类型变量会被认为是逻辑型变量,此时0为假,非0为真。为假的0可能是整数0,小数0.0,或编号为0的字符(并非字符0,’0’的编号为48)。

2.       逻辑运算的结果为真时用1表示,为假时,用0表示。

  特别强调这种编码的不对称性,参与运算时非0为真,表示运算结果时真为1。

关系表达式的重点:

1.  “关系“运算就是数学上的比较操作,但需注意计算机中计算的特点,如’a’>’A’为真(字符的值为其编号);3-5u>0为真。

2.  能分析表达式的实际含义,如a%2!=0与“整型变量a是奇数”这一结论等价。(与整型变量a是偶数等价吗?)

3.  关系操作符的优先级为什么低于算术操作符的?(先求值再比较)

4.  能证明表达式a>b==2恒假,而99<x<2恒真。

(二)   逻辑表达式

x为三位的正整数,在数学上可以用99<x<1000表示,但在C语言中无论x为何值,关系表达式99<x<1000恒真,显然两者并不通用。只有99<x为真并且 x<1000为真时,x才是三位的正整数,C语言中用逻辑操作符&&(逻辑与)表示这种“并且”的关系,即99<x && x<1000与数学上99<x<1000等价。逻辑操作符的特点是相关操作数为逻辑量,即3&&5实际上“真且真”,其中的整数会被认为是逻辑量“真”。注意C语言的这个特点,如关系操作会进行数学上的比较,它会把操作数作为数学上的“数”,99<x<2中99<x的结果为逻辑量,但在与2进行比较操作时会被认为是数1或数0,因此它肯定小于2,整个表达式的结果自然恒真。逻辑量只有两个,显然,逻辑与&&的运算总共只有4种情况。

重点:

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语言中,逻辑与&&和逻辑或||采用“短路计算”,其实就是“简便运算”。以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会发现问题,自增操作符的优先级最高,可为何不先执行自增操作呢?因为逻辑与&&有一个序列点。简单地说,表达式求值时先考虑序列点的影响,要保证序列点左边的子表达式先于其右边的子表达式求值。由于序列点的作用,2>3先于++i求值,之后又由于“短路计算”,使得子表达式++i没有了执行的机会!

逻辑或||和逗号操作符,也各有一个序列点。&&和||的序列点保证了“短路计算”,逗号操作符的序列点保证了自左向右依次求值子表达式。

下面看看例4-5中“号称”C语言中最难的两个表达式。

#include <stdio.h>

void main( )

{

int a = 0;

        printf("%d\n", '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))

注意:赋值表达式“客串”逻辑量只为直观地分析求值过程,实际编程时绝不允许出现类似的用法!

总结:

1.  C语言中表达式求值需注意四点:优先级,结合性、数据类型和序列点。

2.  复杂变量的定义也可根据“求值”顺序判断其类型。如int *p(int, int);中先括号则变量p为一个函数名称,整个语句为函数声明;而int (*p)(int, int);中两个括号,左结合,先(*p),则变量p为一个指针变量,指向一类函数。

(三)   if结构

从上一章的思考题“输出用户输入数的绝对值”可知,此问题虽然简单,但实际上却难于写出程序。如果用户输入一个非负数(用变量f存储用户输入的数据),则直接输出即可;如果用户输入一个负数,则需输出其相反数,可用语句f=-f;表示。难于写出程序的原因在于程序中语句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结构后写求绝对值的程序,简单得不能再简单!不学if结构写这个程序,即使写不出来,但能分析出问题何在其实也已经成功了,因为找到问题的症结往往就意味着解决了问题。

一点启示:

1.  编写复杂程序需要“逻辑运算”能力,即把条件转化为相应的逻辑表达式的能力。

2.  编程就是分析问题,给出解决问题的步骤,再把步骤“翻译”成相应的C语句。关于步骤:不限形式,初学者最好用自然语言;要根据计算机解决问题的特点〈只会循环〉,因此有些所谓数学上的简便运算,计算机可不一定认为那样做简便,如35×16=35×2×8=560;步骤到语句是编程能力,语句到步骤是“阅读”能力,对于初学者,一定要思考程序中每条语句的作用,争取“读懂”程序,提高“阅读”能力,以促进编程能力;步骤就是所谓的“算法“。

3.  常听人说“一出校门所学知识就过时了!”。知识是问题的答案,学会知识并非就是记住答案,更重要是学习知识中蕴含的分析解决问题的方法与能力,从而培养自己分析解决问题的能力。与其记住结论,不如理解证明过程。

重点:

1.  if结构的作用。(视条件确定是否执行语句)

2.  if结构对程序的影响。(程序中有了多条可执行路径;“正确的程序”需保证每条可执行路径都要正确)

3.  if结构的执行流程图。

4.  if结构的语法。(分写两行,但是一个整体算一条C语句,只有一个分号。如何理解if(f<0);f=-f;。复合语句在if结构中的作用)

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结构的影响。(含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组数据,每组数据检测一条可执行路径。

测试时许多同学会发现逻辑错误!通过调试单步执行程序可找到出错的原因:else的配对!总结C语言中else的配对原则。最后使用复合语句影响else的配对。

通过编程通过调试,“发现”else的配对原则。重要的不是掌握了知识,而是培养了解决问题的能力。

(五)   其它

条件操作符:唯一的三目操作符。

重点:

1.  作用。(用于改写简单的if-else结构)

2.  显然?处有一个序列点,保证了执行过程与if-else结构的完全一致。

3.  总结C语言中操作符的4个序列点的作用。

switch结构

重点:

1.  理解switch结构的语法。(常量表达式,default子句的次序)

2.  分析switch结构的执行过程。(体会case标号的作用。为什么常量表达式的值不能为浮点数?)

3.  break语句对switch结构的影响。

4.  有break语句的switch结构的作用。(改写为if结构时如何确定default子句的条件)

(六)   典型例题

关键在于学习如何分析解决问题,培养锻炼自己的编程能力。不要在意你能否做出这些题,如果能,一定要对比分析;如果不能,一张白纸也并非什么坏事。

例4-18升序排列输入的3个整数a,b,c。(注意处理后应让a的值最小,c的值最大)

步骤:

1.  第一步先把b插入到前面有序的子序列中使a,b有序;第二步再把c插入到前面有序的子序列中使a,b,c有序,任务完成。
这一步似乎解决了问题,但又好像没有“解决”问题。体会这种分析方法的特点。(从宏观上研究问题,由于忽略了次要因素从而易于把握关键点。又好像在做可行性分析。)

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的值是否最小还不一定)

注意:

1.           体会从分析问题到给出解决问题的步骤再到写出程序的编码过程。体会程序中每行代码究竟意味着什么样的处理过程。两者对照,体会“编程”。

2.           本题采用的是“自顶向下,逐步求精”的分析方法,体会其特点。

3.           练习4.20的算法也解决了这个问题,体会两者的区别。再通过练习4.21排序5个整数,分析两者的区别。

4.           练习4.21不仅让大家通过实践体会了这种排序方法“先进性”,更重要的是引导大家发现“重复性”,为用“循环”解决这个问题埋下伏笔。

如果学完了例4-18,只学会了对三个数排序而没有体会到什么是编程,什么是“自顶向下,逐步求精”,不能不说是个遗憾。

例4-19判断闰年。

算法:

如果不是4的倍数,则不是闰年。

    否则〈是4的倍数〉

         如果不是100的倍数,则是闰年。

              否则〈是100的倍数〉

                   如果不是400的倍数,则不是闰年。

                        否则〈是400的倍数〉

                                   是闰年。

重点:

1.  体会编程时需要的条理性、逻辑性。

2.  else if的用法。(仅仅是简写形式)

例4-20百分制成绩转换成A-E五级成绩。

重点:

1.           先分析出关键处理的流程图。再次体会思路的逻辑性,条理性。(也可直接用五条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=6.23是否成立。

算法:

1.  获得用户的输入数据

2.  根据等式中的运算符(+-*/)计算出正确的结果。

3.  比较运算结果与用户的输入结果判断用户输入的等式是否成立。

重点:

1.  用户的输入分为浮点型和字符型。

2.  用switch结构实现“相等关系”的多分支,而非if-else结构。

3.  浮点型数据通常不直接比较是否相等,需要比较时常用的方法是如果两数之差绝对值小于某数就认为两者相等。
所谓“误差累积”是指3.11赋值给变量fa时有误差3.12赋值给变量fb时也有误差,fa与fb之和的误差由于累积跟6.23赋值给变量fc时产生的误差不同了,因此fa+fb不等于fc。(如果fa=6.23;fb=6.23;,则fa和fb当然相等。如果fa=0.3;fb=0.5;fc=0.8;则fa+fb与fc相等吗?)

    再回顾问题:switch结构中为何通常不用浮点型做标号?

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值