C语言的分支与循环

C语言的分支与循环

——TechZone(Harris)


如果说我们之前写过的代码都是“直男”,也就是说不管三七二十一,一路走到黑,那么今天我们就来见识下“多虑”型的还有“徘徊”型的代码。(你没有看错,代码不仅可以犹豫,还可以徘徊)

不过,在这之前,我还是来介绍两个之前就应该讲到的知识,与下文可能关系不大,但是放哪儿讲都差不多。

字符(串)

我们其实已经知道了C语言只有字符一种变量,就是char,而并没有像某些语言那样规定字符串这种类型。但是,是不是字符串在C语言里面就没有呢?

其实不然,只不过没有专属的类型而已。

定义一个字符的的方法,想必大家都已经掌握了,就像下面这个例子一样:

//Example 01
#include <stdio.h>
int main(void)
{
    char Letter;
    Letter = 'A';
    return 0;
}

这样,我们就定义了一个叫Letter的字符变量,并且赋值为字符A。

那么字符串该如何定义呢?

//Example 02
#include <stdio.h>
int main(void)
{
    char String[8];
    String = {'T','e','c','h','Z','o','n','e'};
    printf("%s",String);
    return 0;
}

这样,我们就定义了一个名字叫String的字符串,还让它输出到我们屏幕上了。如果你了解过数组,就觉得这种方式很像数组对吧,这里提前告诉大家,字符串就是一种数组。但是,如果你去执行下这段代码你就会发现问题了。输出的TechZone后面,还会跟着一些乱码……

咦?我明明只定义了8个字符,怎么还多出来这些鬼东西呢?

那么,我们来了解下C语言时如何处理字符串的。我们可以理解,字符串的长度是有限的,而且长度一旦确定,在其生命周期里就无法再次更改。那么,系统为了确保字符串是真的结束了,会在末尾加一个\0来表示字符串的结束。我们做如下的修改,就可以让字符串正常工作了:

//Example 03
#include <stdio.h>
int main(void)
{
    char String[9];
    String = {'T','e','c','h','Z','o','n','e','\0'};
    printf("%s",String);
    return 0;
}

你可能会想了,这样也太麻烦了吧,要是我要输入一句话,那得多少个引号逗号,最后还可能忘记加\0。你这是把程序员不当人看??

当然不会。我们可以这样定义:

//Example 04
#include <stdio.h>
int main(void)
{
    char String[];
    String = {"TechZone"};
    printf("%s",String);
    return 0;
}

看到了吧,是不是简单很多了?而且后面还会自动补上一个\0,简直就是高枕无忧了。

有人可能有意见了,说你怎么不早点告诉我,害得我差点不学了……

学习当然得从学基础的开始啦,别打我,别打我……

强制类型转换

操作数不同的情况下,大部分的运算符中,都会将精度低的类型转成精度高的类型,这样做,其实也就是为了确保精度罢了。

但是有的时候,我们也许并不希望像系统那样的转换,那么我们可以自己进行强制转换。

具体方法:(类型名称)变量名或值

例如:

//Example 05
#include <stdio.h>
int main(void)
{
    int num;
    num = (float)5/9;
    printf("%d",num);
    return 0;
}

在这个例子中,我们将5这个整型转化成了浮点型,那么为了确保精度,系统也会将后面的9也转化成浮点型。那么这样就可以输出结果,不然结果就是0(因为两个整型相除结果也会取整,然鹅,强制取整的结果就是保留整数,而不是四舍五入)。


分支(条件语句)

我们高中数学中,如果有学过程序框图的同学,应该就还依稀记得程序的几种结构,其中分支循环就是今天要谈的两大结构。

首先我们来看看一些分支里面经常使用的运算符:

  1. 关系运算符

    运算符 优先级
    <
    <=
    >
    >=
    ==
    !=
  2. 逻辑运算符

    运算符 优先级
    ! (逻辑非)
    &&(逻辑与)
    || (逻辑或)

此外,在编译器中,所有的字符都被看成ASCII,因此ASCII字符之间运算或者比较等等都是合法的

