在算竞中经常需要读取数据,这篇文章对Cpp的输入方式总结。

输入方式

1.cin

(1)cin>>等价于cin.operator>>(),即调用成员函数operator>>()进行读取数据。

(2)当cin>>从缓冲区中读取数据时,若缓冲区中第一个字符是空格、tab或换行这些分隔符时,cin>>会将其忽略并清除,继续读取下一个字符,若缓冲区为空,则继续等待。但是如果读取成功,字符后面的分隔符是残留在缓冲区的,cin>>不做处理。如果后续接getline()要记得处理换行符!

补充:

  • Tab键:通常在键盘上标记为Tab,按下后插入一个制表符(Tab字符)。制表符的长度通常为多个空格,可以在不同的编辑器或环境中设置长度,常见的设置是4个或8个空格。【在计算机中用于对齐文本的特殊字符】

  • 空格键:按下后插入一个空格字符。每次按下空格键插入一个空格,长度固定。

(3)不想略过空白字符,那就使用 noskipws 流控制。比如:cin>>noskipws>>input;

示例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <string>
using namespace std;

std::istream& operator>>(std::istream& is, std::string& str) {
str.clear(); // 清空字符串
char ch;
is >> std::ws; // 跳过前导的空白字符
while (is.get(ch) && !isspace(ch)) {
str += ch; // 将字符添加到字符串
}
return is; // 返回输入流对象
}

int main() {
string str;
cin >> str;
cout << str;
return 0;
}

补充:

C 库函数 int isspace(int c) 检查所传的字符是否是空白字符。

标准的空白字符包括:

1
2
3
4
5
6
' '     (0x20)    space (SPC) 空格符
'\t' (0x09) horizontal tab (TAB) 水平制表符
'\n' (0x0a) newline (LF) 换行符
'\v' (0x0b) vertical tab (VT) 垂直制表符
'\f' (0x0c) feed (FF) 换页符
'\r' (0x0d) carriage return (CR) 回车符

示例2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<bits/stdc++.h>

using namespace std;

signed main(){
char n,m,k,p,q;
cin>>std::noskipws>>n>>m>>k>>p>>q;
cout<<n<<endl;
cout<<m<<endl;
cout<<k<<endl;
cout<<p<<endl;
cout<<q<<endl;
return 0;
}

输入

1
q w e a z

输出

1
2
3
4
5
6
7
q w e a z
q

w

e

2.cin.get()

单用cin.get()只能读取其中一个字符,不能存储为string全部的字符。

若要存储全部则必须传入一个数组以及长度。

1
2
3
4
5
6
7
8
9
10
#include <iostream>
using namespace std;
main ()
{
   char a[n];
   cin.get(a,n); //实际包括空格只能存n-1个字符,最后一个字符
'\n'以回车作为结束符。
   cout<<a<<endl;
   system("pause");
}

3.cin.getline()

接受一个字符串,可以接收空格并输出,吃掉末尾的换行符的。

1
2
3
4
5
6
7
8
#include <iostream>
using namespace std;
int main ()
{
char m[20];
cin.getline(m,5);
cout<<m<<endl;
}

在调用 cin.getline(m, 5) 时,函数从输入流中读取最多 5 - 1 = 4 个字符,并在读取到的字符后添加一个终止空字符 \0

延伸:

cin.getline()实际上有三个参数,cin.getline(接受字符串的看中间的m,接受个数5,结束字符)

当第三个参数省略时,系统默认为’\0’ 。

如果将例子中cin.getline()改为cin.getline(m,5,‘a’);当输入jlkjkljkl时输出jklj,输入jkaljkljkl时,输出jk

当用在多维数组中的时候,也可以用cin.getline(m[i],20)之类的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <string>
using namespace std;
int main()
{
char m[3][20];
for (int i = 0; i < 3; i++)
{
cout << "\n请输入第" << i + 1 << "个字符串:" << endl;
cin.getline(m[i], 20);
}
cout << endl;
for (int j = 0; j < 3; j++)
cout << "输出m[" << j << "]的值:" << m[j] << endl;
}

string输入

4.getline()

接受一个字符串,可以接收空格并输出,需包含“#include”

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str;
getline(cin, str); // 和cin.getline()一样可以按某个字符结尾
cout << str << endl;
}

5.getchar()

函数原型:int getchar(void);

返回类型为int,参数为void

