程序语言中的控制流语句用于控制各运算执行的次序

语句与程序块

表达式后加上一个';',就变成了一个语句,如:x=0;i++;printf("");

c中';'是语句结束符(简单语句),但Pascal等语言中将';'用作语句之间分隔符

空语句指什么都不执行,语句本身只有一个分号';'

当一个语句过长时,可以直接换行,并不会影响语句的编译,特殊情况下需要在待续的行末尾加上一个反斜杠'\'

复合语句(也叫程序块)是以花括号{}将说明和若干个有序的语句(一组声明和语句)组合在一起而构成的语句集合
复合语句在语法上等价于单条语句
{
    内部数据说明

    简单(复合)语句表
}
如:函数体中、if-else、while、for中花括号括起来的多条语句
右花括号用于结束程序块,其后不需要分号

程序块的基本结构有三种:顺序结构,分支结构,循环结构

选择语句

用于条件判定

if语句

功能为单选结构
语句既可以是简单语句也可以是复合语句(等价)

基本格式:if (表达式)
            语句(或复合语句);
if ((s1+s2+s3)/3.0>=90)
{

    printf("AV_score=%f\n",(s1+s2+s3)/3.0>=90);

    printf("excellence!\n");

}

if-else语句

功能为二选一分支结构


基本格式:if(表达式)
	语句A;
        else
        语句B;

例:取a,b中的较大值(等效于三目运算符表达式:max=a>=b?a:b)

    if(a>=b)max=a

    else max=b
语句执行时先计算表达式的值,如果其值为真(非0),执行if语句,值为假(0),执行else语句

在编写if-else结构时最好加花括号来强制实现正确的匹配关系

嵌套语句中if与else的配对在不加花括号时是以最近配对为原则(与缩进无关)

因此即使编写的程序缩进结构明确表达了设计意图,但编译器无法获得这一信息,导致会给阅读程序人的理解带来歧义
例1:if(表达式1)
    if(表达式2)                 将每个else与最近的前一个没有else配对的if进行配对
       语句1;
    else 语句2;                 与内层if配对

例2:一个歧义性有害的例子
    if(n>=0)                   外层if没加花括号
    for(i=0;i<n;i++)
    if(s[i]>0)
    {
        printf("…");
        return i;
    }
    else printf("error--n is negative\n");   这里的else是与for里面的if配对,与程序原本的逻辑(判断n正负)相悖
由于if语句只是简单测试表达式的值,因此可以对某些代码进行简化,最明显的例子是:

if(表达式!=0)用if(表达式)替代

多数情况下这种表达紧凑美观、自然清晰,但也有部分情况会含义不清

else-if语句 功能为多选一分支结构,是多路判定最常用的方法

实质上是else后面嵌套了一个if语句
基本格式:if(表达式1)
         语句1;
         else if(表达式2)
         语句2;
          ...
         else if(表达式n)
         语句n;
         else
         语句n+1;
其中各表达式将被依次求值,一旦某个表达式结果为真,则执行与之相关的语句,然后终止整个语句序列的执行

最后的else用来处理“上述条件均不成立”的情况或默认情况

在不需要针对默认情况执行显式操作时,可以将结构末尾的else语句部分省略掉

该部分也可以用来检查错误,以捕获“不可能”的条件

switch 多路开关语句

使用if-else的多重嵌套方式,可以实现多路分支的判定结构,但c提供了更简洁的多重选择语句switch

switch语句也是一种多路判定语句,测试表达式是否与一些常量整数值中的某一个匹配,并执行相应的分支动作

基本格式:switch(表达式)          表达式类型为int
        {
            case 常数1:语句1;
            break;             如果case分支中没有加break,那么程序执行完对应语句后还会往下继续执行
            case 常数2:语句2;
            break;
            ...
            case 常数n:语句n;
            break;
            default:语句n+1;
         }
每个分支由一个或多个整数值常量或常量表达式标记,各分支表达式必须互不相同

如果没有哪一分支匹配表达式,则执行标记为default的分支

如果没有default分支也没有其他分支与表达式的值匹配,该switch语句不执行任何动作,各分支及default分支在逻辑上的排列次序是任意的

跳出switch语句最常用的方法是使用break和return语句,这两个语句将导致程序执行立即从switch语句中跳出

switch语句中case的作用只是一个标号,因此某个分支中的代码执行完后,程序进入下一分支继续执行(除非在程序中进行显式的跳转)

在switch语句中依次执行各分支的做法有优点也有缺点:

好的一面在于可以把若干个分支组合在一起完成一个任务,即可以对多种情况进行统一操作

但这种从一个分支直接进入下一个分支的做法并不健全,在修改时很容易出错,除了一个计算需要多个标号的情况外,应尽量避免这种操作

