🛡️

身份验证

答对题目即可进入,验证你是谁

剩余尝试次数:3

输入输出 — 让程序与用户对话

C语言学习教程

输入输出 — 让程序与用户对话

🌟 课前故事:从”广播”到”对话”

到目前为止,你的程序像是一个”广播电台”——它不停地向世界说话(用 printf 输出信息),但从不聆听回应。这样的程序固然有用,但它每次运行的结果都是一样的,没有”参与感”。

真正的交互式程序像一个聊天机器人——它问你问题,等你的回答,再根据你的回答继续对话。

广播模式(只有输出)          对话模式(输入+输出)
┌───────────┐              ┌───────────┐
│  程序     │              │  程序     │
│           │              │           │
│  printf──→│ 屏幕          │  printf──→│ 屏幕
│           │              │      ↑    │
│           │              │   scanf   │
│           │              │      │    │
│           │              │ 键盘──┘    │
└───────────┘              └───────────┘
   单向输出                   双向交互

今天,我们给程序”装上耳朵”——用 scanf 读取用户的输入。从此你的程序不再自言自语,而是可以和用户真正地”对话”了。


📖 概念讲解:为什么需要输入?

什么是输入?

输入(Input) 是程序从外部世界获取数据的过程。最常见的外部来源是键盘,也可以是文件、网络、传感器等。

为什么需要输入?

没有输入的程序是”死”的——它每次运行都做同样的事。有了输入,程序可以:

  1. 个性化:不同用户输入不同数据,得到不同结果
  2. 交互性:程序根据用户的选择做出不同的响应
  3. 实用性:计算器需要用户输入数字,登录需要用户输入密码
  4. 灵活性:同一个程序可以处理各种不同的数据

什么时候用到输入输出?

几乎所有有用的程序都涉及输入输出:


📊 输出部分:printf 深度掌握

printf 基础回顾

#include <stdio.h>

int main() {
    // 最基础:输出固定文字
    printf("Hello, World!\n");

    // 带变量:用占位符输出变量
    int age = 25;
    printf("我今年 %d\n", age);

    // 多个变量:占位符和参数一一对应
    int a = 10, b = 20;
    printf("%d + %d = %d\n", a, b, a + b);

    return 0;
}

printf 格式化占位符参考表

占位符数据类型输出格式示例
%d%iint有符号十进制整数printf("%d", 42)42
%uunsigned int无符号十进制整数printf("%u", 42)42
%ffloat / double十进制浮点数(默认6位)printf("%f", 3.14)3.140000
%lfdouble(scanf用)%f 用法略有不同scanf("%lf", &d)
%cchar单个字符printf("%c", 'A')A
%schar[](字符串)字符串printf("%s", "Hello")Hello
%x%Xint十六进制整数printf("%x", 255)ff
%oint八进制整数printf("%o", 8)10
%p指针指针地址printf("%p", &x)0x7ffe...
%%(无)输出百分号本身printf("%%")%

printf 格式化控制详解

printf 的强大之处在于格式化控制——你可以精确控制数字的宽度、对齐方式、小数位数等。

格式说明符的完整结构:
%[标志][宽度][.精度][长度]类型

例如:%08.2f
      │││ │└── f:浮点类型
      │││ └──── .2:保留2位小数(精度)
      ││└────── 8:总宽度8个字符
      │└─────── 0:用0填充(标志)
      └──────── %:起始符号
#include <stdio.h>

