用程序解决简单问题一般需要3步:①输入数据;②处理数据;③输出数据。例如,小 程是一家销售瓷砖的网上商铺老板,需要经常根据用户提供的铺设瓷砖的面积和选择瓷砖 的尺寸,计算需要瓷砖的块数。 这是一个小学生就能解决的问题,假设铺设面积用area表示,单位为平方米,瓷砖的长 用legt宽用wdh 表示 , iera×100÷ (egt nh表示 , it单位为厘米。则瓷砖块数tls=aelnh× width)。编写程序解决这一实际需求可以分为3步。 (1)输入数据。通过键盘输入area、length、width的值。 (2)处理数据。根据上述公式求出瓷砖的块数。 (3)输出数据。将瓷砖块数输出到屏幕 。 确定总体思路后,还需要确定程序的若干细节问题 。 (1)aa等变量的值如何从键盘输入,s中的值如何输出到屏幕上呢 ? retile 62 (2)根据公式计算出来的瓷砖块数一般带有小数点,如32.6块,而实际需要33块,如何 实现向上取整呢? 本章主要介绍顺序结构、C语句分类、输入以及常用函数的有关知识,运用本章的知识, 读者可以编写出顺序结构的人机交互小程序。 3.1 顺序结构 如果用计算机程序解决某一个问题或完成某一任务,需要若干条语句配合完成,而反映 图3-1 顺序结构流程图 语句之间的这种配合和执行顺序关系的就是程序流程控制问题。 从程序流程控制视角看,程序结构可分为3种基本结构:顺序结 构、选择结构和循环结构。无论功能多复杂的程序,皆是由这3种 基本结构中的一种或几种灵活组合而成。 本章介绍的顺序结构是其中最简单也是最常用的一种结构。 顺序结构是指按照语句在程序中出现的先后顺序逐条执行,每条 语句必须执行且只执行一次。其流程图如图3-1所示,即语句1执 行完毕后再执行语句2。 注意:顺序结构中的某些语句执行顺序影响程序执行结果,不 可以交换。例如,计算瓷砖数的案例中,按(2)→(1)→(3)顺序执行,会得到错误结果,因为 计算tiles的值时area、length、width还没有输入数据,当然tiles无法得到正确结果。但某 些语句交换后不影响程序结果,读者可以根据个人偏好调整,例如,area、length、width3个 变量的赋值顺序可以调整,只要使用时保证已经赋值即可。 3.2 C语句分类 一个C语言程序由一个或多个函数组成,而函数主要由语句组成。因此函数是C语言 程序的基本组成单位,而语句是最小单位。根据语句的功能,C语言中的语句可以分为以下 4类。 (1)表达式语句。表达式语句的突出特征是以分号“;”结尾。任何一个合法的表达式 后面跟上一个分号就可以构成一条表达式语句,即其一般格式如下: 表达式; 表达式由运算符和运算数组合而成,其中运算符可以是C语言中任何的算术、关系、赋 值、逗号、指针等运算符,运算数可以是变量、常量、宏定义、函数调用等形式,只要组成的表 达式合法即可。其中,最典型、最常用的是赋值表达式和函数调用语句。 例如:赋值表达式语句 x=y+z; 扫一扫 扫一扫 63 是由算术运算符“+”和赋值运算符“=”以及x、y、z3个变量组成的算术、赋值混合表达式 语句,其作用为计算y+z 的值,然后赋给变量x。因为最后进行的是赋值操作,可以简称 为赋值表达式语句。 例如:函数调用语句 printf("Try my best!"); 是由一个函数调用“printf("Trymybest!")”和一个分号组成,其作用是调用printf函数, 在屏幕上输出“Trymybest!”。 当然,任何合法的表达式加分号都构成表达式语句,但不一定有实际意义,应避免使用。 例如: y+z; //只计算出y +z 的值,但计算结果没有保留,也没有输出,无实际意义 z=z; //把z 变量的值又赋给z ,对程序功能没起任何作用,无实际意义 (2)流程控制语句。在C语言中,除了顺序结构通过出现顺序控制之外,其他流程均需 要通过特定的语句定义符控制。C语言中的流程控制语句共有3大类9小种。 ① 选择语句:if语句、switch语句。 ② 循环语句:while语句、for语句和do…while语句。 ③ 转向语句:break语句、continue语句、return语句、goto语句。 (3)空语句。空语句是一条只有一个分号“;”构成的语句。它其实是一个表达式语句, 只不过表达式为空,所以什么也不做,一般用来占位、作被转向点或空循环体。例如: for(i=0;(a[i]=getchar())!='\n';i++) ; //注意循环体为空语句 该程序段的功能为从键盘上输入一串字符放到数组中,按回车键结束。因为循环必须 有循环体,因此该空语句必不可少。当然,此处的空语句并不是空循环体的唯一表达方式, 换成一条空复合语句,即一对花括号“{}”也可以。 虽然什么也不做,但空语句毕竟是一条语句,不能在程序中随便加,否则可能会造成程 序语法或者逻辑错误。例如: int count=0; while((ch=getchar())!='\n') count++; 此程序段的功能为计算输入字符的个数。但如果写成: int count=0; while((ch=getchar())!='\n'); count++; 则会发现无论输入多少个字符,count中的值都为1。这是因为while循环的循环体变成 了空语句“;”,而“count++;”变成了循环结束后才能执行到的顺序结构语句,只执行 一次。 64 (4)复合语句。前面的一条表达式语句、空语句或流程控制语句均可统称为一条单语 句,而将n(n≥0)条语句用一对花括号“{}”括起来则称为复合语句。例如: { temp=max; max=min; min=temp; } 就是一条复合语句,功能为借助中间变量temp交换变量max、min的值。 如果去掉两端的花括号,则是三条单语句,但功能不变。既然花括号加与不加功能不 变,为什么要画蛇添足地加呢? 因为在某些场景中,比如选择或循环语句,要求分支体或者 循环体只能是一条语句,所以,当分支体或者循环体是多条语句时,则可以加花括号将多条 语句转换为一条复合语句。例如: if (max<min) { temp=max; max=min; min=temp; } 功能为只有在满足max小于min的情况下才会交换max、min的值,也就是始终保持 max大于或等于min。此时若去掉花括号,则受if控制的分支体就只有单语句“temp= max;”,程序就会产生逻辑错误。 【例3-1】 编程实现,将一个24小时制时间转换为12小时制时间。例如,输入:13∶15, 则输出:下午1∶15;输入:8∶20,则输出:上午8∶20。 程序分析:时间由时和分两部分组成,转换为12小时制时只有时变,而分不用变,因此 将时和分放在两个整型变量中。 #include <stdio.h> //文件包含命令,不是语句,所以没有分号 int main() { int hour,minute; //变量声明语句 printf("输入24 小时制时间:(小时:分钟)\n");//函数调用语句,输出提示信息 scanf("%d:%d",&hour,&minute); //函数调用语句,从键盘输入时分,用:分隔 } 65 3.3 常用数据输出输入函数 输入与输出是程序与用户之间的一种交互方式,一个程序至少有0个输入和1个输出。 C语言通过调用标准函数库中的输入、输出函数来实现数据的输入、输出操作。因为输入设 备可能是标准输入设备键盘,也可能是非标准输入设备,如文件、扫描仪等,输出设备可能是 标准输出设备屏幕,也可能是非标准输出设备,如文件、打印机等,因此在标准函数库中提供 了针对不同设备、不同读写方式的多个函数,这些函数的定义一般在头文件stdio.h(stdio是 standardinput&output的缩写)中。因此,如果程序中要使用它们,则需要在程序的开头 加上预编译命令: #include <stdio.h> C语言中的输出输入函数都是成对的,即有一个某功能的输入函数,就有一个相同功能 的输出函数。例如,scanf和printf是一对,都是格式化输入输出函数;getchar和putchar是 一对,都是字符输入输出函数。一对函数在格式、用法等方面有很多雷同点,因此可以对比 着记忆、学习。 此章要介绍的输入函数皆为标准输入函数,即输入设备为键盘;输出函数皆为标准输出 函数,即输出设备为屏幕。输入和输出设备为文件的若干函数将在文件一章中介绍。 3.3.1 格式输出函数printf 1 . printf 函数调用方法 格式输出函数printf的功能是按指定格式将字符串输出到标准输出设备———屏幕上。 其中,函数名中的f就是format(格式)的缩写。printf函数是应用最广、功能最强大的输出 函数,因此使用语法也比较琐碎,初学者只需掌握基本输出方式即可,不必死抠细节,尤其是 繁杂的格式说明项,用时查阅即可。 printf函数的函数原型为: int printf(const char *format é,argument…. ) 其中,参数format称为格式控制串,参数argument后面的“…”表示此参数的个数不确定, 所以argument称为输出列表,“é. ”表示参数argument为可选项,可有可无。 例如: 扫一扫 66 (1)格式控制串。format前面的数据类型“constchar*”说明此处应是一个字符型指 针,常为一个用双引号引起来的常量字符串,功能为指定输出数据项的类型和格式,可以包 含两种信息。 ① 格式说明项,由%和格式说明符组成,作用是将输出列表中对应的数据按照指定格 式输出。如例中的%d说明按十进制整型格式输出,%c说明按字符格式输出,但具体输出 值需要在输出列表中指定。 ② 普通字符,即除格式说明项以外的字符,会原样输出,在输出结果中起提示作用。如 例中的“a=”和“,b=”,尤其注意“,b=”中的“,”也是普通字符。 普通字符一般采用字符常量形式,特殊情况也可以采用转义字符形式,例如: printf("a\b*"); 输出结果为:*。因为“\b”为退格键的转义字符形式,输出a以后会倒退一格,然后输 出*,所以*就会把a覆盖掉。 (2)输出列表。输出列表是需要在格式控制串指定位置输出的一些表达式集合,之间 用逗号隔开。第i 个表达式的值会按照第i 个格式说明项所指定格式,代替第i 个格式说 明项输出。所以表达式的个数一定要和格式说明项的个数保持一致,否则程序会出现意想 不到的逻辑错误。 在上例中,假如变量a、b 均为整型变量,且值均为65,则输出结果为: a=65,b=A 即第一个表达式的值代替第一个格式说明项,以整数形式65输出;第二个表达式的值 代替第二个格式说明项,以字符形式A 输出(字符A 对应的ASCII码为65)。除此之外,其 他均为普通字符,原样输出。 (3)函数返回值。printf函数如果输出成功,则返回所输出字符的个数,否则返回一个 负数。但最常用的是printf单独构成语句,因此极少使用其返回值。 2 . 格式说明项 格式说明项的一般形式为: % é标志. é最小宽度. é.精度. é长度. 格式字符 由一般形式可以看出,格式说明项必须以%开头,以格式字符结尾,中间是若干可选项, 根据需要选择输出宽度、精度等控制信息。 (1)格式字符:用于指定输出数据的类型,常用格式字符及其功能描述如表3-1所示。 同一个功能可能会有多个格式字符,如输出十进制整数,d、i均可,输出一个字符,c、C均可。 但为了提高程序可读性和减少记忆负担,建议初学者统一使用第一个小写字符。 (2)标志:标志字符有-、+、#、空格、0共5种,其功能描述如表3-2所示。 (3)最小宽度:用十进制整数表示所输出数据占据的最少位数。如果实际位数多于定 义宽度,则按实际位数输出,若实际位数少于定义宽度,则补以空格或0。 (4)精度:如果输出数据为实数,则表示小数的位数;如果输出的是字符串,则表示输 67 出字符的个数。 表3-1 常用格式字符及其功能描述 数据类型格式字符功能描述 int d或i 以十进制形式输出带符号整数(默认正数不输出符号) o 以八进制形式输出无符号整数(默认不输出前缀0) x或X 以十六进制形式输出无符号整数(默认不输出前缀0x或0X) u 以十进制形式输出无符号整数 char c或C 输出一个字符 s 输出一个字符串 floatdouble f 以小数形式输出单、双精度带符号实数,默认小数位数为6位 e或E 以指数形式输出单、双精度实数 g或G 以%f和%e中较短的输出宽度输出单、双精度实数 其他% 输出符号% 表3-2 标志字符功能描述 标志字符功能描述 - 表示输出结果左对齐,若结果不够指定宽度,则右边补充空格;默认为右对齐 + 输出数据前冠以符号(正号或负号);默认只有负数前有符号 # 加在格式字符o(八进制)前,则输出结果加前缀o;加在格式字符x或X(十六进制)前,则 输出结果加前缀0x或0X 空格输出数据为正时,前面冠以空格 0 若实际位数少于定义宽度,则左边补0,但与'-'一起使用时不起作用,此时右边填充空格 (5)长度:长度格式符有h和l两种,一般用于整型格式字符前,h表示按短整型输出; l表示按长整型输出,例如,ld表示按长整型输出,lld按长长整型输出。l如果用于f前,即 lf则表示按double型输出。 【例3-2】 printf格式输出综合测试程序。 #include <stdio.h> int main() { int datai=66; char datac='a'; float dataf=138.3576278; //精度超出float 的7 位,会有误差 double datad=138.3576278; //精度在double 的范围内,没有误差 //以不同进制形式输出datai 的值66 printf("十进制: %d,八进制: %o,十六进制: %x\n",datai,datai,datai); printf("datai=%+d,-datai=%+d\n",datai,-datai); //输出带符号整数 printf("datai=%-5d,%5d\n",datai,datai); //按指定宽度5,左、右对齐输出 printf("datac=%c,ASCII=%d\n",datac,datac); //按字符和整数输出字符型数据 printf("dataf=%f,dataf=%10.4f\n",dataf,dataf); //按指定宽度与精度输出实数 printf("datad=%lf\n",datad); //输出双精度实数 } 68 程序运行结果为:(//后面为对结果的解释说明) 十进制: 66,八进制: 102,十六进制: 42 datai=+66,-datai=-66 //%+d 是指定数据无论正负都输出符号,%d 只是负数才输出符号 datai=66□□□,□□□66 //%-5d 为左对齐,%5d 为右对齐,不足用空格补齐 datac=a,ASCII=97 //字母a 的ASCII 码为97 dataf=138.357620,dataf=□□138.3576 //%10.4f 指总体宽度10,精度4,小数点占1 位 datad=138.357628 //%ld 输出double 型数据 学完此小节,可以解决计算瓷砖数案例中的问题“tiles中的值如何输出到屏幕上”,一 种解决方案为: printf("tiles=%d",tiles); 注意:语句中的两个tiles含义完全不同,第一个tiles是格式控制串中的普通字符,输 出时原样输出;第二个tiles是变量名,表示其值代替%d输出。 3.3.2 格式输入函数scanf 1 . scanf 函数调用方法 scanf函数是与printf函数相对应的一个标准输入库函数,其功能为按指定格式从键盘 输入数据,并存放到指定地址的内存中。同样,scanf函数为了解决复杂的数据输入,其语法 复杂、琐碎,稍不留神就出错,因此建议初学者使用最简单的输入方式,不需酷炫效果。 scanf函数的函数原型为: int scanf(const char *format é,argument…. ) scanf和printf的函数原型非常相似,参数format仍为格式控制串,多为常量字符串。 参数argument同样可有可无,但此处为地址列表。 例如: (1)格式控制串。用于指定数据的接收类型和格式,同样包括格式说明项和普通字符 两种信息。格式说明项用于指定接收类型,如上例中的%d指定按一个整数接收数据,%c 指定按一个字符接收数据。普通字符用于指定接收格式,“a=”和“,b=”需原样输入。 (2)地址列表。用于指定存放输入数据的内存地址。具体形式多种多样,如变量地址, 数组地址或指针变量,甚至是三者组成的表达式,只要结果是一个地址就可以。如上例中, 要把输入数据存放在变量a 中,则需指定变量a 的地址:&a。 上例中“scanf("a=%d,b=%c",&a,&b);”,格式控制串为字符串常量“a=%d, b=%c”,说明输入时“a=”“,b=”要原样输入,而%d和%c的位置分别用一个整数和一个 扫一扫 69 字符代替。假设输入信息如下(CR表示回车键Enter): a=36,b=r<CR> 则运行结果为:36,被存放在变量a 中,字符r' '被存放在变量b 中。 特别提醒:使用scanf输入数据时,一定要和指定的格式保持一致,否则变量无法接收 到正确数据,但又不会提示任何错误信息,因此会造成程序逻辑错误。例如,上例中如果输 入“36r<CR>”“36□r<CR>”或“a=36b=r<CR>”等,均不能正确接收。提醒初学者用 最简单的输入语句即可。 函数返回值。scanf函数的返回值为成功接收数据的项数,出错时则返回EOF,即-1。 【例3-3】 根据函数返回值判断正确接收数据的个数。 #include <stdio.h> int main() { int a,b,number; number=scanf("a=%d,b=%c",&a,&b); //将scanf 返回值赋给number printf("正确接收数据数: %d\n",number); } 程序运行结果为: 输入: a=36,b=r<CR> 输出: 正确接收数据数: 2 输入格式与指定格式完全一致,a、b均正确接收。 输入: a=36b=r<CR> 输出: 正确接收数据数: 1 因为a=36与指定格式一致,a 能正确接收,但“b=r<CR>”因为b 前少了一个“,”, 与指定格式不一致,b 不能正确接收。 输入: 36r<CR> 输出: 正确接收数据数: 0 因为a、b 均不能正确接收。 2 . 格式说明 格式说明项的一般形式为: % é*. é宽度. é长度. 格式字符 scanf函数和printf函数的格式说明项极其相似,仍然必须以%开头,以格式字符结尾, 但中间控制信息比较简单,尤其注意没有精度信息,说明输入时不能指定输入数据的精度, 例如,假设变量c 为float类型: scanf("3.1%f",&c); 70 这样的语句无论输入什么数据,变量c 都无法正确接收到,而程序又不会提示任何错误 信息,因此一定避免使用。 (1)格式字符:用于指定输入数据的数据类型,常用的部分格式字符和功能描述如 表3-3所示。建议初学者统一使用第一个小写字符。 表3-3 常用格式字符功能描述表 数据类型格式字符功能描述 int d或D或i或I 输入十进制整数 o 输入八进制整数 x或X 输入十六进制整数 u或U 输入无符号十进制整数 char c或C 输入一个字符 s 输入一个字符串 float f或e或E 输入实型数(用小数形式或指数形式) 【例3-4】 输入一个字符,输出其ASCII码值。 #include <stdio.h> int main() { char c; printf("请输入一个字符: "); scanf("%c",&c); //输入一个字符,存放到变量c 中 //输出列表中有两个c ,第一个按字符输出,第二个按整型输出 printf("%c 的ASCII 为: %d\n",c,c); } 举一反三: ① 输入一个整数,输出对应的字符。 ② 输入一个十或八进制数,输出其对应的十六进制数。 (2)“*”符:表示跳过对应的输入数据。例如: scanf("%d%*d%d",&a,&b); 当输入如下信息时: 30□15□6<CR> 系统会将30赋给a,6赋给b,而15被跳过,不赋给任何变量。 【例3-5】 用*忽略整数和字符之间的间隔符。 #include <stdio.h> int main() { 71 int age; char sex; printf("请输入年龄和性别(M 或F),空格分开: "); scanf("%d%*c%c",&age,&sex); //忽略中间间隔符 printf("年龄是: %d,性别是: %c\n",age,sex); } 程序运行结果为: 输入: 18□F<CR> 输出: 年龄是: 18,性别是: F 输入数据时,我们习惯性地将两个数据用间隔符(空格、Tab键和回车键)分开,但用%c 接收数据时,间隔符也会当正常字符一样被接收。例3-5中的输入语句如果写成: scanf("%d%c",&age,&sex); 同样输入: 18□F<CR> 则sex会接收空格(□),而不是F。因此常常使用类似“%*c”的形式忽略间隔符。 (3)宽度:用十进制正整数指定输入数据的宽度。例如: scanf("%3d%2d",&a,&b); 输入: 123456<CR> 则123赋给变量a,45赋给变量b,最后的6会留在缓冲区中,用于下一个输入。 输入: 1234<CR> 则123赋给a,4赋给b。即使输入数据不够所需位数,遇到回车也终止接收。 输入: 1<CR>2345<CR> 则1赋给a,23赋给b,45留在缓冲区。 【例3-6】 日期格式转换:输入日期20220520,转换为2022年5月20日。 #include <stdio.h> int main() { int year,month,day; printf("请按样例格式输入一个日期: 20220520\n"); scanf("%4d%2d%2d",&year,&month,&day); //按位数截取年月日 printf("%d 年%d 月%d 日\n",year,month,day); } 程序运行结果为: 输入: 20220520 输出: 2022 年5 月20 日 72 (4)长度:长度格式符有l和h两种,l表示输入长整型数据(如%ld,%lo,%lx)或双精 度浮点数(如%lf,%le);h表示输入短整型数据(如%hd,%ho,%hx)。 3 . 使用scanf 函数时需注意的问题 (1)特别注意scanf函数中的第二个参数必须是地址列表。 【例3-7】 地址列表错误示例。 #include <stdio.h> int main() { int a=0; scanf("%d",a); //a 前缺少& } 则程序会出现运行错误,程序终止运行。Dev-C++中的错误提示如图3-2所示。 图3-2 缺少& 时的运行错误提示信息 单击“联机检查解决方案并关闭该程序”选项,则会发现程序的返回值为3221225477, 如图3-3所示。 图3-3 返回错误编码图 此处的错误编码3221225477(0xC0000005)表示访问越界。访问越界是指访问了不属 于自己的内存空间。内存就像国土一样,绝不允许非法占用。应用程序运行时,操作系统要 为其变量分配内存空间,只有分配给该应用程序的内存可以随意存取数据,但绝对不可以向 其他内存中存放数据。而此例中,数据本来应该存放到首地址为&a,即为a分配的内存中 去,结果却存放到了首地址为a,即地址为0的内存中去,而首地址为a的空间并不属于该应 用程序,造成了越界访问。 (2)当使用scanf函数从键盘输入数据完毕后,一定要按下回车键,函数才会接收到数 据。其实scanf函数,以及后面要介绍的getchar函数,都是缓冲输入函数,即并不是直接接 73 收键盘数据,而是将用户输入的数据暂时存放在缓冲区中,当用户键入回车或缓冲区满了之 后,scanf函数才会从缓冲区中依次读取数据。如果缓冲区中的数据已经读取完,但仍需数 据,则函数会等待输入,直到满足函数要求或遇到非法数据为止;如果输入的数据多于函数 要求输入的数据,则多余的数据将留在缓冲区中,供下一个输入使用。 (3)当scanf中的两个相邻格式说明项紧密相连时,一般遇到以下三种情况之一,则认 为当前数据结束: ① 遇到间隔符(空格、Tab键和回车键)。 ② 遇到宽度限制,如%3d,只取3列。 ③ 遇到非法数据。 例如: scanf("%d%c",&a,&b); 输入: 12c34<CR> 则会把12赋给变量a,把字符c赋给变量b,而34留在缓冲区中。 输入: 12□c34<CR> 则会把12赋给变量a,把字符空格□赋给变量b,而c34留在缓冲区中。因为间隔符也 会被看作一个字符,正常接收。因此在C语言中不建议%c与其他格式符连用,否则特别容 易出现莫名其妙的错误。 例如: scanf("%d%d",&a,&b); 输入:12□34<CR>,12<Tab>34<CR>,12<CR>34<CR>,三种方式均能正确 接收,即a接收12,b接收34。 输入: 12c34<CR> 则a接收12,因为字母c对于整数是非法数据,因此认为数据结束,这样b就无法接收 到值,即保持原值不变。 (4)如果格式控制串中有普通字符串,即有多个紧密相连的普通字符,则输入数据时严 格按照“普通字符串前不加间隔符”(即如果格式控制串为:普通字符串+格式说明项,则普 通字符串和数据之间可以加入若干间隔符;如果格式控制串为:格式说明项+普通字符串, 则数据和普通字符串之间不能加入任何间隔符)的原则输入即可,除非格式说明项中有宽度 限制。例 如: scanf("a=%d,b=%d",&a,&b); 使用该语句把46接收到变量a 中,把18接收到变量b 中,则下面几种输入方式均合法 (其中□代表空格字符): ①a=46,b=18<CR>:不加任何间隔符,严格遵循输入格式。 74 ②a=□46,b=18<CR>:普通字符串a=和数据46间加入若干个空格间隔符。 ③a=<Tab>46,b=□18<CR>:普通字符串a=和数据46间加入若干个Tab间 隔符。④ a=46,b=18□<CR>:数据18后面加入若干个空格、Tab等间隔符。 而下面两种情况则不能正确接收: ① <Tab>a=46,b=18<CR>:普通字符串a=前加入Tab间隔符,违背输入原则。 ②a=46,□b=18<CR>:在普通字符串“,b=”中插入一个空格,导致与普通字符串 不匹配。 (5)用%c输入字符时,间隔符都会作为有效字符输入,而不再起间隔数据的作用。如: scanf("%c%c",&a,&b); 如果输入:c□d<CR>,则变量a 接收字符c,变量b 接收字符空格,字符d留在缓冲 区中。如 果输入:c<CR>,则变量a 接收字符c,变量b 接收字符回车。 学完此小节,可以解决计算瓷砖数案例中的问题“area等变量的值如何从键盘输入”。 一种解决方案为: scanf("%f%f%f",&area,&length,&width); 第二种解决方案为: scanf("area=%f,length=%f,width=%f",&area,&length,&width); 注意:这两种解决方案在具体输入时一定严格按照规定格式输入,否则无法接收到正 确数据。一定不要忘记各个变量前的&,否则程序会出现运行错误。 3.3.3 单字符输出函数putchar putchar是一个功能单一、使用简单的字符输出函数,其函数原型为: int putchar(int ch) 功能:向标准输出设备(显示器)输出一个字符。 (1)参数ch表示要输出的字符,字符应该为char类型,为什么此处为int呢? 在计算 机中,字符其实是以其对应ASCII码值形式存在的,char类型数据的存储、运算都会隐式转 换为int类型。因此,此处的实际参数具体形式可能千变万化,但本质是一个整数,只要表 达式最终能转换为一个整数即可。 【例3-8】 putchar函数参数测试程序。 #include <stdio.h> //把头文件stdio.h 包括到文件中 int main() { char a='A'; //定义字符型变量 int b=65; //定义整型变量,65 是字母A 的ASCII 码值 扫一扫 75 putchar(a+1); //参数为字符型表达式,可自动隐式转换为整型。输出字母B putchar(b+1); //参数为整型表达式,输出字母B putchar('B'); //参数为字符型常量,可自动隐式转换为整型。输出字母B putchar('\102'); //参数为转义字符,注意转义符中的102 是八进制数 putchar(7); //有ASCII 码的不可见字符也可输出,7 为一次响铃。 } 程序输出结果为: BBBB<响铃> 本程序除响铃语句外,每条输出语句的输出结果相同,均为字母B,但具体表达形式不 同,以期为读者提供正确的使用参考。 特别提醒:虽然参数ch为int类型,但ASCII码只有256个(除附录A 中的128个外 还有128个扩展字符)。因此,当ch的值超过255时,会自动进行取余运算:ch%256,使ch 始终在正确范围内。因此putchar(263)和putchar(7)的功能是一样的。 (2)函数返回值类型为整型,如果输出成功,则返回输出字符的ASCII码值,即参数 ch;若输出失败,则返回EOF(-1)。 putchar(ch)与printf("%c",ch)的功能等效,都是输出一个字符,经常替换使用,但返 回值含义不同:putchar返回的是输出字符的ASCII码值,而printf返回的是输出字符的个 数,此处永远为1。 3.3.4 单字符输入函数getchar getchar函数与putchar函数是一对,同样功能单一,使用简单,其函数原型为: int getchar() 功能:从标准输入设备(键盘)上输入一个字符。 getchar函数是一个无参函数,如果正确接收,则函数的返回值为输入字符的ASCII码 值,否则返回EOF(-1)。 【例3-9】 输入一个字符,输出它的后继字符。 程序分析:一个字符的后继字符是指其在ASCII码表中的后一个字符,如字母A 的后 继字符是B,d的后继字符是e。一个字符和其后继字符之间的ASCII码值差1。 #include <stdio.h> int main() { char ch; ch=getchar(); //接收一个字符 printf("%c 的后继字符是: %c\n",ch,ch+1); //输出其后继字符 } 使用getchar函数时需要注意以下问题: (1)getchar函数是一个缓冲输入函数,输入字符后必须按回车键才能接收数据。 扫一扫 76 (2)如果接收的是一个数字,也会按照字符处理。例如程序输入:1<CR>,则程序运 行结果为“1的后继字符是:2”。即接收的是1这个数字的ASCII码值,即49。 (3)空格、Tab键以及回车键等都会作为一个getchar函数能接收的有效字符。例如程 序输入:<CR>,则程序运行结果如图3-4所示。 图3-4 接收回车键时的程序结果 (4)如果输入多于一个字符,则只接收第一个字符。例如程序输入:32<CR>,则程序 运行结果为“3的后继字符是:4”,另一个字符2将留在缓冲区中。 例3-9中的ch=getchar()与scanf("%c",&ch)的功能基本等效,都是输入一个字符放 在变量ch中,区别在于:scanf的返回值为成功接收数据的项数,此处为1或0,而getchar 的返回值为接收字符的ASCII码值。在某些应用中,用getchar实现的程序代码更简洁,可 读性更强,如5.1节中的例5-2,6.4.2节中的例6-15等,读者学习到相关内容时再深入体会 即可。 3.3.5 不回显输入函数getch getch不是一个标准库函数,它位于conio.h中。conio是ConsoleInput/Output(控制 台输入输出)的简写,其中定义了通过控制台进行数据输入和数据输出的一些函数。conio.h 是Windows系统特有的,其他操作系统中不一定提供,因此可移植性较差。 函数原型为: int getch() 从函数原型看,getch与getchar的调用形式完全相同,功能也类似,都是从标准输入设 备(键盘)上输入一个字符,但主要有以下两点区别。 (1)getch为不回显函数,即用户输入的字符只默默接收,但不显示在屏幕上。 (2)getch为非缓冲输入函数,即有输入时立即接收,不需要按回车键。 getch经常应用于以下两种场景中。 (1)实现暂停功能,解决闪退或闪过问题。在有些开发环境中运行C语言程序或者在 操作系统中直接执行C语言生成的.exe文件时,会出现一闪就退出的现象,无法看清执行 结果;或者程序输出数据过多,一些有用信息一闪而过。这时都可以加一个getch语句,留 住当前界面,实现“按任意键继续”功能。 (2)实现密码输入功能。用户输入密码时一般不显示真正的输入字符,而用特殊符号 代替。 【例3-10】 编程实现,利用getch实现一位密码输入功能。 扫一扫 77 #include <stdio.h> #include <conio.h> //包含相应头文件 int main() { int ch; printf("请输入一位密码: "); ch=getch(); //接收字符,但屏幕不显示 putchar('*'); //屏幕输出替代字符 printf("\n 您输入的密码是: %c",ch); } 程序运行结果如图3-5所示。 图3-5 程序运行结果图 3.4 其他常用函数 为更便捷、更高效地使用C语言,标准函数库中提供了众多函数,读者只需使用#include 命令将其对应的头文件包含到程序中,然后按照函数原型说明使用即可。函数原型就像一 份函数使用说明书,让读者清楚函数需要几个参数,参数的数据类型是什么,函数执行完毕 后会返回一个什么数据类型的值。 本节内容初学时只需浏览框架,后期用到时再返回本节仔细阅读即可。 3.4.1 常用数学函数 数学函数对应的头文件是math.h。 (1)求双精度浮点数绝对值函数fabs(求整数绝对值函数为abs)。 函数原型:doublefabs(doublex)。 函数功能:求x 的绝对值。 通过函数原型可以看出,fabs函数需要一个double类型的参数,实际应用时,参数可以 是一个常量、变量或者表达式,只要最终结果值的数据类型为或者能隐式转换为double即 可。函数返回值为double类型,说明其结果可以参与double类型数据能参与的一切运算。 【例3-11】 编程实现:求数轴上两点之间的距离。 #include <stdio.h> #include <math.h> 扫一扫 78 int main() { double x=2.3,y=8.9,distance; //x、y 为数轴上的两个点 /*将x-y 的值,即-6.6 传递给函数fabs;因为fabs 函数的返回值类型为double,因此 需要定义一个double 类型的变量distance 存放其返回值*/ printf("distance=%lf\n",fabs(x-y)); //注意输出时一定用%lf (2)求平方根函数sqrt。 函数原型:doublesqrt(doublex)。 函数功能:计算x 的平方根。 【例3-12】 编程实现:求平面中两点之间的距离。 #include <stdio.h> #include <math.h> int main() { float x1,y1,x2,y2,distance; //点1(x 1,y 1) 点2(x 2,y 2) printf("请输入第一个点: 空格分开"); scanf("%f%f",&x1,&y1); printf("请输入第二个点: 空格分开"); scanf("%f%f",&x2,&y2); distance=sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)); //距离公式 printf("distance=%f\n",distance); } (3)上取整函数ceil。 函数原型:doubleceil(doublex)。 函数功能:求不小于x 的最小整数,即上取整。 【例3-13】 编程实现:根据铺设瓷砖的面积(平方米),所选择瓷砖的尺寸(厘米×厘 米),计算需要瓷砖的块数。 #include <stdio.h> #include <math.h> int main() { float area,length,width; int tiles; //瓷砖块数,整型变量 printf("请输入铺设面积(平方米):") ; scanf("%f",&area); printf("请输入瓷砖的长度和宽度(厘米):"); scanf("%f%f",&length,&width); //ceil 返回值为double 型,赋给整型变量tiles 时,最好加一个强制类型转换 tiles=(int)ceil(area*100/(length*width)); printf("大约需要瓷砖块数: %d",tiles); } 79 (4)求方函数pow。 函数原型:doublepow(doublex,doubley)。 函数功能:计算x 的y 次方的值。 【例3-14】 编程实现:计算n 位二进制位能表示的最大整数。 #include <stdio.h> #include <math.h> int main() { int n,maxint; printf("请输入二进制位的位数: "); scanf("%d",&n); maxint=(int)pow(2,n-1)-1; //n 位二进制位能表示的最大整数2n -1-1 printf("%d 位能表示的最大整数为: %d",n,maxint); } 例如,整型变量占4B,32位,程序执行结果为: 输入: 32 输出: 32 位能表示的最大整数为: 2147483647 3.4.2 常用字符函数 字符函数对应的头文件为ctype.h。 (1)小写字母转大写函数toupper。 函数原型:inttoupper(intc)。 函数功能:如果c有对应的大写字母,则返回大写字母,否则返回原值。 (2)大写字母转小写函数tolower。 函数原型:inttolower(intc)。 函数功能:如果c有对应的小写字母,则返回小写字母,否则返回原值。 【例3-15】 编程实现:如果一个字符是小写字母,则转换为大写字母,否则保持不变。 #include <stdio.h> #include <ctype.h> int main() { char ch; printf("请输入一个小写字母: "); ch=getchar(); putchar(toupper(ch)); } 程序执行结果为: 输入: a //输入是小写字母 输出: A //则转换为大写字母 输入: 3 //输入不是小写字母 输出: 3 //则保持不变 扫一扫 80 (3)检查字母函数isalpha。 函数原型:intisalpha(intc)。 函数功能:判断字符c是否为英文字母,若是,则返回非0(小写字母为2,大写字母为 1),否则返回0。 例如:isalpha('*')的值为0,isalpha('E')的值为1,而isalpha(e' ')的值为0。 3.4.3 其他常用工具函数 (1)获取系统时间函数time,包含在头文件time.h中。 函数原型:time_ttime(time_t*t)。 函数功能:获取计算机的当前日历时间。日历时间是指从一个时间点(一般是1970年 1月1日0时0分0秒)到现在的秒数。 函数中用到的数据类型time_t,实质是整型,为了提高程序的通用性,一般将32位编译 器中的long(长整型),将64位编译器中的__int64(即长长整型)取别名为time_t。 函数获取时间的方式既可以通过参数t,也可以通过返回值,甚至参数和返回值同时获 取也可以,但重复数据也没有实际意义。建议读者采用返回值形式,此时参数设置为空 (NULL)即可。 【例3-16】 编程实现:获取自时间纪元至今的秒数和年数。 #include <stdio.h> #include <time.h> int main() { time_t curtime; //定义接收当前时间的变量,也可以定义为long 型 curtime=time(NULL); //用返回值形式返回日历时间,参数为NULL printf( "时间纪元已经飞逝了%ld 秒, 约%ld 年", curtime, curtime/(24* 60* 60* 365)); } 因为当前时间在不断变化,所以程序的运行结果中的秒数每次均不同。 (2)产生一个伪随机数函数rand,包含在头文件stdlib.h中。 函数原型:intrand()。 函数功能:产生一个范围在0~RAND_MAX的伪随机整数,其中RAND_MAX是一 个符号常量,在stdlib.h中可以找到其定义“#defineRAND_MAX0x7fff”。0x7fff就是 32767,即短整型的最大值,因此产生的就是一个短整型非负数。 (3)随机数生成器初始化函数srand,包含在头文件stdlib.h中。 函数原型:voidsrand(unsignedseed)。 函数功能:为随机数生成器提供一粒新的随机种子。 如果随机种子相同,rand产生的随机数也是相同的。为真正达到每次产生的随机数不 同,需要在调用rand之前用srand设置不同的随机种子。一般采用time函数产生的系统时 间作为随机种子,因为系统时间精确到秒,变化很快,基本能满足程序多次执行时种子不同 的需求。 【例3-17】 编程实现:随机产生一个三位整数。 扫一扫