C++ 中cin的输入原理及字符串(string、char[])输入问题

目录

一、cin 是 C++ 封装的一个类对象

二、对字符数组的输入方法(char str[N]):

1、cin 使用空白字符作为一次输入的结尾,并忽略该空字符。

2、使用 getline() / get() 函数完成面向行的输入

3、数字与字符串的混合输入问题

三、对 string 对象的输入方法


C++中常用的输入方式为 cin 。通过 cin 来获取字符串是常用的操作,但是字符串中往往可能会包含空格等空白字符,而在一般的输入场景中,由于不能通过键盘输入字符 '\0'(在存储中,字符串都以空字符 '\0' 结尾),因此 cin 使用空白字符(空格、制表符和换行符)来确定字符串的结束位置,因此想要输入空格必须得修改输入的方式。

一、cin 是 C++ 封装的一个类对象

C或C++并没有将输入输出建立在语言中,也就是说像 int、if、for ...等等这些都是C++的关键字,但其中的I/O语句 cin / cout 并不在C++的关键字列表中,也就不是 C++ 的关键字了。

实际上cin / cout 是C++在头文件<iostream>中封装的类对象,cin 是 istream类对象,cout是ostream类对象,其输入输出操作符 cin>> 和 cout<< 也是通过重载机制赋予的输入输出功能。

使用 cin 执行输入操作是有一个输入缓冲区的,如果是从键盘获取的输入数据,则在按下 Enter 键(即回车键)之后输入的数据(包括回车键)才会被送到输入缓冲区中执行实际的 I/O 操作。

如:我们使用 cin 输入 1[空格]2[空格]3[空格]4[空格]5[回车]。则实际上送入缓冲区的是 1 2 3 4 5'\n'。注意只有最后一个回车按下去后输入数据才会被送到输入缓冲区。

输入:1[空格]2[空格]3[空格]4[空格]5[回车]
则实际上送入缓冲区的是: 1 2 3 4 5'\n' ,共10个字符。

为什么会设置输入/输出缓冲区?我从《C++ Primer Plus 第六版》的有关介绍中总结了两点原因:

1、如果每次读入一个字符都直接处理,由于从磁盘文件中读取一个字符需要大量硬件活动,在读取量较大时,这是很耗时间的(硬盘的读写速度远小于内存和CPU)。缓冲的方式是每次从硬盘中读取大量信息放在内存中,然后每次从缓冲区读取字节仅从操作,由于内存的读写速度远高于硬盘,因此这样可以大大提升读写效率。

2、由于C++程序通常在用户键入回车后刷新缓冲区,因此对键盘输入进行缓冲可以再将输入传给程序之前返回并更正。也就是说在你按下回车键前,你输入的数据都是可以删除并重新输入(修改)的。

这里只介绍了点 cin 的基础原理,用于理解后面的内容,详细内容就不展开细说了。

二、对字符数组的输入方法(char str[N]):

1、cin 使用空白字符作为一次输入的结尾,并忽略该空字符。

int main(void)
{
    char line[10];
    cout << "input what you want:";
    cin >> line;  // 可以尝试输入 "123 456"
    cout << "what you input is:" << line << endl;  // 只能输出 "123"
    return 0;
}
/* 测试用例
input what you want:123 456
what you input is:123
*/

也就是说,这种情况下一次只能完成一个单词的输入,其后的单词都会被忽略掉。

2、使用 getline() / get() 函数完成面向行的输入

getline() / get() 函数都可以接收一行字符串输入,并以回车作为输入的结尾。区别就是 getline 会在输入结束后丢弃最后的换行符,而 get 则会在输入队列中保留最后的换行符。

1)cin.getline()

