C语言基础2——初级程序结构的构思

C语言基础2——初级程序结构
本文章版权属于http://yzfy.org 雨中飞燕之家论坛所有,转载请务必注明出处

之前的C语言基础帖子里讲解过int,char和double,
结构讲了分支和循环,现在再来更深入的讲解。


一.更简洁的while循环
其实你要是弄懂了for,那while循环的理解只要花你几秒钟。
while(表达式)
{
    //这里写循环的内容
}


for( ; 表达式;)
{
    //这里写循环的内容
}

两者完全等价。不过有一点不同是,for里的表达式可以忽略,
忽略的话就作为true处理,而while里的表达式不可以忽略。


while还有一种循环方式:
do
{
    //这里写循环的内容
}while(表达式);


这种循环方式不能直接改写成for。如果要硬改成for的形式,
那就是:


//这里写循环的内容,要和for里面的一样
for( ; 表达式;)
{
    //这里写循环的内容
}


可以看得出如果需要循环的内容至少要执行一次的情况下,采用
do-while结构可以使代码更为简洁。


二.更有效地运用循环
这里结合一些实例来介绍:

题目1. 因子分解:
输入:45
输出:3,3,5,


这时最常规的思维,当然就是一个一个数去除啊,
你可能会立刻写出:


#include <stdio.h>
int main(void)
{
    int n;
    scanf("%d", &n);        // 输入要分解的n
    for(int a=2; a<=n; ++a)  // a用于试除
    {
        //
    }
    return 0;
}


好了,写到这里你会不会卡在这里?怎么知道一个数除以另一个
数的结果是没有小数的或者要算出余数呢?判断一个数是不是整数
你可能会想if((int)f == f),其中f是浮点数,用强制类型转换
(int)f强制把f转化为int整数类型。但你要注意这样写是不好的,
原因以后解释。这里更有效的方法是使用%运算符。
%运算符就是求余数,a%b就是a除以b所得到的余数。
知道这一点后,然后你可能会写成:


#include <stdio.h>
int main(void)
{
    int n;
    scanf("%d", &n);        // 输入要分解的n
    for(int a=2; a<=n; ++a)  // a用于试除
    {
        if(n % a == 0)      // 要余数为0
        {
            printf("%d,", a);// 输出成功试除的这个数
        }
    }
    return 0;
}


看起来貌似已经成功了,真是这样吗?运行一下试试看!
输入6,输出2,3,6  (好像是对的)
输入12,输出2,3,4,6,12  (明明变成了输出所有大于1的约数)
知道不知道问题是什么?题目是说因子分解,现在却是输出了约数。

现在你应该考虑的是你平时是怎么进行因子分解的:
  2|30
  3|15
  5|5
    1
所以输入30应该输出2,3,5
和刚刚的代码的过程有什么差别之处?
很明显的差别是试除的时候,并不是用最原始的数,是用的上一次
除法算出来的结果,每找到一个能整数的数以后,
就需要n = n/a;这样。然后,你可能会把代码写成:


#include <stdio.h>
int main(void)
{
    int n;
    scanf("%d", &n);        // 输入要分解的n
    for(int a=2; a<=n; ++a)  // a用于试除
    {
        if(n % a == 0)      // 要余数为0
        {
            printf("%d,", a);// 输出成功试除的这个数
            n = n/a;        // 去掉a这个因子
        }
    }
    return 0;
}


貌似成功了,是吗?再多找点数试试:
输入600,输出2,3,5,10
明显不对啊,问题出在哪里?你再自己手算一下:
  2|600
  2|300
  2|150
  3|75
  5|25
    5|5
      1
恍然大悟了没?那个a不能只试除一次。可能你又会问,
但那个a每循环一次就自加一次啊?
但代码是活的,你可以不让它每次都自增,你完全可以把++a删掉。
但你可能又问删掉了那怎么控制循环结束?
这时,你应该再回想你刚刚是怎么手算的,什么时候用2试除,
什么时候用更大的数试除。想明白逻辑关系之后,你就会知道,
如果当前试除的这个数不能整除,才去试除下一个数,
在原来的代码怎么加不能整除的条件?很简单,在那个if块的后面
再加一个else就可以了。完整正确代码如下:


#include <stdio.h>
int main(void)
{
    int n;
    scanf("%d", &n);        // 输入要分解的n
    for(int a=2; a<=n; )    // a用于试除,这里不写a自加的代码
    {
        if(n % a == 0)      // 要余数为0
        {
            printf("%d,", a);// 输出成功试除的这个数
            n = n/a;        // 去掉a这个因子
        }
        else
        {
            ++a;            // 试除失败,试下一个数
        }
    }
    return 0;
}

本文章版权属于http://yzfy.org 雨中飞燕之家论坛所有,转载请务必注明出处


