前面章节读者认识了C语言的诞生发展过程以及C语言的数据类型,对C语言有了初 步的了解。C语言功能强大,既能够编写Windows、Linux这样的大型系统软件,也能够编 写航天飞机、交通信号指示、电子商务等应用软件。 当然,读者现在还无法编写复杂的应用软件,但是编写航天飞机程序的设计者也是从零 学起的。本章将从算法入手,学习程序流程控制以及数据的输入输出,带领读者编写简单的 顺序结构程序。 3.算法 1 算法(Algorithm)是解决问题的有限步骤,它不依赖于某种程序的语言风格、语法规则 等。生活中做任何事情都要按照一定的顺序来操作,就像我们想喝水要先倒水一样,这是生 活中的“算法”,“喝水”这个动作可以用下面的算法步骤完成: (1)走到厨房。 (2)拿起水壶,倒水。 (3)拿杯子喝水。 (4)放下杯子。 可以看出,算法是按照一定次序、步骤完成的。解决计算机程序问题特别是一些复杂问 题更需要制定详细、高效的算法,可以说,算法是程序设计的灵魂(DonaldE.Knuth)。 1.算法的概念 3.1 不管使用什么程序语言,编程人员必须在程序中明确而详细地说明他们想让计算机做 什么以及如何做。算法,简单来说是解决一个具体问题所采取的方法、步骤。在这里我们讲 的算法是程序设计算法,即计算机能够执行的算法。可以通过以下几个重要的特征来衡量 一个算法的正确性: (1)有穷性(Finitenes)。算法的有穷性是指算法必须能在执行有限个步骤之后终止。 (2)确定性(Definitenes)。算法的每一步骤都是确定的,不能够有歧义。例如,如果 x≥0则输出YES;如果x≤0则输出NO这个算法存在歧义,因为在x=0的情况下不知道 应该输出什么。 (3)有效性(Efectivenes)。算法中的每个步骤都能够执行,并得到确定的结果。 42 (4)输入项(nu根据问题初始条件而定。例 Ipt)。一个算法可以没有输入或有多个输入, 如,计算1~100 的累加和不需要输入,但是计算1~n的累加和需要一个输入项n才能计算。 (5)输出项(Output)。一个算法有一个或多个输出,以反映对输入数据加工后的结果, 没有输出的算法是毫无意义的。 一个算法的评价主要从时间复杂度和空间复杂度来考虑。时间和空间复杂度越低越 好。例如,编程时尽量不要使用多重循环解决问题,这样会增加算法的时间复杂度。 3.2 算法的描述 1. 描述一个算法,可以用不同的方法。常用的有自然语言、传统流程图、结构化流程图等。 1. 自然语言表示 自然语言就是人们日常生活中使用的语言,用自然语言表示算法,通俗易懂,例如本节 开始的“喝水”算法。但自然语言文字冗长,容易出现“歧义性”,表示的含义往往不太严谨, 要根据上下文才能判断其正确含义。此外,用自然语言描述包含分支和循环的算法,很不方 便。因此,除了很简单的问题以外,一般不用自然语言描述算法。 2. 传统流程图表示 流程图(FlowChart)是一个描述程序的控制流程和指令执行情况的有向图,它是程序 的一种比较直观的表示形式,美国国家标准化协会(AmericanNationalStandardInstitute, ANSI)规定了一些常用的流程图符号(如图3-1所示)。 图3-1 传统流程图中常用符号 用传统流程图描述算法的优点是形象直观,各种操作一目了然,不会产生“歧义性”,便 于理解,算法出错时容易发现,并可以直接转化为程序。但缺点是所占篇幅较大,由于允许 使用流程线,过于灵活,不受约束,使用者可使流程任意转移,从而造成程序阅读和修改上的 困难,不利于结构化程序设计。算法上难免会包含一些分支和循环,而不可能全部由一个一 个框顺序组成。为了解决这个问题,人们设想,规定出几种基本结构,然后由这些基本结构 按一定规律组成一个算法结构,整个算法的结构是由上而下地将各个基本结构顺序排列起 来的。如果能做到一点,算法的质量就能得到保证和提高。 1966 年,Bohra和Jacopini提出了以下三种基本结构,作为表示一个良好算法的基本 单元 ( 。 1)顺序结构,如图3-2所示,虚线框内是一个顺序结构。 (2)分支结构,或称选取结构、选择结构,如图3-3所示。 43 它的功能是:从a点进入条件判断,如果P成立则执行分支语句块A,否则执行分支语 句块B,运行完某一个分支语句块之后,从b点结束选择结构。 (3)循环结构,如图3-4所示。 它的功能是从a点进入循环判断,当给定的条件P成立时,执行语句块A,执行完A 后,再判断条件P是否成立,如果仍然成立,再执行语句块A,如此反复执行语句块A,直到 某一次P条件不成立为止,此时不执行A,而从b点结束本循环结构。 图3-2 顺序结构流程图图3-3 分支结构流程图图3-4 循环结构流程图 3.N-S结构化流程图表示算法 S结构化流程图是1973年由美国学者INasi和B.n提出。NS就是以 N-Shneiderma 这两位学者的名字首字母命名的。它最显著的特(.) 点就是完全去掉了带箭头的流程线,这样 算法被迫只能从上到下顺序执行,从而避免了算法流程的任意转向,保证了程序的质量。与 传统的流程图相比,N-S图的另一个优点就是既形象直观,画出来后又比较节省篇幅,尤其 适用于结构化程序设计。 N-S流程图用以下流程图符号表示程序的三种基本结构。 (1)顺序结构:如图3-5所示,A和B两个框组成一个顺序结构。 (2)分支结构:如图3-6所示,当条件P成立时执行A操作,P不成立则执行B操作。 请注意图3-6是一个整体,代表一个基本结构。 (3)循环结构:如图3-7所示,表示当P条件成立时反复执行A操作,直到P条件不成 立为止。 图3-5 顺序结构图3-6 分支结构图3-7 循环结构 3.2 C语言语句分类 C程序的执行部分是由语句组成的。程序的功能也是通过执行语句实现的。C语句可 分为五类:表达式语句、函数调用语句、控制语句、复合语句和空语句,下面分别作详细 说明: 44 1.表达式语句 表达式语句由表达式加上分号(;)组成。一般形式为: 表达式; 执行表达式语句就是计算表达式的值。例如: x=y+z;a=520; //两条赋值语句 x+3; //加法运算语句,但计算结果不能保留,无实际意义 2.函数调用语句 由函数名、实际参数加上分号“;”组成。其一般形式为: 函数名(实际参数表); 例如: printf("%d%d%d\n",a,b,c); /*调用printf 函数*/ sort(a); /*调用用户自定义sort 函数,参数为a*/ 3.控制语句 控制语句用于控制程序的流程,以实现程序的各种结构方式,它们由特定的语句定义符 组成。C语言有八种控制语句,可分成以下三类: (1)条件判断语句。 if语句、switch语句。 (2)循环语句。 do-while语句、while语句和for语句。 (3)转向语句。 break语句、continue语句、return语句。 4.复合语句 { ……/*多条语句*/ } 把多个语句用括号{}括起来组成的语句组称为复合语句。在C语言语法上,把复合语 句看成是一条语句,而不是多条语句,例如: { x=y+z; a=b+c; printf("%d%d",x,a); } {}括起来的这三行代码作为一条复合语句处理。复合语句内的各条语句都必须以分号 “;”结尾。此外,在括号“}”外不能加分号。 45 5.空语句 ; 只有分号“;”组成的语句称为空语句。空语句是什么也不执行的语句。在程序中空语 句可用来作空循环体。 例如,“while(getchar()!=\' n');”语句的功能是,只要从键盘输入的字符不是回车则重 新输入。这里的循环体为空语句。 3.3 数据的输入和输出 C语言输入和输出数据通过函数来实现。前面的章节已经多次使用过输出函数,本节 重点介绍不同数据类型的输入和输出格式。 3.3.1 库函数 C标准库提供了丰富的函数库,能完成常用的数学计算、字符串操作、字符操作、输入/ 输出等多种有用的操作(如前面讲过的printf函数、scanf函数等),这些函数统称为标准库 函数。它们由C 编译系统提供,用户无须定义,也不必在程序中作函数声明,只需用# include命令将相应的头文件包含进来,就可以在自己的程序中直接调用这些库函数以实现 相应的功能。 使用标准库函数既可以节省程序开发的时间,又可以使程序具有很好的可移植性,因此 应尽可能多地熟悉和掌握C语言中的标准库函数。 1.库函数的调用 在调用库函数时,必须弄清楚以下几点: ● 需要包含的头文件。 ● 函数的功能和名称。 ● 参数的个数和顺序,每个参数的意义和类型。 ● 返回值的意义和类型。 例:计算e3.5,这是一个数学函数,头文件为math.h,经查阅附录C可知使用exp函数, 函数原型为:doubleexp(doublex),表示函数有一个参数,类型为double,代表指数;函数 返回值为double类型,所以e3.5的函数调用为exp(3.5)。 附录C中分类列出了常用的库函数,内容包括函数名、功能、函数原型、需要包含的头 文件等,可供读者查阅。 2.常用头文件 库函数在调用之前必须首先声明,而C语言提供了非常丰富的库函数,要想全部记住 每个函数的原型是很困难的,为此,C编译系统根据功能对库函数进行分类,把每类库函数 的有关信息(包括声明)集中包含在一个头文件中,这样如果在程序中调用库函数,只要在程 46 序的开头包含相应的头文件即可。 常用的头文件如下: ● stdio.h:标准输入输出函数的头文件。 ● ctype.h:与字符相关的判断或处理函数的头文件。 ● string.h:字符串处理函数的头文件。 ● math.h:数学函数的头文件。 ● graphics.h:图形模式的图形函数的头文件。 ● stdlib.h:标准库头文件。 3.3.2 数据输入函数 与printf函数对应,scanf函数用来提供系统输入数据。该函数也在stdio.h中声明。 它的作用是运行程序时,通过键盘输入数据给变量赋值。其基本格式为: scanf(格式控制字符串,& 变量1,& 变量2,……,& 变量n); 格式控制字符串包含两种内容:格式控制符和普通字符。 (1)格式控制符:按指定的格式读入数据,它包含以%开头的格式控制符,与不同数据 类型的变量一一对应。例如,int类型变量使用%d,float数据类型使用%f,double类型数 据使用%lf(这里的l是long的首字母,不是数字1)。 (2)普通字符:输入数据时,需要将这些字符原样输入。为了简便,编程时应尽量避免 包含普通字符。 例如: scanf("a=%d",&a); 在运行输入时,需要输入:a=9,固定字符“a=”需要原样输入,否则出现错误。 注意:格式控制符和变量列表的顺序、个数、类型必须一一对应。例如,给两个变量a、b 通过输入赋值,其类型分别为int和float,则输入时代码应这样写: scanf("%d%f",&a,&b); /*a 对应%d,b 对应%f*/ 3.3.3 整型数据的输入和输出 整数输入或输出格式控制符有%d、%u、%o和%x,具体说明请见表3-1。 表3-1 整型数据输入或输出格式符 数据类型 输入输出形式 十进制八进制十六进制 int %d %o %x long %ld %lo %lx unsigned %u %o %x unsignedlong %lu %lo %lx 47 其实,在大部分的C语言编译器中,long类型的输入输出格式符不需要加l(long的首 字母),在用户看来,long和int在输入输出、存储空间上基本没有区别。例如: int a; long b; scanf("%d%d",&a,&b); /*通过键盘输入两个数给a,b 赋值,格式符都为%d*/ printf("a=%d,b=%d\n",a,b); /*输出a,b 的值,格式符都为%d*/ 输出整数时,可以对输出宽度和左右对齐加以控制,规则如下: (1)在格式控制符前加入一个正整数m,该整数右对齐,且最少占用m 个字符位置,若 整数位数大于m 按实际位数输出。例如: printf("%6d,%d,%3d\n",10,10,10000); /*输出结果: 10,10,10000*/ 第一个数据以%6d格式输出整数10,该数字占2个字符位置,左边补了4个空格。 (2)在格式控制符加一个负整数,则整数左对齐输出,例如: printf("%-6d,%d,%x\n",10,10,10); /*输出结果: 10 ,10,a*/ 第一个数据以%-6d格式输出整数10,“-”表示数字左对齐,右边补了4个空格。 3.3.4 实型数据的输入和输出 实型数据格式控制符说明如表3-2所示。 表3-2 实型数据输入或输出格式控制符 函数数据类型格 式 符说 明 printf float double %f,%e,%g %f:以小数形式输出,保留6位小数 %e:以指数形式输出,小数点前有且仅有一位非零数字 %g:由系统选择较短的格式输出,不输出小数尾数0 scanf float %f,%e %f:以小数形式输入一个单精度浮点数 %e:以指数形式输入一个单精度浮点数 double %lf,%le %lf:以小数形式输入一个双精度浮点数 %le:以指数形式输入一个双精度浮点数 输出单精度或双精度浮点数都可以使用格式符%f正确输出,但是使用scanf输入不同 精度数据时,double类型的变量需要使用%lf(f前必须加long的首字母l)。例如: float a; double b; scanf("%f %lf",&a,&b); /*b 是double 类型,必须使用%lf 格式输入,否则运行出错*/ printf("%f,%f\n",a,b); /*a,b 都可以用%f 格式输出*/ 在输出浮点数时,通过在格式符前加入m.n可以指定控制输出宽度为m,并且保留n 位小数。若实际的位数小于m,则左边补空格,若大于m,按实际位数输出。例如: double d=3.1415926; printf("%f,%e\n",d,d); /*输出结果: 3.141593,3.141593e+000*/ 48 printf("%6.2f,%.3f,%.8f\n",d,d,d); /*输出结果: 3.14,3.142,3.14159260*/ 输出d的值时,%6.2f输出:3.14(左补2 个空格,数字和小数点占据4 个字符位 置),%.3f保留3位小数,按实际位数,左右两侧都没有空格输出:3.142,%.8f保留8位小 数输出:3.14159260。 3.3.5 字符型数据的输入和输出 字符型数据输入和输出常用函数为getchar、putchar、scanf和printf。与printf、scanf 不同的是,getchar、putchar函数一次只能输入或输出一个字符。 1.printf函数与scanf函数 使用printf函数与scanf函数的格式控制符为:%c。例如: scanf("%c%c%c",&a,&b,&c); /*输入三个字符,分别赋值给变量a,b,c*/ 运行该行代码时,注意三个字符中间不能有空格或逗号间隔,因为空格和逗号也会作为 字符赋值给字符变量。 printf("%c,%c\n", 'a', 'a'+1); /*输出结果: a,b*/ 2.putchar函数 putchar函数是字符输出函数,其功能是在显示器上输出单个字符。其一般形式为: putchar(字符数据); 这里字符数据指一个字符型或整型表达式,因为字符数据的ASCII码值范围是0~ 127,所以若函数参数是整型数据,则这个整数也必须在这个范围内才能正确输出对应的字 符。例如: putchar('A'); /*输出大写字母A*/ putchar('\n'); /*回车换行*/ putchar(78); /*输出ASCII 码值是78 的字符*/ 【例3-1】 putchar函数使用示例。 源程序 #include int main() { int c=65; char a='B'; putchar(c); putchar('\n'); printf("%c %c\n",c,a+2); return 0; } 49 运行结果: AA D 3.getchar函数 getchar函数的功能是从键盘上输入单个字符。其一般形式为: 变量=getchar(); 通常把输入的字符赋予一个字符变量,构成赋值语句,例如: char c; c=getchar(); 注意:getchar函数只能接收单个字符,输入数字也按字符处理。输入多于一个字符 时,只接收第一个字符。 【例3-2】 getchar函数使用示例。 源程序 #include int main() { char c; printf("input a character:"); c=getchar(); putchar(c); return 0; } 运行结果: input a character:A ↙ A 【练习3-1】 输入一个大写字符,输出对应的小写字符。 3.4 顺序结构程序设计 顺序结构(SequentialStructure)是最简单的C语言程序结构,在程序执行中,一个操作 完成后接着执行跟随其后的下一个操作。顺序结构的程序基本上是由函数调用语句和赋值 语句构成。 下面给出几个经典顺序结构程序实例,读者可以通过在编译器中单步运行程序,理解程 序运行流程,观察每行代码运行结果,学习独立编写程序。 50 【例3-3】 从键盘输入一个三位数,获取其个位、十位和百位的值。 源程序 #include int main() { int a; /*存储一个三位整数*/ int gw,sw,bw; /*个位、十位、百位数*/ scanf("%d",&a); /*从键盘输入一个整数赋值给a*/ gw=a%10; /*获取a 的个位数*/ sw=a/10%10; /*获取a 的十位数*/ bw=a/100; /*获取a 的百位数*/ printf("a=%d\n",a); printf("%d,%d,%d\ n",gw,sw,bw); /*输出结果*/ return 0; } 运行结果: 123 ↙ a=123 3,2,1 本例中,用户需要控制输入的是一个3位整数,(而不是2位、4位等)否则运行结果会 有错误。以后学习了分支结构,可以对输入的整数进行判断之后再运算,让程序更具有健 壮性。 【例3-4】 从键盘输入身高(米),体重(千克),计算肥胖指数并输出。计算公式:肥胖 指数=体重/身高2。 源程序 #include int main() { double h,w,k; printf("请输入身高(米):"); scanf("%lf",&h); /*h 是double 型,需要使用%lf 格式*/ printf("请输入体重(千克):"); scanf("%lf",&w); /*w 是double 型,需要使用%lf 格式*/ k=w/h/h; /*若k=w/h*h,结果会怎样? */ printf("肥胖指数为%g\n",k); return 0; } 运行结果: 例3-3 51 请输入身高(米):1.72 ↙ 请输入体重(千克):75 ↙ 肥胖指数为25.3515 本例中,需要根据实际情况确定身高、体重的变量类型,并体会顺序结构程序运行的 流程。 【例3-5】 输入三个实数,输出它们的和以及平均数。 源程序 #include int main() { float a,b,c; float sum=0; printf("请输入三个实数(逗号间隔):"); scanf("%f,%f,%f",&a,&b,&c); sum=a+b+c; printf("和=%g 平均值=%g\n",sum,sum/3); return 0; } 运行结果: 请输入三个实数(逗号间隔):4.5,67.9,10.2 和=82.6 平均值=27.5333 注意:由于scanf函数格式控制符有逗号间隔,则运行输入数据时也必须用逗号间隔, 而且中英文逗号和代码一致,否则出错。 这里使用%g格式输出数据,避免了无意义的零。 【例3-6】 输入三角形三边长,求三角形面积(为简单起见,设输入的三边长能构成三 角形)。 提示:求三角形面积公式为: s(s-a)(s-b)(s-c),其中a、b、c为三角形的三个边 长,s=(a+b+c)/2。 源程序 #include #include int main() { float a,b,c,s,area; printf("请输入三角形的三条边的长度,以逗号间隔: "); scanf("%f,%f,%f",&a,&b,&c); s=1.0/2*(a+b+c); area=sqrt(s*(s-a)*(s-b)*(s-c)); /*sqrt(x)函数用于求x 平方根*/ printf("a=%7.2f,b=%7.2f,c=%7.2f,s=%7.2f\n",a,b,c,s); 52 printf("area=%7.2f\n",area); return 0; } 运行结果: 请输入三角形的三条边的长度,以逗号间隔: 3,4,6 ↙ a= 3.00,b= 4.00,c= 6.00,s= 6.50 area= 5.33 习题 一、单项选择题(题目中□表示空格。) (1)若有语句“inta,b,c;”,则下面输入语句正确的是( )。 A.scanf("%D%D%D",a,b,c); B.scanf("%d%d%d",a,b,c); C.scanf("%d%d%d",&a,&b,&c); D.scanf("%D%D%D",&a,&b,&c); (2)有以下程序 int main() { int a=10,b=20; printf("a+b=%d\n",a+b); /*输出计算结果*/ return 0; } 程序运行后的输出结果是( )。 A.a+b=10 B.a+b=30 C.30 D.出错 (3)以下程序段的输出结果是( )。 int a=1234; printf("%3d\n",a); A.1234 B.123 C.34 D.提示出错,无结果 (4)设变量均已正确定义,若要通过“scanf("%d%c%d%c",&a1,&c1,&a2,&c2);” 语句为变量a1和a2赋数值10和20,为变量c1和c2赋字符X和Y。以下所示的输入形式 中正确的是( )。 A.10□X□20□Y↙ B.10X20Y↙ C.10□X↙ D.10X↙ 20□Y↙ 20□Y↙ (5)已知字符'A'的ASCII代码值是65,字符变量c1的值是'A',c2的值是'D'。执行语句 “printf("%d,%d",c1,c2-2);”后,输出结果是( )。 A.A,B B.A,68 C.65,66 D.65,68 (6)若有如下语句: 53 int a; float b; 以下能正确输入数据的语句是( )。 A.scanf("%d%6.2f",&a,&b); B.scanf("%c%f",&a,&b); C.scanf("%d%f",&a,&b); D.scanf("%d%d",&a,&b); (7)有如下语句: int k1,k2; scanf("%d,%d",&k1,&k2); 要给k2、k2分别赋值12和34,从键盘输数据的格式应该是( )。 A.12□□34 B.12,34 C.12□□,34 D.%12,%34 (8)有如下语句: int m=546, n=765; printf(" m=%5d,n=%6d",m,n); 则输出的结果是( )。 A.m=546,n=765 B.m=546□□,n=□□□765 C.m=%546,n=%765 D.m=□□546,n=□□□765 (9)有如下程序,这样输入数据25,12,14↙之后,输出结果是( )。 #include int main() { int x,y,z; scanf(" %d%d%d",&x,&y,&z); printf(" x+y+z=%d\n",x+y+z); return 0; } A.x+y+z=51 B.x+y+z=41 C.x+y+z=60 D.不确定值 (10)有以下语句: char c1,c2; c1=getchar(); c2=getchar(); putchar(c1);putchar(c2); 若输入为:a,b↙,则输出为( )。 A.a, B.a,b C.b,a D.b, (11)有定义“intd;doublef;”要正确输入,应使用的语句是( )。 A.scanf("%ld%lf",&d,&f); B.scanf("%ld%ld",&d,&f); C.scanf("%ld%f",&d,&f); D.scanf("%d%lf",&d,&f); (12)根据题目中已给出的数据的输入和输出形式,程序中输入输出的语句的正确内容 是( )。 54 #include int main() { int x;float y; printf("enter x,y:"); /*此处为输入和输出语句*/ return 0; } 输入为:2□3.4 输出为:x+y=5.40。 A.scanf("%d,%f",&x,&y); printf("\nx+y=4.2f",x+y); B.scanf("%d%f",&x,&y); printf("\nx+y=%.2f",x+y); C.scanf("%d%f",&x,&y); printf("\nx+y=%6.1f",x+y); D.scanf("%d%3.1f",&x,&y); printf("\nx+y=%4.2f",x+y); (13)已知i、j、k为int型变量,若从键盘输入:1,2,3<回车>,使i的值为1、j的值为 2、k的值为3,以下选项中正确的输入语句是( )。 A.scanf("%2d%2d%2d",&i,&j,&k); B.scanf("%d%d%d",&i,&j,&k); C.scanf("%d,%d,%d",&i,&j,&k); D.scanf("i=%d,j=%d,k=%d",&i,&j,&k); (14)已知“inta,b;”,用语句“scanf("%d%d",&a,&b);”输入a、b的值时,不能作为 输入数据分隔符的是( )。 A., B.空格C.回车D.[Tab]键 (15)以下程序不用第三个变量,实现将两个数进行对调的操作,请填空( )。 #include int main() { int a,b; scanf("%d%d",&a,&b); printf("a=%d b=%d",a,b); a=a+b;b=a-b;a= ; printf("a=%d b=%d\n",a,b); return 0; } A.a=b B.a-b C.b*a D.a/b (16)下列程序段的输出结果为( )。 float x=213.82631; 55 printf("%3d",(int)x); A.213.82 B.213.83 C.213 D.3.8 (17)设变量定义为"inta,b;",执行下列语句时,输入( ),则a和b的值都是10。 scanf("a=%d, b=%d",&a, &b); A.1010 B.10,10 C.a=10 b=10 D.a=10,b=10 二、阅读程序题 (1)以下程序运行时若从键盘输入:102030↙。输出结果是: 。 #include int main() { int i=0,j=0,k=0; scanf("%d%d%d",&i,&j,&k); printf("%d%d%d\n",i,j,k); return 0; } (2)以下程序运行后的输出结果是。 #include int main() { int x=0210; printf("%x\n",x); return 0; } 三、程序设计题 (1)从键盘上输入两个浮点数,计算和、差、积、商,将结果保留两位小数输出。 (2)使用printf函数编写程序,运行时显示如图3-8所示界面。 图3-8 程序界面 (3)从键盘输入两个字符,并输出它们的后序字符。例如:输入aP,输出bQ。