(38)C语言 ‘printf‘ 和 ‘scanf‘ 的进阶用法!以及所有输入输出函数总结!scanf 的返回值?数组溢出!error C4996: ‘scanf‘:《小鹅说 C 语言》

部分项目展示

【双人迷宫游戏】:

在这里插入图片描述

【俄罗斯方块】:

在这里插入图片描述

【完整笔记链接】:

https://mp.weixin.qq.com/s/J5bTlJdA613DB9cCb94eag


暂时无法在飞书文档外展示此内容

  • 温馨提示:是之前的章节补充!

(19)输入 & 输出语句(1)(20)输入 & 输出语句(2)

1、scanf() 的高级用法

1.1、demo - 输入多个整形变量

scanf(“%d%d%f%f”, &i, &j, &x, &y);

解释说明:

格式字符串 %d%d%f%f ,表示用户输入的前两个是整数,后两个是浮点数。

比如输入 1 -20 3.4 -4.0e3

则这四个值将依次放入 i 、 j 、 x 、 y 四个变量。

scanf() 处理数值占位符时,会自动过滤空白字符,包括空格、制表符、换⾏符等。

所以,用户输入的数据之间,有⼀个或多个空格不影响 scanf() 解读数据。另外,用户使用回车键,将输入分成几行,也不影响解读(小心 %c)。

注意:

所有占位符之中,除了 %c 以外,都会自动忽略起首的空白字符。

如果要强制跳过字符前的空白字符,可以写成 scanf(" %c", &ch) ,即 %c 前加上⼀个空格,表示跳过零个或多个空白字符。

1↙
-20↙
3.4↙
-4.0e3↙

解释说明:

表示按下回车键。

上面示例中,用户分成四行输入,得到的结果与⼀行输入 1 -20 3.4 -4.0e3 是完全⼀样的。

每次按下回车键以后,scanf() 就会开始解读,如果第⼀行匹配第⼀个占位符,那么下次按下回车键时,就会从第⼆个占位符开始解读。

scanf() 处理用户输入的原理是:

用户的输入先放入缓存,等到按下回车键后,按照占位符对缓存进行解读。

解读会从上⼀次解读遗留的第⼀个字符开始,直到读完缓存,或者遇到第⼀个不符合条件的字符为止。

1.2、在输入时注意格式对应
#include <stdio.h>
int main()
{
    int a;
    float x;
    char c1;
    scanf("a=%d",&a);
    scanf("x=%f",&x);
    scanf("c1=%c",&c1);
    printf("a=%d,x=%f,c1=%c",a,x,c1);
    return 0;
}

若在输入时用错空格键或者换行符,则会出现错误:

a=1 x=1.2 c1=3

上述输入只能输出 a=1 因为空格键取代了 x 的位置 输入完 x=1.2 后空格键有取代了应该输入 c1 的位置。

正确的输入应为:

a=1x=1.2c1=3
1.3、scanf 是有返回值的
#include <stdio.h>
#include <stdlib.h>
int main()
{
    int a;
    int b;
    int c;
    printf("请输入三个整数:");
    int x = scanf("%d%d%d", &a, &b, &c);
    printf("d%\n%d\n",a,x);
}

测试 1 输出:

请输入三个整数:1 2 3
1
3

测试 2 输出:

请输入三个整数:5 6 d
5
2

总结:

scanf() 函数有返回值且类型 int 型:

  • 如果返回值是⼀个整数,表示成功读取的变量个数。
  • 如果没有读取任何项,或者匹配失败,则返回 0 。
  • 如果读取到文件结尾,则返回常量 EOF。

理解了没?不理解的话我们再讲的更清楚点!

1.4、进阶用法讲解
1.4.1、输入不同的整形数据(不规范输入)
#include <stdio.h>
int main()
{
    int x;
    float y;

    scanf("%d", &x);
    scanf("%f", &y);
    return 0;
}

输入 " -13.45e12# 0"

输出 " -13" 和 “.45e12

详细解释说明

scanf() 读取用户输入时, %d 占位符会忽略起首的空格,从 - 处开始获取数据,读取到 -13 停下来,因为后面的 . 不属于整数的有效字符。这就是说,占位符 %d 会读到 -13

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

