C++ scanf 完全指南:格式化输入详解

前言

scanf 是 C/C++ 中最常用的格式化输入函数,与 printf 相对应。它可以从标准输入(键盘)读取数据并按照指定格式进行解析。本文将全面介绍 scanf 的用法、注意事项和常见陷阱。

1. 基本语法

#include <cstdio>  // C++ 中使用 cstdio

int scanf(const char* format, ...);

返回值:成功匹配并赋值的参数个数,失败返回 EOF(-1)。

2. 格式说明符

2.1 常用格式符

格式符 对应类型 说明 示例
%d int* 十进制整数 scanf("%d", &num);
%ld long* 长整型 scanf("%ld", &num);
%lld long long* 长长整型 scanf("%lld", &num);
%u unsigned int* 无符号整型 scanf("%u", &num);
%f float* 单精度浮点数 scanf("%f", &num);
%lf double* 双精度浮点数 scanf("%lf", &num);
%Lf long double* 长双精度 scanf("%Lf", &num);
%c char* 单个字符 scanf("%c", &ch);
%s 字符串(不含空格) scanf("%s", str);
%p void** 指针地址 scanf("%p", &ptr);
%x / %X int* 十六进制整数 scanf("%x", &num);
%o 八进制整数 scanf("%o", &num);
%[] char* 字符集合 scanf("%[^\n]", str);

2.2 修饰符

// 宽度限制:最多读取 n 个字符
char str[100];
scanf("%10s", str);   // 最多读取10个字符

// 忽略赋值:读取但不存储
int num;
scanf("%*d %d", &num);  // 跳过第一个整数,读取第二个

// h 修饰符:short 类型
short s;
scanf("%hd", &s);

// hh 修饰符:char 类型(作为整数)
char c;
scanf("%hhd", &c);

3. 基础示例

3.1 单个变量输入

#include <cstdio>

int main() {
    int age;
    double height;
    char grade;
    
    printf("请输入年龄、身高、等级:");
    scanf("%d %lf %c", &age, &height, &grade);
    
    printf("年龄: %d, 身高: %.2f, 等级: %c\n", 
           age, height, grade);
    
    return 0;
}

3.2 多个变量输入

#include <cstdio>

int main() {
    int a, b, c;
    
    printf("请输入三个整数(用空格或回车分隔):");
    scanf("%d %d %d", &a, &b, &c);
    
    printf("总和: %d, 平均值: %.2f\n", 
           a + b + c, (a + b + c) / 3.0);
    
    return 0;
}

3.3 字符串输入

#include <cstdio>

int main() {
    char name[50];
    char city[50];
    
    printf("请输入姓名:");
    scanf("%s", name);  // 注意:不需要 &,name 本身就是地址
    
    printf("请输入城市:");
    scanf("%s", city);
    
    printf("你好,%s 来自 %s\n", name, city);
    
    return 0;
}

4. 高级用法

4.1 读取带空格的字符串

#include <cstdio>

int main() {
    char sentence[100];
    
    // 方法1:使用 %[^\n] 读取直到换行符
    printf("请输入一句话(可包含空格):");
    scanf(" %[^\n]", sentence);  // 前面的空格跳过残留的换行符
    
    printf("你输入的是:%s\n", sentence);
    
    // 方法2:使用 %[^x] 读取直到遇到特定字符
    char data[100];
    scanf("%[^,]", data);  // 读取直到遇到逗号
    
    return 0;
}

4.2 字符集合匹配

#include <cstdio>

int main() {
    char hex[20];
    char only_letters[50];
    
    // 只读取十六进制字符
    printf("请输入十六进制数:");
    scanf("%[0-9a-fA-F]", hex);
    printf("读取的十六进制: %s\n", hex);
    
    // 只读取字母
    printf("请输入字母(遇到非字母停止):");
    scanf("%[A-Za-z]", only_letters);
    printf("读取的字母: %s\n", only_letters);
    
    return 0;
}

4.3 抑制赋值和宽度限制

#include <cstdio>

int main() {
    int year, month, day;
    
    // 跳过前两个数字,读取第三个
    printf("请输入日期(格式:2024-01-15):");
    scanf("%*d-%*d-%d", &day);
    printf("日: %d\n", day);
    
    // 宽度限制防止溢出
    char username[10];
    printf("请输入用户名(最多9个字符):");
    scanf("%9s", username);
    printf("用户名: %s\n", username);
    
    return 0;
}

4.4 混合格式输入

#include <cstdio>

int main() {
    int id;
    char name[30];
    float score;
    
    // 按特定格式读取
    printf("请输入(格式:ID,姓名,成绩):");
    scanf("%d,%[^,],%f", &id, name, &score);
    
    printf("ID: %d, 姓名: %s, 成绩: %.1f\n", 
           id, name, score);
    
    return 0;
}

5. 返回值检查

5.1 错误处理

#include <cstdio>

int main() {
    int num;
    int result = scanf("%d", &num);
    
    if (result == 1) {
        printf("成功读取整数: %d\n", num);
    } else if (result == 0) {
        printf("输入不匹配,未能读取整数\n");
    } else if (result == EOF) {
        printf("输入结束或发生错误\n");
    }
    
    return 0;
}

5.2 循环读取

#include <cstdio>

