流程控制
C 语言的程序是顺序执行,即先执行前面的语句,再执行后面的语句。开发者如果想要控制程序执行的流程,就必须使用流程控制的语法结构,主要是条件执行和循环执行。
if 语句
if
语句用于条件判断,满足条件时,就执行指定的语句。
1 | if (expression) statement |
上面式子中,表达式expression
为真(值不为0
)时,就执行statement
语句。
if
后面的判断条件expression
外面必须有圆括号,否则会报错。语句体部分statement
可以是一个语句,也可以是放在大括号里面的复合语句。下面是一个例子。
1 | if (x == 10) printf("x is 10"); |
上面示例中,当变量x
为10
时,就会输出一行文字。对于只有一个语句的语句体,语句部分通常另起一行。
1 | if (x == 10) |
如果有多条语句,就需要把它们放在大括号里面,组成一个复合语句。
1 | if (line_num == MAX_LINES) { |
if
语句可以带有else
分支,指定条件不成立时(表达式expression
的值为0
),所要执行的代码。
1 | if (expression) statement |
下面是一个例子。
1 | if (i > j) |
如果else
的语句部分多于一行,同样可以把它们放在大括号里面。
else
可以与另一个if
语句连用,构成多重判断。
1 | if (expression) |
如果有多个if
和else
,可以记住这样一条规则,else
总是跟最接近的if
匹配。
1 | if (number > 6) |
上面示例中,else
部分匹配最近的if
(即number < 12
),所以如果number
等于6,就不会执行else
的部分。
这样很容易出错,为了提供代码的可读性,建议使用大括号,明确else
匹配哪一个if
。
1 | if (number > 6) { |
上面示例中,使用了大括号,就可以清晰地看出else
匹配外层的if
。
三元运算符 ?:
C 语言有一个三元表达式?:
,可以用作if...else
的简写形式。
1 | <expression1> ? <expression2> : <expression3> |
这个操作符的含义是,表达式expression1
如果为true
(非0值),就执行expression2
,否则执行expression3
。
下面是一个例子,返回两个值之中的较大值。
1 | (i > j) ? i : j; |
上面的代码等同于下面的if
语句。
1 | if (i > j) |
switch 语句
switch 语句是一种特殊形式的 if…else 结构,用于判断条件有多个结果的情况。它把多重的else if
改成更易用、可读性更好的形式。
1 | switch (expression) { |
上面代码中,根据表达式expression
不同的值,执行相应的case
分支。如果找不到对应的值,就执行default
分支。
下面是一个例子。
1 | switch (grade) { |
上面示例中,根据变量grade
不同的值,会执行不同的case
分支。如果等于0
,执行case 0
的部分;如果等于1
,执行case 1
的部分;否则,执行default
的部分。default
表示处理以上所有case
都不匹配的情况。
每个case
语句体的结尾,都应该有一个break
语句,作用是跳出整个switch
结构,不再往下执行。如果缺少break
,就会导致继续执行下一个case
或default
分支。
1 | switch (grade) { |
上面示例中,case 0
的部分没有break
语句,导致这个分支执行完以后,不会跳出switch
结构,继续执行case 1
分支。
利用这个特点,如果多个case
分支对应同样的语句体,可以写成下面这样。
1 | switch (grade) { |
上面示例中,case 0
分支没有任何语句,导致case 0
和case 1
都会执行同样的语句体。
case
后面的语句体,不用放在大括号里面,这也是为什么需要break
的原因。
default
分支用来处理前面的 case 都不匹配的情况,最好放在所有 case 的后面,这样就不用写break
语句。这个分支是可选的,如果没有该分支,遇到所有的 case 都不匹配的情况,就会直接跳出整个 switch 代码块。
while 语句
while
语句用于循环结构,满足条件时,不断执行循环体。
1 | while (expression) |
上面代码中,如果表达式expression
为非零值(表示真),就会执行statement
语句,然后再次判断expression
是否为零;如果expression
为零(表示伪)就跳出循环,不再执行循环体。
1 | while (i < n) |
上面示例中,只要i
小于n
,i
就会不断增加2。
如果循环体有多个语句,就需要使用大括号将这些语句组合在一起。
1 | while (expression) { |
下面是一个例子。
1 | i = 0; |
上面代码中,循环体会执行10次,每次将i
增加1
,直到等于10
才退出循环。
只要条件为真,while
会产生无限循环。下面是一种常见的无限循环的写法。
1 | while (1) { |
上面的示例虽然是无限循环,但是循环体内部可以用break
语句跳出循环。
do…while 结构
do...while
结构是while
的变体,它会先执行一次循环体,然后再判断是否满足条件。如果满足的话,就继续执行循环体,否则跳出循环。
1 | do statement |
上面代码中,不管条件expression
是否成立,循环体statement
至少会执行一次。每次statement
执行完毕,就会判断一次expression
,决定是否结束循环。
1 | i = 10; |
上面示例中,变量i
先减去1,再判断是否大于0。如果大于0,就继续减去1,直到i
等于0
为止。
如果循环部分有多条语句,就需要放在大括号里面。
1 | i = 10; |
上面例子中,变量i
并不满足小于10
的条件,但是循环体还是会执行一次。
for 语句
for
语句是最常用的循环结构,通常用于精确控制循环次数。
1 | for (initialization; continuation; action) |
上面代码中,for
语句的条件部分(即圆括号里面的部分)有三个表达式。
initialization
:初始化表达式,用于初始化循环变量,只执行一次。continuation
:判断表达式,只要为true
,就会不断执行循环体。action
:循环变量处理表达式,每轮循环结束后执行,使得循环变量发生变化。
循环体部分的statement
可以是一条语句,也可以是放在大括号里面的复合语句。下面是一个例子。
1 | for (int i = 10; i > 0; i--) |
上面示例中,循环变量i
在for
的第一个表达式里面声明,该变量只用于本次循环。离开循环体之后,就会失效。
条件部分的三个表达式,每一个都可以有多个语句,语句与语句之间使用逗号分隔。
1 | int i, j; |
上面示例中,初始化部分有两个语句,分别对变量i
和j
进行赋值。
for
的三个表达式都不是必需的,甚至可以全部省略,这会形成无限循环。
1 | for (;;) { |
上面示例由于没有判断条件,就会形成无限循环。
break 语句
break
语句有两种用法。一种是与switch
语句配套使用,用来中断某个分支的执行,这种用法前面已经介绍过了。另一种用法是在循环体内部跳出循环,不再进行后面的循环了。
1 | for (int i = 0; i < 3; i++) { |
上面示例中,break
语句使得循环跳到下一个i
。
1 | while ((ch = getchar()) != EOF) { |
上面示例中,一旦读到换行符(\n
),break
命令就跳出整个while
循环,不再继续读取了。
注意,break
命令只能跳出循环体和switch
结构,不能跳出if
结构。
1 | if (n > 1) { |
上面示例中,break
语句是无效的,因为它不能跳出外层的if
结构。
continue 语句
continue
语句用于在循环体内部终止本轮循环,进入下一轮循环。只要遇到continue
语句,循环体内部后面的语句就不执行了,回到循环体的头部,开始执行下一轮循环。
1 | for (int i = 0; i < 3; i++) { |
上面示例中,有没有continue
语句,效果一样,都表示跳到下一个j
。
1 | while ((ch = getchar()) != '\n') { |
上面示例中,只要读到的字符是制表符(\t
),就用continue
语句跳过该字符,读取下一个字符。
goto 语句
goto 语句用于跳到指定的标签名。这会破坏结构化编程,建议不要轻易使用,这里为了语法的完整,介绍一下它的用法。
1 | char ch; |
上面示例中,top
是一个标签名,可以放在正常语句的前面,相当于为这行语句做了一个标记。程序执行到goto
语句,就会跳转到它指定的标签名。
1 | infinite_loop: |
上面的代码会产生无限循环。
goto 的一个主要用法是跳出多层循环。
1 | for(...) { |
上面代码有很复杂的嵌套循环,不使用 goto 的话,想要完全跳出所有循环,写起来很麻烦。
goto 的另一个用途是提早结束多重判断。
1 | if (do_something() == ERR) |
上面示例有四个判断,只要有一个发现错误,就使用 goto 跳过后面的判断。
注意,goto 只能在同一个函数之中跳转,并不能跳转到其他函数。