更好的阅读体验 \huge{\color{red}{更好的阅读体验}} 更好的阅读体验
本人水平有限,语言组织能力低下,不保证绝佳的阅读体验,也不保证内容完全准确,如有错误和建议,欢迎指出。才怪。
你先别急,我知道你很急,但是别急,所以你先别急。
在了解输入输出输出缓冲区时,需要明确以下几个基本概念:
I/O
设备,指的是用于从程序内部向外部设备(屏幕、打印机等)或从外部设备向程序内部传输数据的设备(鼠标、键盘等);I/O
设备进行与用户之间的数据交互,而为了适应不同的设备之间数据的传输,提出了输入输出流的概念。C
语言中:
C
标准库中,标准输入流输出流分别是 stdin
和 stdout
,另外还有标准错误流 stderr
。
头文件里的 scanf()
函数和 printf()
函数。C++
语言中:
C++
标准库中,没有 stdin
这样的标准输入流,而是使用 std::cin
和 std::out
来进行标准输入和标准输出。
头文件里的 getline()
函数或是 >>
和 <<
操作符。C++
中,输入输出流的使用通常是通过 iostream
库实现的,而在 C
中则是通过 stdio
库实现的。C
语言中:
C
标准库中的文件指针 FILE*
来实现。fopen()
, fclose()
, fread()
, fwrite()
等。C++
语言中:
C
标准库中的文件操作函数封装而成,即 fstream
类。std::ifstream
和 std::ofstream
类实现,它们是 std::istream
和 std::ostream
类的派生类。相比标准输入输出流,文件输入输出流需要显式地指定要读写的文件,因此使用起来比较繁琐,但也更加灵活:文件输入输出流可以处理任何类型的文件,包括文本文件和二进制文件,而标准输入输出流只能处理字符流。此外,文件输入输出流可以通过随机访问文件的方式读写文件,而标准输入输出流只能顺序读写。
顾名思义,输入输出缓冲区就是输入输出缓冲的区域。
在 C/C++
中,输入输出缓冲区是用来存储输入输出数据的临时存储区域:
说人话:输入输出缓冲区就是为了保存这些输入输出流而临时开辟出的一块内存。
众嗦粥汁,因为需要,所以设置:
因此,当程序需要读取或写入大量数据时,使用缓冲区可以将这些数据先存储到内存中,然后再一次性地写入或读取,避免了频繁访问硬件的开销。此外,缓冲区还可以优化数据的排列和格式,以便更高效地读取和写入数据。
说人话:缓冲区的存在是为了提高输入输出效率,减少对外设的访问次数。
首先别急,其次别急,所以我们先来了解下:**输入输出缓冲区的空间由什么来分配?开辟在哪里?何时开辟?**这个问题:
具体地:
分配缓冲区的时机:
分配缓冲区的大小:
缓冲区的大小应该足够容纳输入或输出数据的常规大小,同时又不能过大以致于浪费内存。
由实现库来完成对缓冲区大小的分配,具体实现细节可能会因编译器或操作系统的不同而有所差异。
一般来说,实现库会通过调用操作系统提供的系统调用或动态内存分配函数来分配缓冲区的空间。
在内存空间紧张的情况下,缓冲区的大小可能会被限制,从而可能影响到程序的性能和可靠性。
我知道你急了,但是你先别急,这部分其实不用太纠结。的。对吧:
C
语言中,标准输入输出库
提供了输入输出缓冲区的实现。
setbuf()
,setvbuf()
,fflush()
。setbuf()
和 setvbuf()
可以用来设置缓冲区,而 fflush()
用来清空缓冲区并把缓冲区中的数据输出到文件。C
中的输入输出函数,如 scanf()
和 printf()
等,是非类型安全的:
C++
中,
库提供了输入输出缓冲区的实现。
streambuf
和 filebuf
。streambuf
是
库的基类,提供了对输入输出缓冲区的访问;而 filebuf
是
库的基类,提供了对文件输入输出缓冲区的访问。
库还提供了一些类似 setbuf()
,setvbuf()
,flush()
等函数,用来管理输入输出缓冲区。在关闭同步流之后,
库使用了一种不同于标准输入输出库的机制来提高效率,例如使用字符串流 stringstream
和缓冲流 buffer stream
等。C++
中的输入输出函数,如 std::cin
和 std::cout
等,是类型安全的:
C++
流语义,其中数据类型是静态确定的,而不是动态确定的。C++
的输入输出更加类型安全。这就是为什么,你仍然可以在 C++
中使用 scanf()
和 printf()
,但是仍建议在 C++
中使用
库所提供的标准输入输出的原因,以及为什么我们常说 C++
比 C
更适于面向对象。
总结:这部分真的不用太纠结。中肯的。正确的。理智的。一针见血的。真的。
你急了,你急了,你急了,因为你很迷,你不明白 stdin
、scanf
、cin
、std::cin
、getline
、stringstream
还有 stdout
、printf
、cout
、std::cout
这些都是什么寄吧玩意,对吧?再来明确一下:
stdin
是 C
语言中的标准输入流。cin
是 C++
中的标准输入流,而 std::cin
是 C++
标准库命名空间中的标准输入流,cin
是使用命名空间 std
的缩写,即cin
是 std::cin
的别名。scanf()
是 C
语言中的输入函数,而 cin
和 std::cin
是 C++
中的输入流。scanf()
的参数需要使用格式化字符串来指定输入数据的类型,而 cin
和 std::cin
可以自动识别输入数据的类型。getline()
是 C++
中的输入函数,可以用于从输入流中读取一行文本数据,可以指定分隔符。getline()
可以替代 scanf()
和 cin
用于读取字符串类型数据。stdout
是 C
语言中的标准输出流。cout
是 C++
中的标准输出流,而 std::cout
是 C++
标准库命名空间中的标准输出流。它们之间的区别同 cin
和 std::cin
。printf()
是 C
语言中的输出函数,而 cout
和 std::cout
是 C++
中的输出流。printf()
的参数需要使用格式化字符串来指定输出数据的类型,而 cout
和 std::cout
可以自动识别输出数据的类型。stringstream
这个因为我们对这两个东西再熟悉不过了,所以我们对这两个东西根本不陌生,这俩是 C
语言中的标准输入和标准输出函数。
对于 printf()
,只需要注意下面几点:
scanf(format, argument_list);
而对于 scanf()
,除了基本注意点:
scanf(format, argument_list);
scanf()
输入数据时要求数据格式与 format
字符串中指定的格式匹配,否则会产生错误。还需要注意:scanf()
函数的缓冲区不会自动清空,因此需要使用fflush(stdin)
语句清空缓冲区,以防止输入的数据被下一个输入函数接收,如果仅仅为了处理掉换行符 \n
,可以使用 getchar()
读取,将换行符“吃掉”。
举个栗子:
观察下列代码:
#include
int main(){
int n; //声明 int 类型变量 n
scanf("%d", &n); //读入 int 类型变量 n
printf("%d\n", n); //输出 int 类型变量 n 并且换行
char c = getchar(); //读入一个字符,并存储在 char 类型变量 c 中
printf("%c", c); //输出 char 类型变量 c
printf("14\n"); //输出 14 并且换行
return 0;
}
假设运行并且在控制台输入如下内容:
114
5
理论上,我期望得到输出:
114
514
但实际上,控制台哼哼哼啊啊啊输出了如下内容:
114
14
甚至控制台根本就没有接收你后续输入的 5
这个字符。
在该例子中,scanf("%d", &n)
会读取输入流中的数字 114
,并将其存储在变量 n
中。但是,由于输入缓冲区中还有一个换行符 \n
,getchar()
函数会读取这个换行符,并存储在变量 c
中,导致产生了这样的结果。在缓冲区中的数据没有被自动清空,这就是为什么控制台根本没有鸟你后续输入的东西,并输出了不符合预期的内容。
那么继续观察如下代码:
#include
int main(){
int n; //声明 int 类型变量 n
scanf("%d", &n); //读入 int 类型变量 n
printf("%d\n", n); //输出 int 类型变量 n 并且换行
getchar(); //用 getchar() 吃掉缓冲区中的 '\n'
char c = getchar(); //读入一个字符,并存储在 char 类型变量 c 中
printf("%c", c); //输出 char 类型变量 c
printf("14\n"); //输出 14 并且换行
return 0;
}
重新编译运行并在控制台输入如下内容:
114
5
可以发现控制台哼哼哼啊啊啊输出了:
114
514
在该例子中,为了避免上述缓冲区没有清空的情况,我们在读取完数据后手动清空输入缓冲区,利用 getchar()
读取了缓冲区里的换行符 \n
,使得后续的字符 5
被成功读入,最终输出了符合预期的内容。
cin
和 cout
是 C++
的输入输出流,可以使用它们来实现控制台的输入输出操作。一般地,使用 cin
和 cout
时可以通过引入 using namespace std;
简化代码,但也可以不引入命名空间,使用完整限定名 std::cin
和 std::cout
。
由于 cin
和 cout
的输入输出会自动匹配对应数据类型,所以针对这两者的格式化输入输出并非此处讨论的重点,而在此处,我们需要提及其关于**同步流(synchronized stream)**的概念:
虽然同步流可以确保输入输出的正确性,但是在一些场景下会影响程序的效率,特别是在大量数据输入输出的情况下。
这就是为什么,即使 C++
宁愿舍弃 scanf()
和 printf()
的高性能,也要得到输入输出流同步所带来的安全性和正确性,这也使得 C++
更适合面向对象开发。
注意:
scanf()
和 printf()
也存在同步流机制,但其缓冲区的实现更为底层,效率更高。cin
和 cout
的类型检查机制以及其他各种操作也是影响其性能的因素之一。把这两个放一起存粹是因为他们长得很像,但是两者天差地别:
getchar()
函数从标准输入(stdin)中读取一个字符,返回该字符的 ASCII
码值。
通常用于读取单个字符或者字符数组,可以实现简单的输入操作。
使用时需要注意的是,由于输入的字符是直接通过键盘输入的,因此需要按下回车键才能将输入的字符送入缓冲区,此时getchar()
才能够读取到输入的内容。
getline()
函数从输入流中读取一行文本,并将其存储到一个字符串对象中,可以读取包含空格在内的一整行输入。
使用时需要注意的是,如果使用默认的分隔符 \n
,getline()
会将换行符读取到缓冲区,如果下一次使用 getline()
读取输入,就会导致缓冲区中的换行符被读取,而不是期望的输入。此时可以通过调用cin.ignore()
来清除缓冲区中的字符,或者指定其他分隔符。
关于 getchar()
缓冲区的问题已经讲过,下面举个 getline()
的栗子:
观察下列代码:
#include
#include
using namespace std;
int main() {
string s;
getline(cin, s); //读入 string 类型 s
cout << "First: " << s << endl; //输出 s
getline(cin, s); //在此读入
cout << "Second: " << s << endl; //再次输出 s
return 0;
}
假设运行并且在控制台输入如下内容:
114
514
理论上,我期望得到输出:
First: 114
Second: 514
但实际上,控制台哼哼哼啊啊啊输出了如下内容:
First: 114
Second: 514
你会惊讶地发现符合期望,然后你想:“诶这不是没毛病垃圾 L y s Lys Lys 玩我呢?”
你先别急,让我先急。
getline()
其参数实际上有三个,第三个参数为分隔符参数,即 getline()
会以该参数分割处理数据,默认缺省该参数的情况下,getline()
会以 \n
为分隔符,即默认我们使用的是 getline(cin, s, '\n');
。
那么在该例子中,输入 114
后按下回车键,该回车键被视为一个分隔符并从输入流中删除,此时 \n
仍然留在缓冲区中 。然后第二个 getline()
调用会读取缓冲区中剩余的字符,即 "\n514"
,将其中的 \n
删除并存储 514
。因此输出符合预期。
我们重新指定一下 getline()
的分隔符,修改得到如下代码:
#include
#include
using namespace std;
int main() {
string s;
getline(cin, s, ','); //读入 string 类型 s,并以 ',' 为分隔符
cout << "First: " << s << endl; //输出 s
getline(cin, s, ','); //在此读入
cout << "Second: " << s << endl; //再次输出 s,并以 ',' 为分隔符
return 0;
}
假设运行并且在控制台输入如下内容:
114,
514,
理论上,我期望得到输出:
First: 114
Second: 514
但实际上,控制台哼哼哼啊啊啊输出了如下内容:
First: 114
Second:
514
你会惊讶地发现这次不符合期望了,然后你想:“诶这不废话吗垃圾 L y s Lys Lys 玩我呢?”
你急了,但是你先别急。
在该例子中,输入 114,
后按下回车键,','
则被视为了一个分隔符并从输入流中删除,但后续输入的 \n
保留在了缓冲区中 。然后第二个 getline()
调用会读取缓冲区中剩余的字符,即 "\n514,"
,将其中的 ','
删除并存储 \n514
。因此输出了不符合预期的内容。
为了避免这种结果,我们同样需要手动清空缓存区,可以使用 getchar()
“吃掉”缓冲区中的 \n
,但更建议使用如下方法:
#include
#include
using namespace std;
int main() {
string s;
getline(cin, s, ','); //读入 string 类型 s
cout << "First: " << s << endl; //输出 s
// 使用 cin.ignore() 忽略掉输入缓冲区中的换行符
// 也可以使用 cin.get() 读取缓冲区中的换行符
cin.ignore();
// cin.get();
getline(cin, s, ','); //在此读入
cout << "Second: " << s << endl; //再次输出 s
return 0;
}
最终得到了符合预期的结果。
First: 114
Second: 514
总体而言,getchar()
适用于读取单个字符或者字符数组,而getline()
适用于读取一整行文本,两者使用时需要注意不同的输入方式和缓冲区处理。
stringstream
是 C++
标准库提供的一种数据流对象,用于在内存中对字符串进行输入输出操作。cin
和 cout
一样进行输入输出,并且具有和输入输出流相似的接口和方法,例如 <<
和 >>
操作符。C++
中,stringstream
也是类型安全的。stringstream
和 cin
、cout
等输入输出流都有类似的接口和方法,可以进行输入输出操作,但它们的作用域不同。cin
、cout
等输入输出流通常用于标准输入输出流,而 stringstream
通常用于字符串的处理。
通常我们可以使用 stringstream
对字符串进行分割、转换、拼接等操作,然后再使用 cin
或 cout
输出到标准输入输出流中:
getline()
函数从标准输入读取一行字符串;stringstream
将其转换为数值类型,最后再使用 cout
输出到标准输出流中。举个栗子:
观察如下代码:
#include
#include
#include
using namespace std;
int main() {
stringstream s;
string name = "Lys";
int age = 13;
double height = 1.86;
string status = "is a dog";
s << "Name: " << name << ", Age: " << age << ", Height: " << height << ", Status: " << status;
string str = s.str();
cout << str << endl;
return 0;
}
在这个示例中,我们首先创建了一个 stringstream
对象 s
,然后使用<<
运算符将字符串、整数和浮点数和一个字符串插入到 s
中,最后使用 str()
方法将所有插入的数据转换为一个字符串,并将其打印到标准输出中。
再比如,观察如下代码:
#include
#include
#include
using namespace std;
int main() {
string s;
getline(cin, s);
stringstream ss(s);
string str;
while(ss >> str){
cout << str << endl;
}
return 0;
}
编译运行并且在控制台输入如下内容:
Lys is a dog.
然后得到如下输出:
Lys
is
a
dog.
在这个示例中,我们首先创建了一个 string
类型的 s
,并用 getline(cin, s)
读入字符串,然后将字符串 s
转换为了stringstream
对象 ss
,再通过该对象过滤空格后不断赋值给 str
,最终将其打印到标准输出中。
前面提到了,由于 cin
和 cout
存在同步流机制和类型检查机制等影响其性能的功能。因此,在面对需要大量输入输出的场景时, scanf()
和 printf()
输入输出的效率显著优于 cin
和 cout
,但我们仍然可以通过设置 cin
和 cout
的同步流标志位来关闭同步流,从而提高程序的效率,甚至优于 scanf()
和 printf()
。
在 C++
程序中,添加如下语句以优化输入输出流速度和交互性:
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
ios::sync_with_stdio(false)
:关闭 C++
的标准输入输出流与 C
语言输入输出流的同步,从而加快输入输出的速度。cin.tie(nullptr)
:解除 cin
和 cout
的绑定,从而避免在读取输入时,每次输出缓存区都被刷新的问题。cout.tie(nullptr)
:cout
默认绑定的是 nullptr
,实际上这句话并没有必要添加。相关讨论参见 Ok, lets talk about cout.tie once and forever。需要注意的是,关闭输入输出流同步后,不能再在 C++
代码中使用 C
语言的输入输出函数了,否则可能会导致输出不完整或者输出顺序错误等问题。此外,解除绑定后,需要手动刷新输出缓存区,否则输出的内容可能不完整或者不及时。因此,在使用这些语句时,需要谨慎地考虑使用场景和执行顺序,避免出现不可预料的错误。
下列语句:
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
同样可以达到提高输入输出速度的目的。这种写法比使用 nullptr
更加通用,因为在某些旧的 C++
编译器中可能不支持 nullptr
。
总的来说,这两种写法的区别并不大,只是在解除绑定时所使用的空指针常量不同,但都可以实现提高输入输出速度的效果。
Original Link
描述:
众所周知,在网络安全中分为明文和密文,凯撒加密是将一篇明文中所有的英文字母都向后移动三位( Z Z Z 的下一位是 A A A),比如 a a a 向后移动三位就变成了 d d d, A A A 向后移动三位就变成了 D D D, Z Z Z 向后移动三位就变成了 C C C,但是泛凯撒加密可没有这么简单,它是将明文中的每个字母向后移动k位得到密文,并且在密文的结尾会附加一个 ?
,本题想让你通过得到密文反解出原本的明文。
输入格式:
第一行,输入一个正整数 k k k 表示字母向后移动的位数。
接下来输入若干行字符串,表示密文,数据输入保证仅密文的最后一个字符是 ?
。
输出格式:
输出原本的明文。
数据范围:
0 ≤ k ≤ 100 0 \le k \le 100 0≤k≤100。
样例输入:
2
*eee/peee++?
样例输出:
*ccc/nccc++
你已经是一个成熟的 ACMer \text{ ACMer } ACMer 了,要学会自己分析并解决问题。实在解决不了就解决自己吧。
#include
#include
using namespace std;
void solve(){
int k; cin >> k;
string s;
k %= 26;
getchar(); //清空缓冲区中的 '\n'
while(getline(cin, s)){
for(int i = 0; i < s.size(); i ++){
char st = s[i];
if(st >= 'a' && st <= 'z') cout << char(st - k < 'a' ? st - k + 26 : st - k);
else if(st >= 'A' && st <= 'Z') cout << char(st - k < 'A' ? st - k + 26 : st - k);
else if(st == '?') break;
else cout << st;
}
cout << endl;
}
}
int main(){
solve();
return 0;
}