我们将在本章中介绍C程序的三种最基本的结构:顺序结构,选择结构和循环结构。读者们很快就会发现,掌握了这些最基本的程序结构之后,我们就可以用C语言写出很多很多有意思的程序。
语句和语句块
如果把一个C程序比喻成一栋高楼,那么一行行的语句就是垒成高楼的砖块。程序员的工作就是把这些相对简单的砖块组织在一起,建造出风格不同,功能不同的建筑。
简单语句
粗略地说,一条简单语句就是一个最基本的执行单元。在前面几章中我们已经接触了很多简单语句。比如:Hello world中的printf:
printf("Hello world!\n");
和紧接着的返回语句:
return 0;
变量的定义和声明:
int i = 10;
简单的运算:
sum = 1.7f + 2.5f;
这是一条赋值语句,它将右边数学表达式的结果赋值给左边的变量。在书写简单语句时,不要忘记在结尾加上分号。
语句块
语句块是用花括号括起来的一行或多行语句。比如:
{
float sum = 0.0f;
sum += 3.5f;
printf("%f", sum);
}
这是一个语句块,它包含了三条语句:第一条语句定义了一个变量sum,并将sum的值初始化为0;第二条语句将sum的值自增了3.5;第三条语句将sum的值输出到控制台上。
将语句组织成语句块一般并不会影响语句的执行效果,不过合理的使用语句块可以让程序在逻辑上显得更加清晰。我们在本章随后将要看到的判断结构和循环结构中都包含了语句块,这些语句块能让我们更清楚地识别这些结构。
变量的作用域
我们在第三章中简单地介绍过变量。现在我们已经知道,每一个变量需要有自己的名字和类型。如果变量被初始化,那么它还会有一个初值。在这一节中,我们将介绍变量的作用域。读者可以把变量的作用域理解为这个变量在程序的哪些地方是可以被有效访问的:在这个有效范围之内,我们可以正确操作这个变量,如给变量赋值,对变量进行计算,输入输出等;在这个有效范围之外,我们不能使用这个变量。
变量包括局部变量和全局变量。局部变量定义在一个函数内,只在这个函数内使用,比如我们之前的所有例程中,所有的变量都是定义在main函数内部的,也只能在main函数里使用;全局变量定义在函数外,可以被程序的所有函数使用。在这一节中,我们重点向大家介绍局部变量的作用域。
局部变量的声明定义位置规则
在C中,如果你想要使用一个变量,你必须要事先声明它。在声明变量之前使用它是不合法的,比如下面的例子:
#include
int main()
{
sum = 3;
int sum;
return 0;
}
在main函数中我们声明了一个变量sum,但是在声明它之前我们对sum进行了赋值。问题就来了:在对sum进行赋值时,sum还没有定义,于是编译器并不知道sum究竟是个什么东西。如果你在VS中尝试编译,编译器会提示你sum未定义。
局部变量的作用域规则
我们之前说过局部变量是定义在函数内的,上一节中的例子又告诉了我们变量要在定义之后才能使用。把两者结合起来,我们就可以得到局部变量的作用域:
一般地,局部变量的作用域从定义开始,到函数的右括号结束。
以我们最熟悉的main函数为例,上面的作用域定义相当于告诉我们:在main函数中定义的局部变量,从定义的地方开始,到
嵌套语句块的同名变量作用域规则
如果程序中包含了语句块,语句块可能会影响变量的作用域。请看下面的例子:
:动手体验:在语句块内使用变量
#include
int main()
{
int sum = 3;
{
int sum = 6;
printf("%d\n", sum);
}
printf("%d\n", sum);
return 0;
}
我们首先在main函数中定义了一个变量sum,并将它赋值为3.随后我们定义了一个语句块,在语句块内重新定义了一个变量sum,并初始化为6,同时在语句块内输出sum的值。猜猜看这时候输出的sum是多少?
跳出了语句块之后,我们再次输出sum的值,猜猜看,这次输出的sum又是多少?
如果在语句块中定义了变量,那么这个变量的作用域从定义开始,一直延伸到语句块结束。如果它和语句块外部的某个变量重名了,在语句块内访问到的将是语句块内定义的变量。换句话说,外部的重名变量在语句块内失效了。
在刚刚的例子中,进入main函数首先定义的变量(我们称它为sum1)
int sum = 3;
它的作用域是从定义开始,一直到main函数结束。而在语句块内定义的sum(我们称它为sum2)
int sum = 6;
它的作用域是从定义开始,一直到语句块结束。而且,由于它和语句块外部的sum重名了,在语句块内的printf能看到的和能使用的都将是sum2.
执行完了第一个printf之后,我们跳出了语句块,也就离开了sum2的作用域。但是,由于我们还在main函数内,因此这个时候我们还在sum1的作用域里,所以,第二个printf能够访问的sum是sum1.最后我们的输出结果如下:
6
3
最常见的语句执行顺序--顺序结构
介绍完了语句和变量的作用域之后,我们就可以开始编写一些真正的程序了!在本节中我们会向大家介绍顺序结构,这是最常见的语句执行顺序。让我们从一个例子开始:
想象你是一个刚上路的出租车司机,这时来了一位乘客:"请把我送到机场。"
很不幸的是,作为一个菜鸟,你自己根本不认识去机场的路,于是你很无奈地表示自己恐怕不能送他去机场了。"哦,没关系。"乘客说,"你出了城区,沿着高速公路一直向前开就可以了,到了出口我会告诉你的。"
C语言中的顺序结构和上面的这个例子很相似。在顺序结构中,程序也只有唯一的一条"路"可以走,那就是从前向后一条一条地执行程序中的语句。我们其实早就看过这样的程序:
#include
int main()
{
printf("Hello world!\n");
return 0;
}
在这个例子中,程序从main函数开始后,按照从上往下的顺序一步一步地执行printf和return两行语句。执行完成后,整个程序也就结束了。
读者可能会疑惑:所有的程序不都应该是这样吗?并不完全是这样。正如同在路上开车会遇到岔路口一样,在程序执行过程中也会遇到一些"岔路口".在这些岔路口处我们需要做出判断,决定走岔路口中的哪一条路,有的时候我们会跳过一些代码,这就是后面要介绍的判断结构。还有的时候,我们要在一小段代码中来回跑,这时候我们的程序就像是北京地铁二号线的环线一样,在一小段程序中来回绕圈,直到找到一个合适的地方再出去,这就是循环结构。我们将在后面两节更加详细地介绍这两种结构。在此之前,还是让我们首先从最简单的顺序结构开始。
基本输入输出
让我们首先从基本的输入输出语句开始。严格地说,它们和顺序结构没有什么关系,但是学会了基本输入输出语句可以让我们快速地开始编写有意思的程序,因此我们把基本输入输出语句放在这里介绍,以方便读者们快速上手。
我们首先从基本的输出语句printf开始。printf是一个很神奇的东西,它可以向控制台输出我们想要显示的内容,比如最简单的Hello world:
printf("Hello, world!");
这是我们要介绍的printf的第一种用法:直接输出一个字符串。不要忘记用一对英文双引号把要输出的字符串括起来。如果你还记得字符类型中的\n,你也可以试试在Hello world后面加上一个\n:
printf("Hello, world!\n");
仔细看看,输出的结果有什么变化没有?记住:字符串无非就多个连接在一起的字符类型数据,printf会将每一个字符忠实地显示在屏幕上。
:动手体验:用printf输出字符串
新建一个工程,或者找到之前你的Hello world程序,尝试下面的printf语句:
printf("我是反斜杠\\");
以及:
printf("如何打印双引号 \"");
在运行程序之前,先猜猜看输出的结果会是什么样的?
当然,如果你新建了一个工程,不要忘了加上必要的include和main函数!
除了输出一个字符串,printf还有更加灵活的用法:输出格式控制字符串。假设我们有一个float类型的变量a,我们将它初始化为0:
float a = 0;
如果我想在屏幕上输出a的值,应该怎么办呢?按照我们此前的方法,我们可以这样:
printf("0");
好吧,这算是一种解决方法。可是,如果我们在写程序的时候不知道a的值,却希望在程序运行到这里的查看a的大小,应该怎么办呢?
举个例子:我正在写一个能将千克转成磅的小程序,由于我的同事fish恰好写过这样的一小段代码。作为一个懒惰的程序员,我懒得去google这两个单位的转换公式,而是将fish的那几行关键代码复制了过来:
int weight = 62.0f;//以千克为单位
//从这里开始是fish的代码,负责把weight的值转成以磅为单位的重量
blablabla……
blablabla……
blablabla……
//计算完成了!这个时刻weight里面保存以磅为单位的重量,我想在屏幕上显示weight的值:
printf(???);
这里就是我们要介绍的printf的另一种用法:格式化输出字符串。对于上面这个例子,我们可以用这样的printf语句来完成:
printf("weight的值是 %f.", weight);
在这里我们的字符串当中出现了一个新的符号%f,它表示在字符串的这个地方我们要输出一个浮点数,浮点数的值在后面的weight变量里面。在这里,%f像是一个占位符,告诉printf函数:我在这里要输出一个浮点数了,请到后面去查找这个浮点数的值,然后用那个值来替换我!
如果你还是不太清楚%f是什么意思,我们来看一个生活中的例子:假设我正在组织大学同学的聚会,我统计了一张表,按照当年的学号顺序,挨个打电话问大家现在的家庭住址:
学号
住址
1
A1市B1小区C1室
2
A2市B2街C2号
3
4
A3市B3镇C3乡
5
6
很遗憾,我发现我暂时打不通3号,5号和6号同学的手机,于是我给他们发了短信:"我在统计大家的家庭住址,打不通你的电话,看到短信后请回复。"现在我的这张表就变成了这个样子:
学号
住址
1
A1市B1小区C1室
2
A2市B2街C2号
3
等回复
4
A3市B3镇C3乡
5
等回复
6
等回复
很快,3号同学回复了一个"A4市B4小区C4栋",我找到表格中3号的地址那一栏,把原来的"等回复"删了,换成了他的回复。
类比到printf的例子当中,这里的"等回复"就像是printf当中的%f一样,我们并不是要在这里显示"%f"或者是"等回复"这三个字,而是要用别的内容来替换它们。在printf的例子中,我们是把随后float类型的变量weight的值放在%f的位置上输出;在同学会的例子中,我们是把同学回复的短信放在"等回复"的位置上。
类似地,我们用%d来表示输出一个整数,%c表示输出一个字符,%x表示用十六进制形式输出一个整数,等等。
表 51常用printf格式字符
常用格式字符
含义
%c
输出一个字符
%d
以十进制输出一个整型
%x
以十六进制输出一个整型
%f
输出一个浮点数
%s
输出一个字符串