C/C++标准输入输出函数终极最全解析(不全捶我)

打印 上一主题 下一主题

主题 1010|帖子 1010|积分 3034

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
C/C++的一众输入输出函数的区别常常搞得人晕头转向,二者之中又以输入函数更加令人头疼。本文尝试整理C/C++的各种输入输出函数。
由于输入涉及空格、换行符的读取、忽略等问题,因此输入比输出更麻烦。所以本文将以输入为主线,对应的输出用法是类似的。
水平有限,如有疏漏,欢迎提出。
标准输入流

C 标准输入

C语言使用标准输入输出函数,需要包含头文件。而在 C++ 中,只要包含头文件,就完全可以使用这些 C 中的输入输出函数。
标准输入流及对缓冲区的理解

stdin是一个文件描述符(Linux)或句柄(Windows),它在 C 程序启动时就被默认分配好。在 Linux 中一切皆文件,stdin也相当于一个可读文件,它对应着键盘设备的输入。因为它不断地被输入,又不断地被读取,像流水一样,因此通常称作输入流
stdin是一种行缓冲I/O。当在键盘上键入字符时,它们首先被存放在键盘设备自身的缓存中(属于键盘硬件设备的一部分)。只有输入换行符时,操作系统才会进行同步,将键盘缓存中的数据读入到stdin的输入缓冲区(存在于内存中)。所有从stdin读取数据的输入流,都是从内存中的输入缓冲区读入数据。当输入缓冲区为空时,函数将被阻塞。
若无特殊说明,以下所有的“缓冲区”均是指内存中的stdin输入缓冲区。用户程序中自定义的buffer数组、str数组等,将称作“数组”、“变量”,以免产生混淆。
scanf()

按照特定格式从stdin读取输入。
用法示例:
  1. char str[100];
  2. int a;
  3. scanf("%s %d", str, &a);    // 注意,传入的一定是变量的地址
复制代码
对空白字符的处理:

  • 缓冲区开头:丢弃空白字符(包括空格、Tab、换行符),直到第一个非空白字符才认为是第一个数据的开始。
  • 缓冲区中间:开始读取第一个数据后,一旦遇到空白字符(非换行符), 就认为读取完毕一次。遇到的空白字符残留在缓冲区,直到下一次被读取或刷新。例如输入字符串this is test,则会被认为是3个字符串。
  • 缓冲区末尾:按下回车键时,换行符\n残留在缓冲区。换行符之前的空格可以认为是中间的空白字符,处理同上。
注意,格式控制符只会读取正确类型的变量。如果输入格式不正确,比如在%d处输入了一个字符a,则会使读取中断,即后续不读取任何变量。
格式控制符说明:
类型类型输入参数的类型%d十进制整数int *%u无符号十进制整数unsigned int *%o八进制整数int *%x十六进制整数int *%f、%e、%g浮点数float *%lf、%le、%lg双精度浮点数double *%c单个字符(含空白字符)char *%s字符串char *%%读 % 符号注意,%c是一个比较特殊的格式符号,它将会读取所有空白字符,包括缓冲区开头的空格、Tab、换行符,使用时要特别注意。
scanf()的读取也没有边界,所以并不安全。C11 标准提供了安全输入scanf_s()。
scanf()对应的输出函数是printf()。
gets() - 不建议

按下回车键时,从stdin读取一行。
用法示例:
  1. char str[100];
  2. gets(str);
复制代码
对空白字符的处理:

  • 所有空格、Tab等空白字符均被读取,不忽略。
  • 按下回车键时,缓冲区末尾的换行符被丢弃,字符串末尾没有换行符\n,缓冲区也没有残留的换行符\n。
注意,gets()不能指定读取上限,因此容易发生数组边界溢出,造成内存不安全。C11 使用了gets_s()代替gets(),但有时编译器未必支持,因此总体来说不建议使用gets()函数来读取输入。
gets()对应的输出函数是puts()。
fgets()

从指定输入流读取一行,输入可以是stdin,也可以是文件流,使用时需要显式指定。
读取文件流示例:
  1. char str[100];
  2. memset(str, 0, sizeof(str));
  3. int i = 1;
  4. FILE *fp = fopen("...test.txt", "r");
  5. if (fp == NULL) {
  6.     printf("File open Error!\n");
  7.     exit(1);
  8. }
  9. while (fgets(str, sizeof(str), fp) != NULL)
  10.     printf("line%d [len %d]: %s", i++, strlen(str), str);
  11. fclose(fp);
复制代码
读取stdin示例:
  1. char str[100];
  2. memset(str, 0, sizeof(str));
  3. int i = 1;
  4. while (fgets(str, sizeof(str), stdin) != NULL)
  5.     printf("line%d [len %d]: %s", i++, strlen(str), str);
复制代码
对空白字符的处理:

  • 所有空格、Tab等空白字符均被读取,不忽略。
  • 按下回车键时,缓冲区末尾的换行符也被读取,字符串末尾将有一个换行符\n。例如,输入字符串hello,再按下回车,则读到的字符串长度为6。
