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

天天见闻 天天见闻 2022-03-03 科技 阅读: 589
摘要: 由于输入涉及空格、换行符的读取、忽略等问题,因此输入比输出更麻烦。从指定输入流读取一个字符,输入可以是stdin,也可以是文件流,使用时需要显式指定。输出函数通常没有针对对空格、制表符的特殊行为,比输入要简单一些。stderr是标准错误流,它是无缓冲的,会立即输出到屏幕,而不是等待换行符才输出。如果希望立即输出,需要自己加上换行符\n。因此如果希望立即输出,需要自己加上换行符\n。如果希望立即输出,需要自己加上换行符\n。

C/C++的一众输入输出函数的区别常常搞得人晕头转向,二者之中又以输入函数更加令人头疼。本文尝试整理C/C++的各种输入输出函数。

由于输入涉及空格、换行符的读取、忽略等问题,因此输入比输出更麻烦。所以本文将以输入为主线,对应的输出用法是类似的。

水平有限,如有疏漏,欢迎提出。

猛戳这里,在我的Github博客上阅读本篇文章(施工中,求踩orz)

文章目录

标准输入流 C 标准输入

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

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

stdin是一个文件描述符(Linux)或句柄(Windows),它在 C 程序启动时就被默认分配好。在 Linux 中一切皆文件,stdin也相当于一个可读文件,它对应着键盘设备的输入。因为它不断地被输入,又不断地被读取,像流水一样,因此通常称作输入流。

stdin是一种行缓冲I/O。当在键盘上键入字符时,它们首先被存放在键盘设备自身的缓存中(属于键盘硬件设备的一部分)。只有输入换行符时,操作系统才会进行同步,将键盘缓存中的数据读入到stdin的输入缓冲区(存在于内存中)。所有从stdin读取数据的输入流,都是从内存中的输入缓冲区读入数据。当输入缓冲区为空时,函数将被阻塞。

若无特殊说明,以下所有的**“缓冲区”**均是指内存中的stdin输入缓冲区。用户程序中自定义的buffer数组、str数组等,将称作“数组”、“变量”,以免产生混淆。

scanf()

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

用法示例:

char str[100];
int a;
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读取一行。

用法示例:

char str[100];
gets(str);

对空白字符的处理:

所有空格、Tab等空白字符均被读取,不忽略。按下回车键时,缓冲区末尾的换行符被丢弃,字符串末尾没有换行符\n,缓冲区也没有残留的换行符\n。

注意,gets()不能指定读取上限,因此容易发生数组边界溢出,造成内存不安全。C11 使用了gets_s()代替gets(),但有时编译器未必支持,因此总体来说不建议使用gets()函数来读取输入。

gets()对应的输出函数是puts()。

fgets()

从指定输入流读取一行,输入可以是stdin,也可以是文件流,使用时需要显式指定。

读取文件流示例:

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。例如,输入字符串hello,再按下回车,则读到的字符串长度为6。

fgets()函数会自动在字符串末尾加上\0结束符。

第 2 个参数n指定了读取的最大长度。函数读到n-1个字符(包括换行符\n)就会停止,并在末尾加上\0结束符。剩余字符将残留在缓冲区。

建议使用fgets()完全替代gets()。

fgets()对应的输出函数是fputs()。

fgetc() & getc()

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

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

用法示例:

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

对空白字符的处理:

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

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

getchar()

从stdin读取一个字符。

getchar()实际上也由fgetc()宏定义而来,只是默认输入流为stdin。

用法示例:

char a;
a = getchar();

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

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

C++ 标准输入

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

cin

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

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

cin的用法非常多,只列举常用的几种。最常用的就是使用>>符号(我认为该符号形象地体现了“流”的特点)。

用法示例:

int a, b;
cin >> a >> b;
char str[20];
cin >> str;

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

如果不想跳过空白字符,可以使用流控制关键词noskipws(no skip white space),但这只对单个字符有效(类似于scanf中的%c)。

char c;
cin >> noskipws >> c;

注意,cin对象属于命名空间std,如果想使用cin对象,必须在 C++ 文件开头写using namespace std,或者在每次用到的时候写成std::cin。

cin.get()

读取单个或指定长度的字符,包括空白字符。

用法示例:

char a, b;
char str[20];
 
// 读取一个字符,读取失败时返回0,多余字符残留在缓冲区(包括换行符)
a = cin.get();
 
// 读取一个字符,读取失败时返回EOF,多余字符残留在缓冲区(包括换行符)
cin.get(b);
 