头 文 件:#include<stdio.h>

返 回 值:

  1. getchar返回的是字符的ASCII码值(整数)
  2. getchar在读取结束或者失败的时候,会返回EOF。(EOF意思是end of file,本质上是-1)

读取方式:只能输入字符型,输入时遇到回车键才从缓冲区依次提取字符。
结束输入的方式:以Enter结束输入(空格不结束),接受空格符。
舍弃回车符的方法:以Enter结束输入时,接受空格,会舍弃最后的回车符。

getchar函数执行过程详解

1
2
3
4
5
6
7
#include<stdio.h>
int main()
{
char ch = getchar();//输入字符
putchar(ch);
return 0;
}

它的简单意思就是从键盘读入一个字符,然后输出到屏幕。理所当然,我们输入A,输出就是A,输入B,输出就是B。

那么我们如果输出的是ABC呢?答案是A。

解释如下:当我们从键盘输入字符‘A’,‘B’, ‘C’,并按下回车后,我们的输入被放入了输入缓冲区,这个时候getchar()会从缓冲区中读取我们刚才的输入,一次只读一个字符,所以字符A就被拿出来了,赋值给了ch,然后putchar()又将ch放在了标准输出,也就是这里的屏幕,所以我们看见了最终的显示结果A。同时字符‘A’也被缓冲区释放了,而字符‘B’,'C’仍然被留在了缓冲区。而这样是很不安全的,有可能下次使用的时候,我们的缓冲区会读到一些垃圾,但是当程序结束的时候,它会自动刷新。

解释:现在,考虑这样一个场景:你的程序在接收了用户的一些输入之后,由于某种原因突然终止了,没有正常退出。这种情况下,这些未处理的输入可能会留在输入缓冲区中,没有被清理掉。当你的程序下次运行并再次试图从输入缓冲区获取输入时,它可能会先读取到这些残留的旧数据,而不是新的用户输入。这就是我们所说的"可能会读到一些垃圾"的含义。

6.gets()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
gets(m)用于string类的,需包含#include<string>。可以接收空格,遇回车结束。

#include <iostream>
#include <cstring>//必须是cstring,否则strlen()方法不能用
#include <stdio.h>
using namespace std;

int main()
{
char a[40000];
gets(a);//必须是char型数组,不能是其他类型数组
int len=strlen(a);//得到char型数组的实际长度
//执行其余操作
return 0;
}

可用于多维数组。

可用于多维数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  #include<iostream>
  #include<string>
  using namespace std;
  main ()
  {
  char m[3][20];
for (int i = 0; i < 3; i++)
{
cout << "\n请输入第" << i + 1 << "个字符串:" << endl;
gets_s(m[i]);
}
cout << endl;
for (int j = 0; j < 3; j++) {
int len = strlen(m[j]);//得到char型数组的实际长度
for (int k = 0; k < len; k++)
cout << "输出m[" << j << k << "]的值:" << m[j][k] << endl;
}
  }

算竞输入补充

我利用代码生成了三千万个【1,100】以内的随机数,并且将分别用cinscanf输入到一个数组中。

1
2
3
4
5
6
7
8
const int MAXNUM  = 30000000;
int RandomNumber;
srand((unsigned)time(NULL));
for (int i = 0; i < MAXNUM; i++)
{
RandomNumber = rand() % 100 + 1;
}

开始测试

1
2
3
4
5
6
7
8
int start = clock();
for (int i = 0; i < MAXNUM; i++)
{
//scanf("%d ", &number[i]);
cin >> number[i];
}
printf("scanf用时:%.3lf\n", double(clock() - start) / CLOCKS_PER_SEC);

1
2
3
4
5
6
for(int i = 0;i < 100000; i++)
{
printf("%d\n", i);
//cout << i << endl;
}

我在Windows系统GCC编译器下得出的用时数据(单位:秒),且已验证过不存在偶然性:

scanf cin
6.836 11.303

同时,我用printf和cout分别输出十万个正整数

printf cout
6.172 11.509

于是可以得出结论:cin&cout的确在效率上是低于scanf&printf的

而我们可以通过std::ios::sync_with_stdio(false);指令关闭同步(这里要注意:当关闭同步之后为了确保准确,不要使用 printf&scanf了),除此之外我们还可以通过std::cin.tie(nullptr);获取cin更优的性能(解除std :: cin和std :: cout之间的绑定,来降低IO的负担使效率提升)。那么在对iostream优化之后的 printf&scanf 和 cin&cout效率差距又有多大呢?我通过上述的实验得出以下数据:

1
2
3
4
5
6
7
8
9
#include <iostream>

using namespace std;

signed main(){
std::ios::sync_with_stdio(false);
std::cout << "Hello, ";
printf("world!");
}

输出

1
world!Hello,

Note:std::ios::sync_with_stdio(false); 这条语句的作用是关闭C++的iostream和C的stdio之间的同步。

在默认情况下(同步开启),程序的输出会是 “Hello, world!”。因为cout和printf是同步的,所以他们的输出顺序是按照我们写的顺序来的。

但是,如果我们关闭同步,那么程序的输出就可能不是"Hello, world!"了。可能cout的输出先出现,也可能printf的输出先出现,这取决于哪个操作更快。所以,在关闭同步后,我们就不能再预期混合使用C++的iostream和C的stdio时的输出顺序了。

疑问:关闭C++的iostream和C的stdio之间的同步为什么能提高速度?

有趣的解答:

假设你正在举办一场派对,你邀请了两个DJ,一个是专门播放摇滚音乐的,另一个是专门播放爵士乐的。你希望他们能够交替播放音乐,这样你的派对就可以同时享受到摇滚和爵士的风格。

但是,为了确保他们能够顺利地交替播放,你需要找一个协调员来时刻注意两位DJ的状态,告诉他们什么时候该停,什么时候该播。这个协调员就相当于同步机制,他确保了摇滚DJ和爵士DJ(相当于iostream和stdio)能够和谐工作。

然而,这个协调员并不是免费的。他需要时间去观察和指挥,这就可能会延迟音乐的切换,也就是说,派对的整体节奏可能会因为等待协调员的指示而变慢。这就像为了维持iostream和stdio的同步,系统需要做额外的工作(比如刷新缓冲区),这会导致效率的降低。

现在假设你决定不再需要协调员,让两位DJ自己决定何时播放音乐。这样,你就节省了协调员的成本,派对的节奏也可能会更快。但是,你也无法再保证摇滚音乐和爵士乐能够完美交替,他们可能会同时播放,也可能会有短暂的静音。这就像当你关闭iostream和stdio的同步时,虽然提高了效率,但也无法保证他们的操作顺序。

总的来说,关闭iostream和stdio的同步可以提高效率,但同时也会牺牲他们的协调性。在某些情况下,这可能是值得的,但在其他情况下,可能就需要考虑其他解决方案了。

scanf cin(优化后)
6.836 2.695
printf cout (优化后)
6.172 6.178

可以看见在关闭同步之后cin的效率已经是高于scanf了,并且cout的速度与printf的速度也相差无几,那我们还能不能继续优化呢?

我们注意到通常在用cout输出的时候,更习惯去使用endl,它既可以达到换行的需求又可以刷新缓冲区,然而在如此高度的循环下它一直对缓冲区的操作却降低了效率,所以将endl换成’\n’cout的效率将会起飞

(顺带一提,我在开启同步的条件发现endl或是\n 对cout效率的影响并不大,不知道是不是我的问题,希望各位指出)

printf cout(‘\n’)
6.172 1.106

如此一来我们再一次得出结论:在同步开启时,scanf&printf的效率要高于cin&cout;当同步关闭时,cin&cout的效率要高于scanf&printf。

最后,附上ACMer喜欢的快读模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/*快读快写*/
template <typename T>
inline T read()//这里加inline是为了解决一些频繁调用的函数大量消耗栈空间(栈内存)的问题
{
//自定义的类型T
register T sum = 0, f1 = 1;//f1是标志位
register int ch = getchar();
//如果输入的是负数
for(; !isdigit(ch); ch = getchar())
{
if(ch == '-')
{
f1 = -1;
}
}
for(; isdigit(ch); ch = getchar())
{
sum = sum * 10 + ch - '0';
}
return sum * f1;
}
//int a = read<int>();
template <typename T>
inline void write(T x)
{
static int stk[20];//模拟压栈
int top = 0;
do{
stk[top++] = x % 10;
x /= 10;
}while(x);
while(top)
{
putchar(stk[--top] + '0');
}
}
//write(a);

read cin
0.485 2.695
write cout
18.908 1.106