int main() {
    int num;
    int sum = 0;
    int count = 0;
    
    printf("请输入多个整数(输入非数字结束):\n");
    while (scanf("%d", &num) == 1) {
        sum += num;
        count++;
    }
    
    if (count > 0) {
        printf("共输入 %d 个数,总和: %d,平均值: %.2f\n",
               count, sum, (double)sum / count);
    }
    
    return 0;
}

6. 常见陷阱与解决方案

6.1 缓冲区残留问题

#include <cstdio>

int main() {
    int num;
    char ch;
    
    // ❌ 问题代码
    printf("输入一个整数:");
    scanf("%d", &num);
    printf("输入一个字符:");
    scanf("%c", &ch);  // 会读取残留的换行符!
    
    // ✅ 解决方案
    printf("输入一个整数:");
    scanf("%d", &num);
    getchar();  // 消耗掉换行符
    printf("输入一个字符:");
    scanf("%c", &ch);
    
    // 或者使用空格跳过空白字符
    printf("输入一个整数:");
    scanf("%d", &num);
    printf("输入一个字符:");
    scanf(" %c", &ch);  // 空格会跳过所有空白字符
    
    return 0;
}

6.2 字符串缓冲区溢出

#include <cstdio>

int main() {
    char buffer[10];
    
    // ❌ 危险:可能溢出
    // scanf("%s", buffer);
    
    // ✅ 安全:限制宽度
    scanf("%9s", buffer);  // 最多读取9个字符,留一个给 '\0'
    
    return 0;
}

6.3 浮点数格式混淆

#include <cstdio>

int main() {
    float f;
    double d;
    
    // ❌ 错误:%f 对应 float,%lf 对应 double
    // scanf("%f", &d);   // 错误!
    // scanf("%lf", &f);  // 错误!
    
    // ✅ 正确
    scanf("%f", &f);   // float 使用 %f
    scanf("%lf", &d);  // double 使用 %lf
    
    return 0;
}

6.4 混合 cin 和 scanf

#include <iostream>
#include <cstdio>

int main() {
    int num;
    
    // ⚠️ 混用可能导致问题
    std::cin >> num;
    scanf("%d", &num);  // 可能跳过或读取错误
    
    // ✅ 解决方案:统一使用一种方式
    // 或使用 ios::sync_with_stdio(false) 同步
    
    return 0;
}

7. 实用示例

7.1 读取未知数量的整数

#include <cstdio>

int main() {
    int arr[100];
    int n = 0;
    
    printf("请输入一组整数(Ctrl+D/Ctrl+Z 结束):\n");
    while (scanf("%d", &arr[n]) == 1 && n < 100) {
        n++;
    }
    
    printf("共读取 %d 个数:", n);
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    
    return 0;
}

7.2 解析时间格式

#include <cstdio>

int main() {
    int h, m, s;
    
    printf("请输入时间(格式:HH:MM:SS):");
    if (scanf("%d:%d:%d", &h, &m, &s) == 3) {
        printf("时: %d, 分: %d, 秒: %d\n", h, m, s);
        printf("总秒数: %d\n", h * 3600 + m * 60 + s);
    } else {
        printf("格式错误!\n");
    }
    
    return 0;
}

7.3 简单计算器

#include <cstdio>

int main() {
    double a, b;
    char op;
    
    printf("请输入表达式(如:3.5 + 2.1):");
    if (scanf("%lf %c %lf", &a, &op, &b) == 3) {
        switch (op) {
            case '+': printf("%.2f\n", a + b); break;
            case '-': printf("%.2f\n", a - b); break;
            case '*': printf("%.2f\n", a * b); break;
            case '/': 
                if (b != 0) printf("%.2f\n", a / b);
                else printf("除数不能为零!\n");
                break;
            default: printf("不支持的运算符!\n");
        }
    } else {
        printf("输入格式错误!\n");
    }
    
    return 0;
}

8. 快速参考卡片

// 基本类型
scanf("%d", &i);      // int
scanf("%ld", &l);     // long
scanf("%lld", &ll);   // long long
scanf("%u", &u);      // unsigned int
scanf("%f", &f);      // float
scanf("%lf", &d);     // double
scanf("%c", &c);      // char
scanf("%s", s);       // 字符串(无空格)
scanf("%p", &p);      // 指针

// 修饰符
scanf("%10s", s);     // 宽度限制
scanf("%*d", &i);     // 忽略输入
scanf("%hd", &s);     // short
scanf("%hhd", &c);    // char(整数)
scanf("%[^\n]", s);   // 读取整行
scanf("%[0-9]", s);   // 只读数字

// 返回值检查
int result = scanf(...);
if (result == EOF) { /* 错误或文件结束 */ }
if (result == 0) { /* 匹配失败 */ }
if (result == n) { /* 成功读取 n 个值 */ }

9. 最佳实践总结

  1. 总是检查返回值:确认成功读取了期望数量的输入
  2. 限制字符串宽度:防止缓冲区溢出
  3. 注意缓冲区残留:使用 getchar()" %c" 处理换行符
  4. 统一输入方式:避免混用 cinscanf
  5. 使用正确的格式符float%fdouble%lf
  6. 初始化变量:防止读取失败时使用未初始化的值

结语

scanf 是强大而灵活的输入函数,但也需要谨慎使用。掌握格式说明符、理解缓冲区行为、做好错误处理,就能安全高效地使用它。记住本文的最佳实践,避免常见陷阱!


参考资料