C++ 输入流的那些坑——cin、scanf与getline的混用问题

news/2025/3/20 18:28:13/文章来源:https://www.cnblogs.com/ofnoname/p/18783553

在C与C++编程中,输入数据是最常见也是最基础的操作之一。然而,不同的输入函数在处理空格、换行符(\n)和缓冲区安全性方面行为各异,也可能出现混用问题。

scanf

scanf函数最早来源于C语言,作为标准库中的输入函数,已经有数十年的历史。通过指定的格式化字符串来精确地控制输入内容,例如scanf("%d", &num);,用于读取整数。

scanf 严格按照给定的格式说明符来读取数据,读取过程中如果遇到空白符(空格、换行、制表符),通常会停止读取(不过空格不会严格匹配),并将这些字符留在输入缓冲区中。也就是其可以处理空格、换行、制表符。

scanf没有自动缓冲区管理能力,将长字符串输入短数字易于出现缓冲区溢出等严重安全漏洞。

1. 一般情况下 (%d, %f, %s 等)

scanf 遇到 %c%[ ] 相关的格式(如 %d, %f, %s),它会 自动跳过前导的空白字符(空格、制表符 \t、换行 \n),并且:

  • 输入数据之后的空格、换行等不会留在输入流,因为 scanf 只会读取 满足格式的输入,多余的空白字符不属于数据的一部分,它们仍然留在输入流中。

示例 1

#include <stdio.h>int main() {int a, b;printf("请输入两个整数: ");scanf("%d %d", &a, &b);  // %d 会跳过空格、换行printf("a = %d, b = %d\n", a, b);return 0;
}

输入

10 20⏎

输入流分析

  1. 10%d 读取。
  2. scanf 跳过空格,20%d 读取。
  3. 回车 \n(Enter)不会被消费,它仍然留在输入流中。

空格不会严格匹配,格式字符串的多个连续空格也会被视为“一个空白符”,而输入中的多个连续空格、换行符和制表符都会被 scanf 视作一个空白符,自动跳过。

2. 使用 %c

但如果 scanf 读取 %c(字符类型),它不会跳过空格、换行等 可见字符,而是直接读取下一个。这时 最后的回车或空格会被读取。若你的系统回车为\n\r,那么两个可以被读两次回车。

示例 2

#include <stdio.h>int main() {int a;char ch;printf("输入一个整数和一个字符: ");scanf("%d", &a);scanf("%c", &ch);  // 读取字符printf("a = %d, ch = '%c'\n", a, ch);return 0;
}

输入

10⏎

输出

a = 10, ch = '
'

分析

  1. 10%d 读取。
  2. scanf("%c", &ch); 不会跳过空白字符,所以它读取了 \n(回车)。
  3. ch 变量存储的是 \n,因此程序输出 ch = '\n'

解决方案
可以在 %c 之前添加一个空格,以表明跳过输入流中的空白字符:

scanf(" %c", &ch);  // 加一个空格,跳过空格或回车

3. 使用 %[ ]

如果使用 %[ ](扫描集合),它也不会自动跳过空格

示例 3

#include <stdio.h>int main() {char str[100];printf("输入一个字符串: ");scanf("%[^\n]", str);  // 读取整行直到换行printf("str = \"%s\"\n", str);return 0;
}

输入

Hello World⏎

输出

str = "Hello World"

分析

  • %[^\n]scanf 读取 直到遇到 \n(回车),回车不会被读取,仍留在输入流。
  • 但是作为结束标志的\n仍在输入流中,如果接下来还有 scanf("%c", &ch);,那么 ch 会读取到这个 \n

这样可以避免 \n 残留,确保正确读取输入数据。

cin

cin 是C++语言特有的输入流对象,它伴随着 C++ 的标准库诞生。通过流提取操作符 >> 简单直观地读取数据,例如 cin >> num;

1. cin >>(默认行为)

当使用 cin >> 变量; 进行输入时:

  • 仍会自动跳过空格、换行和制表符' '\t\n)。
  • 输入值以空白字符(空格、换行、制表符)作为分隔符,即遇到这些字符时会停止读取。

2. cin.get() 读取单个字符

如果你用 cin.get(char) 来读取字符,它类似 %c, 不会跳过空格或换行,而是会读取它们