这种做法在不得不使用的时候,应当加上适当的程序注释

一般情况下为了防止直接进入下一分支,每个分支后面都会以break语句结尾

作为一种良好的程序设计风格,在switch语句的最后一个分支后面也应该加上一个break语句

从而降低日后需要向switch语句后再添加其他分支时发生错误的可能性

switch语句示例程序:从键盘输入一个月号,输出对应月份的英文名称
int main()
{
    int month;
    int r=scanf("%d",&month);
    if (r==1)
    {
    	switch (month)
    	{
    		case 1:printf("January");
    		break;
    		case 2:printf("February");
    		break;
    		case 3:printf("March");
    		break;
		case 4:printf("April");
		break;
		case 5:printf("May");
		break;
		case 6:printf("June");
		break;
		case 7:printf("July");
		break;
		case 8:printf("August");
		break;
		case 9:printf("September");
		break;
		case 10:printf("October");
		break;
		case 11:printf("November");
    		break;
		case 12:printf("December");
		break;
		default:printf("Data error");
		break;
	}
    }
}

循环语句

功能为判断条件,只要为真(非0)则执行循环语句,并再次求该表达式的值

这一循环过程一直进行下去,直到条件为假(0)退出循环,随后执行语句后面的部分

分三种结构:for, while, do-while 三种结构均可嵌套使用,并且一般条件下可以相互替换

for语句
    基本格式:for(表达式1;表达式2;表达式3)
    循环体;
    等价于: 表达式1          特别的,当while或for循环语句中包含continue语句时,上述二者之间不一定等价
    while(表达式2){
	语句;表达式3;
    }
其中,表达式1为进入循环体之前运算的表达式在整个语句块中只执行一次

表达式2为循环的判断条件,循环体每执行完一次后重新判断表达式2,决定是否进入下次循环

表达式3为循环体执行完成后进行的运算,表达式3完成后一次循环结束

从语法角度看,for循环语句的3个组成部分都是表达式

最常见情况是,表达式1与表达式3是赋值表达式或函数调用,表达式2是关系表达式

这三个组成部分中任何部分都可以省略,但分号必须保留

如果for语句中省略表达式1与表达式3,就退化为while循环语句

如果省略测试条件即表达式2,则认为其值永远是真值

因此,如:for(;;);是无限循环语句,必须借助其他手段(如break语句或return语句)才能终止执行

例: 求n的1到5次方
int main()
{
    float n,i,result=1;
    scanf("%f",&n);
    for (i=0;i<=4;i++)
    {
    	result*=n;
    	printf("%.03f\n",result);
    }
}
使用for语句有很多优点:

可以在表达式中使用逗号运算符,增加数据处理和判断的灵活性
例:for(i=0,j=100;j-i>=0;i++,j-=5)
    允许在循环体内改变循环变量的值
例: int sum=0,number
    for(count=1;count<=100;count++)
    {
	scanf("%d", number);
	sum+=number;
	if (sum>=300)
		count=100;
    }
表达式,循环体均可缺省
省略表达式3 例:for(i=0;i!=100;)    实际上,由于表达式3在循环体之后进行,因此均可以把表达式3放到循环体最后
    		scanf("%d",i);
省略表达式1 例:i=(a+b)/2
	for(;i>20;i++);          同理,表达式1在循环开始前运算,因此完全可以将其放到整个循环语句之前
省略所有表达式 例:for (;;)循环体     甚至可以直接在循环体中设置判断循环的条件和跳出循环的手段,将三个表达式完全省略;如果没相关的条件则形成死循环
省略循环体 例: for(i=1,j=-1;i<=1000;i++,j*=j); 表达式3实际在相当于起到循环体的作用,循环体为一个空语句
for循环嵌套示例:输出九九乘法表
int main()
{
    int row,line;
    for(row=1;row<=9;row++)
    {
    	for(line=1;line<=9;line++)printf("%d*%d=%d\t", row,line,row*line);
	printf("\n");
    }
}

while, do-while 语句

通常for语句用于可以预知循环次数,若不能的话也可以考虑使用while, do-while语句

while语句基本格式:    while(表达式)循环体;
do-while语句基本格式:   do{                  do while语句特殊性在于先执行循环体,再判断条件
			循环体;             至少需要执行循环体一次,典型的直到型循环
		      }while (表达式)       除了条件测试的语义不同外,do-while循环与Pascal语言的repeat-until语句等价
因此循环可分为:当型循环(for循环、while循环)和直到型循环(do while循环)

两种循环区别即在于先判断还是先执行一次循环体再判断

