【C++】浅论(cin和cout)的解锁、缓冲区的理解、CC++输入方法汇总和详解

2024-05-27 16:04

本文主要是介绍【C++】浅论(cin和cout)的解锁、缓冲区的理解、CC++输入方法汇总和详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、cin,cout解锁

1.1:cin,cout解锁以及why

首先cin和cout是在c++中为了提供类型安全易用性设计的,它兼容了c语言的输入和输出,以上几点导致它在性能行(读取和输出速度)远不如传统c语言的输入和输出。

在看到一些代码里面,会在main函数开头加上这两行代码,叫做cincout解锁,使用之后确实能对性能有一定的提升:

ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);

🚨注意:cin cout解锁使用时,不能与 scanf,getchar, printf,cin.getline()混用,一定要注意,会出错。

我们来看看这两行代码其实是在做什么,但在这之前,让我们先学习一下底层的一些知识:输入输出缓冲区。

二:C++输入输出缓冲区

ios::sync_with_stdio(false);首先,上文提到过,由于cin、cout是兼容了C语言的输入和输出,这意味着C++的IO标准库和C的IO标准库是同步的,这能保证可以在程序中混用cin、coutscanf、printf。但是这种同步带来的代价是,在用户进行输入和输出时,这两个库之间会协调同一片共享缓冲区,保证程序能按顺序的执行,这种协调会导致资源的消耗。这个代码相当于关闭了这种协调机制,减少了这部分的资源消耗,但是同时你也就不能混用两种输入输出方式!
cin.tie(0),cout.tie(0): 首先我们需要明白,在C++的输入和输出过程中有两个独立的缓冲区:输入缓冲区、输出缓冲区。

在这里插入图片描述

  1. 输入缓冲区:当我们用标准输入从外部设备(如键盘or硬盘)向程序(也就是内存)输入数据时,数据不会立马跑到程序里被程序的变量所接收从而处理,而是先到内存的输入缓冲区,等到 cin函数进行缓冲区数据读取时,数据才会从缓冲区这个中间站流向内存
  2. 输出缓冲区: 它和输入缓冲区的性质是一样的,区别在于它数据的流向:从内存流向设备(硬盘、显示屏);和cin输入数据一样,当我们用cout将数据从程序(内存)往外部设备输出时,它不会直接输出,而是先将数据送到缓冲区(打住,cout的作用就是这个),当某些条件触发(如:缓冲区满了、缓冲区被刷新),才会将在缓冲区的数据送到外部设备。

 而C++默认cincout是绑定的,也就是说,当我们用cin输入数据时,它会自动提前刷新输出缓冲区,以确保将之前就被拿出来的数据能输出。而这种绑定机制可能会频繁无意义的多次刷新缓冲区,造成资源浪费。而cin.tie(0),cout.tie(0)的作用就是解除这种绑定机制,谁也不用等谁。但是这样带来的问题是:在进行输入之前,必须手动刷新缓冲区,否则前面的提示信息还没显示,后面的输入先来了。
在这里插入图片描述

 关于上面的手动刷新缓冲区,意思就是说,你需要手动把cout从内存拿到缓冲区的数据输出到屏幕上,否则它就一直在在缓冲区,除非手动(<<flush或<<endl)刷新才能显示。这里举个例子:

int main(){ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);int n = 0;cout << "请输入棋盘(nxn)的大小:n = ";cin >> n;return 0;
}

这个代码,按理来说,应该是先出现提示信息,再进行输入n,但是实际运行结果是:

在这里插入图片描述
我们看到,是先输入,再输出了本该提前输出的提示信息。这是因为我们对cincout进行解绑后,cout不再具有在cin运行前自动刷新输出缓冲区(意味着将缓冲区的数据输出到屏幕)的功能,所以其实在输入2之前,cout已经将提升信息从程序(内存)拿到了缓冲区,但是没有将其刷新送到屏幕,而是在程序结束后不得不刷新缓冲区。

所以,在对cincout进行解绑后,一定要记得手动刷新缓冲区(flushendl):

cout << "请输入棋盘(nxn)的大小:n = " << flush;
cin >> n;
cout << "请输入棋盘(nxn)的大小:n = " << endl;
cin >> n;

三、C的标准输入

