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

一、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()一致。
  • 10
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

是瑶瑶子啦

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值