可以通过 cin.getline(line, nums, (optional)delim实现调用:从输入队列中获取 nums-1 个字符到 line 中,因为字符串的最后一个字符是 '\0'。如果输入的长度超出 nums-1 个,则 getline 和 get 将把余下的字符留在输入队列中,另外 getline 还会设置失效位,并关闭后面的输入(即后面的所有输入将不可用),此时可以用 cin.clear() 清除标准位就可以恢复后面的输入。

这种句点表示法表明,函数 getline() 是 istream 类的一个方法。另外需要注意的是 cin 是一个 istream 对象。cin.getline() 的第一个参数是目标数组,第二个参数是数组长度,这样来避免超越数组的边界。后面介绍通过输入对 string 赋值的时候调用方式会有区别。

cin.getline(line, nums)

int main(void)
{
    char line1[10];
    char line2[10];
    char line3[10];
    cout << "input what you want:" << endl;
    cin.getline(line1, 10);  // 可以尝试输入 "123 456 789" ('\n') 含有11个字符
    // cin.clear();
    cin.getline(line2, 10);  // 不可用,直接退出
    cin.getline(line3, 10);  // 不可用,直接退出
    cout << "what you input is:" << endl;
    cout << "line1:" << line1 << endl;  // 输出 "123 456 7"
    cout << "line2:" << line2 << endl;  // 输出 空
    cout << "line3:" << line3 << endl;  // 输出 空
    return 0;
}
/*
input what you want:
123 456 789
what you input is:
line1:123 456 7
line2:
line3:
*/


// 使用 clear
int main(void)
{
    char line1[10];
    char line2[10];
    char line3[10];
    cout << "input what you want:" << endl;
    cin.getline(line1, 10);  // 可以尝试输入 "123 456 789" ('\n') 含有11个字符
    cin.clear();   // 清除标志位
    cin.getline(line2, 10);  // 可用,输出 "89",这是上一步输入队列中的字符
    cin.getline(line3, 10);  // 可用,你输入啥输出啥
    cout << "what you input is:" << endl;
    cout << "line1:" << line1 << endl;  // 输出 "123 456 7"
    cout << "line2:" << line2 << endl;  // 输出 空
    cout << "line3:" << line3 << endl;  // 输出 空
    return 0;
}
/*
input what you want:
123 456 789
123
what you input is:
line1:123 456 7
line2:89
line3:123
*/

cin.getline 还有第三个参数 delim(全称应该是 delimiter,中文意思:定界符/分隔符),作用是指定用作分界符的字符(没有这个参数的版本将以换行符为分界符)。

遇到分界符后,当前字符串的输入将停止,即使还未读取最大数目的字符。因此在默认情况下,这种分界符的方式或者到达输入的指定数目都将停止读取输入。和默认情况一样 get() 将分界符留在输入队列中,而 getline() 不保留。

cin.getline(line, nums, delim),也就是以 delim 指定的字符作为一次字符串输入的结束,其后的在 nums-1 范围内的输入会保留在输入队列中作为下一个字符串的开始。

int main(void)
{
    char line1[10];
    char line2[10];
    cout << "input what you want:" << endl;
    cin.getline(line1, 10, 's');  // 可以尝试输入 "123s456" ('\n')
    cin.getline(line2, 10);  // 
    cout << "what you input is:" << endl;
    cout << "line1:" << line1 << endl;  // 输出 "123"
    cout << "line2:" << line2 << endl;  // 输出 "456"
    return 0;
}
/* 测试用例
input what you want:
123s456
what you input is:
line1:123
line2:456
*/

getline 函数每次读取一行,它通过换行符来确定行尾,但不保存换行符。相反,在存储字符串时他用空字符 '\0' 来替换换行符。

2)cin.get()

调用方式类似 getline,即cin.get(line, nums, (optional)delim)

前面说过,get 函数会在输入队列中保留最后的换行符。注意是输入队列,也就是在一个 get()

函数完成输入后,会在输入缓存中保留组后的换行符,如果不做处理在下次运行 get() 时会出问题。

int main(void)
{
    char line1[10];
    char line2[10];
    cout << "input what you want:" << endl;
    cin.get(line1, 10);  // 可以尝试输入 "123" ('\n'), 此时回车符将会留在输入队列中
    cin.get(line2, 10);  // 可以尝试输入 "456",可以发现 line1 输入完后,这步直接被“跳过了”
    cout << "line1:" << line1 << endl;  // 输出 "123"
    cout << "line2:" << line2 << endl;  // 输出空字符,由于 cin.get(line2, 10); 读入的是上一个字符输入结束时留输入队列中的回车符
    return 0;
}

/* 测试用例
input what you want:
123
what you input is:
line1:123
line2:
*/

由于第一次调用后换行符留在了输入队列中,因此第二次看到第一个字符便是换行符,因此就直接结束了。如果不借助与任何帮助,get() 将不能跨过该换行符(在多个get的情况下)。可以使用下面这种方式避免这种情况:

cin.get(line2, 10).get();
\\ 或者
cin.get(line2, 10);
get();

3、数字与字符串的混合输入问题

使用 cin 输入一个整数后,最后的回车符会留在输入队列中,类似于get,这会对后续的 getline get 输入产生影响,因为 getline / get 看到换行符后会认为是一个空行,然后会把空字符赋给后面的字符串,如下:

int main(void)
{
    int a, b;  // 输入 123 ('\n') 456 ('\n')
    char s[5];
    cin >> a;
    cin >> b;
    cin.getline(s, 5); // 得到换行符,直接赋空值
    cout << a << " " << b << " " << s << endl;
}
/* 测试用例
123
456
123 456 
*/

此时可以使用在后面添加 cin.get() 或者 (cin >> b).get() 的方式来解决这个问题:

​
int main(void)
{
    int a, b;  // 输入 123 ('\n') 456 ('\n')
    char s[7];
    cin >> a;
    // cin >> b;
    // cin.get();    
    (cin >> b).get();
    cin.getline(s, 7); // 输入 "string" ('\n')
    cout << a << " " << b << " " << s << endl;
}
/* 测试用例
123
456
string
123 456 string
*/

三、对 string 对象的输入方法

使用 cin<<str 的语法来将输入存储到 string 对象中与字符数组的操作是一样的。不同的是当每次读取一行而不是一个单词时使用的语法是不同的。

int main(void)
{
    string str;
    cout << "input what you want:" << endl;
    getline(cin, str);
    cout << "what you input is:" << endl;
    cout << str << endl;
}
/* 测试用例
input what you want:
123 456 789
what you input is:
123 456 789
*/

注意这里的面向行的输入是 getline(cin, str)

这里没有使用句点表示法,这表明这个 getline 不是类方法。他将 cin 作为参数,指出到哪里去查找输入。另外也没有指出字符串长度的参数,因为 string 对象将根据字符串的长度自动调整自己的大小。

至于为什么字符数组和string对象的 getline 方法不一样呢,其实这是个历史问题。早期的 C++ 还没引入 string 类的时候就有 istream 类了,因此当时也就没有针对 string 对象的输入方式了,所以最后才会演变出这两种方式。

猜你喜欢

转载自blog.csdn.net/Flag_ing/article/details/125209588