虽然在某些情况下,使用cincout解绑后能提升性能,但是肯定还是没有用传统的c语言的输入输出效率和性能高。所以我打算法竞赛的朋友也建议我用传统的输入输出方法,下面也在这里做一个简单的总结:

3.1:scanf和printf

  • 定义:按照特定格式从stdin读取输入。

  • 用法示例:

    char str[100];
    int a;
    scanf("%s %d", str, &a);    // 注意,传入的一定是变量的地址
    
  • 从缓冲区读取数据流程:

    • 缓冲区开头:读取并丢弃空白字符(包括空格、Tab、换行符),直到第一个非空白字符才认为是第一个数据的开始。
    • 缓冲区中间:开始读取第一个数据后,一旦遇到空白字符(非换行符), 就认为读取完毕一次。遇到的空白字符不读取残留在缓冲区,直到下一次被读取或刷新。例如输入字符串hello yyz,则会被认为是2个字符串。
    • 缓冲区末尾:按下回车键时,换行符\n残留在缓冲区。换行符之前的空格可以认为是中间的空白字符,处理同上。
    char c[100]; // 分配长度为100的字符数组scanf("%s", c); // 不需要使用&,因为数组名已经是地址printf("%s", c);
    

    在这里插入图片描述

  • 格式控制符说明:
    在这里插入图片描述

注意,%c是一个比较特殊的格式符号,它将会读取所有空白字符,包括缓冲区开头的空格、Tab、换行符,使用时要特别注意。

3.2:字符和字符串(char [])

3.2.1:fgetc() & getc()读取字符

  • 定义:从指定输入流读取一个字符,输入可以是 stdin,也可以是文件流 ,使用时需要显式指定。

    这两个函数完全等效,getc()由fgetc()宏定义而来。不同的是,gets()和fgets()相互之间没有关系。

    fgetc()和getc()对应的输出函数是fputc()putc()

  • 用法示例:

    char a, b;
    a = fgetc(stdin);
    b = getc(stdin);
    

    在下面代码中,如果输入一个字符按下回车,那么a读取到的是第一个字符,而b读取到的是紧跟着的回车

    char a, b;
    a = fgetc(stdin); // 从标准输入读取一个字符
    b = getc(stdin);  // 再从标准输入读取另一个字符fputc(a, stdout); // 输出字符a到标准输出
    putc(b, stdout);  // 输出字符b到标准输出
    

    在这里插入图片描述

  • 读取数据流程:
    所有空格、Tab、换行等空白字符,无论在缓冲区开头、中间还是结尾,均会被读取,不丢弃也不会残留在缓冲区
    如果输入一个字符如’a’,然后按下回车键,则读取到的是字符’a’,同时换行符\n残留在缓冲区
     因为只读取一个字符,所以如果输入多于1个字符(包括换行符),则它们均会残留在缓冲区。具体地说,如果什么字符都不输入,直接按下回车键,则读取到的是换行符\n,缓冲区无任何残留;