// 在遇到指定终止字符(参数3)前,至多读取n-1个(参数2)字符
// 当不指定终止字符时,默认为换行符\n
// 如果输入的字符个数小于等于n-1(不含终止字符),则终止字符不残留在缓冲区
// 如果输入的字符个数多于n-1(不含终止字符),则余下字符将残留在缓冲区
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()

读取指定长度的字符,包括空白字符。

用法示例:

char str[20];
cin.getline(str, sizeof(str));    // 第3个参数也可以指定终止字符

cin.getline()与cin.get()指定读取长度时的用法几乎一样。区别在于,如果输入的字符个数大于指定的最大长度n-1(不含终止符),cin.get()会使余下字符残留在缓冲区,等待下次读取;而cin.getline()会给输入流设为 Fail 状态,在主动恢复之前,无法再进行正常输入。

getline()

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

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

用法示例:

string str;
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
int main(int argc, char* argv[])
{
    printf("Hello World!\n");
    while(1){}
    return 0;
}
 
// 程序 2
int main(int argc, char* argv[])
{
    printf("Hello World!");
    while(1){}
    return 0;
}
 
// 程序 3
int main(int argc, char* argv[])
{
    printf("Hello World!")
    return 0;
}
 
// 程序 1
int main(int argc, char* argv[])
{
    printf("Hello World!\nABCDE");
    while(1){}
    return 0;
}

程序 1 中,printf()输出内容的最后有换行符\n,所以将在屏幕上输出Hello World!并换行,然后进入while(1)循环阻塞住。

程序 2 中,把\n去掉了,此时终端不会显示任何内容。因为程序进入死循环后,没有机会向stdout中写入\n使其清空缓冲。

程序 3 中,虽然没有写入换行符,但是依然能够在终端打印Hello World!(只是没有换行)。这是因为程序结束时会自动清空缓冲区。(除此之外,当缓冲区被填满时也会自动清空)

程序 4 能够进一步加深对行缓冲的理解。它在程序 1 的基础上,在换行符之后又加上了几个字符。运行可以发现终端只打印了Hello World!并换行,而没有打印ABCDE。

输出函数通常没有针对对空格、制表符的特殊行为,比输入要简单一些。特殊的处理一般只有换行符。

printf()

按照特定格式将stdout缓冲区的内容打印到终端。

用法示例:

printf("Number a = %d", a);      // 十进制整数
printf("Number b = %.2f", b);    // 浮点数,保留两位小数
printf("String s = %s", s);      // 字符串

printf()的写法与scanf()十分相像。区别在于scanf()中一般只有格式控制字符,而没有其他普通字符,而printf()中常常是在一串字符中把要替换的内容写为格式控制字符,从而形成格式化输出的效果。

puts()

将字符串和一个尾随的换行符\n写入到stdout的缓冲区。根据行缓冲的性质,终端也会立即进行打印显示。

用法示例:

puts("hello");    // 立即输出hello并换行

puts()对换行符的处理与gets()“相反”。gets()会自动丢弃一个换行符,而puts()则是自动写入一个换行符。

fputs()

将字符串写入指定输出流,可以是文件流、stdout或stderr等。stderr是标准错误流,它是无缓冲的,会立即输出到屏幕,而不是等待换行符才输出。

用法示例:

fputs("hello world", stdout);    // 不会立即输出
fputs("hello world\n", stdout);  // 立即输出
fputs("hello world", stderr);  // 立即输出

与fgets()一样换行符,fputs()不会主动操作换行符。如果希望立即输出,需要自己加上换行符\n。

fputc() & putc()

将一个字符写入指定输出流,可以是文件流、stdout或stderr等。

用法示例:

char c = 'q';
fputc(c, stdout);
c = '\n';
putc(c, stdout);

fputc()和putc()只是把字符写入stdout,没有任何额外操作。因此如果希望立即输出,需要自己加上换行符\n。

putchar()

将一个字符写入到标准输出流stdout。

用法示例:

char c = 'x';
putchar(c);

同上,putchar()不操作换行符。如果希望立即输出,需要自己加上换行符\n。

fflush()

该函数的功能是强制刷新缓冲区,将数据立即写到对应的文件(或设备)。其参数可以是文件流指针,也可以是stdout。

用法示例:

fputs("Hello World!", stdout);
fflush(stdout);
while (1);

上面的程序在进入死循环前,会输出Hello World!字符串到屏幕。

注意:不能够将fflush()用于stdin!这可能导致不可预料的后果。

C++ 标准输出 cout

cout是ostream类的一个实例。cout是行缓冲的。

用法示例:

char str[] = "hello world";
cout << "str: " << str << endl;

插入endl对象时,将立即清空输出缓冲区并显示,然后输出一个换行符\n。