示例 2:cin.get() 捕获回车

#include <iostream>
using namespace std;int main() {char ch;cout << "输入一个字符: ";cin.get(ch);cout << "你输入的是: '" << ch << "'" << endl;return 0;
}

输入

输出

你输入的是: '
'

混用 scanfcin 的问题

在 C++ 中,混用 scanfcin 的问题
可能会导致问题
,主要有两个方面需要注意:

  1. 输入缓冲区问题
  2. 同步问题

1️⃣ 输入缓冲区问题

scanfcin 处理输入的方式不同,导致它们在混用时可能出现残留换行符(\n的问题。

🔍 问题示例:混用 scanfcin

#include <iostream>
#include <cstdio>
using namespace std;int main() {int a;char str[100];printf("输入一个整数:");scanf("%d", &a);  // 读取整数,但输入流中会残留 '\n'cout << "输入一行字符串:";cin.getline(str, 100);  // 读取字符串,但可能直接读取到换行符cout << "a = " << a << ", str = " << str << endl;return 0;
}

🚨 运行示例
输入:

10⏎
Hello, World!⏎

实际输出(错误):

输入一个整数:10
输入一行字符串:a = 10, str =

分析问题

  • scanf("%d", &a); 读取 10,但 回车 \n 仍然留在输入流 中。
  • cin.getline(str, 100); 看到输入流里的 \n,直接读取并结束,导致 str 为空。

解决方案 1:清空缓冲区
scanf 之后 手动清除输入流的残留换行符

scanf("%d", &a);
while (getchar() != '\n');  // 清空缓冲区

或者:

cin.ignore(numeric_limits<streamsize>::max(), '\n');  // C++ 方式

修正代码

#include <iostream>
#include <cstdio>
#include <limits>  // 需要引入
using namespace std;int main() {int a;char str[100];printf("输入一个整数:");scanf("%d", &a);cin.ignore(numeric_limits<streamsize>::max(), '\n');  // 清空缓冲区cout << "输入一行字符串:";cin.getline(str, 100);cout << "a = " << a << ", str = " << str << endl;return 0;
}

输入:

10⏎
Hello, World!⏎

输出:

输入一个整数:10
输入一行字符串:Hello, World!
a = 10, str = Hello, World!

2️⃣ 同步问题

C++ 标准库 cin 使用的是C++流缓冲区,而 scanf 使用的是C 标准库缓冲区,两者的缓冲区是独立管理的。

默认情况下,cinprintf/scanf 是同步的,这意味着:

  • cin 可能会比 scanf ,因为它需要额外的缓冲处理。
  • cinscanf 不会交错执行,因为 cin 在同步模式下必须先刷新 stdio 缓冲区,再执行输入。

🚀 解决方案 2:禁用 cin 的同步,提高性能
如果你大量使用 cin 而不是 scanf,可以使用:

ios::sync_with_stdio(false);
cin.tie(nullptr);
  • ios::sync_with_stdio(false);cin 运行更快,但 cinscanf/printf 可能会交错执行(不推荐混用)。
  • cin.tie(nullptr);cincout 不绑定,可以提高 cin 的执行效率(避免每次 cin 都刷新 cout)。

🚀 高效输入方案

#include <iostream>
using namespace std;int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int a;cin >> a;cout << "a = " << a << endl;return 0;
}

这段代码 不适用于 scanfcin 混用,但如果你只用 cin,它会大大提高输入速度

getline

getline 在 C语言就存在,其用途读取整行,包含空格,直到回车才结束。C++ 又有重新实现,有多个版本:

名称 语言标准 头文件 所属类型
getline() C语言 (POSIX标准,非C标准) <stdio.h> 自由函数
std::getline() C++标准库 <string> 自由函数
cin.getline() C++标准库 <iostream> 成员函数(属于istream

POSIX getline()(C语言)

ssize_t getline(char **lineptr, size_t *n, FILE *stream);
  • 动态分配内存读取整行,包括换行符,并返回读取到的字符数。
  • 参数
    • lineptr:指向一个动态分配的缓冲区,getline()会自动扩展它。
    • n:缓冲区大小,自动调整。
    • stream:输入流(如stdin)。

三个函数都会将回车字符移出输入流,但此函数会将这个回车留在目标字符串中,而另外两种 getline 不会

C++标准库的std::getline()

std::getline(std::istream &input, std::string &str, char delim = '\n');
  • 读取输入流直到遇到分隔符(默认\n),丢弃分隔符
  • 参数
    • 输入流(std::cin 或其他istream
    • 用于保存读取内容的std::string
    • 分隔符(可选,默认换行符)

C++的成员函数cin.getline()

std::istream& std::istream::getline(char* s, std::streamsize count, char delim = '\n');
  • 读取输入流直到遇到分隔符或达到给定的长度,丢弃分隔符
  • 参数
    • 字符数组指针,用来保存读取内容。
    • 最大读取长度(避免缓冲区溢出)。
    • 分隔符(默认换行)。
方面 POSIX getline() C++ std::getline() C++ cin.getline()
标准支持 POSIX标准(非ISO C) ISO C++标准库 ISO C++标准库
内存管理 自动动态分配内存,需手动释放 使用C++ std::string自动管理内存 使用固定大小的字符数组
是否丢弃换行符 (换行符会保留在缓冲区) (默认丢弃) (默认丢弃)
缓冲区类型 char * 动态分配 std::string 自动管理 固定大小的字符数组
是否安全 ✅安全,自动扩容 ✅安全,自动管理内存 ⚠️需注意长度,可能溢出
是否为成员函数 ❌ 否(自由函数) ❌ 否(自由函数) ✅ 是(istream类成员)

纯C语言代码只能使用 POSIX 的 getline();现代C++代码优先使用 std::getline(),因为它更安全,易用(推荐)。

cin >> 和 getline 的混用

在 C++ 中,cin >>std::getline() 混用时可能会导致 输入错误,主要原因是:

  • cin >> 会跳过空白字符(空格、换行 \n、制表符 \t)。
  • std::getline() 读取整行,但不包括换行符,如果输入流中有换行 \n,它可能会立即结束读取。

这会导致 std::getline() 直接读取到一个残留的换行符 \n,从而造成程序错误。

#include <iostream>
#include <string>
using namespace std;int main() {int a;string str;cout << "请输入一个整数:";cin >> a;  // 读取整数,但换行符 `\n` 仍然留在输入流中cout << "请输入一行文本:";getline(cin, str);  // 这里可能直接读取到 `\n`,导致 str 为空cout << "a = " << a << ", str = " << str << endl;return 0;
}

🔍 输入:

10⏎
Hello World!⏎

🚨 错误输出:

请输入一个整数:10
请输入一行文本:a = 10, str = 

❌ 问题分析

  • cin >> a; 读取 10,但 不会读取 \n,这个回车仍然留在输入流中。
  • getline(cin, str); 看到输入流里有 \n,立即读取并返回空字符串,导致 str 为空。

解决方案

cin >> 之后 手动清理输入流的换行符

cin.ignore(numeric_limits<streamsize>::max(), '\n');

修正代码

#include <iostream>
#include <string>
#include <limits>  // 需要引入
using namespace std;int main() {int a;string str;cout << "请输入一个整数:";cin >> a;cin.ignore(numeric_limits<streamsize>::max(), '\n');  // 清除换行符cout << "请输入一行文本:";getline(cin, str);cout << "a = " << a << ", str = " << str << endl;return 0;
}

🔍 输入:

10⏎
Hello World!⏎

✅ 正确输出:

请输入一个整数:10
请输入一行文本:Hello World!
a = 10, str = Hello World!
  • 尽量不要混用 cin >>getline(),如果必须混用,就用 cin.ignore() 清除换行符。
  • 对于整行输入,建议全部用 std::getline() 处理,然后手动解析数据

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/902130.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

keil仿真时导出数据操作

keil仿真时导出数据操作 save D:\savedata.txt 0x20001013,0x20001035

spring-boot-starter-validation

官方提供的注解 spring-boot-starter-validation 是 Spring Boot 提供的一个 starter,是一个用于验证 Java Bean 的标准,它提供了一套注解和相应的运行时 API 来定义和执行校验规则。 具体来说,当你在项目中引入 spring-boot-starter-validation 后,你可以使用一系列预定义…

省选算法复习

省选算法复习 1. 线段树优化建图 当我们需要向区间内所有点连边或者从区间中所有点连到某个点的时候,便可以使用线段树来优化,如果需要从区间每一个点连到另一个区间每一个点的话,加一个虚点就好了。 这不是一个很困难的技巧,关键在于要建模。 P5471 [NOI2019] 弹跳 - 洛谷…

fastadmin订单父子表管理端

fastadmin后台父子表使用方法 发布于 2021-01-22 12:48:10fastadmin后台的所有表格都是支持父子表配置的,只需要简单修改一下对应的JS即可,下面直接进入主题。示例是我的全国省市行政区划表,是从国家统计局网站采集下来的,共五级行政数据,非常适合用来做父子表,按照级别一…

Rudolf and k Bridges

Rudolf and k Bridges 题目 大致题意上图为俯视图 有一个\(nXm\)的网格,下标从\(1-n\) 以及从 \(1-m\),\((i, j)\) 的值就是这个这垂直一格水的深度 现在要安装支架,有几个信息:\((i, 1)\) 和 \((i, m)\) 处必须要安装相邻支架的距离不能超过 \(d\), 相邻距离为 \(abs(j - …

背离Divergence Trading ,贪小便宜

趋势交易(trend trading)和背离交易(divergence trading),代表了两种不同的交易策略。做背离交易相当于赌市场短期失效,承认你比市场聪明,虽然能赚小钱,但往往是亏大钱的根源。 贪小便宜爱背离,贪小便宜(gain small advantages)不爱止损(cut losses),所以背离和不止损…

在鸿蒙NEXT开发中实现一个语音识别组件

鸿蒙系统发布以后都不知道叫它5.0版本还是NEXT版本了,哈哈,反正是最新版本就对了。对于语音转换文字,鸿蒙系统提供了离线语音识别模型speechRecognizer,语种目前支持中文,识别效果非常不错。今天要分享的是使用speechRecognizer实现一个语音识别组件。要实现语音识别,首先…

激光代加工产品一览-代加工-外协加工-委外加工-激光代加工-河南郑州亚克力切割雕刻代加工-芯晨微纳(河南)

关键词:河南省郑州市、激光代加工、激光打标、激光切割、激光雕刻、激光打孔、激光毛化、激光分切 简介:芯晨微纳(河南)光电科技有限公司,专注于激光微纳代加工、设备/耗材代理销售、设备租赁、技术推广服务,可处理材料类型及应用范围十分广泛,欢迎来电咨询(韩经理1823…

20242801 2024-2025-2 《网络攻防实践》第4次作业

20242801 2024-2025-2 《网络攻防实践》第4次作业 一、实验内容 ​ 在虚拟机环境中完成TCP/IP协议栈重点协议的攻击实验,学习ARP缓存欺骗攻击、ICMP重定向攻击、SYN Flood攻击、TCP RST攻击、TCP会话劫持攻击的原理和相关知识,并动手进行实践。 二、实验过程 (一)ARP缓存欺…

缓存监控治理在游戏业务的实践和探索

通过对 Redis 和 Caffeine 的缓存监控快速发现和定位问题降低故障的影响面。作者:来自 vivo 互联网服务器团队- Wang Zhi 通过对 Redis 和 Caffeine 的缓存监控快速发现和定位问题降低故障的影响面。 一、缓存监控的背景游戏业务中存在大量的高频请求尤其是对热门游戏而言,而…

从零开始驯服Linux(一):ZYNQ-Linux启动文件构建全解析

从零开始驯服Linux(一):ZYNQ-Linux启动文件构建全解析 ZYNQ系列芯片集成了ARM处理器和FPGA(可编程逻辑单元),正是因为由于ARM处理器的存在,所以我们可以在ZYNQ系列芯片上面运行Linux系统。 在ZYNQ系列芯片上运行Linux会给我们带来很多优势:首先,我们可以将部分逻辑处理…

# 20241902 2024-2025-2 《网络攻防实践》第六周作业

1.实验内容 通过本周的学习和实践,学习使用metasploitable对windows进行远程的渗透测试实验;学习利用wireshark进行日志文件的分析和攻击取证,解读攻击者所利用的攻击、攻击者的具体操作以及如何对攻击行为进行防范;实践同一内网中对利用metasploitable对其他windows系统进…