3.2.2:getchar()读取字符

  • 定义:从stdin读取一个字符。

    getchar()实际上也由fgetc()宏定义而来,只是默认输入流为stdin。( 它和前面两个唯一不同的就是不需要显示指定输入流

    getchar()对应的输出函数是putchar()

  • 用法示例:

    char a;
    a = getchar();
    

    getchar()常常用于清理缓冲区开头残留的换行符。当知道缓冲区开头有\n残留时,可以调用getchar()但不赋值给任何变量,即可实现冲刷掉\n的效果。

3.2.3:字符串读入——fgets()

  • 定义: char *fgets(char *str, int n, FILE *stream)从指定输入流读取一行,输入可以是stdin,也可以是文件流,使用时需要显式指定, 并把它存储在 str 所指向的字符串内。当读取(n-1)个字符时,或者读取到换行符\n时,或者到达文件末尾时,它会停止读取,并在末尾补上\0

    • str – 这是指向一个字符数组的指针,该数组存储了要读取的字符串。
    • n – 这是要读取的最大字符数(包括最后的空字符\0)。通常是使用以 str 传递的数组长度。(当输入的字符小于最大个数n-1时,会读取到\n并停止,\n会被读取到,不会丢弃,和普通字符地位一样)
    • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。
  • 读取文件流示例:

char str[100];
memset(str, 0, sizeof(str));
int i = 1;FILE *fp = fopen("...test.txt", "r");
if (fp == NULL) {printf("File open Error!\n");exit(1);
}while (fgets(str, sizeof(str), fp) != NULL)printf("line%d [len %d]: %s", i++, strlen(str), str);fclose(fp);
  • 读取stdin示例:
char str[100];
memset(str, 0, sizeof(str));
int i = 1;
while (fgets(str, sizeof(str), stdin) != NULL)printf("line%d [len %d]: %s", i++, strlen(str), str);
  • 读取数据流程:
    • 所有空格、Tab等空白字符均被读取,不忽略。
    • 按下回车键时,缓冲区末尾的换行符也被读取,字符串末尾将有一个换行符\n作为字符串的一部分
    • fgets()函数会自动在字符串末尾加上\0结束符。所以当输入字符串hello,再按下回车,则读到的字符串长度为6(hello + \n),字符数组大小为7(因为包括了\0)
    • 第 2 个参数n指定了读取的最大长度(算上\0的)。函数读到n-1个字符(包括换行符\n)就会停止,并在末尾加上\0结束符。剩余字符将残留在缓冲区。

四、C++标准输入

4.1:cin和cout

  • 定义:cin是 C++ 的标准输入流对象,即istream类的一个对象实例。cin有自己的缓冲区,但默认情况下是与stdin同步的,因此在 C++ 中可以混用 C++ 和 C 风格的输入输出(在不手动取消同步的情况下)。

    cin与stdin一样是行缓冲,即遇到换行符时才会将数据同步到输入缓冲区。

  • 读取数据流程:cin对空白字符的处理与scanf一致。即:跳过开头空白字符,遇到空白字符停止读取,且空白字符(包括换行符)残留在缓冲区

🚨注意:所以cin用来读取字符串是读取不到空白字符的!!!字符串string的读取需要用到下面专门读取字符串的函数!!!!

4.2:字符串读取

4.2.1:给C类型字符串(字符数组 char [])赋值

1:cin.get()
  • 定义:
  • 读取单个或指定长度的字符,包括空白字符
  • 当使用无参数时,它读取并返回下一个字符
  • 也可以与一个参数一起使用(字符的引用),用来读取一个字符,包括换行符\n
  • 当与两个参数一起使用时(字符数组和长度)std::cin.get(buffer, SIZE);
    • 它会读取至多 SIZE - 1 个字符到 buffer 中(SIZE 是数组大小的参数)。这是因为最后一个位置需要留给空字符(null character,'\0'),这是 C 风格字符串的结束标志。
    • 如果在读取 SIZE - 1 个字符之前遇到了文件结束符(EOF,通常由用户输入 Ctrl+D 或 Ctrl+Z 产生),或者遇到了换行符 '\n',std::cin.get() 就会停止读取。
    • 如果在读取 SIZE - 1 个字符之前遇到了换行符 '\n'遇到换行符\n,随之停止读取;换行符\n留在输入缓冲区
  • 用法示例:
#include <iostream>int main() {const int SIZE = 10;char buffer[SIZE];std::cout << "Enter some text: ";std::cin.get(buffer, SIZE);// 注意:手动添加 null 字符来确保字符串正确终止buffer[SIZE - 1] = '\0';std::cout << "You entered: " << buffer << std::endl;// 如果需要清理输入流if (std::cin.peek() == '\n') {// 如果下一个字符是换行符,只需忽略它std::cin.ignore();} else {// 清除剩余的输入直到下一个换行符std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');}return 0;
}
  • cin.get()读取单个字符时,类似于 C 中的fgetc(),对空白字符的处理也与其一致,可以读取回车\ncin.get()读取的字符也可以赋值给整型变量。
  • cin.get()读取指定长度个字符时,类似于 C 中的fgets(),但在换行符的处理上不同:fgets()会将缓冲区末尾的换行符\n也写入字符串,而cin.get()遇到缓冲区末尾的\n,立即停止读入,\n会被残留在缓冲区。即:当输入test时,用fgets()读取得到的字符串长度为5(把\n读入),用cin.get()读取得到的字符串长度为4。
