前面五篇文章我们熟悉了变量、常量、数据类型但程序还像个闷葫芦——要么沉默不语要么只喊一句固定的“Hello, World”。要让程序真正和人互动就得学会两样本事输出把数据展示给用户看printf。输入接收用户输入的数据scanf。今天我们就来攻克它们。掌握了输入输出你的程序就不再是“静态的”而是能对话的活物了。一、printf把数据打扮好了再显示printf名字里的f是 “formatted” 的意思也就是“按格式打印”。它能让你决定输出内容的样子是左对齐还是右对齐小数点后保留几位占多宽的位置。这些控制全靠格式字符串里的转换说明来完成。1. 基本格式printf(格式字符串,参数1,参数2,...);格式字符串里包含两类东西普通字符原样输出比如Hello。转换说明以%开头用来“挖坑”后面的参数会填进去并格式化。例intage20;printf(我今年 %d 岁。\n,age);%d就是一个坑age的值 20 填进去输出我今年 20 岁。2. 常见转换说明一览转换说明对应类型输出形式%d/%iint有符号十进制整数%uunsigned int无符号十进制整数%ffloat/double十进制浮点数默认 6 位小数%e/%Efloat/double科学计数法如1.234568e03%g/%Gfloat/double自动选%f或%e去掉多余零%cchar单个字符%schar *字符串字符串直到\0%p指针地址十六进制%%无对应参数输出一个%符号注意printf输出double和float都可以用%f因为float在传递时会被自动提升为double。示例#includestdio.hintmain(void){intcount3;doubleprice19.95;chargradeA;printf(买了 %d 件单价 %.2f 元评级 %c\n,count,price,grade);return0;}输出买了 3 件单价 19.95 元评级 A3. 修饰符控制宽度、精度和对齐在%和字母之间还可以加修饰符来精细控制最小字段宽度%5d表示至少占 5 个字符宽度不足则右对齐补空格。%-5d表示左对齐。精度%.2f表示浮点数保留 2 位小数%.5s表示字符串最多输出前 5 个字符。长度前缀%ld用于long int%lld用于long long int%hd用于short int。printf里%lf也可以用于double但通常%f足够。示例intn42;doublepi3.14159265;printf(|%8d|\n,n);// 输出 | 42|printf(|%-8d|\n,n);// 输出 |42 |printf(%.4f\n,pi);// 输出 3.1416printf(%10.2f\n,pi);// 输出 3.1410宽度2位小数4. 常见错误参数数量或类型不匹配intx10;printf(%d %d\n,x);// 少参数可能打印垃圾值printf(%f\n,x);// 类型不匹配未定义行为务必要保证转换说明和后面参数的类型、数量一一对应否则结果是未定义行为。二、scanf读取用户输入输出是把数据给出去输入就是把数据收进来。scanf同样是格式化输入基本用法和printf类似但有一点致命不同——你必须把变量的地址交给它而不是变量本身。1. 基本格式scanf(格式字符串,地址列表);例intage;printf(请输入你的年龄);scanf(%d,age);// 注意 取地址符printf(你输入的年龄是 %d\n,age);age表示“变量 age 的地址”scanf需要知道这个地址才能把读取的值写进去。这个和printf直接传值完全不同是初学者最容易忘记的区别。2. 常见输入转换说明转换说明对应变量类型说明%dint读取十进制整数%uunsigned int读取无符号整数%ffloat读取浮点数%lfdouble读取 doublescanf 里必须用%lf%cchar读取一个字符包括空格、换行%schar数组即字符串读取一个单词遇到空白停止特别注意scanf里float用%fdouble必须用%lf这和printf不一样。3. 多个输入intday,month,year;printf(请输入年月日用空格分开);scanf(%d %d %d,day,month,year);printf(输入的日期是%d-%d-%d\n,year,month,day);用户可以在一行或多行输入空白字符空格、换行、制表符会被当作分隔符。4.scanf的返回值scanf会返回成功读取并赋值的项数如果遇到输入结束或匹配失败返回EOF或小于你期望的项数。这可以用来判断用户输入是否有效。intnum;printf(请输入一个整数);if(scanf(%d,num)1){printf(你输入了 %d\n,num);}else{printf(输入无效\n);}三、输入缓冲区的“坑”scanf并不是直接从键盘读字符而是从一个叫做输入缓冲区的地方读取。你用键盘输入的字符先暂存在缓冲区里按下回车后scanf再按格式解析。这个机制会带来一些陷阱特别是混合%c和%d的时候。陷阱一残留的换行符intage;charfirst_char;printf(年龄);scanf(%d,age);printf(一个字符);scanf(%c,first_char);printf(age%d, char%c\n,age,first_char);运行后你可能发现第二个scanf根本没等你输入直接结束了first_char里是\n回车。这是因为输入18后按下的回车还留在缓冲区%c直接把它吃了。解决办法在%c前加一个空格scanf( %c, first_char);这个空格会跳过之前的所有空白字符包括换行符。陷阱二输入不匹配导致死循环intnum;while(1){printf(请输入一个整数);if(scanf(%d,num)!1){printf(输入错误\n);// 但错误的输入还在缓冲区里下次循环会无限失败}else{break;}}如果用户输入了字母scanf匹配失败那些字母仍留在缓冲区下一次循环scanf又会读到同样的错误字符导致死循环。解决方法是清空缓冲区可以使用一个循环while (getchar() ! \n);来丢弃剩余字符。后文讲到字符串和文件 I/O 时会详细介绍更健壮的输入处理方法。现在只需知道“缓冲区”这个概念即可。四、一个小互动程序把printf和scanf组合起来写一个简单的计算器#includestdio.hintmain(void){doublenum1,num2;charop;printf(请输入算式例如 3 5);scanf(%lf %c %lf,num1,op,num2);if(op){printf(结果%.2lf\n,num1num2);}elseif(op-){printf(结果%.2lf\n,num1-num2);}elseif(op*){printf(结果%.2lf\n,num1*num2);}elseif(op/){if(num2!0){printf(结果%.2lf\n,num1/num2);}else{printf(错误除数不能为零。\n);}}else{printf(不支持的运算符。\n);}return0;}这里用了简单的if-else分支下一篇文章我们会系统学习分支结构。现在你只需感受一下——程序终于可以根据你的输入做出不同的反应了。五、小结printf和scanf是你和程序之间的翻译官。printf把内存里的数据变成人能读的文字scanf把人敲的字符变成内存里的变量。它们的核心是格式字符串学会了%d、%f、%c这些符号就等于学会了和 C 语言对话的基本语法。但要真正让程序“有判断力”光会输入输出还不够。下一篇我们就进入分支结构让程序学会根据不同条件执行不同代码——从if到switch让程序聪明起来。课后小练习写一个程序提示用户输入圆的半径然后计算并输出周长和面积周长 2 * 3.14 * r面积 3.14 * r * r输出时保留两位小数。让用户输入三个整数计算它们的平均值并输出平均值保留一位小数。写程序让用户输入一个字符输出该字符的 ASCII 码值提示用%d格式输出char变量。陷阱挑战先读取一个整数再读取一个完整的字符串可能包含空格你会怎么实现提示%s会停在空格处要用其他方法可以自己搜索fgets试试看。我们下期见获取本系列示例代码请访问 GitCode 仓库。