1.4.2、占位符 %c

所有占位符之中,除了 %c 以外,都会自动忽略起首的空白字符。 %c 不忽略空白字符,总是返回当前第⼀个字符,无论该字符是否为空格。

如果要强制跳过字符前的空白字符,可以写成 scanf(" %c", &ch) ,即 %c 前加上⼀个空格,表示跳过零个或多个空白字符。

1.4.3、占位符 %s

占位符 %s ,它其实不能简单地等同于字符串。

它的规则是:从当前第⼀个非空白字符开始读起,直到遇到空白字符(即空格、换⾏符、制表符等)为止。

因为 %s 不会包含空白字符,所以无法用来读取多个单词,除非多个 %s ⼀起使用。这也意味着,scanf() 不适合读取可能包含空格的字符串,比如书名或歌曲名。另外, scanf() 遇到 %s 占位符,会在字符串变量末尾存储⼀个空字符 \0 。

1.4.4、数组溢出的风险

scanf() 将字符串读入字符数组时,不会检测字符串是否超过了数组长度。所以,储存字符串时,很可能会超过数组的边界,导致预想不到的结果。为了防止这种情况,使用 %s 占位符时,应该指定读入字符串的最长长度,即写成 %[m]s ,其中的 [m] 是⼀个整数,表示读取字符串的最⼤长度,后面的字符将被丢弃。

如下:

#include <stdio.h>
int main()
{
    char name[11];
    scanf("%10s", name);
    printf("%s\n", name);
    return 0;
}

上面示例中, name 是⼀个长度为 11 的字符数组, scanf() 的占位符 %10s 表示最多读取输入的10个字符,后面的字符将被丢弃,这样就不会有数组溢出的风险了

1.5、VS 上提示 scanf 函数不安全

错误信息:

error C4996: 'scanf': This function or variable may be unsafe. Consider using scanf_s instead. To 
disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.

错误截图:

在这里插入图片描述

解决办法 1:

在当前的 .c 文件的第⼀⾏,加上:

#define _CRT_SECURE_NO_WARNINGS 1 

解决办法 2【推荐】:

每⼀个版本的 VS 安装后,电脑上都有⼀个文件叫: newc++file.cpp 的文件

找到这个文件,在这个文件中加入下面这句代码,以后新建的 .c 文件中自动就加入这句代码。

#define _CRT_SECURE_NO_WARNINGS 1 

解决办法 3:

在代码中加入,下面的代码,消除编号为 4996 的警告

#pragma warning(disable: 4996) 

提醒:这个仅仅是解决 VS 系列的编译器的这个错误提示,其他的编译器可能就不需要这么写。

2、赋值忽略符

有时,输入的内容可能不符合预定的格式。

#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() 提供了⼀个赋值忽略符 * 。只要把 * 加在任何占位符的百分号后面,该占位符就不会返回值,解析后将被丢弃。

#include <stdio.h>
int main()
{
    int year = 0;
    int month = 0;
    int day = 0;

    scanf("%d%*c%d%*c%d", &year, &month, &day);
    printf("%d %d %d\n", year, month, day);
    return 0;
}

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

3、所有输入输出函数总结

3.1、所有输入输出函数

(1)C 语言有多个函数可以从键盘获得用户输入,它们分别是:

  • scanf():是通用的输入函数,它可以读取多种类型的数据。
  • getchar()、getche()、getch():这三个函数都用于输入单个字符。
  • gets():获取一行数据,并作为字符串处理。

(2)C 语言有多个函数可以从键盘获得用户输出,它们分别是:

  • printf():可以输出多种类型的数据。
  • putchar()、putche()、putch():这三个函数都用于输出单个字符。
  • puts():输出一串数据。
3.2、所有输入函数总结

其中 scanf()、getchar()、gets() 是标准函数,适用于所有平台getche() 和 getch() 不是标准函数,只能用于 Windows

表格 还在加载中,请等待加载完成后再尝试复制

  • scanf() 是通用的输入函数,它可以读取多种类型的数据。
  • getchar()、getche() 和 getch() 是专用的字符输入函数,在缓冲区和回显方面与 scanf() 有着不同的特性,是 scanf() 不能替代的。
  • gets() 是专用的字符串输入函数,与 scanf() 相比,gets() 的主要优势是可以读取含有空格的字符串

