关于流程控制,一般来说,计算机使用有着三种流程:一就是正常走,按照预先设定的语句序列执行,二就是重复做那么几个动作,直到满足条件,三就是有几条分支走向,根据目前条件去选择分支序列执行。
在C语言中,针对循环这一特性的就有常见于循环次数不明确的while关键字,循环次数明确的for关键字。
while,英文意思是当…时候,所以它的通用形式很明显,如下:
while (expression)
statement
上面伪代码解读起来就是,while关键字出现,判断括号内表达式是否正确,如果正确就继续执行statement中语句,statement语句可以是以分号结尾的单条语句也可以是大括号括起来的语句块,如果不正确就跳过statement部分,执行后面的语句。
关于expression,这里需要知道的就是它是否为真。很多数据类型都有着自己的零值,而非零值的就是真值,当然,布尔值就是最直显的真假值。常见的表达式如比较常见的"1 == 2"、“1 != 7”、"7 > 6"这种数值判断,同类型间比对大小,不同类型的比较往往有一个隐性的类型转换,如整型和浮点型的比较,往往是整型转换成浮点型,特殊的比如字符型和整型的比较:'a’和6的比较,由于浮点型也被称为单字节型整数,所以转换起来不会丢失精度,两者的比较相对整型和浮点型的比较要更加准确可靠,而后者往往因为精度丢失无法得到理想输出。
另外,在C语言中可用的用于比较的运算符有:<、>、<=、>=、==、!=,常见的用于比较数值的可以用运算符进行,但如果是有特殊规则的比较–比如比较字符往往就要用一些比较特殊的方法,常用的就是ctype.h、string.h中的函数,也有一些是属于自定义的函数方法。掌握循环,非常重要的就是控制expression这一个循环条件,因为循环中往往会对这一条件进行操作,如果操作不当就会出现无限循环,一直没有停止循环的条件。
简单例子:
//正确例子
int i = 10;
while(i < 20)
i++;
printf("i: %d\n", i); //"i: 20"
//错误例子1
int i = 10;
while(i > 10)
i++;
printf("i: %d\n", i); //"i: 10"
//错误例子2
int i = 10;
while(i > 9)
i++;
如上, 可以设定终止条件为i自加将达到的值,比如20,当它自加达到20时,不符合while条件,判定表达式为非真,while循环中断;而当它设定为大于10时,根本没达到循环条件,所以根本没进入循环;另外设定为一开始就满足的条件,大于9,那它一直自加都是满足条件的,根本不会退出循环,而是会一直运行下去,有时候病毒就是干的这种活,就这么做无用的事但是又占着你的资源。
char ch;
while(scanf("%c", ch)&&(ch != 'n'))
printf("是的,我还想进行下去");
printf("不想玩了,拜拜");
作为expression,随便一个语句都能成立,比如上面的scanf语句,在成功接收数据后再判断赋值初始化后的ch变量接收的是否不是’n’字符,这个走完才确定循环是否进行。简单地说,就是输入的是否是n,不是就继续循环,是就退出循环。需要注意的是,上面的&&,是逻辑运算符的一种,表示两个都为真表达式方为真。上面的例子可以进一步简单化:
char ch;
scanf("%c", ch);
while(ch != 'n'){
scanf("%c", ch);
printf("是的,我还想进行下去");
}
printf("不想玩了,拜拜");
上面只是一个范例,对于很多程序来说,持续运行就是进入了一个类似while循环的无穷循环,退出条件是可控的。
这也是while循环的一种,不过正常的while循环是先进行判断,然后再来确定是否执行循环语句,但do-while不同,它是先跑了一次,然后再来看是否符合循环条件,这样无论是否符合条件它都先跑了一次。为何?因为它的通用形式是这样的:
do
statement
while(expression);
上面statement和expression的情况和寻常的while循环一样,没有变化,但由于代码是顺序式进行的,在这种情况下就先执行do部分的statement,然后再来进行while的expression是否为真的判断。
int i = 10;
do {
printf("%d\n", i);
i++;
} while(i <10);
printf("outside: %d\n", i);
/*输出:
10
outside: 11
*/
上面的例子就可以看出来,循环条件并不成立,但是它依然是执行了do后面的语句块,然后因为循环条件不成立所以退出继而执行后面的语句了。
和while循环不同,for循环属于计数循环,天然地适合上面最早出现的例子所在场合。这种循环,我们在开始之前,就能确定它大概会经历多少次循环然后停止了的,而while循环则是不明确的,你得一直等的那种,就比如上面的第二种输入型例子。
for循环的通用形式如下:
for ( init; condition; increment )
statement(s);
上面需要关注的主要就是condition部分,和while里面的expression一样,这里是循环条件的判断,这里的语句为真,循环继续,语句为假,循环结束;另外,init部分一般都用来进行初始化,而increment则是做递增或者递减的运算。具体可参考下面的例子。
int i;
for(i = 0; i < 10; i++)
printf("%d, ", i);
printf("%d, ", i);
//输出"0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10"
上面就是基础例子,用来对i变量进行初始化,然后每次加一运算,最后在i为10时跳出循环,所以上面的例子才会输出在一行,也可以这么修改一下:
int i;
for(i = 0; i < 10; i++)
printf("%d, ", i);
printf("\n%d, ", i);
//输出"0, 1, 2, 3, 4, 5, 6, 7, 8, 9,"
//"10"
这样就分层比较明显了,单纯看输出的话。
其实init部分和increment部分都可以不做任何操作,也可以做不止一个操作,比如:
int i = 0;
for(;i < 10;){
printf("%d, ", i);
i++
}
printf("\n%d", i);
//输出"0, 1, 2, 3, 4, 5, 6, 7, 8, 9,"
//"10"
//甚至可以这样
char ch;
for(ch = 'a';ch != 'f';ch++)
printf("%c, ", ch);
printf("\nI' m out.\n");
/*输出
a, b, c, d, e,
I' m out.
*/
如上进行,同样可以实现上面的效果,然后来多搞几个
int i, j;
for(i=10, j=0;i > j;i--, j++)
printf("i:%d, j: %d\n");
printf("outside: i:%d, j: %d\n");
/*输出
i:10, j: 0
i:9, j: 1
i:8, j: 2
i:7, j: 3
i:6, j: 4
outside: i:5, j: 5
*/
如上,同时进行两种计数,然后让他们交叠在一起做判断条件,只要你设计得好,花样少不了(反正面试题是这样玩花样的)。其实,上面的condition部分也可以为空置的,啥语句都没有,啥操作都没有,然后就是一个另类的while循环了,不过,这样没啥意思,因为要搞这样还不如用while。
上面就是基本的循环使用,除此以外还有一些配合循环使用的关键字,比如:break和continue。这两个关键字都可用于循环体的代码之中,前者用来终止循环,跳到循环后面的语句;后者用来跳出本次循环,略过循环体中continue语句后面的代码,直接进行下一次循环。不过这两者往往是和if语句进行配合使用,所以这里进行一次简单强调和介绍。
和循环一样的,就是分支跳转同样是需要条件的,比如,十八岁才能开车这一条件就形成分流,把十八岁以下和十八岁乃至以上人群分开。正如用于这两种场景的关键字if和switch的意思,前者是当…时候,后者是切换,结合英文意思可以很好理解它们的通用形式和应用场景。
其通用模式如下:
//单if
if (expression)
statement
//if-else
if (expression1)
statement1
else
statement2
//if-else if-else
if (exp1)
statement1
else if (exp2)
statement2
...
else
statementn
如上,if代表的跳转控制有大概三种:
一般来说,if语句使用场景是和循环或者其他跳转语句进行配合用来设计更为复杂的情况,单个拿出来,更多是作为一些例子讲解或者应用于简单函数。配合上面的else-if组合,这种多重选择的应用场景就是switch的使用场合。
switch通用形式如下:
switch (整型表达式) {
case 常量1:
语句1
case 常量2:
语句2
default:
语句3
}
对整型表达式求值,根据这个值来对号入座,如果这个值是下面case后面的任一常量,就执行下面对应语句。整型表达式必须是一个整型或者枚举类型,事实上,枚举类型更多。当上面case对应常量都不不符合整型表达式的值,就执行default对应语句。
int choice;
scanf("%d", &choice);
switch(choice){
case 0:printf("0");break;
case 1:printf("1");break;
case 2:printf("2");break;
default:printf("not 0, 1, 2\n");
}
可以看得到,break语句也用可用于switch当中,尤其是case后面的执行语句,每一部分case都会带一个break来跳出switch,不然就会一直顺序执行下去。比如都不带break,然后输入的是1,那它就会输出case1和case2以及defautlt对应语句。值得一提的是,continue也同样可以用于跳出switch,但它本义是跳出当前循环不执行循环后面的部分,所以它用在循环中的switch中也是合理的,简单的switch没有用它的场景。当然,浮点型被称为单字节整型,也是可以作为上面的常量表达式进入switch分支中使用的。比如:
char ch;
switch(ch) {
case 'a':printf("a");break;
case 'e':printf("e");break;
case 'i':printf("i");break;
case 'o':printf("o");break;
default:printf("balabala");
}
上面在说明while循环和scanf配置应用的时候,有提到&&这么一个逻辑运算符,除此以外,还有其他逻辑运算符,如下:
逻辑运算符 | 说明 |
---|---|
&& | a&&b,表达式a和表达式b都为真,结果为真,反之为假 |
|| | a || b,表达式a和表达式b,两者有一个为真,结果为真,两个都假,结果为假 |
! | !a,表达式a为真,!a为假;表达式a为假,!a为真 |
逻辑运算符的应用比较简单,真假判断就完了。有意思的是,在iso646.h头文件中,and可用于替代&&,or可替代||,not可替代!,这样使用更能明白其意义。
条件运算符
上面的if-else组合,当其应用的场景中,statement都是单语句情况下,用条件运算符替代if-else也可达到同等效果。是的,这是个替代品,用来写出更紧凑的语句。如下:
if (y < 0)
x = -y;
else
x = y
//转换成条件运算符
(y < 0) ? (x = -y) : (x = y)
?:就是条件运算符,从上面例子就可以很直白得看出来它的通用形式如下:
(exp1) ? (exp2): (exp3)
//exp1为真,执行exp2语句;为假,执行exp3语句
作为跳转控制,goto估计是最为简单的一个了,就是给某个可以跳转的地方加个label,然后在需要使用的时候,使用goto label跳转到该处执行对应语句即可。但对于初学者来说,往往难以搞清分支的对应,然后把定向goto用成乱向乱飞;另外加就算是一些老手,针对超大项目的管理,使用goto也是很犯迷糊的事,几十个文件,每个文件几百行,随便跳一下,脑子都要炸了。往常学习用得少的缘故,这里也不进行。
作为流程控制,跳转和循环之间往往可以相互嵌套使用,甚至跳转内也可以继续套一层跳转,循环内再套好几层循环,只要你能设计得分明,那它就能给你需要的理想输出。所以这个其实是自定义性质的内容,要说起来就太泛了,只说那么几个应用场景和对应措施供以参考。
场景一:
玩一个游戏,像猜数猜单词游戏,可以玩五次,每次都持续询问,对就退出不对继续问,或者不想玩了就退出来,这种场景就可以如下嵌套:
for 5次
while(是否是某个值&&(!退出字符))
问
答
如上,可以用while循环形成和玩家的持续对话,而玩家答对或者输入退出字符的时候就退出while循环,然后这样一个流程就结束了。这样的流程可以进行5次,当然,当你不想进行的时候,for循环也可以针对某个退出条件添加一个if,达成条件就执行break即可。
场景二:
拆分不同难度的关卡,进行不同难度的游戏
switch(难度) {
case 1:while(){game1};break;
case 2:while(){game2};break;
...
default:printf("没有这个难度,意料外输入");
}
如上,可以用switch拆分难度,在输入难度选择后,进行不同难度的游戏(由while循环把控,内设退出条件break),当输入出问题就进入default分支退出;同样的,switch-case也可被else-if来进行替代。