fgets()函数会自动在字符串末尾加上\0结束符。
第 2 个参数n指定了读取的最大长度。函数读到n-1个字符(包括换行符\n)就会停止,并在末尾加上\0结束符。剩余字符将残留在缓冲区。
建议使用fgets()完全替代gets()。
fgets()对应的输出函数是fputs()。
fgetc() & getc()

从指定输入流读取一个字符,输入可以是stdin,也可以是文件流,使用时需要显式指定。
这两个函数完全等效,getc()由fgetc()宏定义而来。不同的是,前述的gets()和fgets()相互之间没有关系。
用法示例:
  1. char a, b;
  2. a = fgetc(stdin);
  3. b = getc(stdin);
复制代码
对空白字符的处理:

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

从stdin读取一个字符。
getchar()实际上也由fgetc()宏定义而来,只是默认输入流为stdin。
用法示例:
  1. char a;
  2. a = getchar();
复制代码
getchar()常常用于清理缓冲区开头残留的换行符。当知道缓冲区开头有\n残留时,可以调用getchar()但不赋值给任何变量,即可实现冲刷掉\n的效果。
getchar()对应的输出函数是putchar()。
C++ 标准输入

C++中使用标准输入输出需要包含头文件。一般使用iostream类进行流操作,其封装很完善,也比较复杂,本文只介绍一部分。
cin

cin是 C++ 的标准输入流对象,即istream类的一个对象实例。cin有自己的缓冲区,但默认情况下是与stdin同步的,因此在 C++ 中可以混用 C++ 和 C 风格的输入输出(在不手动取消同步的情况下)。
cin与stdin一样是行缓冲,即遇到换行符时才会将数据同步到输入缓冲区。
cin的用法非常多,只列举常用的几种。最常用的就是使用>>符号(我认为该符号形象地体现了“流”的特点)。
用法示例:
  1. int a, b;
  2. cin >> a >> b;
  3. char str[20];
  4. cin >> str;
复制代码
cin对空白字符的处理与scanf一致。即:跳过开头空白字符,遇到空白字符停止读取,且空白字符(包括换行符)残留在缓冲区。
如果不想跳过空白字符,可以使用流控制关键词noskipws(no skip white space),但这只对单个字符有效(类似于scanf中的%c)。
  1. char c;
  2. cin >> noskipws >> c;
复制代码
注意,cin对象属于命名空间std,如果想使用cin对象,必须在 C++ 文件开头写using namespace std,或者在每次用到的时候写成std::cin。
cin.get()

读取单个或指定长度的字符,包括空白字符。
用法示例:
  1. char a, b;
  2. char str[20];
  3. // 读取一个字符,读取失败时返回0,多余字符残留在缓冲区(包括换行符)
  4. a = cin.get();
  5. // 读取一个字符,读取失败时返回EOF,多余字符残留在缓冲区(包括换行符)
  6. cin.get(b);
  7. // 在遇到指定终止字符(参数3)前,至多读取n-1个(参数2)字符
  8. // 当不指定终止字符时,默认为换行符\n
  9. // 如果输入的字符个数小于等于n-1(不含终止字符),则终止字符不残留在缓冲区
  10. // 如果输入的字符个数多于n-1(不含终止字符),则余下字符将残留在缓冲区
  11. cin.get(str, sizeof(str), '\n');
复制代码
cin.get()读取单个字符时,类似于 C 中的fgetc(),对空白字符的处理也与其一致。cin.get()读取的字符也可以赋值给整型变量。
cin.get()读取指定长度个字符时,类似于 C 中的fgets(),但在换行符的处理上不同。它们都不会使换行符残留在缓冲区,但fgets()会将缓冲区末尾的换行符\n也写入字符串,而cin.get()会丢弃缓冲区末尾的\n。即:当输入test时,用fgets()读取得到的字符串长度为5,用cin.get()读取得到的字符串长度为4。
cin.getline()

读取指定长度的字符,包括空白字符。
用法示例:
  1. char str[20];
  2. cin.getline(str, sizeof(str));    // 第3个参数也可以指定终止字符
复制代码
cin.getline()与cin.get()指定读取长度时的用法几乎一样。区别在于,如果输入的字符个数大于指定的最大长度n-1(不含终止符),cin.get()会使余下字符残留在缓冲区,等待下次读取;而cin.getline()会给输入流设为 Fail 状态,在主动恢复之前,无法再进行正常输入。
getline()

getline()并不是标准输入流istream的函数,而是字符串流sstream的函数,只能用于读取数据给string类对象,使用时也需要包含头文件。
如果使用getline()读取标准输入流的数据,需要显式指定输入流。
用法示例:
  1. string str;
  2. getline(cin, str);
复制代码
getline()会读取所有空白字符,且缓冲区末尾的换行符会被丢弃,不残留也不写到字符串结尾。同时,由于string对象的空间是动态分配的,所以会一次性将缓冲区读完,不存在读不完残留在缓冲区的问题。
需要注意的是,假如缓冲区开头就是换行符(比如可能是上一次cin残留的),则getline()会直接读取到空字符串并结束,不会给键盘输入的机会。所以这种情况下要注意先清除开头的换行符。
总结