也有cout.put()等函数,不常用。

cerr

cerr是标准错误流,也是ostream类的一个实例,并默认输出设备为显示屏上的命令行终端。它默认与stderr同步。

cerr是非缓冲的,即插入数据时会立即输出。

用法示例:

char str[] = "File open FAILED!";
cerr << "[Error] " << str;

clog

clog是标准日志流,也是ostream类的一个实例,并默认输出设备为显示屏上的命令行终端。

clog是有缓冲的,但具体的刷新条件没有找到资料。实测以下代码是可以输出在屏幕的:

clog << "Failed!";
while(1){}

总结

标准输出相比输入来说较为简单。需要注意的是stdout和cout是行缓冲的,而stderr和cerr是无缓冲的。

C++ 流的高级用法请参考其他资料。

其他相关
删除awk中的换行符

删除awk中的换行符

作者: 天天见闻 时间:2022-03-09 阅读: 754
本文介绍了删除awk中的换行符的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!我想删除一个日期函数换行字符,并将它包含空格。,而不是试图将变量插入到你正在做的命令字符串,你可以通过它来awk的是这样的:然后,您可以使用变量日期作为脚本中一个awk变量:这篇关于删除awk中的换行符的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!...
【干货】Excel中的换行符,这几种用法你会那些?

【干货】Excel中的换行符,这几种用法你会那些?

作者: 天天见闻 时间:2022-02-24 阅读: 765
提起换行符,大家更多地可能会知道在Word里面的应用换行符,但是对于在Excel中并不很了解。今天与大家聊聊Excel中的换行符的问题!CHAR是表示换行符,10表示换行符的ASCII码值。将下面的文本串的换行符替换为“XXX”。注意:如果一个空单元格中使用了组合键输入了一个强制换行符,那么使用LEN函数可以测量其长度,结果为1.在Excel自带的Power Query的功能中换行符,可以使用相关的功能将带有换行符的文本串拆分成列或者行。...
如何在 PHP 中创建换行符?

如何在 PHP 中创建换行符?

作者: 天天见闻 时间:2022-02-23 阅读: 1711
它是 PHP 的一个内置函数,用于在字符串中的所有换行符之前插入 HTML 换行符。虽然,我们也可以在源代码中使用 PHP 换行符\n或\r\n来创建换行符,但是这些换行符在浏览器上是不可见的。因此,nl2br() 在这里很有用。nl2br() 函数包含这些换行符 \n 或 \r\n 组合创建换行符。单独的 "\n" 和 "\r\n" 不足以在字符串中创建换行符,因为整个字符串显示在一行中。为了安全起见,请改用“\r\n”字符来创建换行符。...
如何删除换行符,换行符的几种输入方法

如何删除换行符,换行符的几种输入方法

作者: 天天见闻 时间:2022-02-22 阅读: 1740
说明《“偷懒”的技术:打造财务Excel达人》出版四年以来,得到了广大读者的喜爱。《偷懒的技术》2017年名列当当网办公类年度畅销榜第二名换行符,Excel类第一名,好评率99.8%!部分读者朋友反馈说《“偷懒”的技术:打造财务Excel达人》P37那些输入换行符的方法不起作用,输入后,还是删除不了换行符。如果第二种方法输入换行符也不管用,有可能是已经输入了多个换行符,导致查找不到。...
各操作系统中的换行符差异

各操作系统中的换行符差异

作者: 天天见闻 时间:2022-02-19 阅读: 3715
HTTP/1.1将CRLF的序列定义为任何协议元素的行尾标志,但这个规定对实体主体除外。它的每一行都是以CRLF结尾的。计算机出现之前,是使用电传打字机打印字符的,它每秒可以打10个字符。Windows中打开Linux/UNIX系统下的文件时,所有的文字都会变成一行。而^M符号是Linux等系统规定的一个特殊标记,它占一个字符的大小,它不是^和M的组合,是打印不出来的...
冬奥会闭幕式如何熄灭火种?张艺谋最新“剧透”来了

冬奥会闭幕式如何熄灭火种?张艺谋最新“剧透”来了

作者: 天天见闻 时间:2022-02-19 阅读: 2982
都市快报综合消息 18日晚,2022北京冬奥会闭幕式进行了最后一次全要素彩排,将于2月20日晚在国家体育场“鸟巢”正式亮相。目前,闭幕式各项准备工作已经就绪。闭幕式希望以运动员为主体,另外包括有“双奥”元素。2022北京冬奥会闭幕式导演沙晓岚介绍,闭幕式最重点的环节,就是体现“双奥”理念。...
我来说两句

年度爆文