经验表明,do-while循环比while循环和for循环用得少得多
例:读入字符并回显,直到读入'*'为止
int main()          输入abcde?f*  输出结果:abcde?f*
{                   输入abcde?f** 输出结果:abcde?f*
    int c;
    do{
        c=getchar();
	putchar(c);
    }while (c!='*');
}
例:itoa函数,将数字n转换为字符串并保存到s中
char*itoa(int n,char*s){
    int i=0,sign;
    if((sign=n)<0)n=-n;
    do{                         这里使用do-while语句会方便一些,因为当n<10时也可以将该字符保存
	s[i++]=n%10+'0'         do-while语句中只有一条语句,但仍然建议用花括号将语句括起来
    }while(n/=10);              从而避免程序阅读者误以为while循环是个无限循环
    if(sign<0)s[i++]='-';
    s[i]='\0';
    return reverse(s);
}
一般在设计程序时到底选用while循环语句还是for循环语句,主要取决于个人偏好

如:while((c=getcahr()==' '||c=='\n'||c=='\t'))中没有初始化或重新初始化的操作,因此使用while循环语句更自然些

如果语句中需要执行简单的初始化或变量递增,使用for语句更合适一些,它将循环控制语句集中放在循环的开头,使结构更紧凑、清晰

如:for(i=0;i<n;i++)是c处理数组前n个元素的一种习惯性写法,类似于Fortran语言中的DO循环或Pascal语言中的for循环

这种类比也不完全准确,因为在c中,for循环语句的循环变量和上限在循环体内可以修改,并且当循环因某种原因终止后循环变量i的值仍保留

并且由于for语句的各组成部分可以是任意表达式,所以for语句并不限于通过算术级数进行循环控制

但是尽管如此,牵强地把一些无关的计算放到for语句的初始化和变量递增部分是一种不好的程序设计风格,放到循环控制部分更合适

for循环将循环控制部分集中在一起,对多重嵌套循环优势更为明显
例:Shell排序算法
   Shell排序算法是D.L.Shell于1959年发明的,其基本思想是:
   先比较距离远的元素,而不是像简单交换排序算法那样先比较相邻的元素
   可以快速减少大量的无序情况,从而减轻后续的工作。被比较的元素之间的距离逐渐减小,直到减小为1,排序变成相邻元素的互换
    void shellsort(int* v,int n)
    {
        int n,gap,i,j,tmp;
        for(gap=n/2;gap>0;gap/=2)for(i=gap;i<n;i++)for(j=i-gap;v[j]>v[j+gap]&&j>=0;j-=gap)
        {
            tmp=v[j];
            v[j]=v[j+gap];
            v[j+gap]=tmp;
        }
     }

break, continue, goto语句

不通过循环头部或尾部的条件测试而跳出循环,有时是很方便的

break语句

break语句能使程序从switch语句或最内层循环中立即跳出

在循环语句中可以提前退出循环
例:while(1){
	scanf("%c",&ch);
	if(ch='s')break;
   }

continue语句

在一次循环当中,跳过continue余下语句,直接进入下次循环操作

continue语句与break语句是相关联的,但它没有break语句常用

continue用于使for、while或do-while语句开始下一次循环的执行:

在while、do-while语句中,continue语句的执行意味着立即执行测试部分

在for循环中,则意味着使控制转移到递增循环变量部分

continue只用于循环语句,不用于switch语句,因而某个循环中包含switch语句中的continue语句时,将导致进入下一次循环

当循环后面的部分比较复杂时,常常会用到continue语句;如果不用continue,则可能需要将测试颠倒过来或者缩进另一层循环,使循环嵌套更深
例:for(i=0;i<n;i++)
   {
       scanf("%f",&a);
       if(a<0)continue;
       printf("%f",a);
   }

goto语句

无条件转移语句,直接从goto语句转移到标号所指语句

c提供了可随意滥用的goto语句以及标记跳转位置的标号

从理论上讲,goto语句是没有必要的,实践中不使用goto语句也可以很容易地写出代码
基本格式:goto 标号;               goto语句通常不用,因为它将使程序层次不清,且不易读,但在多层嵌套退出时,用goto语句则比较合理
        标号: 语句;
标号的命名同变量命名形式相同,标号后面要紧跟一个冒号

标号可以位于对应的goto语句所在函数的任何语句前面,标号的作用域是整个函数

某些场合下goto语句用得到,最常见的用法是终止程序在某些深度嵌套的结构中的处理,如一次跳出两层或多层循环
如:for(…)
      for(…)
      {
         …
         if(disaster)
         goto error;
      }
      …
      error:处理错误情况
这种情况下,如果错误处理的代码很重要,并且错误可能出现在多个地方,使用goto语句比较方便

所有使用了goto语句的程序代码都能改写成不带goto语句的程序,但可能会增加一些额外的重复测试或变量

但是在大多数情况下,使用goto语句的程序段比不使用goto语句的程序段要难以理解和维护,要尽可能少使用或避免使用goto语句