2.cin.getline()
  • 定义:读取指定长度的字符,包括空白字符。
  • 用法示例:
    char str[20];
    cin.getline(str, sizeof(str));    // 第3个参数也可以指定终止字符
    
    cin.getline()cin.get()指定读取长度时的用法几乎一样。
    唯一区别在于,
    • cin.getline()读取到\n会停止读取,但不会将\0追加到字符串末尾而是直接将其丢弃,所以不同于cin.get() cin.getline()不会把\n残留在输入缓冲区。
    • 如果输入的字符个数大于指定的最大长度n-1(不含终止符),cin.get()会使余下字符残留在缓冲区,等待下次读取;而cin.getline()会给输入流设为 Fail 状态,在主动恢复之前,无法再进行正常输入。

4.2.2:给string类型赋值:getline()

  • 定义:getline()并不是标准输入流istream的函数,而是字符串流sstream的函数,只能用于读取数据给string类对象,使用时也需要包含头文件<string>

    如果使用getline()读取标准输入流的数据,需要显式指定输入流。

  • 用法示例:

    int n;
    string s;
    cin >> n;
    getchar(); //cin.get() 清空前面cin留下的换行符,避免下面读到空字符串
    getline(cin, s);//可正确读入下一行的输入
    
  • 和前面cin.get()函数不同的是,它是 遇到换行符\n读取并丢弃,随之停止读取;换行符\n既不加到字符串末尾,也不会留在输入缓冲区(这点和cin.getline()是一样的)

  • 需要注意的是,假如缓冲区开头就是换行符(比如可能是上一次cin残留的),则getline()会直接读取到空字符串并结束,不会给键盘输入的机会。所以这种情况下要注意先清除开头的换行符

五、C&C++的输入方法总结

 首先在C语言中

  • scanf用来格式化读取各种基本数据(但是遇到空比字符则视为读入的终止标志而停止,所以不可用来读取带有空格的字符串)
  • getchar()fgets()用来读取字符(可读白字符:如空格、换行),区别仅在于getchar()默认是stdin输入流,不用显示传参
  • 在读取字符串时(C语言格式的字符数组char []),使用fgets读入,其会读取到换行符\0便终止读取,并把\0算入字符串的一个字符加入到字符串末尾,并且自动在字符串末尾加上\0

 在C++中

  • cin可以用来读取各种基本数据,和scanf一样即:跳过开头空白字符,遇到空白字符停止读取,且空白字符(包括换行符)残留在缓冲区。所以不可用来直接读取带有空格的字符串
  • cin.get()cin.getline()用法方面类似,都是对传统C形式字符串即字符数组char []的读取(cin.get()在读取字符时和putchar()用法一致)。二者而和fgets()不同的是,这两者都不会将\0作为字符串的一部分追加到字符串末尾;但cin.get()的做法是直接将\0残留在了缓冲区,而cin.getline()是将其读取并且丢弃,输入缓冲区不会残留。同时它们也会自动给字符数组末尾追加\0,即仅从输入流中读取 n-1个字符,字符数组末尾会用 '\0'补充
  • getline()是专门对string这种字符串类型变量的读取,它对换行符的处理和上面的cin.getline()一致。

这篇关于【C++】浅论(cin和cout)的解锁、缓冲区的理解、CC++输入方法汇总和详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1007944

相关文章

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

浅谈主机加固,六种有效的主机加固方法

在数字化时代,数据的价值不言而喻,但随之而来的安全威胁也日益严峻。从勒索病毒到内部泄露,企业的数据安全面临着前所未有的挑战。为了应对这些挑战,一种全新的主机加固解决方案应运而生。 MCK主机加固解决方案,采用先进的安全容器中间件技术,构建起一套内核级的纵深立体防护体系。这一体系突破了传统安全防护的局限,即使在管理员权限被恶意利用的情况下,也能确保服务器的安全稳定运行。 普适主机加固措施:

webm怎么转换成mp4?这几种方法超多人在用!

webm怎么转换成mp4?WebM作为一种新兴的视频编码格式,近年来逐渐进入大众视野,其背后承载着诸多优势,但同时也伴随着不容忽视的局限性,首要挑战在于其兼容性边界,尽管WebM已广泛适应于众多网站与软件平台,但在特定应用环境或老旧设备上,其兼容难题依旧凸显,为用户体验带来不便,再者,WebM格式的非普适性也体现在编辑流程上,由于它并非行业内的通用标准,编辑过程中可能会遭遇格式不兼容的障碍,导致操