6. 格式化输入输出:和程序说说话

前面五篇文章,我们熟悉了变量、常量、数据类型,但程序还像个闷葫芦——要么沉默不语,要么只喊一句固定的“Hello, World”。要让程序真正和人互动,就得学会两样本事:

  • 输出:把数据展示给用户看(printf)。
  • 输入:接收用户输入的数据(scanf)。

今天我们就来攻克它们。掌握了输入输出,你的程序就不再是“静态的”,而是能对话的活物了。


一、printf:把数据打扮好了再显示

printf 名字里的 f 是 “formatted” 的意思,也就是“按格式打印”。它能让你决定输出内容的样子:是左对齐还是右对齐,小数点后保留几位,占多宽的位置。这些控制,全靠格式字符串里的转换说明来完成。

1. 基本格式

printf("格式字符串", 参数1, 参数2, ...);

格式字符串里包含两类东西:

  • 普通字符:原样输出,比如 "Hello"
  • 转换说明:以 % 开头,用来“挖坑”,后面的参数会填进去并格式化。

例:

int age = 20;
printf("我今年 %d 岁。\n", age);

%d 就是一个坑,age 的值 20 填进去,输出 我今年 20 岁。

2. 常见转换说明一览

转换说明对应类型输出形式
%d / %iint有符号十进制整数
%uunsigned int无符号十进制整数
%ffloat / double十进制浮点数(默认 6 位小数)
%e / %Efloat / double科学计数法(如 1.234568e+03
%g / %Gfloat / double自动选 %f%e,去掉多余零
%cchar单个字符
%schar *(字符串)字符串(直到 \0
%p指针地址(十六进制)
%%无对应参数输出一个 % 符号

注意:printf 输出 doublefloat 都可以用 %f,因为 float 在传递时会被自动提升为 double

示例:

#include <stdio.h>

int main(void) {
    int count = 3;
    double price = 19.95;
    char grade = 'A';
    printf("买了 %d 件,单价 %.2f 元,评级 %c\n", count, price, grade);
    return 0;
}

输出:

买了 3 件,单价 19.95 元,评级 A

3. 修饰符:控制宽度、精度和对齐

% 和字母之间,还可以加修饰符来精细控制:

  • 最小字段宽度%5d 表示至少占 5 个字符宽度,不足则右对齐补空格。%-5d 表示左对齐。
  • 精度%.2f 表示浮点数保留 2 位小数;%.5s 表示字符串最多输出前 5 个字符。
  • 长度前缀%ld 用于 long int%lld 用于 long long int%hd 用于 short intprintf%lf 也可以用于 double(但通常 %f 足够)。

示例:

int n = 42;
double pi = 3.14159265;
printf("|%8d|\n", n);        // 输出 |      42|
printf("|%-8d|\n", n);       // 输出 |42      |
printf("%.4f\n", pi);        // 输出 3.1416
printf("%10.2f\n", pi);      // 输出       3.14(10宽度,2位小数)

4. 常见错误:参数数量或类型不匹配

int x = 10;
printf("%d %d\n", x);        // 少参数,可能打印垃圾值
printf("%f\n", x);           // 类型不匹配,未定义行为

务必要保证转换说明和后面参数的类型、数量一一对应,否则结果是未定义行为。


二、scanf:读取用户输入

输出是把数据给出去,输入就是把数据收进来。scanf 同样是格式化输入,基本用法和 printf 类似,但有一点致命不同——你必须把变量的地址交给它,而不是变量本身。

1. 基本格式

scanf("格式字符串", 地址列表);

例:

int age;
printf("请输入你的年龄:");
scanf("%d", &age);   // 注意 & 取地址符
printf("你输入的年龄是 %d\n", age);

&age 表示“变量 age 的地址”,scanf 需要知道这个地址,才能把读取的值写进去。这个和 printf 直接传值完全不同,是初学者最容易忘记的区别。

2. 常见输入转换说明

转换说明对应变量类型说明
%dint读取十进制整数
%uunsigned int读取无符号整数
%ffloat读取浮点数
%lfdouble读取 double(scanf 里必须用 %lf
%cchar读取一个字符(包括空格、换行!)
%schar 数组(即字符串)读取一个单词(遇到空白停止)

特别注意:scanffloat%fdouble 必须用 %lf,这和 printf 不一样。

3. 多个输入

int day, month, year;
printf("请输入年月日(用空格分开):");
scanf("%d %d %d", &day, &month, &year);
printf("输入的日期是:%d-%d-%d\n", year, month, day);

用户可以在一行或多行输入,空白字符(空格、换行、制表符)会被当作分隔符。

4. scanf 的返回值

scanf 会返回成功读取并赋值的项数,如果遇到输入结束或匹配失败,返回 EOF 或小于你期望的项数。这可以用来判断用户输入是否有效。

int num;
printf("请输入一个整数:");
if (scanf("%d", &num) == 1) {
    printf("你输入了 %d\n", num);
} else {
    printf("输入无效!\n");
}

三、输入缓冲区的“坑”

scanf 并不是直接从键盘读字符,而是从一个叫做输入缓冲区的地方读取。你用键盘输入的字符先暂存在缓冲区里,按下回车后,scanf 再按格式解析。

这个机制会带来一些陷阱,特别是混合 %c%d 的时候。

陷阱一:残留的换行符

int age;
char first_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);,这个空格会跳过之前的所有空白字符(包括换行符)。

陷阱二:输入不匹配导致死循环

int num;
while (1) {
    printf("请输入一个整数:");
    if (scanf("%d", &num) != 1) {
        printf("输入错误!\n");
        // 但错误的输入还在缓冲区里!下次循环会无限失败
    } else {
        break;
    }
}

如果用户输入了字母,scanf 匹配失败,那些字母仍留在缓冲区,下一次循环 scanf 又会读到同样的错误字符,导致死循环。解决方法是清空缓冲区:可以使用一个循环 while (getchar() != '\n'); 来丢弃剩余字符。

后文讲到字符串和文件 I/O 时,会详细介绍更健壮的输入处理方法。现在只需知道“缓冲区”这个概念即可。


四、一个小互动程序

printfscanf 组合起来,写一个简单的计算器:

#include <stdio.h>

int main(void) {
    double num1, num2;
    char op;

    printf("请输入算式(例如 3 + 5):");
    scanf("%lf %c %lf", &num1, &op, &num2);

    if (op == '+') {
        printf("结果:%.2lf\n", num1 + num2);
    } else if (op == '-') {
        printf("结果:%.2lf\n", num1 - num2);
    } else if (op == '*') {
        printf("结果:%.2lf\n", num1 * num2);
    } else if (op == '/') {
        if (num2 != 0) {
            printf("结果:%.2lf\n", num1 / num2);
        } else {
            printf("错误:除数不能为零。\n");
        }
    } else {
        printf("不支持的运算符。\n");
    }

    return 0;
}

这里用了简单的 if-else 分支,下一篇文章我们会系统学习分支结构。现在你只需感受一下——程序终于可以根据你的输入,做出不同的反应了。


五、小结

printfscanf 是你和程序之间的翻译官。printf 把内存里的数据变成人能读的文字,scanf 把人敲的字符变成内存里的变量。它们的核心是格式字符串,学会了 %d%f%c 这些符号,就等于学会了和 C 语言对话的基本语法。

但要真正让程序“有判断力”,光会输入输出还不够。下一篇,我们就进入分支结构,让程序学会根据不同条件执行不同代码——从 ifswitch,让程序聪明起来。


课后小练习

  1. 写一个程序,提示用户输入圆的半径,然后计算并输出周长和面积(周长 = 2 * 3.14 * r,面积 = 3.14 * r * r),输出时保留两位小数。
  2. 让用户输入三个整数,计算它们的平均值并输出,平均值保留一位小数。
  3. 写程序让用户输入一个字符,输出该字符的 ASCII 码值(提示:用 %d 格式输出 char 变量)。
  4. (陷阱挑战)先读取一个整数,再读取一个完整的字符串(可能包含空格),你会怎么实现?提示:%s 会停在空格处,要用其他方法,可以自己搜索 fgets 试试看。

我们下期见!

代码下载链接: https://pan.quark.cn/s/a4b39357ea24 第 一 章 概述 1-1 简述计算机程序设计语言的发展阶段。 解: 自从计算机诞生以来,程序设计语言经历了从机器语言、汇编语言到高级语言的演变过程,C++语言作为一种面向对象的编程语言,也属于高级语言范畴。 1-2 面向对象的编程语言具备哪些特性? 解: 面向对象的编程语言与传统的编程语言有着本质的区别,其设计初衷是为了更直观地模拟现实世界中存在的事物及其相互关系。这类编程语言将客观事物视为具有属性行为的对象,通过抽象方法提取出同一类对象的共同属性(静态特征)行为(动态特征),从而构建类。借助类的继承与多态机制,能够便捷地实现代码复用,显著缩短软件开发周期,并确保软件风格的一致性。因此,面向对象的编程语言使得程序能够较为准确地反映问题域的本质,软件开发人员可以运用人类惯用的思维模式进行开发工作。C++语言是目前应用最为广泛的面向对象编程语言。 1-3 结构化程序设计方法是什么?这种方法有哪些优势不足? 解: 结构化程序设计的核心思想是自顶向下、逐步求精;其程序结构按照功能划分为多个基本模块;各模块之间的关联尽可能简化,在功能上保持相对独立性;每个模块内部均由顺序、选择循环三种基本结构构成;模块化实现的具体途径是利用子程序。结构化程序设计由于采用模块分解与功能抽象,自顶向下、分而治之的策略,从而有效地将一个较为复杂的程序系统设计任务分解成许多易于管理处理的子任务,便于开发与维护。 尽管结构化程序设计方法具备诸多优点,但它本质上仍是一种面向过程的程序设计方法,将数据与处理数据的操作分离为相互独立的实体。当数据结构发生变化时,所有相关的处理过程都需要进行相应的调整,每一种...
已经博主授权,源码转载自 https://pan.quark.cn/s/a4b39357ea24 【高清晰度壁纸】是一种适用于计算机或移动设备的高解析度图像,通常用于定制用户界面,以增强视觉感受。$4K$分辨率指的是宽度约为$3840$像素,高度约为$2160$像素的显示标准,这种分辨率提供了极为清晰的细节,使得图像在大尺寸屏幕上呈现更为生动逼真的效果。本压缩文件内含$20$张$4K$高清晰度壁纸,每张均从知名搜索引擎必应及彼岸图网中经过细致挑选。这些壁纸的题材丰富多样,涵盖了自然景观、科幻元素、游戏场景以及人物画像等多个方面,能够满足不同用户的需求。 1. **$125c1aa02ad94869ef055b870a54af560ad1574e144e03-qL6oaN_fw658.gif$**:这可能是一张动态壁纸,由于$gif$格式支持动态效果,或许包含有趣的动画元素,为桌面增添活力。 2. **$204b05b99e9b404aa6436f3c7c03d9c9.jpeg$**:$JPEG$是一种常见的静态图像格式,适合存储高品质照片,可能是一张风景或人物图片。 3. **加拿大班夫国家公园的朱砂湖的星空$4K$壁纸_彼岸图网.jpg**:这张壁纸展现了自然的宏伟,将班夫国家公园的优美湖泊与璀璨星空相结合,为用户带来宁静且谐的视觉体验。 4. **《星球大战堕落秩序(Star Wars Jedi_ Fallen Order)》$4K$游戏壁纸_彼岸图网.jpg**:这是一张基于热门游戏《星球大战:堕落秩序》设计的壁纸,对于游戏爱好者而言极具吸引力,可能包含游戏中的角色或场景。 5. **陈钰琪倚天屠龙记$4K$壁纸_彼岸图网.jpg**:陈钰琪...
源码下载地址: https://pan.quark.cn/s/95927341e579 该方法适用于二进制数值向十进制数值的转化,其中A代表十进制数值,B代表二进制数值。{A,B}序列会执行位移操作,每次左移一位,同时检验A中的每四位数值是否>4,若超过四则进行加三调整,否则维持原状;B的位数决定了左移操作的重复次数。最终,A的数值即为B转换后的十进制表达。此代码示例专注于32位二进制数值向十进制数值的转换。在数字操作领域,二进制与十进制之间的相互转换是一项基础性操作。二进制体系(Base-2)采用01两种符号来表示数值,而十进制体系(Base-10)则使用0到9这十个符号。在计算机科学范畴内,特别是在硬件描述语言(例如Verilog)的应用中,掌握并执行此类转换显得尤为关键。下文将深入阐述如何借助Verilog代码实现32位二进制数值向十进制数值的转换。 我们必须明确Verilog是一种用于数字系统逻辑设计与验证的硬件描述语言。在所提及的代码中,`module b32_o(bdata, odata)`定义了一个名为 `b32_o` 的Verilog模块,该模块接收一个32位输入 `bdata`(二进制数据)并输出一个32位结果 `odata`(十进制数据)。 转换的核心逻辑在于对二进制数值进行逐位解析并依据特定规则实施调整。文中指出,针对每四位分组,我们需评估这四位数值是否大于4(4h4)。若超过四,则执行加三操作,此调整源于二进制的1000相当于十进制的8,故需将此部分值递增至下一位,即加三。该操作会在32位二进制数值的每个四位组上反复执行,总共进行32次。 代码中的 `always @(bdata)` 区块设定了一个触发机制,当 `bdata` 发生变化...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值