C语言中scanf详解

9.2 scanf

当我们有了变量,我们需要给变量输⼊值就可以使⽤的时候可以使⽤scanf 函数 ,如果需要将变量的值输出在屏幕上prinf 函数,下⾯看⼀个例⼦:

画图来演示这一流程:
标准输入一般就是指键盘,标准输出一般就是屏幕

9.2.1 scanf基本⽤法

scanf() 函数⽤于读取用户的键盘输⼊。

程序运⾏到这个语句时,会停下来,等待⽤⼾从键盘输⼊。
用户输⼊数据、按下回⻋键后,scanf() 就会处理用户的输⼊,将其存⼊变量 。它的原型定义在头⽂件<stdio.h>中 。

scanf() 的语法跟printf() 类似。

cpp 复制代码
int a = 0;
scanf("%d",&a);

它的第⼀个参数是⼀个格式字符串,⾥⾯会放置占位符 (与printf()的占位符基本一致,告诉编译器如何解读用户的输⼊,需要提取的数据是什么类型。

这是因为C语⾔的数据都是有类型的,scanf() 必须提前知道⽤⼾输⼊的数据类型,才能处理数据。它的其余参数就是存放⽤⼾输⼊的变量,格式字符串⾥⾯有多少个占位符,就有多少个变量

上⾯⽰例中,scanf() 的第⼀个参数i 。%d ,表⽰⽤⼾输⼊的应该是⼀个整数。%d就是⼀个占位符,%是占位符的标志,d表示整数.第二个参数&a表示将用户从键盘输入的整数存放入变量a之中.

注意:

(1). 变量前面必须加上&运算符(指针变量除外),因为scanf()传递的不是值而是地址,即将变量a的地址指向用户输入的值.

(2).如果这里的变量是指针变量(比如字符串变量),就不需要加&运算符.因为指针变量里储存的就是地址.(指针变量后面详细讲解).

下⾯是⼀次将键盘输⼊读⼊多个变量的例⼦。

cpp 复制代码
scanf("%d%d%f%f",&i,&j,&x,&y);

上面的示例中,格式字符串%d%d%f%f,表示用户输入的前两个数是整数,后两个是浮点数.比如输入: 1 -10 5.5 2.0e3.那么这四个值会被依次放入i,j,x,y四个变量中.

scanf() 处理数值占位符时,会⾃动过滤空⽩字符,包括空格、制表符、换⾏符等。 所以,⽤⼾输⼊的数据之间,有⼀个或多个空格不影响scanf() 解读数据。另外,用户使⽤回⻋键,将输⼊分成⼏⾏,也不影响解读。如:

上⾯⽰例中,⽤⼾分成四⾏输⼊,得到的结果与⼀⾏输⼊是完全⼀样的。每次按下回⻋键以后, scanf() 就会开始解读,如果第⼀⾏匹配第⼀个占位符,那么下次按下回⻋键时,就会从第⼆个占 位符开始解读。

输入原理:

scanf() 处理用户输⼊的原理是,用户的输⼊先放⼊缓存,等到按下回⻋键后,按照占位符对缓存 进⾏解读。

解读用户输⼊时,会从上⼀次解读遗留的第⼀个字符开始,直到读完缓存,或者遇到第⼀个不符合条件的字符为⽌.

具体事例如下:

讲解: 上⾯⽰例中,scanf() 读取⽤⼾输⼊时,%d 占位符会忽略起⾸的空格,从处开始获取数据,读取到-13停下来,因为后⾯的.不属于整数的有效字符。这就是说,占位符%d 会读到-13 .

第⼆次调⽤scanf() 时,就会从上⼀次停⽌解读的地⽅,继续往下读取。这⼀次读取的⾸字符是.,由于对应的占位符是%f,会读取到.45e12 ,这是采⽤科学计数法的浮点数格式。后⾯的# 不属于浮点数的有效字符,所以会停在这⾥。

由于scanf() 可以连续处理多个占位符,所以上⾯的例⼦也可以写成下⾯这样:

cpp 复制代码
scanf("%d%f", &x, &y);

9.2.2 scanf 的返回值

scanf() 的返回值是⼀个整数,并不是从键盘输入的数据,而是表⽰成功读取的变量个数。

如果没有读取任何项,或者匹配失败,则返回0。

如果在成功读取任何数据之前,发⽣了读取错误或者遇到读取到⽂件结尾,则返回常量EOF(-1)。
EOF-endoffile ⽂件结束标志.

cpp 复制代码
#include <stdio.h>
int main()
{
    int a = 0;
    int b = 0;
    float f = 0.0f;
    int r = scanf("%d %d %f", &a, &b, &f);
    printf("a=%d b=%d f=%f\n", a, b, f);
    printf("r = %d\n", r);
    return 0;
}

输⼊输出测试:

如果输⼊2个数后,按ctrl+z ,提前结束输⼊,在VS环境中按3次ctrl+z ,才可以结束了输⼊,我们可以看到r是2,表⽰正确读取了2个数值:

如果⼀个数字都不输⼊,直接按3次ctrl+z ,输出的r是-1,也就是EOF.

9.2.3 占位符

9.2.3.1 常见scanf()占位符

scanf() 常⽤的占位符如下,与printf() 的占位符基本⼀致。

9.2.3.2 %s占位符特性

下面特别说一下**%s占位符** ,,它其实不能简单地等同于字符串。

1. 特性一:

**核心读取规则:「非空开始,空白结束」
%s 是专门用于读取字符串的占位符,但它有严格的扫描边界,分为两个阶段:

  1. 起始条件:跳过前导空白,从「第一个非空白字符」开始.
    scanf() 遇到 %s 时,会自动跳过开头所有空白字符(空格、回车 \n、Tab \t),直到找到第一个非空白字符,才开始正式读取。
  2. 终止条件:遇到「第一个空白字符」就停止.
    读取过程中,一旦再次遇到任意空白字符(空格、回车、Tab),立即停止读取,并将空白字符之前的所有内容作为字符串存入变量。**

举个例子:

cpp 复制代码
char str[20];      //定义一个字符数组
scanf("%s", str);   //输入字符串

因为 %s 的终止条件是空白字符,而空格 / 回车正是多个单词的分隔符,所以⽆法⽤来读取多个单词,除⾮多个 %s ⼀起使⽤。这也意味着, scanf("%s") 仅适用于读取「无空格的简单字符串」(如单词、用户名、编号),完全不适合处理书名、歌曲名、句子等含空格的内容.

2. 特性二:.

scanf("%s") 成功读取字符串后,会在存储的字符末尾自动添加一个 \0(空字符)作为字符串的结束标志。如下代码:

风险提醒:如果输入字符数超过数组长度(如数组长度为 3,输入abcd),%s 会直接缓冲区溢出,覆盖后续内存,导致程序崩溃。

9.2.4 五个大坑

接下来这里介绍scanf使用中的五个大坑.

1. 遇到空白字符就停止,只能读取一个单词 / 数字

这个上面提到过,直接看案例:

可见 name 中只存了 "Li"," Hua" 被留在缓冲区.

解决方法有两个:

  • fgets() 替代 scanf("%s"),可以完整读取带空格的整行输入.
  • %[^\n] 扫描集,指定「读到换行符为止」.
cpp 复制代码
scanf("%[^\n]", name); // 读取到换行前的所有字符,包含空格

2.scanf()读完后把 \n 留在缓冲区,污染后续输入

原理: scanf() 读取数字 / 字符串时,不会消费输入末尾的回车符 \n(不仅是 \n,所有空白符(空格、Tab)都可能残留,尤其是用%d/%s读取后,缓冲区会残留输入时的换行符 ),会把它留在输入缓冲区里。如果后面用 getchar()、fgets()、scanf("%c") 等函数,会直接读到这个残留的 \n,导致逻辑错误。

解决方法:

cpp 复制代码
// 方法1:用 getchar() 循环吃掉所有残留字符
while (getchar() != '\n');
// 方法2:用 scanf("%*[^\n]") 跳过所有非换行字符,再读走换行
scanf("%*[^\n]"); getchar();

3.不检查数组长度,直接导致缓冲区溢出

原理:scanf("%s", arr) 和被废弃的 gets() 一样,完全不检查目标数组的长度,如果用户输入的字符数超过数组容量,会直接越界写入内存,导致程序崩溃、数据被篡改,甚至产生安全漏洞。

解决方法:

(1). 给 %s 加长度限制,指定最多读取的字符数.

cpp 复制代码
char buf[5];
scanf("%4s", buf); // 最多读4个字符,自动补 '\0',永远不会溢出

(2).直接使用fgets,它会严格限制读取长度,更安全.

4. 输入类型不匹配,程序直接「卡死」

scanf() 对输入类型匹配要求极严格:如果格式串要求 %d(整数),但用户输入了字母 / 符号,匹配会直接失败,并且不会从缓冲区移除错误数据,导致后续所有 scanf() 都直接失败,程序陷入死循环。

cpp 复制代码
int num;
while (1)
{
    scanf("%d", &num); // 输入 abc
    // 匹配失败,abc 留在缓冲区,下一轮 scanf 继续读 abc,永远失败
    printf("输入:%d\n", num);
}

解决方法:

  • 检查 scanf() 的返回值:scanf() 会返回成功匹配的参数个数,用它判断输入是否合法.
  • 类型不匹配时,清空缓冲区,重新输入:
cpp 复制代码
int num;
while (scanf("%d", &num) != 1) 
{
    // 匹配失败,清空缓冲区
    while (getchar() != '\n');
    printf("输入非法,请重新输入整数:");
}

5. %c 会读取空白字符,导致误读

原理:scanf() 中,只有 %c 不会自动跳过空白字符,会直接读取缓冲区里的空格、回车、Tab。如果前面有 scanf() 残留的 \n%c 会直接把它读走,而不是等待用户输入新字符.

cpp 复制代码
int age;
char op;
scanf("%d", &age); // 输入 18 回车
scanf("%c", &op);  // op 直接读到了残留的 '\n',不是用户输入的字符

解决方法:

(1). 在 %c 前面加一个空格 ,让 scanf() 先跳过所有空白字符,再读取有效字符.

cpp 复制代码
scanf(" %c", &op); // 注意 %c 前面的空格!

(2).同样可以用「先清空缓冲区」的方法,避免残留字符干扰.

9.2.4.1 总结:

内容太多,我们来做一个总结:

9.2.5 赋值忽略符

有时,用户的输⼊可能不符合预定的格式。如下代码:

cpp 复制代码
#include <stdio.h>
int main()
{
    int year = 0;
    int month = 0;
    int day = 0;
    scanf("%d-%d-%d", &year, &month, &day);
    printf("%d %d %d\n", year, month, day);
    return 0;
}

上⾯⽰例中,如果⽤⼾输⼊ 格式,⽐如 2020-01-01 ,就会正确解读出年、⽉、⽇。问题是⽤⼾可能输⼊其他 2020/01/01 ,这种情况下, scanf() 解析数据就会失败。

为了避免这种情况,scanf() 提供了⼀个赋值忽略符 (assignmentsuppressioncharacter) * 。 只要把 * 加在任何占位符的百分号后⾯,这个占位符只负责读取匹配的字符,但不会把值赋给任何变量,读取后直接丢弃.

简而言之,其作用是跳过指定位置的分隔符.

cpp 复制代码
#include <stdio.h>
int main()
{
    int year = 0;
    int month = 0;
    int day = 0;
    scanf("%d%*c%d%*c%d", &year, &month, &day);
    return 0;
}

上⾯⽰例中,%*c就是在占位符的百分号后⾯,加⼊了赋值忽略符*,表⽰这个占位符没有对应的变量,解读后不必返回。

相关推荐
凤山老林3 小时前
26-Java this 关键字
java·开发语言
ZenosDoron3 小时前
keil软件修改字体,Asm editor,和C/C++ editor的区别
c语言·开发语言·c++
山栀shanzhi3 小时前
C/C++之:构造函数为什么不能设置为虚函数?
开发语言·c++·面试
lsx2024063 小时前
.toggleClass() 方法详解
开发语言
yuan199973 小时前
C&CG(列与约束生成)算法,来解决“风光随机性”下的微网鲁棒配置问题
c语言·开发语言·算法
李白的天不白3 小时前
读到数据为undefind是的几种情况
开发语言·javascript·ecmascript
LeocenaY4 小时前
C语言面试题总结
c语言·开发语言·数据结构
爱吃芹菜炒肉4 小时前
Chapter 16: Power Management
服务器·c语言·网络·tcp/ip·pcie
城管不管4 小时前
嵌入模型Embedding Model
java·开发语言·python·embedding·嵌入模型