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(因为两个整型相除结果也会取整,然鹅,强制取整的结果就是保留整数,而不是四舍五入)。
分支(条件语句)
我们高中数学中,如果有学过程序框图的同学,应该就还依稀记得程序的几种结构,其中分支和循环就是今天要谈的两大结构。
首先我们来看看一些分支里面经常使用的运算符:
-
关系运算符
运算符 优先级 < 高 <= 高 > 高 >= 高 == 低 != 低 -
逻辑运算符
运算符 优先级 ! (逻辑非) 高 &&(逻辑与) 中 || (逻辑或) 低
此外,在编译器中,所有的字符都被看成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()
就被跳过去了。
可能有很多同学都觉得for
和while
其实差不多,但是,在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,才能提升自己!这期博客就到此结束啦,是目前为止最长的一篇博客了,下期见!