短路求值

在C语言中,如果设置两个表达式,在求出第一个式子即可判断出结果的时候,系统就不会继续求第二个式子。这看上去是编译器的一种算法的优化,但是如果写代码的时候不注意的话,很有可能造成一些隐性的漏洞,看下面的例子:

//Example 06
#include <stdio.h>
int main(void)
{
    int a = 3,b = 3;
    (a = 0) && (b = 5);
    printf("a=%d, b=%d\n",a,b);
    //==========================
    (a = 1) || (b = 5);
    printf("a=%d, b=%d\n",a,b);
    return 0;
}

如果你愿意自己尝试的话,可以自己去环境里面试试,如果不愿意的话那直接看下面的结果:

//Consequence 06
a=0,b=3
a=1,b=3

如果没有开头的那段解释,我相信很多初学者都会很懵逼,怎么会这样?

不着急,我们来分析下代码。

我们看看分割线上面的那部分,a和b都被初始化成了整型并且赋值为3。在第一句逻辑判断语句里面,是判断的逻辑与,也就是说,全真则真,一假全假。那么在判断数字的时候,则默认0为假,非0为真。那么这句(a = 0) && (b = 5);前面,a已经被赋值为0,那么已经为假了,在逻辑与的情况下,就算后面的条件再真,整体也是假的了,这就是所谓的一假全假。那么,在判断了第一个表达式之后,就可以做出判断结果,那么后面的b=5不会被执行。

同理,分割线下面的代码也是一个道理。只不过这里判断变成了逻辑或。逻辑或的标准就是一真则真,全假才假。那么,前面的a=1,系统发现,欸,已经是真了哟!那么马上罢工,后面的b=5也就不予理会了。那么输出的结果就是上面那样了,理解了吧?

我们现在单独这样讲这个特性,貌似感觉看不出什么,但是以后写代码的时候其实要留意下短路求值,因为这样子可能就会造成你的程序里面有Bug,而且还相当不好找!!

if语句

if语句实际上就是判断条件,若符合的就输出。之所以叫它分支,那是因为如果到了条件语句,那么这里面的代码就不一定都能执行了,而是看情况来。也就是说,条件语句里面的部分代码,可能你的程序跑完了都没有没用到,因此就叫分支。

那么if语句的通式就像下面那样

if (...)
{
    ...;
}
else if (...)//可选
{
    ...;
}
else if (...)//可选
{
    ...;
}
...;//else if可有多个
else //上述条件都不符合,则执行这个语句,可选
{
    ...;
}

我想大家看里面的注释就能够大致明白的差不多了。其中if语句是必须的,其他的是可选的。else if则表示的是当if的条件不符合的时候,就会继续判断所有的else if,若有符合条件的就执行后面的语句,若都不符合就执行else的语句。如果没有else if而有else,那么当if不符合的时候就执行else后面的语句。若有else if没有else,则无符合条件的话不执行任何语句。else if和else都没有的话,那么if不符合的时候就啥都不干。

看下面的例子,我们来制作一个识别星期的程序并且给出建议:

//Example 07
#include <stdio.h>
int main(void)
{
    int Weekday;
    printf("输入今天星期的数字:");
    scanf("%d",&Weekday);//此处用于获取用户输入的内容并赋值给Weekday
    if (Weekday >= 1 && WeekDay <= 5)
    {
        printf("今天是工作日!好好工作!\n");
    }
    else if (Weekday >=6 && Weekday <=7)
    {
        printf("今天是周末!好好休息!");
    }
    else
    {
        printf("你输入有误哦!");
    }
    return 0;
}

看到了吧,我们成功地运用了分支。这个只是一个简单的例子,以后大家写程序的时候,大概就没有这么简单的了。很多情况下,我们需要嵌套分支来使用。那么嵌套的时候,有些地方就要注意了,下面引用一个例子:

//Example 08
#include <stdio.h>
int main(void)
{
    char isRain,isFree;
    
    printf("是否有空?(Y/N)");
    scanf("%c",&isFree);
    
    printf("是否下雨?(Y/N)");
    scanf("%c",&isRain);
    
    if (isFree == 'Y' || isFree == 'y')
        if (isRain == 'Y' || isRain == 'y')
            printf("记得带伞哦!\n");
    else 
        printf("女神没空!\n");
    return 0;
}

程序实现如下:

//Consequence 08-1
是否有空?(Y/N)Y
是否下雨?(Y/N)Y
记得带伞哦!

貌似没什么问题,但是如果下面这样:

//Consequence 08-2
是否有空?(Y/N)Y
是否下雨?(Y/N)N
女神没空!

我的天,女神有空而且天公作美的情况下,程序居然告诉我女神没空???

其实我们分析下代码。代码的意思,大概是先在有空的情况下,如果下雨就提醒带伞,如果没空就直接告诉你没空。可是上面的问题是怎么回事呢?

其实你们被上面的缩进骗了。从上面的缩进来看,else应该是与第一个if同级,而不是第二个。但是!由于你没有加大括号,C语言没法判断你的语句块从属关系,那么就默认else跟随最近的那一个if。所以,这里的else是跟随第二个if的,这么看就不难理解输出结果了。如果要更改,那么像下面这样就可以了:

//Example 08 V2
#include <stdio.h>
int main(void)
{
    char isRain,isFree;
    
    printf("是否有空?(Y/N)");
    scanf("%c",&isFree);
    
    printf("是否下雨?(Y/N)");
    scanf("%c",&isRain);
    
    if (isFree == 'Y' || isFree == 'y')
    {
        if (isRain == 'Y' || isRain == 'y')
        {
            printf("记得带伞哦!\n");
        }
    }
    else 
        printf("女神没空!\n");
    return 0;
}