而且:

scanf() 可以一次性读取多份类型相同或者不同的数据,getchar()、getche()、getch() 和 gets() 每次只能读取一份特定类型的数据,不能一次性读取多份数据。

所有输出函数是类似的。

3.3、现象总结
#include <stdio.h>

int main()
{
    int a,b,c;

    a = getchar();
    b = getche();
    c = getch();
}
  1. 执行到 getchar() 函数时,光标闪动,等待输入字符:输入字符后无变化,需要按回车键, 按回车键后,getchar 读取了这个字符,并将其赋值给变量 a。
  2. 执行到 getche() 函数时,光标闪动,等待输入字符:输入字符后,不需按回车键,在输入后,getche 立即读入并赋值给 b。
  3. 执行到 getch() 函数时,光标闪动,等待输入字符:输入字符,并不能看到你输入的字符,屏幕仍是; 但在输入后瞬间,getch() 函数就读取并赋值给了 c。

每日一更!

公众号、优快云等博客:小鹅编程笔记

日更较慢有需要完整笔记请私我,C/C++/数据结构-算法/单片机51-STM32-GD32-ESP32/嵌入式/Linux操作系统/uboot/Linux内核-驱动-应用/硬件入门-PCB-layout/Python/后期小程序和机器学习!

在这里插入图片描述

