C语言使用标准输入输出函数,需要包含头文件
。而在 C++ 中,只要包含头文件
,就完全可以使用这些 C 中的输入输出函数。
stdin
是一个文件描述符(Linux)或句柄(Windows),它在 C 程序启动时就被默认分配好。在 Linux 中一切皆文件,stdin
也相当于一个可读文件,它对应着键盘设备的输入。因为它不断地被输入,又不断地被读取,像流水一样,因此通常称作输入流。
stdin
是一种行缓冲I/O。当在键盘上键入字符时,它们首先被存放在键盘设备自身的缓存中(属于键盘硬件设备的一部分)。只有输入换行符时,操作系统才会进行同步,将键盘缓存中的数据读入到stdin
的输入缓冲区(存在于内存中)。所有从stdin
读取数据的输入流,都是从内存中的输入缓冲区读入数据。当输入缓冲区为空时,函数将被阻塞。
若无特殊说明,以下所有的**“缓冲区”**均是指内存中的stdin
输入缓冲区。用户程序中自定义的buffer
数组、str
数组等,将称作“数组”、“变量”,以免产生混淆。
scanf()
按照特定格式从stdin
读取输入。
scanf(格式控制符,地址列表);
char str[100];
int a;
scanf("%s %d", str, &a); // 注意,传入的一定是变量的地址
1.缓冲区开头:丢弃空白字符(包括空格、Tab、换行符),直到第一个非空白字符才认为是第一个数据的开始。
2.缓冲区中间:开始读取第一个数据后,一旦遇到空白字符(非换行符), 就认为读取完毕一次。遇到的空白字符残留在缓冲区,直到下一次被读取或刷新。例如输入字符串this is test
,则会被认为是3个字符串。
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 * (字符数组的地址) |
%lld |
长整型 | long long * |
%Lf |
更高精度浮点数 | long double * |
注意,%c
是一个比较特殊的格式符号,它将会读取所有空白字符,包括缓冲区开头的空格、Tab、换行符,使用时要特别注意。
scanf()
的读取也没有边界,所以并不安全。C11
标准提供了安全输入scanf_s()
。
scanf()
对应的输出函数是printf()
。
1.如果格式控制串中有非格式字符则输入时也要输入该非格式字符。
scanf("%d,%d,%d",&a,&b,&c);
其中用非格式符“ , ”作间隔符,故输入时应为:5,6,7。又如:
scanf("a=%d,b=%d,c=%d",&a,&b,&c);
则输入应为:a=5,b=6,c=7。
2.控制长度
用十进制整数指定输入的宽度(即字符数)。
例如(数据截取):
scanf("%5d",&a);
输入12345678只把12345赋予变量a,其余部分被截去。
又如(数据切分):
scanf("%4d%4d",&a,&b);
输入12345678将把1234赋予a,而把5678赋予b。
注意,scanf
函数中没有精度控制,如:scanf("%5.2f",&a)
;是非法的。不能企图用此语句输入小数为2位的实数。
gets()
- 不建议按下回车键时,从stdin
读取一行。
char str[100];
gets(str);
1.所有空格、Tab等空白字符均被读取,不忽略。
2.按下回车键时,缓冲区末尾的换行符被丢弃,字符串末尾没有换行符\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);
1.所有空格、Tab等空白字符均被读取,不忽略。
2.按下回车键时,缓冲区末尾的换行符也被读取,字符串末尾将有一个换行符\n
。例如,输入字符串hello
,再按下回车,则读到的字符串长度为6。
fgets()
函数会自动在字符串末尾加上\0
结束符。
第 2 个参数n
指定了读取的最大长度。函数读到n-1个字符(包括换行符\n
)就会停止,并在末尾加上\0
结束符。剩余字符将残留在缓冲区。
建议使用fgets()
完全替代gets()
。
fgets()
对应的输出函数是fputs()
。
getchar()
从stdin
读取一个字符。
getchar()
实际上也由fgetc()
宏定义而来,只是默认输入流为stdin
。
char a;
a = getchar();
1.所有空格、Tab
、换行等空白字符,无论在缓冲区开头、中间还是结尾,均会被读取,不忽略。
2.因为只读取一个字符,所以如果输入多于1个字符(包括换行符),则它们均会残留在缓冲区。具体地说,如果什么字符都不输入,直接按下回车键,则读取到的是换行符\n
,缓冲区无任何残留;如果输入一个字符如a
,然后按下回车键,则读取到的是字符a
,同时换行符\n
残留在缓冲区。
getchar()
常常用于清理缓冲区开头残留的换行符。当知道缓冲区开头有\n
残留时,可以调用getchar()
但不赋值给任何变量,即可实现冲刷掉\n
的效果。
getchar()
对应的输出函数是putchar()
。
gerchar()
是快读的模板基础。
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 中的getchar()
,对空白字符的处理也与其一致。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()
读取整行,用getchar()
读取单个字符。
在 C++ 中,建议使用cin >>
进行格式化读取,而cin.get()
、cin.getline
、getline(string)
有各自的适用情况。
注意fgets()
和cin.get()
在对换行符的清理方面有所区别。
printf()
按照特定格式将stdout
缓冲区的内容打印到终端。
printf("Number a = %d", a); // 十进制整数
printf("Number b = %.2f", b); // 浮点数,保留两位小数
printf("String s = %s", s); // 字符串
printf()
的写法与scanf()
十分相像。区别在于scanf()
中一般只有格式控制字符,而没有其他普通字符,而printf()
中常常是在一串字符中把要替换的内容写为格式控制字符,从而形成格式化输出的效果。
例如输出浮点数printf("%m.nlf",num)
,表示将num
保留n
位小数,长度为m
输出。
printf("%5d",25); //25仅有两位不够五位,25前补4位空格输出
printf("%05d",25); //位数不够前补3个0,输出"00025"
printf("%05.2lf",3.1415926); //输出03.14(小数点也算一位长度)
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
。
putchar()
将一个字符写入到标准输出流stdout
。
char c = 'x';
putchar(c);
同上,putchar()
不操作换行符。如果希望立即输出,需要自己加上换行符\n
。
cout
cout
是ostream
类的一个实例。cout
是行缓冲的。
char str[] = "hello world";
cout << "str: " << str << endl;
插入endl
对象时,将立即清空输出缓冲区并显示,然后输出一个换行符\n
。
也有cout.put()
等函数,不常用。
cin
和cout
优化在默认情况下,std::cin/std::cout
是极为迟缓的输入/输出方式,而 scanf/printf
比 std::cin/std::cout
快得多。
这是因为在默认情况下,cin
与stdin
总是保持同步的,也就是说这两种方法可以混用,而不必担心文件指针混乱,同时cout
和stdout
也一样,两者混用不会输出顺序错乱。由于这个特性,所以导致cin
和cout
有许多额外的开销。
那么我们如何禁用这个特性呢?
std::ios::sync_with_stdio(false)
这个函数是一个“是否兼容 stdio
”的开关,C++ 为了兼容 C,保证程序在使用了 printf
和 std::cout
的时候不发生混乱,将输出流绑到了一起。
这其实是 C++ 为了兼容而采取的保守措施。我们可以在进行 IO 操作之前将 stdio
解除绑定,但是在这样做之后要注意不能同时使用 std::cin/std::cout
和 scanf/printf
。更严格的来说:关闭之后C++ IO和C IO 两者不能混用,否则会造成IO混乱。(这里建议只使用cin
和cout
就好)。
tie
函数加速还有一种影响速度的原因是:在默认的情况下std::cin
绑定的是 std::cout
,每次执行 <<
操作符的时候都要调用 flush()
,这样会增加 IO 负担。
tie
是将两个 stream
绑定的函数,空参数的话返回当前的输出流指针。 可以通过std::cin.tie(0)
和std:cout.tie(0)
(0 表示 NULL)来解除 std::cin
与 std::cout
的绑定,进一步加快执行效率。
在这两种情况优化下,std::cin
和std::cout
的速度就和scanf
和printf
基本一样了,甚至更快。
代码:
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
//如果编译开启了 C++11 或更高版本,建议使用 std::cin.tie(nullptr);
众所周知, getchar()
是用来读入 1 byte 的数据并将其转换为 char
类型的函数,且速度很快,故可以用“读入字符——转换为整型”来代替缓慢的读入
每个整数由两部分组成——符号和数字,整数的 ‘+’ 通常是省略的,且不会对后面数字所代表的值产生影响,而 ‘-’ 不可省略,因此要进行判定。
10 进制整数中是不含空格或除 0~9 和正负号外的其他字符的,因此在读入不应存在于整数中的字符(通常为空格)时,就可以判定已经读入结束
C 和 C++ 语言分别在 ctype.h
和 cctype
头文件中,提供了函数 isdigit
, 这个函数会检查传入的参数是否为十进制数字字符,是则返回 true ,否则返回 false 。对应的,在下面的代码中,可以使用 isdigit(ch)
代替 ch >= ‘0’ && ch <= ‘9’
,而可以使用 !isdigit(ch)
代替 ch <‘0’ || ch> ‘9’
。
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9')
x=x*10+ch-'0',ch=getchar();
return x*f;
}
同样是众所周知, putchar
是用来输出单个字符的函数,因此将数字的每一位转化为字符输出以加速,要注意的是,负号要单独判断输出,并且每次 %(mod)取出的是数字末位,因此要倒序输出。
void write(int x)
{
if(x<0)
putchar('-'),x=-x;
if(x>9)
write(x/10);
putchar(x%10+'0');
return;
}
O2优化
代码:
#pragma GCC optimize(2) //实在不行o3优化