建议所有的if都打上大括号,至于怎么打,是个人习惯,比如{是打在if之后还是下一行,这都无所谓,但是确定了就尽量使用一种,不然以后自己看自己写的代码都会感觉很奇怪了。

switch 语句

虽然if语句十分方便而且灵活,但是在某些情况下,使用switch还是会更直观一些。

switch的通式为:

switch(变量名)
{
    case 常量1:语句1;break;
    case 常量2:语句2;break;
    case 常量3:语句3;break;
    case 常量4:语句4;break;
    case 常量5:语句5;break;
    case 常量6:语句6;break;
        ...
    case 常量n:语句n;break;
    default 常量:语句;break;
}

switch语句中case后面只能跟一个确定的值,而不能是范围等等。所以在特定的条件下会很直观,但是不够灵活。

比如上面Example 07中的代码,用switch可以这么改写:

//Example 07 V2
#include <stdio.h>
int main(void)
{
    int (Weekday);
    printf("输入今天星期的数字:");
    scanf("%d",&Weekday);//此处用于获取用户输入的内容并赋值给Weekday
    switch Weekday
    {
        case 1:printf("今天周一,一鼓作气");break;
        case 2:printf("今天周二,再接再厉");break;
        case 3:printf("今天周三,坚持到底");break;
        case 4:printf("今天周四,效率第一");break;
        case 5:printf("今天周五,最后一击");break;
        case 6:printf("今天周六,好好休息");break;
        case 7:printf("今天周日,玩的开心");break;
        default:printf("你输入的有误哦!");break;
    }
}

这样一来是不是很直观呢?但是虽然直观,不是所有的时候都适合用这样的语句,大家根据自身的情况来选就行啦。

至于为什么一定要加break,那是因为其实case在这里面只是一个标记而已,也就是说,如果我满足case3,那么程序就自动跳到case3,然后继续往下执行。假设我一共有case7,那么不加break的话,case3一直到case7的语句都会被逐个执行,明白了吧?


循环

上面分支讲了那么多,现在我们来看看循环。

可以说,循环的出现,大大减少了程序员的工作量。而且正因为有了循环,程序员才能够通过巧妙的算法来使用循环去解决问题。比如,我现在想输出1-100之间的所有整数,如果没学循环,你可能会这么写:

//Example 09
#include <stdio.h>
int main(void)
{
    printf("1\n");
    printf("2\n");
    printf("3\n");
    ...
    printf("100\n");
    return 0;
}

这样写的逻辑思路没有错,但是谁会愿意呢?这样的编程有什么意义?我还不如拿一张纸记下来还更快呢。但是,我们是可以发现其中的规律的,就是每次输出都比之前的数大1,对吧?这就是接下来要讲的循环。

while循环

while循环也就是我们所说的当型循环。它的通式是:

while(条件表达式)
    循环体

当满足一定的条件的时候,程序会自动循环写在循环体中的代码。比如上面的例子09中所体现出的,输出1-100。那么我们实现思路可以先设置一个变量,让这个变量每循环一次就加个1,然后当加到101的时候停止即可。写成代码如下:

//Example 09 V2
#include <stdio.h>
int main(void)
{
    int i = 1;
    while (i < 101)
    {
        printf("%d\n",i);
        i++;
    }
    return 0;
}

这样,程序就自动执行输出我们想要的结果了,比一个一个手动要快了太多。

do-while循环

如果说while循环是个君子,那么do-while就是莽夫。while是先判断再执行,而本少爷则是管他什么,先执行再说。你可能已经猜到了,这就是直到型循环。do-while的语句通式如下:

do
    循环体
while (条件表达式);//(注意,这里有分号)

比如我们要算1加到100的和,也可以通过上面的例子用do-while进行更改:

//Example 09 V3
#include <stdio.h>
int main(void)
{
    int i = 1,sum = 0;
    do
    {
        sum += i;
        i++;
    }
    while (i<101);
    printf("结果是%d\n",sum);
    return 0;
}

这次就不是每次都输出i了,而是每次吧i的值加到sum里面去,这样就可以算出最终结果了。

for循环

通过上面的两个例子大家也应该有点感觉了,在循环之前,一般是要定义一个计数变量,以此作为循环结束的参考。那么既然这样,有没有一种东西可以一次完成呢?这就是for语句。for循环就把计数变量和条件整合到一起去了,看通式:

for (循环初始化;循环条件;循环调整)
    循环体;

具体是啥意思,我们还是看例子。我们把上面的求和代码改成使用for:

//Example 09 V4
#include <stdio.h>
int main(void)
{
    int i, sum = 0;
    for (i = 1; i < 101; i++)
    {
        sum += i;
    }
    printf("结果是%d", sum);
    return 0;
}

其实也是非常直观的。for语句其实三个成分可以不用都写甚至可以不写,但是分号一定要保留。如果没有明确的目的,那么最好不要这样用,因为会使代码可读性下降。

break语句

这个语句用于跳出循环。如果我们遍历i来寻找一个值,但凡找到,那其实就不用往下继续找了,那么就可以在循环中设置一个条件语句,如果找到结果则使用break跳出循环,这样可以节约算力。

但是,break只能跳出当前所在的循环,使用的时候一定注意。

continue语句

跳过本轮循环然后进入下一轮循环。这个直接说可能没什么概念,我们看看下面的例子:

//Example 10
#include <stdio.h>
int main(void)
{
    int ch;
    while ((ch = getchar()) != '\n')
    {
        if (ch == 'e')
        {
            countinue;
        }
        putchar(ch);
    }
    putchar('\n');
    return 0;
}

这段代码是让用户输入一些字符,然后剔除其中的e然后再输出。

//Consequence 10
TechZone
TchZon

应该很好理解吧,只要遇到了e就跳出本次循环,于是putchar()就被跳过去了。

可能有很多同学都觉得forwhile其实差不多,但是,在continue语句里面很有可能就不一样。比如:

//Example 11
#include <stdio.h>
int main(void)
{
    int i;
    for (i = 1; i < 10; i++)
    {
        if (i == 5)
        {
            continue;
        }
        printf("i = %d\n", i);
    }
    return 0;
}

如果让你把这个改成while,可能有的小伙伴会这么改:

//Example 11 V2
#include <stdio.h>
int main(void)
{
    int i = 1;
    while (i < 10)
    {
        if ( i == 5)
        {
            continue;
            i++;
        }
        printf("i = %d\n", i);
        i++;
    }
}

这么看起来好像没有问题,但是执行到continue的时候,程序就陷入了死循环,无法执行到i++。因此不要随意更改已有的循环,如果真的有需要去改,也一定要思考清楚逻辑,想当然多半是会犯错误的。

逗号运算符

当某个部分需要多个表达式的时候,使用逗号运算符来拼接。逗号运算符的优先级是最低的,但是它可以使整体从左到右进行。比如:

//Example 12
#include <stdio.h>
int main(void)
{
    int a,b,c;
    a = (b = 3, (c = b + 4) + 5);
    printf("a = %d, b = %d, c = %d\n", a, b, c);
    return 0;
}

在第6行里面。虽然c = b + 4被优先级最高的小括号括了起来,但是由于逗号运算符,还是优先从b = 3开始执行。

条件运算符

在C语言中,还有一种分支表达式就是条件运算符,它的通式为exp1?exp2:exp3;,其中exp1是条件表达式,当这个表达式判断为时,执行exp2,反之执行exp3。比如下面的分支:

//Example 13
#include <stdio.h>
int main(void)
{
    int a = 3,b = 2;
    if (a > b)
    {
        printf("Max is a\n");
    }
    else
    {
        printf("Max is b\n");
    }
    return 0;
}

这个就可以改成:

//Example 13 V2
#include <stdio.h>
int main(void)
{
    int a = 3,b = 2;
    a > b ? printf("Max is a\n"):printf("Max is b\n");
    return 0;
}

在条件不复杂的情况下使用这种语句其实更加方便,具体还是看个人的习惯来使用。

goto语句

goto语句其实是个历史遗留问题。早期的编程语言都有汇编语言留下来的痕迹,goto就是其中之一。goto的作用就是让程序直接跳转到指定的位置。比如:

//Example 14
#include <stdio.h>
int main(void)
{
    int i = 5;
    while (i++)
    {
        if (i > 10)
        {
            goto Lable;
        }
    }
Lable: printf("Here, i = %d\n", i);
    return 0;
}

i>10的时候直接跳转到Lable处了。

很多初学者刚刚接触goto的时候可能会觉得很实用,但是提醒大家,在实际开发的过程中,最好不用goto语句。因为在代码之间跳来跳去,会破坏程序原有的逻辑。早期的程序代码就是满篇的goto,没有强大的大脑,很难一次性看懂代码在讲什么。所以早期的程序员还真不是一般人能揽下的活儿。

但是,有一种情况可以使用,就是要一次性跳出多层循环的时候,使用goto会更加直观,比多个break好用。

嵌套循环

刚刚讲过,在实际的开发中,多层嵌套循环是会经常出现的。那么,在这里我们举个简单的例子来说明下,输出九九乘法表:

//Example 15
#include <stdio.h>
int main(void)
{
    int i,j;
    for (i=1; i<=9; i++)
    {
        for (j=1; j<=i; j++)
        {
            printf("%d*%d=%-2d\t", i, j, i*j);
        }
        putchar('\n');
    }
    return 0;
}

输出结果:

//Consequence 15
1*1=1
2*1=2   2*2=4
3*1=3   3*2=6   3*3=9
4*1=4   4*2=8   4*3=12  4*4=16
5*1=5   5*2=10  5*3=15  5*4=20  5*5=25
6*1=6   6*2=12  6*3=18  6*4=24  6*5=30  6*6=36
7*1=7   7*2=14  7*3=21  7*4=28  7*5=35  7*6=42  7*7=49
8*1=8   8*2=16  8*3=24  8*4=32  8*5=40  8*6=48  8*7=56  8*8=64
9*1=9   9*2=18  9*3=27  9*4=36  9*5=45  9*6=54  9*7=63  9*8=72  9*9=81

以上就是分支与循环的基础知识了,如果觉得学会了,那就赶紧开始实践。只有通过不断的debug,才能提升自己!这期博客就到此结束啦,是目前为止最长的一篇博客了,下期见!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343