int main() {
    int n = 42;
    float f = 3.14159f;

    // === 整数宽度控制 ===
    printf("默认:      |%d|\n", n);     // |42|
    printf("宽度5右对齐: |%5d|\n", n);   // |   42|
    printf("宽度5左对齐: |%-5d|\n", n);  // |42   |
    printf("宽度5补零:   |%05d|\n", n);  // |00042|

    // === 浮点数精度控制 ===
    printf("默认:       |%f|\n", f);     // |3.141590|
    printf("保留2位:    |%.2f|\n", f);   // |3.14|
    printf("保留4位:    |%.4f|\n", f);   // |3.1416|(四舍五入)
    printf("宽8精2:     |%8.2f|\n", f);  // |    3.14|
    printf("左对齐宽8精2:|%-8.2f|\n", f); // |3.14    |
    printf("补零宽8精2: |%08.2f|\n", f); // |00003.14|

    // === 组合使用 ===
    printf("\n%-15s %5s %8s\n", "商品", "数量", "单价");
    printf("%-15s %5d %8.2f\n", "苹果", 3, 5.50);
    printf("%-15s %5d %8.2f\n", "香蕉", 5, 3.20);
    printf("%-15s %5d %8.2f\n", "橙子", 2, 6.80);

    return 0;
}
输出效果:
默认:      |42|
宽度5右对齐: |   42|
宽度5左对齐: |42   |
宽度5补零:   |00042|
默认:       |3.141590|
保留2位:    |3.14|
保留4位:    |3.1416|
宽8精2:     |    3.14|
左对齐宽8精2:|3.14    |
补零宽8精2: |00003.14|

商品           数量    单价
苹果              3    5.50
香蕉              5    3.20
橙子              2    6.80

printf 返回值(你可能不知道的知识)

printf 不仅输出文字,还会返回实际输出了多少个字符。这在某些场景下很有用:

#include <stdio.h>

int main() {
    int count;

    count = printf("Hello\n");     // 6 个字符(H,e,l,l,o,\n)
    printf("输出了 %d 个字符\n", count);

    count = printf("");             // 输出空字符串
    printf("输出了 %d 个字符\n", count); // 0

    return 0;
}

📊 输入部分:scanf 详细教程

scanfprintf 的”反向伙伴”——printf 把数据从程序送到屏幕,scanf 把数据从键盘送到程序。

scanf 基本语法

scanf("格式字符串", &变量1, &变量2, ...);
      ↑              ↑
      格式说明符     变量的地址(后面填到哪个变量里)

🔴 关键:为什么要加 &(取地址符号)?

int age;
scanf("%d", &age);
//          ↑
//          & 是取地址运算符
//          意思是:把用户输入的数字,存到 age 变量所在的内存位置

通俗比喻:

scanf 需要知道数据该放到哪里,而 &变量名 就是”这个变量的内存地址”。

int age;
// 变量 age 在内存中占了一块空间(4字节)
// &age 就是这块空间的"门牌号"——地址
// scanf 根据这个地址,把数据写进正确的位置

scanf("%d", &age);   // ✅ scanf 知道往哪里放
// scanf("%d", age); // ❌ scanf 不知道往哪里放,程序崩溃!

读取不同类型的数据

#include <stdio.h>

int main() {
    int age;
    float height;
    double weight;
    char grade;

    // —— 读取整数 ——
    printf("请输入你的年龄:");
    scanf("%d", &age);          // 用 %d 读整数

    // —— 读取 float ——
    printf("请输入你的身高(米):");
    scanf("%f", &height);       // 用 %f 读 float

    // —— 读取 double ——
    printf("请输入你的体重(公斤):");
    scanf("%lf", &weight);      // 用 %lf 读 double(注意是 lf!)

    // —— 读取字符 ——
    printf("请输入你的等级(A/B/C):");
    scanf(" %c", &grade);       // 用 %c 读字符(注意前面的空格!)

    // —— 输出确认 ——
    printf("\n===== 你的信息 =====\n");
    printf("年龄:%d\n", age);
    printf("身高:%.2f\n", height);
    printf("体重:%.1f 公斤\n", weight);
    printf("等级:%c\n", grade);

    return 0;
}

scanf 格式占位符对照表

占位符读取类型变量类型示例
%d十进制整数intscanf("%d", &x)
%f浮点数floatscanf("%f", &f)
%lf双精度浮点数doublescanf("%lf", &d)
%c单个字符charscanf(" %c", &ch)
%s字符串(无空格)char[]scanf("%s", str)
%x十六进制整数intscanf("%x", &x)

⚠️ printf 用 %f 输出 double,但 scanf 用 %lf 读 double