在 C 中,建议使用scanf()进行格式化读取,用fgets()读取整行,用fgetc()或getchar()读取单个字符。
在 C++ 中,建议使用cin >>进行格式化读取,而cin.get()、cin.getline、getline(string)有各自的适用情况。
注意fgets()和cin.get()在对换行符的清理方面有所区别。
标准输出流

C 标准输出

标准输出流及对缓冲区的理解

相应于输入流的stdin,输出流也有其默认的文件描述符stdout,对应着命令行终端(Windows 中称为控制台)的显示。此外,还有对应错误输出的stderr,默认也是终端的显示。它们都可以被重定向到文件中以便持久保存和查看,在此不作赘述。
stdout也是行缓冲I/O,它与stdin类似也有三者之间的数据同步:从用户程序到stdout的输出缓冲区,由用户程序决定;从stdout的输出缓冲区到终端的显示,只有缓冲区末尾遇到换行符\n才会进行。如果输出缓冲区末尾没有换行符\n,是不会打印显示输出的。
例如以下程序:
  1. // 程序 1
  2. int main(int argc, char* argv[])
  3. {
  4.     printf("Hello World!\n");
  5.     while(1){}
  6.     return 0;
  7. }
  8. // 程序 2
  9. int main(int argc, char* argv[])
  10. {
  11.     printf("Hello World!");
  12.     while(1){}
  13.     return 0;
  14. }
  15. // 程序 3
  16. int main(int argc, char* argv[])
  17. {
  18.     printf("Hello World!")
  19.     return 0;
  20. }
  21. // 程序 1
  22. int main(int argc, char* argv[])
  23. {
  24.     printf("Hello World!\nABCDE");
  25.     while(1){}
  26.     return 0;
  27. }
复制代码
程序 1 中,printf()输出内容的最后有换行符\n,所以将在屏幕上输出Hello World!并换行,然后进入while(1)循环阻塞住。
程序 2 中,把\n去掉了,此时终端不会显示任何内容。因为程序进入死循环后,没有机会向stdout中写入\n使其清空缓冲。
程序 3 中,虽然没有写入换行符,但是依然能够在终端打印Hello World!(只是没有换行)。这是因为程序结束时会自动清空缓冲区。(除此之外,当缓冲区被填满时也会自动清空)
程序 4 能够进一步加深对行缓冲的理解。它在程序 1 的基础上,在换行符之后又加上了几个字符。运行可以发现终端只打印了Hello World!并换行,而没有打印ABCDE。
输出函数通常没有针对对空格、制表符的特殊行为,比输入要简单一些。特殊的处理一般只有换行符。
printf()

按照特定格式将stdout缓冲区的内容打印到终端。
用法示例:
  1. printf("Number a = %d", a);      // 十进制整数
  2. printf("Number b = %.2f", b);    // 浮点数,保留两位小数
  3. printf("String s = %s", s);      // 字符串
复制代码
printf()的写法与scanf()十分相像。区别在于scanf()中一般只有格式控制字符,而没有其他普通字符,而printf()中常常是在一串字符中把要替换的内容写为格式控制字符,从而形成格式化输出的效果。
puts()

将字符串和一个尾随的换行符\n写入到stdout的缓冲区。根据行缓冲的性质,终端也会立即进行打印显示。
用法示例:
  1. puts("hello");    // 立即输出hello并换行
复制代码
puts()对换行符的处理与gets()“相反”。gets()会自动丢弃一个换行符,而puts()则是自动写入一个换行符。
fputs()

字符串写入指定输出流,可以是文件流、stdout或stderr等。stderr是标准错误流,它是无缓冲的,会立即输出到屏幕,而不是等待换行符才输出。
用法示例:
  1. fputs("hello world", stdout);    // 不会立即输出
  2. fputs("hello world\n", stdout);  // 立即输出
  3. fputs("hello world", stderr);  // 立即输出
复制代码
与fgets()一样,fputs()不会主动操作换行符。如果希望立即输出,需要自己加上换行符\n。
fputc() & putc()

一个字符写入指定输出流,可以是文件流、stdout或stderr等。
用法示例:
  1. char c = 'q';
  2. fputc(c, stdout);
  3. c = '\n';
  4. putc(c, stdout);
复制代码
fputc()和putc()只是把字符写入stdout,没有任何额外操作。因此如果希望立即输出,需要自己加上换行符\n。
putchar()

一个字符写入到标准输出流stdout。
用法示例:
  1. char c = 'x';
  2. putchar(c);
复制代码
同上,putchar()不操作换行符。如果希望立即输出,需要自己加上换行符\n。
fflush()

该函数的功能是强制刷新缓冲区,将数据立即写到对应的文件(或设备)。其参数可以是文件流指针,也可以是stdout。
用法示例:
  1. fputs("Hello World!", stdout);
  2. fflush(stdout);
  3. while (1);
复制代码
上面的程序在进入死循环前,会输出Hello World!字符串到屏幕。
注意:不能够将fflush()用于stdin!这可能导致不可预料的后果。
C++ 标准输出

cout

cout是ostream类的一个实例。cout是行缓冲的。
用法示例:
[code]char str[] = "hello world";cout
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

刘俊凯

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表