题目2: 3n+1 (详见http://yzfy.org/bbs/viewthread.php?tid=115)
这里为了讲解,只要你算出一个数,把变化过程输出来,也就是:
输入:3
输出:3,10,5,16,8,4,2,1

这里你明显会发现循环次数不确定,在阅读了本文第一点后,
你可能很自然地想到用while来控制这个循环:


#include <stdio.h>
int main(void)
{
    int n;
    scanf("%d", &n);        // 输入要计算的n
    while(n != 1)            // n最后要变成1才退出
    {
        //
    }
    return 0;
}


首先第一反应就要写出这个框架。然后再在while里头做文章。
这个题非常简单,直接判断被2除的余数是不是0,分开来处理:


#include <stdio.h>
int main(void)
{
    int n;
    scanf("%d", &n);        // 输入要计算的n
    while(n != 1)            // n最后要变成1才退出
    {
        if(n % 2 == 0)
        {
            //
        }
        else
        {
            //
        }
    }
    return 0;
}


接着按题意,在中间添油加醋:

#include <stdio.h>
int main(void)
{
    int n;
    scanf("%d", &n);      // 输入要计算的n
    while(n != 1)        // n最后要变成1才退出
    {
        if(n % 2 == 0)
        {
            n = n/2;
        }
        else
        {
            n = 3*n + 1;  // 千万别写成3n + 1,这是低级错误
        }
    }
    return 0;
}


最后,还要输出。你可能问输出写在哪里好?应该怎么去写?
如果把那个处理的if..else看成一块,输出的地方有两个选择,
写在if前面或者整块的后面。两种写法有区别不?千万别以为
是一样,运行是按顺序的,在中间过程里的n改变了,放在前
和后的结果肯定不同。你要是动手实验一下,
加一个printf("%d,",n);
分别放在前面和放在后面的话,你就会发现,输入3,
放前面的输出:3,10,5,16,8,4,2
放后面的输出:10,5,16,8,4,2,1
两种放法和要求相比都少了一个数,分别少了最后面和最开头
的数。你可能还会问,这错在哪里了,怎么才能把少了的数也
给输出来啊?

那我说,这还算错吗?你缺少了哪个就补上那个嘛!
你少了最开头的,就在while前面加printf("%d,",n);
少了最后面的,就在while后面面加printf("%d,",n);或者
printf("1,"); 反正最后n也是1嘛!



题目3.素性判断
输入一个大于1的整数,判断这个数是不是质数,输出yes或者no
输入:41
输出:yes

你可能马上想到,这和刚刚的第一题不是差不多吗?的确差不多,
你可能也马上写出:


#include <stdio.h>
int main(void)
{
    int n;
    scanf("%d", &n);        // 输入要分解的n
    for(int a=2; a<=n; ++a)  // a用于试除
    {
        if(n % a == 0)      // 余数为0就是说不是素数了
        {
            //
        }
    }
    return 0;
}


好了,这里很明显,中间要输出no,你可能接着写成:

#include <stdio.h>
int main(void)
{
    int n;
    scanf("%d", &n);        // 输入要分解的n
    for(int a=2; a<=n; ++a)  // a用于试除
    {
        if(n % a == 0)      // 余数为0就是说不是素数了
        {
            printf("no\n");
        }
    }
    return 0;
}


好了,问题来了哪里是输出yes的地方?
你可能问写成这样可以不:


#include <stdio.h>
int main(void)
{
    int n;
    scanf("%d", &n);        // 输入要分解的n
    for(int a=2; a<=n; ++a)  // a用于试除
    {
        if(n % a == 0)      // 余数为0就是说不是素数了
        {
            printf("no\n");
        }
        else
        {
            printf("yes\n");
        }
    }
    return 0;
}


你运行一下就发现,输出了很多行yes和no的东西,明显不对。
很明显的一点,你要是找到了一个因子,输出了no以后,后面
你并不需要再循环下去了。现在你可能写成:


#include <stdio.h>
int main(void)
{
    int n;
    scanf("%d", &n);        // 输入要分解的n
    for(int a=2; a<=n; ++a)  // a用于试除
    {
        if(n % a == 0)      // 余数为0就是说不是素数了
        {
            printf("no\n");
            break;
        }
        else
        {
            printf("yes\n");
        }
    }
    return 0;
}


总该可以了吧!运行一下看看!输入13,输出了很多行的yes,
再加一行no。晕了没?从结果你可以看出,只要找到了一个不能
整数的数,他就输出一个yes,导致大量输出yes。最后一个no也
很稀奇,这个no是怎么来的?留意一下那个for循环,中间那个
循环条件,写的是什么来着?
找到问题以后就要解决问题,首先肯定不能在for里面输出yes,
因为你要判断完2到n-1之间的数(注意不是2到n),所以你要把
输出yes的语句放在外面。然后要怎么判断要不要输出?其中一个
办法是做标记,加一个变量,记录着有没有输出过no,没有的话
才输出yes。代码可能改写如下:


#include <stdio.h>
int main(void)
{
    int n;
    int iMark = 0;          // 做标记
    scanf("%d", &n);        // 输入要分解的n
    for(int a=2; a<n; ++a)  // a用于试除,注意a<n
    {
        if(n % a == 0)      // 余数为0就是说不是素数了
        {
            printf("no\n");
            iMark = 1;      // 标记输出过no了
            break;
        }
    }
    if(iMark == 0) // 如果标记是0,就是没输出过no
    {
        printf("yes\n");
    }
    return 0;
}


貌似是个不错的想法,输出结果也完全正确了。
但这样就满足了吗,可不可以不加那个变量?
我告诉你,完全可以不加,那个变量可以用a来代替。
怎么代替呢?如果找到了一个因子,就会执行break。
执行break语句的时候,a<n这个条件一定是满足的。
而如果一个因子也没找到的话,最后会因为a<n这个条件不满足
而跳出循环,所以可以改写如下:


#include <stdio.h>
int main(void)
{
    int n;
    int a;                  // 注意变量的作用域的问题
    scanf("%d", &n);        // 输入要分解的n
    for(a=2; a<n; ++a)      // a用于试除,注意a<n
    {
        if(n % a == 0)      // 余数为0就是说不是素数了
        {
            printf("no\n");
            break;
        }
    }
    if(a >= n) // 和a<n相反就可以了,或者可以更直接用a==n
    {
        printf("yes\n");
    }
    return 0;
}