// ✅ printf 输出 double
double d = 3.14159;
printf("%f\n", d);        // printf 中 float 和 double 都用 %f

// ✅ scanf 读取 float
float f;
scanf("%f", &f);           // scanf 读取 float 用 %f

// ✅ scanf 读取 double
double d2;
scanf("%lf", &d2);         // scanf 读取 double 用 %lf(注意!)

为什么? 这是C语言的一个历史遗留问题。记住这条规则就好:printf 输出用 %f,scanf 读取 double 用 %lf


💻 完整交互示例:一个简单的计算器

#include <stdio.h>

int main() {
    int num1, num2;

    printf("╔══════════════════════╗\n");
    printf("║   简易加法计算器      ║\n");
    printf("╚══════════════════════╝\n\n");

    // 读取第一个数字
    printf("请输入第一个数字:");
    scanf("%d", &num1);

    // 读取第二个数字
    printf("请输入第二个数字:");
    scanf("%d", &num2);

    // 计算并输出结果
    printf("\n--- 计算结果 ---\n");
    printf("%d + %d = %d\n", num1, num2, num1 + num2);
    printf("%d - %d = %d\n", num1, num2, num1 - num2);
    printf("%d * %d = %d\n", num1, num2, num1 * num2);
    printf("%d / %d = %d\n", num1, num2, num1 / num2);
    printf("%d %% %d = %d\n", num1, num2, num1 % num2);

    return 0;
}
运行效果:
╔══════════════════════╗
║   简易加法计算器      ║
╚══════════════════════╝

请输入第一个数字:15
请输入第二个数字:4

--- 计算结果 ---
15 + 4 = 19
15 - 4 = 11
15 * 4 = 60
15 / 4 = 3
15 % 4 = 3

⚠️ scanf 的三大经典”坑”

坑1:%c 会”吃”到上一次遗留的换行符

这是 scanf 最著名的坑,几乎所有初学者都会遇到。

// ❌ 问题代码
#include <stdio.h>

int main() {
    int num;
    char ch;

    printf("输入一个数字:");
    scanf("%d", &num);       // 用户输入:42[回车]
                             // scanf 读取了 42,但 [回车] 还在输入缓冲区里!

    printf("输入一个字符:");
    scanf("%c", &ch);        // ch 读到了上次遗留的 '\n'(回车符)!
                             // 程序不等待用户输入,直接跳过了!

    printf("数字:%d, 字符:[%c]\n", num, ch);
    return 0;
}
实际运行效果(注意第二行根本没有等你输入):
输入一个数字:42
输入一个字符:数字:42, 字符:[
]

为什么会这样?

当你输入 42 然后按回车时,实际输入的是四个字符:4 2 \r \n

输入缓冲区:
┌───┬───┬────┬────┐
│ 4 │ 2 │ \r │ \n │    ← 回车产生了 \r 和 \n
└───┴───┴────┴────┘
         ↑      ↑
    %d 读取 42   \n 还留在缓冲区里!
                下一次 %c 就读到了它

解决方案:

// ✅ 方案1:在 %c 前加一个空格(最常用)
scanf(" %c", &ch);        // 前面的空格告诉 scanf:"跳过所有空白字符"
                          // 空白字符包括:空格、\t、\n 等

// ✅ 方案2:手动清空缓冲区
scanf("%d", &num);
getchar();                // 吃掉 \n
scanf("%c", &ch);

// ✅ 方案3:使用 fflush(不推荐,跨平台有问题)
scanf("%d", &num);
fflush(stdin);            // Windows 上行,Linux/Mac 上不行
scanf("%c", &ch);

推荐用方案1——在 %c 前面加空格:" %c"。简单又跨平台。

坑2:%s 遇到空格就停止

// ❌ 问题代码
char name[50];
printf("请输入你的全名:");
scanf("%s", name);              // 用户输入:张三丰
                                // name 里只有"张"!空格后面的丢了

printf("你好,%s\n", name);     // 输出:你好,张

原因: %s空白字符(空格、Tab、换行)作为分隔符,遇到就停止读取。

解决方案:

// ✅ 方案1:用 fgets 读取整行(推荐)
char name[50];
printf("请输入你的全名:");
fgets(name, sizeof(name), stdin);  // 读取整行,包括空格
// 注意:fgets 会保留末尾的 \n,需要手动去掉
name[strcspn(name, "\n")] = '\0';  // 去掉末尾的换行符
printf("你好,%s\n", name);

// ✅ 方案2:用 scanf 的字符集(只读取非换行字符)
char name[50];
printf("请输入你的全名:");
scanf("%[^\n]", name);            // 读取直到遇到 \n 为止
getchar();                        // 吃掉 \n
printf("你好,%s\n", name);

// ✅ 方案3:用 gets(不推荐,不安全,已被C标准废弃)
// gets(name);  // 不要用!

注意: fgets%[^\n] 后面课程的”字符串”部分会详细讲,这里知道有这个问题和解决方案就行。

坑3:scanf 的返回值——检查输入是否成功

// ❌ 脆弱代码:假设用户一定会输入数字
int age;
printf("请输入年龄:");
scanf("%d", &age);       // 如果用户输入 "abc" 会怎样?
                         // scanf 读不到数字,age 的值不变(还是垃圾值)

// ✅ 健壮代码:检查 scanf 的返回值
int age;
printf("请输入年龄:");
if (scanf("%d", &age) == 1) {
    // scanf 返回值 = 成功读取的变量个数
    printf("你的年龄是:%d\n", age);
} else {
    printf("输入无效!请输入一个数字。\n");
}

scanf 的返回值含义:

// 多个变量的返回值检查
int a, b;
int result = scanf("%d %d", &a, &b);
if (result == 2) {
    printf("成功读取两个数:%d%d\n", a, b);
} else if (result == 1) {
    printf("只成功读取了一个数\n");
} else {
    printf("一个都没读到\n");
}

💻 综合示例:学生信息注册系统

#include <stdio.h>

int main() {
    char name[50];
    int studentId;
    int age;
    float chinese, math, english;
    float average;

    printf("╔══════════════════════════════╗\n");
    printf("║     学生信息注册系统         ║\n");
    printf("╚══════════════════════════════╝\n\n");

    // —— 基本信息 ——
    printf("请输入姓名:");
    scanf("%s", name);                 // 名字不含空格用 %s

    printf("请输入学号:");
    scanf("%d", &studentId);

    printf("请输入年龄:");
    scanf("%d", &age);

    // —— 成绩信息 ——
    printf("\n请输入各科成绩(0-100):\n");
    printf("  语文:");
    scanf("%f", &chinese);

    printf("  数学:");
    scanf("%f", &math);

    printf("  英语:");
    scanf("%f", &english);

    // —— 计算统计 ——
    float total = chinese + math + english;
    average = total / 3.0f;

    // —— 输出结果 ——
    printf("\n╔══════════════════════════════╗\n");
    printf("║         注册结果              ║\n");
    printf("╚══════════════════════════════╝\n\n");

    printf("  姓名:%s\n", name);
    printf("  学号:%d\n", studentId);
    printf("  年龄:%d\n", age);
    printf("  ─────────────────────\n");
    printf("  语文:%6.1f\n", chinese);
    printf("  数学:%6.1f\n", math);
    printf("  英语:%6.1f\n", english);
    printf("  ─────────────────────\n");
    printf("  总分:%6.1f\n", total);
    printf("  平均:%6.1f\n", average);

    if (average >= 60.0f) {
        printf("  状态:✅ 及格\n");
    } else {
        printf("  状态:❌ 不及格\n");
    }

    return 0;
}

📊 输入输出流程图

┌─────────────────────────────────────────────┐
│              程序运行流程                      │
│                                              │
│  ① printf 输出提示 ──→ 屏幕显示"请输入..."    │
│         ↓                                    │
│  ② 程序暂停,等待用户输入                      │
│         ↓                                    │
│  ③ 用户敲键盘,输入数据                        │
│         ↓                                    │
│  ④ 用户按回车,数据进入"输入缓冲区"            │
│         ↓                                    │
│  ⑤ scanf 从缓冲区读取数据                      │
│         ↓                                    │
│  ⑥ scanf 把数据写入变量(需要 &地址)          │
│         ↓                                    │
│  ⑦ 程序继续执行,处理变量                      │
│         ↓                                    │
│  ⑧ printf 输出结果 ──→ 屏幕显示结果           │
└─────────────────────────────────────────────┘