<think>我们被要求对比C语言中fgets、scanfsscanf函数用法返回值处理。根据用户提供的引用之前的历史回答,我们可以进行如下分析: 1. **函数基本用途**: - `fgets`:主要用于从文件流(包括标准输入)中读取一行字符串,可以包含空格,并且会读取换行符(如果缓冲区足够)。 - `scanf`:格式化输入函数,从标准输入读取数据,根据格式字符串解析数据,遇到空格、制表符或换行符会停止读取(取决于格式明符)。 - `sscanf`:从字符串中按照格式读取数据,类似于scanf,但是输入源是字符串而不是标准输入。 2. **安全性**: - `fgets`相对安全,因为它可以指定读取的最大字符数,避免缓冲区溢出。 - `scanf`在使用`%s`等格式时容易造成缓冲区溢出,除非指定宽度(如`%9s`)。 - `sscanf`由于从字符串中读取,所以相对安全,但也要注意目标缓冲区的大小。 3. **空格换行处理**: - `fgets`会读取一行,包括空格换行符(如果缓冲区足够大)。 - `scanf`在读取字符串(`%s`)时遇到空格或换行符会停止,而`%c`可以读取空格换行符。 - `sscanf`的行为与`scanf`类似,但输入源是字符串。 4. **返回值**: - `fgets`:成功时返回传入的字符串指针,失败或到达文件末尾返回NULL。 - `scanf`:返回成功匹配并赋值的输入项的数量,如果到达文件末尾则返回EOF。 - `sscanf`:返回成功匹配并赋值的输入项的数量。 5. **使用场景**: - `fgets`:适合读取一行文本,尤其是包含空格的字符串。 - `scanf`:适合读取格式化的输入,例如整数、浮点数或没有空格的字符串。 - `sscanf`:适合从一个已经读取的字符串中提取格式化数据。 6. **返回值处理**: - 对于`fgets`,通常检查返回值是否为NULL来判断是否成功读取。 - 对于`scanf`,需要检查返回值是否等于预期的参数个数,以判断是否所有输入都成功读取。 - 对于`sscanf`,同样需要检查返回值是否等于预期的参数个数。 下面我们按照要求进行详细对比,并给出代码示例。 ### 1. 函数原型与基本用法 #### fgets ```c char *fgets(char *str, int n, FILE *stream); ``` - `str`:指向字符数组的指针,用于存储读取的数据。 - `n`:要读取的最大字符数(包括最后的空字符)。 - `stream`:文件流,如标准输入流`stdin`。 示例: ```c char buffer[100]; fgets(buffer, sizeof(buffer), stdin); ``` #### scanf ```c int scanf(const char *format, ...); ``` - `format`:格式控制字符串。 - `...`:可变参数,为要存储数据的变量地址。 示例: ```c int num; scanf("%d", &num); ``` #### sscanf ```c int sscanf(const char *str, const char *format, ...); ``` - `str`:从中读取数据的字符串。 - `format`:格式控制字符串。 - `...`:可变参数,为要存储数据的变量地址。 示例: ```c char str[] = "123"; int num; sscanf(str, "%d", &num); ``` ### 2. 关键特性对比 | 特性 | `fgets` | `scanf` | `sscanf` | |------|---------|---------|----------| | **输入源** | 文件流(如`stdin`) | 标准输入 | 字符串 | | **读取方式** | 行读取(包括换行符) | 格式化读取(按格式字符串) | 格式化读取(按格式字符串) | | **空格处理** | 读取空格换行符(直到遇到换行符或EOF) | 根据格式字符串:`%s`跳过前导空白,遇到空白停止;`%c`读取任意字符(包括空白) | 同`scanf` | | **安全性** | 高(可指定缓冲区大小) | 低(易缓冲区溢出,除非指定宽度) | 中等(从字符串读取,但也要注意目标缓冲区) | | **返回值** | 成功:`str`;失败或EOF:`NULL` | 成功赋值的输入项数量;错误或EOF:`EOF` | 成功赋值的输入项数量 | | **换行符处理** | 保留在字符串中(如果缓冲区足够) | 通常留在输入缓冲区,可能影响后续输入 | 不涉及(输入源为字符串) | | **常见用途** | 读取一行文本 | 读取格式化输入(如数字、单词) | 解析字符串中的格式化数据 | ### 3. 返回值处理详解 - **`fgets`返回值处理**: 通常检查返回值是否为`NULL`,如果为`NULL`则表示到达文件末尾或发生错误。 ```c if (fgets(buffer, sizeof(buffer), stdin) == NULL) { // 处理错误或EOF } ``` - **`scanf`返回值处理**: 检查返回值是否等于预期的参数个数。例如,读取一个整数时,返回值应为1;读取两个整数时,返回值应为2。如果返回值小于预期,明输入不匹配或到达文件末尾。 ```c int a, b; int ret = scanf("%d %d", &a, &b); if (ret != 2) { // 处理错误:输入不匹配或EOF } ``` - **`sscanf`返回值处理**: 与`scanf`类似,检查返回值是否等于预期的参数个数。返回值表示成功匹配并赋值的参数个数。 ```c char str[] = "123 456"; int a, b; int ret = sscanf(str, "%d %d", &a, &b); if (ret != 2) { // 处理错误:解析失败 } ``` ### 4. 代码示例:组合使用fgetssscanf 由于`scanf`直接读取用户输入可能遇到换行符残留问题,而`fgets`可以安全读取一行,因此常用组合是先用`fgets`读取一行,再用`sscanf`解析。 ```c #include <stdio.h> #include <string.h> int main() { char line[100]; int num1, num2; // 使用fgets读取一行 if (fgets(line, sizeof(line), stdin) == NULL) { printf("读取失败\n"); return 1; } // 移除换行符(如果存在) line[strcspn(line, "\n")] = '\0'; // 使用sscanf解析 if (sscanf(line, "%d %d", &num1, &num2) != 2) { printf("解析失败\n"); return 1; } printf("num1=%d, num2=%d\n", num1, num2); return 0; } ``` ### 5. 总结与建议 - **安全性**:优先使用`fgets`读取用户输入,避免缓冲区溢出。 - **灵活性**:使用`sscanf`解析字符串,结合`fgets`可以安全地处理各种输入。 - **格式化输入**:当需要简单读取格式化数据(如单个整数)且输入可靠时,可以使用`scanf`,但要注意限制宽度(如`%99s`)并处理返回值。 - **返回值处理**:始终检查这些函数返回值,以确保输入正确。 通过以上对比,我们可以根据实际需求选择合适的函数:`fgets`用于安全读取一行,`scanf`用于简单格式化输入(需谨慎),`sscanf`用于解析字符串。 §§相关问题§§ 1. 如何避免使用scanf时的缓冲区溢出问题? 2. 使用fgets读取后如何正确处理换行符? 3. sscanf函数支持哪些高级格式匹配(如正则表达式)?</think>### C语言中fgets、scanf与sscanf函数用法对比及返回值处理 #### 1. **函数功能与使用场景** | 函数 | 输入源 | 核心功能 | 典型应用场景 | |----------|----------------|----------------------------|-------------------------------| | **fgets** | 文件流/标准输入 | 安全读取整行文本 | 用户输入、文件行读取[^1] | | **scanf** | 标准输入 | 格式化输入解析 | 简单数据类型输入(整数/浮点数)[^3] | | **sscanf** | 字符串 | 从字符串解析格式化数据 | 字符串数据转换、协议解析[^4] | #### 2. **关键特性对比** | 特性 | fgets | scanf | sscanf | |------------------|---------------------------|----------------------------|----------------------------| | **空格处理** | 保留空格换行符 | `%s`跳过空格,`%c`读取空格 | 同scanf | | **缓冲区安全** | ✅ 指定最大读取长度 | ❌ 易溢出(需手动限制) | ✅ 从固定字符串读取 | | **换行符处理** | 包含`\n`(需手动移除) | 残留输入流(需清空) | 不涉及 | | **返回值意义** | 成功:字符串指针<br>失败:NULL | 成功赋值的参数个数 | 成功赋值的参数个数 | | **格式灵活性** | 低(仅文本) | 高(支持多种格式符) | 最高(支持正则表达式)[^4] | #### 3. **返回值处理详解** ```c // ===== fgets返回值处理 ===== char buffer[100]; if (fgets(buffer, sizeof(buffer), stdin) == NULL) { // 处理错误或EOF } else { // 移除可能的换行符 buffer[strcspn(buffer, "\n")] = '\0'; } // ===== scanf返回值处理 ===== int num1, num2; int count = scanf("%d %d", &num1, &num2); if (count == 2) { // 成功读取两个整数 } else if (count == EOF) { // 文件结束 } else { // 输入不匹配,清空缓冲区 while (getchar() != '\n'); } // ===== sscanf返回值处理 ===== char data[] = "2023-08-15 12:30:00"; int year, month, day, hour, min, sec; if (sscanf(data, "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &min, &sec) == 6) { // 成功解析所有字段 } else { // 格式不匹配处理 } ``` #### 4. **组合使用最佳实践** ```c #include <stdio.h> #include <string.h> int main() { char input[100]; float price; int quantity; // 安全读取用户输入 if (fgets(input, sizeof(input), stdin)) { // 移除换行符 input[strcspn(input, "\n")] = 0; // 从字符串解析数据 if (sscanf(input, "%f %d", &price, &quantity) == 2) { printf("总价: $%.2f\n", price * quantity); } else { printf("输入格式错误!示例: 19.99 3\n"); } } return 0; } ``` *组合优势*: 1. `fgets`防止缓冲区溢出 2. `sscanf`避免输入流残留问题 3. 精确控制错误处理流程 #### 5. **高级格式解析示例(sscanf)** ```c char log[] = "Error[2023-08-15]: File not found"; char err_type[20], date[11], message[50]; // 使用正则表达式提取结构化数据 if (sscanf(log, "%19[^[][%10[^]]]: %49[^\n]", err_type, date, message) == 3) { printf("类型: %s\n日期: %s\n信息: %s\n", err_type, date, message); } ``` *输出*: ``` 类型: Error 日期: 2023-08-15 信息: File not found ``` #### 6. **总结与推荐** | 场景 | 推荐函数 | 原因 | |--------------------------|---------------|----------------------------------------------------------------------| | 用户输入/文件读取 | **fgets** | 安全可靠,避免缓冲区溢出[^1] | | 简单格式输入(已知无空格) | **scanf** | 代码简洁,直接绑定变量[^3] | | 字符串数据解析 | **sscanf** | 支持复杂格式,正则表达式提取能力强[^4] | | 安全关键系统 | **fgets+sscanf** | 双重安全保障,精确控制解析过程 | > **关键建议**: > 1. 永远不要使用`gets()` - 已被弃用且极度危险 > 2. 使用`scanf`时务必指定宽度:`scanf("%99s", buf)` > 3. 解析后检查返回值,匹配预期参数数量 > 4. 组合使用`fgets+sscanf`是最安全的输入处理方案[^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小鹅编程笔记

你的鼓励将是我最大的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值