本文使用代码案例来自《C++ Primer Plus》(第六版)一书,并对书中原代码稍加改写以方便阅读。这篇文章相当于个人的学习笔记,会充满各种不成熟的想法,所以如果有错误,希望有大佬可以斧正。
一、输入字符串时存在的一些问题
使用cin输入字符串时,cin会把空白(即空格、制表符和换行符)当作字符串结束的位置,这就导致会出现一些问题,如以下代码演示:
#include <iostream>
using namespace std;
int main(){
char name[20]; //客人名字
char dessert[20]; //客人所点的甜品名
cout << "请输入您的姓名:\n";
cin >> name;
cout << "请输入您想点的甜品名:\n";
cin >> dessert;
cout << "我为您提供了一些" << dessert << "," << name << "先生" <<endl;
return 0;
}
以下是程序编译的结果。
很容易发现,cin把John和smith之间的空格作为停止输入的一个标志,所以cin把John给了name,然后又把smith给了dessert,所以发生了这样滑稽的事情。
还有一个问题,那就是name和dessert都是长度为20个元素的数组,如果客人或者甜品的名字比较鬼畜的话,可能就会导致数组越界的问题。
综上所述,必须寻求一种新的方法来输入并储存字符串,来保证程序的正常运行,同时提高程序的安全性。
二、getline()函数
由于上面所介绍的cin的特性,我们必须使用一种函数,这种函数可以直接读取一整行的输入,于是我们得到了istream类中的另外两种类成员函数:getline()和get()。由于本人尚未学C++中关于“类”的相关知识,所以暂时按下不表。
首先先说getline()函数。getline()函数与cin不同,它会直接读取整行,并以换行符为结尾。要使用这种方法,可以使用cin.getline(),该函数有两个参数,即用于储存字符串的数组名称和要读取的字符数,如cin.getline(name,20),这样就会把输入的字符中的前19位储存入name中,同时把一个\0(空字符)作为第二十位储存入name中。
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
char name[30];/*客人名字*/ char dessert[30]; //客人所点的甜品名
int main(){
cout << "请输入您的姓名:\n";
cin.getline(name,20);
cout << "请输入您想点的甜品名:\n";
cin.getline(dessert,20);
cout << "我为您提供了一些" << dessert << "," << name << "先生" <<endl;
system("pause");
return 0;
}
但是如果我们把name和dessert的长度改为2,然后再输入这么长的名字,会有什么效果呢?
现在让我们在中间插入两句cout语句,看看name和dessert在程序运行时究竟发生了什么变化
cout << "请输入您的姓名:\n";
cin.getline(name,20);
cout << name << endl;
cout << "请输入您想点的甜品名:\n";
cin.getline(dessert,20);
cout << name << '\t' << strlen(name) << '\t' << dessert << '\t' << strlen(dessert) << endl;
cout << "我为您提供了一些" << dessert << "," << name << "先生" <<endl;
可以发现,第二句cin.getline()居然把ZNTM的前两个字符一并储存到dessert里面了,这是否与一开始规定的name长度为2有关?所以我把2改为了3试了试,结果果然如此。
这种情况出现的原因在下面会说。
本人在实际使用getline()的过程中发现一个很奇怪的现象,那就是如果储存的数据类型为char时,可以用cin.getline(变量,长度);而类型为string时,只能使用getline(cin,变量)。同样由于水平所限,暂时不清楚具体原理,只好先作为经验记录下来。
三、get()函数
get()和getline()一样,有许多变体类型,其中一种和cin.getline()一样,即cin.get()。与带参数的cin.getline()不同的是,带参数的cin.get()不会丢掉此行输入的换行符,所以如果连续调用两个get()的话,会导致换行符被作为第一个字符读取,从而使得get()误以为输入已经结束,结果就是什么都没有输入进去。
解决方法也很简单,可以使用get的另一种变体,即不带参量的cin.get()来读取下一个字符(即便是个换行符),所以可以这样写:
//第一种方式
cin.get(name,20);
cin.get();
cin.get(dessert,20);
//第二种方式
cin.get(name,20).get();
同理,可以使用cin.getline(name,20).getline(dessert,20)来实现同时读取两个变量。
get()相比起getline()有着更为细致的优势。比如将一行字符串填入数组时,如何才能知道停止读取是由于数组越界还是字符串已全部输入?只需要读取下一个字符,判断是否是换行符。如果是,那就说明已经输入完毕;如果不是,那就说明数组越界了。但是相比起来,getline()使用起来更简单一些。
四、可能存在的一些问题
1.如果读取空行时会怎么样?正常情况下,两个连续的get()或getline()语句,如果前者输入已经结束,那么后者会在前者结束的地方继续开始读取。但是如果读取到了空行,那么这两个函数会设置“失效位”,这玩意会阻断接下来的输入,但是可以使用cin.clear()来恢复输入。
2.如果输入的字符串长度超过数组长度会怎么样?上文中已经演示过这种情况,前两个字符会被储存在第二个数组中。这种情况背后的原理就是,如果输入的字符串长度超过数组长度的情况发生,getline()和get()会把余下的字符留在输入队列中,而getline()还会进一步设置一个失效位来阻断输入。
3.如何解释以下程序会在输入年份后闪退?
#include <iostream>
using namespace std;
int main(){
int year;
cout << "你房子是哪一年建的:" << endl;
cin >> year;
cout << "你房子地址在哪里:" << endl;
char address[80];
cin.getline(address,80);
cout << "建成年份: " << year << '\n' << "地址: " << address << endl;
system("pause");
return 0;
}
原因就是,cin读取年份后,将回车键生成的换行符留在了输入队列中,而cin.getline()看到换行符后,便以为输入已经结束,所以程序关闭。解决方法如上文所述,可以在cin后面加一个cin.get(),也可以直接写(cin >> year).get()。