天天看点

Windows 上 cin>> 与 cin.getline() 混用的问题

经常看到类似下例的问题:

int main()
{
	char buf1[5]={0};
	char buf2[5]={0};
	
	cin >>buf1;
	cin.getline(buf2,5); // 这里似乎不等待输入

	return 0;
}
           

而解决办法如下:

int main()
{
	char buf1[5]={0};
	char buf2[5]={0};
	
	cin >>buf1;
	cin.ignore(); // 或者 cin.sync(); 之类的
	cin.getline(buf2,5);

	return 0;
}
           

这是为什么呢?

因为,首先, Windows 上敲一下回车键,实质上是输入两个字符:回车符,紧跟着换行符。这两个字符的 ASCII 码分别为 0x0D 和 0x0A,一般来说,其C++转义表示分别为 '\r' 和 '\n'。然后,cin>> 默认是以一个或多个接连的白空格为间隔,cin.getline 默认则以单个换行符(0x0A)为间隔。回车符和换行符都属于白空格。

这里不讨论为什么 Windows 上敲回车键会输入这两个字符,以及这两个字符本来应该对应什么动作(如果存在“本来应该”这么一说的话)。

为了展示这个看不见的回车键敲击,用 istringstream 举个例子:

void disp(char *buf,int n)
{
	for(int i=0;i<n;++i)
		printf("0x%02X ",buf[i]);
	printf("\n");
}

int main()
{
	disp("\r\ns1\r\ns2",9);
	// 这相当于在控制台敲回车键,然后敲入s1,然后敲回车键,然后敲入s2
	// 0x0D 0x0A 0x73 0x31 0x0D 0x0A 0x73 0x32 0x00
	printf("\n");

	{
		istringstream iss("\r\ns1\r\ns2");
		char buf1[5]={0};
		char buf2[5]={0};
		
		iss >>buf1;
		iss >>buf2;
		
		disp(buf1,5); // 0x73 0x31 0x00 0x00 0x00
		disp(buf2,5); // 0x73 0x32 0x00 0x00 0x00
	}

	printf("\n");

	{
		istringstream iss("\r\ns1\r\ns2");
		char buf1[5]={0};
		char buf2[5]={0};
		
		iss.getline(buf1,5); 
		iss.getline(buf2,5);

		disp(buf1,5); // 0x0D 0x00 0x00 0x00 0x00
		disp(buf2,5); // 0x73 0x31 0x0D 0x00 0x00
	}

	return 0;
}
           

用 cin 的道理是一样的。区别在于相关的标准库函数会把 0x0D 转换为 0x0A,这就相当于回车键最终敲入缓冲区的是接连的两个换行符。由于这不影响讨论,下文还是用 0x0D 指代。

cin>> 每读到其所期待的东西后碰到 0x0D,就“断”一下,紧跟着的 0x0A 还在缓冲里。此时,如果改用 cin.getline() ,0x0A 立即被读入,而该间隔符前面没有字符,于是就有了 getline 已完成却没有 get 到 line 的错觉。而如果没有改用 cin.getline(),继续用cin>>,那么 cin>> 碰到紧跟着的 0x0A 时,这是在还没读到其所期待的东西就碰到了白空格,它的反应就是跳过该字符(这正是 cin.ignore(); 要做的),接着继续去读其所期待的东西。

继续阅读