The C Programming language(二)控制流与函数

(一)if-else语句

if(表达式)
	语句1else
	语句2

由于if-else语句的else部分是任选的,当在嵌套的if语句序列中缺省某个else部分时会引起歧义。这个问题可以通过使每一个else与最近的还无else匹配的if匹配。
(所以为防止歧义性,建议在if语句嵌套的情况下尽可能使用花括号)

if(表达式)
	语句;
else if(表达式)
	语句;
else
	语句;

(二)switch语句
switch语句是一种多路判定语句,它根据表达式是否与若干常量整数值中的某一个匹配来相应地执行有关的分支动作。

switch (表达式) {
case 常量表达式: 语句序列
case 常量表达式:	语句序列
default: 语句序列

每一种情形都由一个或多个整数值常量或常量表达式标记。如果某一种情形与表达式的值匹配,那么就从这个情形开始执行。各个情形中的表达式必须各不相同。如果没有一个情形能满足,那么执行标记为default的情形。default情形是任选的。如果没有default情形并且没有一个情形与表达式的值匹配,那么该 switch语句不执行任何动作。
break语句用于从switch语句中退出。由于在 switch语句中case情形的作用就像标号一样,在某个 case情形之后的代码执行完后,就进入下一个 case情形执行,除非显式控制转出。转出switch语句最常用的方法是使用 break语句与return语句。 break语句还可用于从 while、for与do循环语句中立即强制性退出。
作为一种好的风格,可以在s w i t c h语句最后一个情形(即d e f a u l t情形)后加上一个b r e a k语句,虽然这样做在逻辑上没有必要。但当以后需要在该 s w i t c h语句后再添加一种情形时,这种防范型程序设计会使我们少犯错误。

(三)while与for循环语句

for(表达式1;表达式2;表达式3)
语句

等价于

表达式1while(表达式2{
	语句
	表达式3}

从语法上看,for循环语句的三个组成部分都是表达式。最常见的情况是, 表达式1与表达式 3是赋值表达式或函数调用,表达式2是关系表达式。这三个表达式中任何一个都可以省略,但分号必须保留。如果表达式 1与表达式3 被省略了,那么它退化成了while循环语句。如果用于测试的表达式2不存在,那么就认为表达式2的值永远是真的,从而, f o r循环语句就是一个无限循环语句,这种语句可以用(break语句或者return语句)才能终止执行。
在这两种循环语句中到底选用 w h i l e语句还是f o r语句主要取决于程序人员的个人爱好。

第三种循环语句do-while循环语句

do
	语句
while (表达式)

在d o - w h i l e循环语句执行时,先要执行语句,然后再求表达式的值。如果表达式的值为真,那么
就再次执行语句,如此等等。当表达式的值变成假的的时候,就终止循环的执行。

(四)break语句与continue语句
break语句可用于从for、 while与do-while语句中提前退出来,正如它可用于从switch语句中提前退出来一样。 break语句可以用于立即从最内层的循环语句或switch语句中退出。
continue语句与break语句相关,但较少用到。 continue语句用于使其所在的 for、 while或do-while语句开始下一次循环。在 while与do-while语句中, continue语句的执行意味着立即执行测试部分;在for循环语句中, continue语句的执行则意味着使控制传递到增量部分。 continue语句只能用于循环语句,不能用于 switch语句。如果某个 continue语句位于 switch语句中,而后者又位于循环语句中,那么该 continue语句用于控制下一次循环。

例子:相传韩信才智过人,从不直接清点自己军队的人数,只要让士兵先后以三人一排、五人一排、七人一排地变换队形,而他每次只掠一眼队伍的排尾就知道总人数了。输入3个非负整数a,b,c ,表示每种队形排尾的人数(a<3,b<5,c<7),输出总人数的最小值(或报告无解)。已知总人数不小于10,不超过100 。

#include<stdio.h>
int main()
{
int i,a,b,c;
scanf("%d%d%d",a,b,c);
for(i=10;i<=100;i++)
{
	if(i%3==a && i%5==b && i%7==c )
	printf("最小人数是%d\n",i);
	break;
}	
if(i==101) 
printf("没有解\n");
return 0;
}

(五)goto语句与标号
C语言提供了可以毫无节制使用的 g o t o语句以及标记 g o t o语句所要转向的位置的标号。在有些情况下使用 g o t o语句可能比较合适。最常见的用法是在某些深度嵌套的结构中放弃处理,例如一次中止两层或多层循环。 b r e a k语句不能直接用于这一目的,它只能用于从最内层循环退出。

for(...)
	for(...)
	{
	...
	if(disaster)
	goto error;
	}
	...
error:
清理操作

标号的形式与变量名字相同,其后要跟一个冒号。标号可以用在任何语句的前面,但要与相应的g o t o语句位于同一函数中。标号的作用域是整个函数。
依赖于 g o t o语句的程序段一般都比不使用 g o t o语句的程序段难以理解与维护。虽然不特别强调这一点,但我们还是建议尽可能减少 g o t o语句的使用。

(六)函数
每一个函数定义均具有如下形式:

返回类型 函数名(变元说明表)
{
说明序列与语句序列
}

函数定义的各个部分都可以缺省。最简单的函数结构如下:
dummy( ) { }
这个函数什么也不做、什么也不返回。像这种什么也不做的函数有时很有用,它可以在程序开发期间用做占位符。如果在函数定义中省略了返回类型,则缺省为 i n t。
程序是变量定义和函数定义的集合。函数之间的通信可以通过变元、函数返回值以及外部变量进行。函数可以以任意次序出现在源文件中。
return语句用于从被调用函数向调用者返回值,return之后可以跟任何表达式:
在必要时要把表达式转换成函数的返回类型(结果类型)。 表达式两边往往要加一对圆括号,但不是必需的,而是可选的。而且, return之后也不一定要跟一个表达式。在 return之后没有表达式的情况下,不向调用者返回值。当被调用函数因执行到最后的右花括号而完成执行时,控制同样返回调用者(不返回值)。

为了说明让函数返回非整数值的方法,编写并使用函数 a t o f ( s ):
首先,由于a t o f函数返回值的类型不是 i n t,因此在该函数中必须说明它所返回值的类型。返回值类型的名字要放在函数名字之前。
其次,调用函数必须知道 a t o f函数返回的是非整数值。为了保证这一点,一种方法是在调用函数中显式说明 a t o f函数。说明语句double sum, atof ( char [ ] );
表明s u m是一个d o u b l e类型的变量, a t o f是一个具有char[ ]类型的变元且返回值类型为 d o u b l e的函
数。(函数a t o f的说明与定义必须一致)

外部变量
C程序由一组外部对象(外部变量或函数)组成,外部变量在函数外面定义,所以可以在许多函数中使用。由于C语言不允许在一个函数中定义其他函数,所以函数本身就是外部的。在缺省的情况下,所有通过名字对外部变量与函数的引用都是引用的同一对象。由于外部变量可以全局访问(函数之间交换数据提供了一种可以代替函数变元与返回值的方法)
外部变量的用途还表现在比内部变量更大的作用域和更长的生存期。自动变量只能在函数内部使用,当其所在函数被调用时开始存在,当函数退出时消失。而外部变量是永久存在的,值在从一次函数调用到下一次函数调用之间保持不变。

作用域规则
一个名字的作用域指程序中可以使用该名字的部分。对于在函数开头说明的自动变量,其作用域是说明该变量名字的函数。在不同函数中说明的具有相同名字的各个局部变量毫不相关。(对于函数的参数也是如此)
外部变量或函数的作用域从其说明处开始一直到其所在的被编译的文件的末尾。
例如,如果main、sp、val、push与pop是五个依次定义在某个文件中的函数与外部变量

int main()
{...}
int sp=0;
double val[MAXVAL];

void push(double f){ ...}
double pop(void) {...}

在push与pop这两个函数中不需做任何说明就可以通过名字来访问变量sp与val,但是这两个变量名字不能用在main函数中,push和pop函数也不能用在main函数中。
即,如果一个外部变量在定义之前就要使用到,或者这个外部变量定义与使用的源文件是不同的,那么相应的变量说明中需要强制性使用extern

需要将外部变量的说明与定义区分开:
变量说明用于说明变量的性质(主要是变量的类型),而变量的定义除此之外还引起存储的分配。
如果在函数的外部包含如下说明

int sp;
double val[MAXVAL];

这个两个说明定义了外部变量sp与val,并为之分配存储单元,同时也用作源文件其他部分使用的说明

extern int sp;
extern double val[MAXVAL];

为源文件剩余部分说明了sp是一个int类型的外部变量,val是一个double数字类型的外部变量,但是这两个说明并没有建立变量或为它们分配存储空间。(在一个源程序的所有源文件中对一个外部变量只能在某个文件中定义一次,而其他文件可以通过 extern 说明来访问它,在定义外部变量的源文件中也可以包含对该外部变量的 extern 说明)
在外部变量的定义中必须指定数组的大小,但在 extern 说明中则不一定要指定数组的大小。

头文件
还有一个问题需要考虑,即这些文件之间的定义与说明的共享问题。我们将尽可能使所要享的部分集中在一起,以使得只需一个拷贝,当要对程序进行改进时也能保证程序的正确性。(即使用#include指令)

静态变量
static说明适用于外部变量与函数,用于把这些对象的作用域限定为被编译源文件的剩余部分。
通过static说明函数,那么该函数名字就不能用在除该函数说明所在的文件之外的其他文件。
static说明也可用于说明内部变量,内部静态变量就像自动变量一样局部与某一特定函数,但不同的是,不管其所在函数是否调用,它都是一直存在的,而不像自动变量那样,随着所在函数的调用与退出而存在而消失。

寄存器变量
register说明用于提醒编译程序所说明的变量在程序中使用频率较高,即将寄存器变量放在寄存器中(在实际使用时,由于硬件环境的实际情况,对寄存器变量会有一些限制)
例如
register int x;

变量的分程序结构
变量的说明(包括初始化)可以跟在用于引入复合语句的左花括号的后面,而不是只能出现在函数的开始部分。
例如

if(n>0)
{
	int i;//说明一个新的i
	for(i=0;i<n;i++)
	...
}	

变量i的作用域是i f语句的“真”分支,这个 i与在该分程序之外说明的 i无关。在分程序中说明与初始化的自动变量每当进入这个分程序时就被初始化。

初始化
没有显式初始化的情况下,外部变量与静态变量都被初始化为0;而自动变量与寄存器变量的初值则没有定义。
对于外部变量与静态变量,初始化符必须是常量表达式,初始化只做一次(在程序开始执行前进行初始化)。对于自动变量与寄存器变量,则每当进入函数或分程序时进行初始化(对于自动变量与寄存器变量,初始化符不一定限定为常量:它可以是任何表达式,其中可以包含前面已定义过的值甚至可以包含函数调用)。

数组的初始化也是通过说明中的初始化符完成的,数组初始化符是用花括号括住并用逗号分隔的初始化符序列。

int days[] = {31,28,31,30,31, 30, 31, 31, 30, 31, 30, 31};

当数组大小缺省时,编译程序就通过统计花括号中初始化符的个数作为数组的长度,本例中数组的大小为12个元素。
如果初始化符序列中初始化符的个数比数组元素数少,那么没有得到初始化的数组元素在该数组为外部变量、静态变量与自动变量时被初始化为0.如果比数组元素数多,那么就是错误的。
(无法一次性地为多个数组元素指定一个初始化符,也不能在没有指定前面数组元素值的情况下初始化后面的数组元素)

字符数组的初始化比较特殊,可以用一个字符串来代替用花括号括住并用逗号分隔的初始化符序列

char pattern [] ="ould";
等价于
char pattern [] ={'o','u','l','d','\0'};

(七)递归

#include<stdio.h>
void up_down(int);
int main(){
	up_down(1);
	return 0;
} 
void up_down(int n)
{
	printf("level%d: n location %p\n",n,&n);
	if(n<4){	
	up_down(n+1);
	}
	printf("level%d: n location %p\n",n,&n);
}

在这里插入图片描述
即可以假设有一条函数调用链:——fun1()调用fun2()、fun2()调用fun3()、fun3()调用fun4().当fun4()结束时,控制传回fun3();当fun3()结束时,控制传回fun2();当fun2()结束时,控制传回fun1()。只不过fun1、fun2、fun3、fun4都是相同的函数。
(1)每级函数调用都有自己的变量,程序创建了4个单独的变量,每一个变量名都是n,但是值各不相同
(2)每一级函数调用都会返回一次,程序必须按顺序逐级返回递归
(3)递归函数中位于递归调用之前的语句,均按照被调函数的顺序执行。
(4)递归函数中位于递归调用之后的语句,均按照被调函数相反顺序执行
(5)虽然每级递归都有自己的变量,但是并没有拷贝函数的代码。除了为每次递归调用创建变量外,递归调用类似于一个循环语句。

C预处理程序
预处理是编译过程中单独进行的第一个步骤,其中常见的预处理功能是#include指令(用于在编译期间把指定文件的内容包含进当前文件中)与#define指令:(用任意字符序列取代一个标记);其他功能如条件编译与带变元的宏。
文件包含:
#include "文件名“或#include<文件名> 被替换成由文件名所指定的文件的内容。
如果文件名用引号括起来,那么就在源程序所在位置查找该文件;如果在这个位置没有找到该文件,或者如果文件名用尖括号<>括起来,那么就按实现定义的规则来查找该文件,被包含的文件本身也可包含#include指令。
宏替换:
#define 名字 替换文本
是一种最简单的宏替换——出现各个的名字都将被替换文本替换。# define指令中的名字与变量名具有相同的形式, 替换文本可以是任意字符串。
正常情况下, 替换文本是#define指令所在行的剩余部分,但也可以把一个比较长的宏定义分成若干行,这时只需在尚待延续的行后加上一个反斜杠 \ 即可。#define指令所定义的名字的作用域从其定义点开始到被编译的源文件的结束。

用替换文本可以定义任何名字,例如:
#define forever for( ; ; )
为无限循环定义一个新的关键词forever

在宏定义中也可以带变元,这样就可以对不同的宏调用使用不同的替换文本。
#define max(A,B) (( A ) > ( B ) ? ( A ): ( B))
定义一个宏max,对max的使用类似于函数调用,但宏调用是直接将替换文本插入代码中,形式参数(A,B)每一次出现都被替换为对应的实际变元。
x = max( p+q,r+s);
被替换成为
x = ( ( p+q ) > ( r+s ) ? ( p+q ) : ( r+s ) );

条件包含:
在预处理语句中还有一种条件语句,用于在预处理中进行条件控制。这提供了一种在编译过程中可以根据所求条件的值有选择地包含不同代码的手段。
#if 语句中包含一个常量整数表达式(其中不得包含sizeof、 类型强制转换运算符或枚举常量),若该表达式的求值结果不等于0,则执行其后的各行,直到遇到#endif、#elif(类似于else if),或#else语句为止。在#if语句中可以使用一个特殊的表达式defined(名字);当名字已经被定义时,其值为1;否则,其值为0.

#if !define(HDR)
#define HDR
#endif
发布了54 篇原创文章 · 获赞 4 · 访问量 1016

猜你喜欢

转载自blog.csdn.net/buzhiquxiang/article/details/103833051