关键理解:输入缓冲区

你在键盘上敲的字符不会立刻被程序收到。它们先存在一个叫”输入缓冲区”的地方,当你按下回车键后,scanf 才去缓冲区里取数据。这也是”坑1”(%c 读到遗留的 \n)的根本原因。


⚠️ 常见错误(5个经典”坑”)

错误1:scanf 忘记 &

// ❌ 错误代码
int age;
scanf("%d", age);        // 忘记 &!
// 程序行为:崩溃(segmentation fault)或 age 的值不变

// ✅ 正确代码
int age;
scanf("%d", &age);       // 必须用 & 取地址

// 例外:字符串数组不加 &
char name[50];
scanf("%s", name);       // name 本身就是地址,不需要 &name

记住: 普通变量(int, float, char, double)用 &,数组/字符串不用 &。为什么?后面数组课程会详细解释。

错误2:double 用 %f 而不是 %lf

// ❌ 错误代码
double pi;
scanf("%f", &pi);        // double 应该用 %lf!

// ✅ 正确代码
double pi;
scanf("%lf", &pi);       // scanf 读取 double 必须用 %lf

// printf 则不同——输出 double 用 %f 就行
printf("%f\n", pi);      // ✅ printf 中 double 和 float 都用 %f

错误3:%c 读到换行符

// ❌ 错误代码
int num;
char ch;
scanf("%d", &num);       // 输入 42[回车]
scanf("%c", &ch);        // ch 变成了 '\n'!

// ✅ 正确代码
int num;
char ch;
scanf("%d", &num);
scanf(" %c", &ch);       // %c 前加空格,跳过空白字符

错误4:scanf 输入格式和用户实际输入不匹配

// ❌ 假设代码这样写
int year, month, day;
scanf("%d-%d-%d", &year, &month, &day);
// 用户必须输入:2024-01-15
// 如果用户输入:2024 01 15(空格分隔)——读取失败!

// ✅ 更宽松的写法
int year, month, day;
scanf("%d %d %d", &year, &month, &day);
// 用户输入:2024 1 15(空格或换行分隔都可以)

// 🏆 最佳做法:一次读一个,每个都有提示
printf("年:"); scanf("%d", &year);
printf("月:"); scanf("%d", &month);
printf("日:"); scanf("%d", &day);

错误5:gets 的缓冲区溢出风险(安全警告!)

// ❌ 危险代码——永远不要用 gets!
char buffer[10];
gets(buffer);            // 如果用户输入 50 个字符……
                         // gets 不管三七二十一全写进去,覆盖了不该覆盖的内存!
                         // 这就是著名的"缓冲区溢出"漏洞

// ✅ 安全替代
char buffer[10];
fgets(buffer, sizeof(buffer), stdin);  // fgets 最多读 sizeof(buffer)-1 个字符

💡 练习建议

初级练习

  1. 自我介绍:让用户输入姓名和年龄,然后程序打招呼
  2. 简单计算器:读取两个整数,输出和、差、积、商、余数
  3. 温度转换:读取摄氏度,转换为华氏度 (F = C * 9/5 + 32)

中级练习

  1. 圆的计算:读取半径,计算并输出圆的周长和面积
  2. 成绩统计:读取语文、数学、英语三科成绩,输出总分和平均分
  3. 字符探索:读取一个字符,输出它和它的ASCII码

进阶思考

  1. 输入验证:修改计算器程序,当用户输入无效数字时给提示而不是崩溃
  2. 格式化收据:读取商品名、数量、单价,输出一张格式整齐的购物小票
  3. 缓冲区实验:故意在 %d 后接 %c,体验”换行符问题”,然后修复它

📝 要点速查清单


🔗 相关主题