C++ 中的 cin 如何处理输入的字符
修改日期: 2023-08-30
C++ 中的字符串
C++ 将字符串存储在 char 类型数组中, 没有专门的类型去保存字符串, 以 空字符 作为字符串的结束标志. 空字符在 ASCII 表中编号 0, 用 \0 表示
初始化字符串时, 可以用单引号逐个列出字符:
char str1[4] = {'a', 'b', 'c', '\0'}
这里字符串结束的标识 \0 需要自己手动指定. 也就在是说, 在一个长度为 4 的字符数组里, 最多只能存储 3 个字符, 最后一个要被用来保存字符串的结束标识.
如果我们没有存入字符串结束标识 \0 呢? 也可以通过编译, 但此时字符串的长度就不受控制了, 程序读取这个字符串时, 会在内存中一直往后面读, 直至遇到结束标识 \0 为止
char str1[4] = {'a', 'b', 'c', 'd'};
cout << "str1: " << str1 << " strlen: " << strlen(str1) << endl;
# output
str1: abcd���� strlen: 10
可以看到, 除了前 4 个字符 abcd 外, 还打印了一些其他的东西, 而且字符串的长度也不是 4, 变成了 10
除了用单引号, 还可以用双引号更方便地初始化字符串:
char str2[5] = "abcd";
双引号初始化字符串不需要在末尾显式的添加空字符, 正常拼写即可(空字符正常输入也是打不出来的, 要用转义符号). 编译器会自动在末尾添加作为结束的空字符, 也就是说要保存长度为 4 的字符串, 需要使用长度为 5 的字符数组. 如果要用长度为 4 的字符数组去保存长度为 4 的字符串, 这实际上超过了字符数组所能容纳的上限, 编译时会触发错误
重点: 字符数组使用空字符 \0 作为字符串结束的标识
std::cin >> 输入字符串
std::cin >> 通过 "空白(whitespace)" 字符判断输入是否结束, 这里的 "空白" 字符指的是空格(spaces), 制表(tabs), 换行符(newlines), 如果以 "空白" 字符开头, 则会忽略这些 "空白" 字符
cout << "Enter the name: ";
cin >> name;
cout << "Enter the class id: ";
cin >> classid;
cout << "name: " << name << "\tstrlen: " << strlen(name) << endl;
cout << "classid: " << classid << "\tstrlen: " << strlen(classid);
# output
Enter the name: Alisa Reinford
Enter the class id: name: Alisa strlen: 5
classid: Reinford strlen: 8
输入 name 时, 先输入了两个空格, 第一个单词输入完成后, 又输入了四个空格, 再输入第二个单词. 执行时, 程序并没有给我们输入 class 的机会, 而是直接将第二个单词作为 class 的输入
在我们输入完 [][]Alisa[][][][]Reinford 并敲击回车键之后, 计算机会将这一串字符和最后的回车键产生的换行符一起送入输入缓冲队列中(队列是一种先入先出的数据结构). 第一个 cin 会忽略前两个空格, 读取完 Alisa 后, 又遭遇空格, 程序认为输入已经结束了, 就将字符串 Alisa 保存到字符数组 name 中
此时, 输入缓冲队列是没有被清空的, 里面还有字符 [][][][]Reinford 和末尾的一个换行符
[space][space][space][space][R][e][i][n][f][o][r][d][\n]
第二个 cin 继续从输入缓冲队列中读取字符, 忽略开头的前四个空格, 读取完 Reinford 之后, 遭遇换行符, 认为输入结束, 将字符串 Reinford 保存到字符数组 classid 之中
于是出现了上面这一幕, 我们没有机会输入 class, 因为输入缓冲队列不是空的. 需要注意的是, 即使在最后读取完字符串 Reinford 之后, 输入缓冲队列也不是空的, 仍然有一个换行符 \n 在里面, 这个在后面使用 cin.get() 方法时会有所体现
如果输入的字符串长度, 超过字符数组的长度呢?
如果我们把上面的程序修改一下, 限制 name 的长度为 4, 然后输入一个长度超过 4 的字符串, 并尝试用下标索引的方式去修改它
char name[4];
char classid[32];
cout << "Enter the name: ";
cin >> name;
cout << "Enter the class id: ";
cin >> classid;
name[5] = ' ';
cout << "name: " << name << "\tstrlen: " << strlen(name)
<< "\tsizeof: " << sizeof(name) << endl;
cout << "classid: " << classid << "\tstrlen: " << strlen(classid) << endl;
# output
Enter the name: Alisa_Reinford
Enter the class id: Seven
name: Alisa Reinford strlen: 14 sizeof: 4
classid: Seven strlen: 5
输入 Alisa_Reinford, 它是一个长度为 14 的字符串, 但仍然被正确保存, 也被正确修改了, 输出时将下划线替换为了空格, 编译器不会去检查数组下标是否越界
实际上, 在输入时 name 数组后面的内存也被写入了数据, 并在最后加上了表示字符串结束的空字符 \0; 同样的, 输出时也从 name 数组的首地址开始读取, 直到遇见空字符 \0. 但这种行为仍然是危险的, 使用 sizeof() 方法查看 name 的大小, 它仍是 4 个字节, 超出 4 个字节以外的内存可能会被重新分配, 导致字符串的内容被修改
重点:
std::cin >>通过 "空白(whitespace)" 判断输入是否结束std::cin >>会忽略开头的 "空白"std::cin >>不会清除末尾的 "空白", 它们仍然残留在输入缓冲队列里std::cin >>保存字符串到字符数组时, 可以输入一个超过字符数组长度的字符串, 不会报错, 但很危险
std::cin.get() 输入字符串
std::cin.get() 方法一次读取整行, 以换行符 \n 判断输入是否结束, 它支持两个参数: str 和 count, 会读取最大不超过 count 长度的字符, 并将它们保存在 str 数组中. 如果不传入任何参数或只传入一个 char 类型参数, 则只会读取一个字符
cin.get(str, count) // 最多读取 count 个字符, 并保存到 str 中
cin.get() // 读取一个字符
cin.get(ch) // 读取一个字符, 保存到 ch 中
std::cin.get() 只通过换行符 \n 判断输入是否结束, 其余字符都作为一般字符处理(包括空格符和制表符), 并且同 std::cin >> 一样, 它也不会对输入缓冲区末尾的换行符做额外处理, 仍然会保留在输入缓冲区队列中
cout << "Enter the name: ";
cin.get(name, 32);
cout << "Enter the class id: ";
cin.get(classid, 32);
cout << "name: " << name << "\tstrlen: " << strlen(name) << endl;
cout << "classid: " << classid << "\tstrlen: " << strlen(classid) << endl;
# output
Enter the name: Alisa Reinford
Enter the class id: name: Alisa Reinford strlen: 16
classid: strlen: 0
我们先输入两个空格, 再正常输入 name. 可以看到, 程序仍然没有给我们输入 classid 的机会, 查看字符长度为 16 表示开头的两个空格没有被特殊对待
# 输入缓冲队列
[ ][ ][A][l][i][s][a][ ][R][e][i][n][f][o][r][d][\n]
在读取到换行符时, 程序认为输入结束, 把 [][]Alisa[]Reinford 保存在 name 中, 但此时输入缓冲队列中还残留有一个换行符 \n, 在下一个 cin.get() 执行的时候, 直接遭遇到换行符, 输入直接结束. 如果后面还有输入语句, 则全部都会失效, 因为输入缓冲队列中一直就残留有一个换行符, 表现起来就像后续的输入都被阻塞了. 可以在每个输入语句后面, 都加上一个 std::cin.get()(不传递任何参数), 它会读取下一个字符, 相当于把残留的换行符丢弃掉
如果输入的字符串长度溢出会发生什么?
这里的溢出有两种情况, 第一种是输入的字符串长度超过了字符数组的大小, 这与之前的 std::cin >> 情况一样, 不会有报错, 但很危险
char name[4];
cout << "Enter the name: ";
cin.get(name, 32);
cout << "name: " << name << "\tstrlen: " << strlen(name) << endl;
# output
Enter the name: Alisa Reinford
name: Alisa Reinford strlen: 16
字符数组 name 长度为 4, 但我们仍然照常输入了一个长度为 16 的字符串, 它还是被正确打印了出来
第二种情况是输入的字符串长度超过了 cin.get() 指定的大小, cin.get() 会将字符串截断, 剩余的字符会残留在输入缓冲区队列内, 被下一个 cin 语句读取
cout << "Enter the name: ";
cin.get(name, 4);
cin.get();
cout << "Enter the class id: ";
cin.get(classid, 32);
cout << "name: " << name << "\tstrlen: " << strlen(name) << endl;
cout << "classid: " << classid << "\tstrlen: " << strlen(classid) << endl;
# output
Enter the name: Alisa Reinford
Enter the class id: name: A strlen: 3
classid: isa Reinford strlen: 12
第一个 cin.get() 被设置读取的字符串长度不超过 4, 同样输入以两个空格开头的字符串 [][]Alisa[]Reinford, 程序在读取前 3 个字符后就停止(因为要保留一位用来保存空字符\0, 作为字符串结束的标识), 在末尾加上空字符\0后, 将 [][]A[\0] 存入字符数组 name 中
此时, 输入缓冲区队列内还残留有 lisa[]Reinford, 第二个 cin.get() 读取第一个字符 l 后丢弃, 第三个 cin.get() 读取剩余的 isa[]Reinford, 并把它保存到 classid 中
所以看起来, 也还是没有给我们机会输入 classid 的值. 需要注意的是, 因为在最后我们没有再添加一个 cin.get() 语句, 此时的输入缓冲队列中还是有一个换行符 \n, 后面如果还有其他的输入语句, 会被直接结束掉
重点:
std::cin.get()只通过换行符判断输入是否结束std::cin.get()不会清除末尾的换行符, 它们仍然残留在输入缓冲队列里, 这会导致后续的输入语句失效- 输入的字符串长度超过
std::cin.get()中指定的大小时, 会把剩余的字符留在输入缓冲队列中, 下一个输入语句会读取它们
std::cin.getline() 输入字符串
std::cin.getline() 与 std::cin.get() 基本相同, 它也接受两个参数传入: str 和 count, 主要区别是 std::cin.getline() 会在读取到换行符后, 将换行符替换为空字符 \0. 也就是说, 使用 std::cin.getline() 方法, 输入缓冲队列中不会残留有换行符
cout << "Enter the name: ";
cin.getline(name, 32);
cout << "Enter the class id: ";
cin.getline(classid, 32);
cout << "name: " << name << "\tstrlen: " << strlen(name) << endl;
cout << "classid: " << classid << "\tstrlen: " << strlen(classid) << endl;
# output
Enter the name: Alisa Reinford
Enter the class id: Seven
name: Alisa Reinford strlen: 16
classid: Seven strlen: 5
在两句 cin 输入语句中间, 不需要额外加上一句 std::cin.get() 区处理残留的换行符. 同样输入字符串 [][]Alisa[]Reinford, 也没有影响第二个 cin 语句的输入
如果输入的字符串长度溢出会发生什么?
如果是输入的字符串长度超出字符数组的大小, 同样的, 不会报错, 但很危险
如果是输入的字符串长度超出 std::cin.getline() 中指定的大小, 会读取指定长度的字符串, 但会关闭后续的输入, 而不是与 std::cin.getline() 一样将剩余的字符保留在输入缓冲区队列中
cout << "Enter the name: ";
cin.getline(name, 4);
cout << "Enter the class id: ";
cin.getline(classid, 32);
cout << "name: " << name << "\tstrlen: " << strlen(name) << endl;
cout << "classid: " << classid << "\tstrlen: " << strlen(classid) << endl;
# output
Enter the name: Alisa Reinford
Enter the class id: name: A strlen: 3
classid: strlen: 0
第一条输入语句被限制最多读取长度为 4 的字符串. 同样地, 我们输入字符串 [][]Alisa[]Reinford, 程序读取了前 3 个字符 [][]A (因为要保留一个字符存储空字符 \0), 将它保存在 name 中. 此时的输入缓冲队列中本应还残留有字符 lisa[]Reinford, 但由于输入的字符串长度超过了 4, 后续的输入被阻断了
如果我们输入长度为 4 的字符串呢?
Enter the name: Rean
Enter the class id: name: Rea strlen: 3
classid: strlen: 0
后续的输入也被阻断了, 字符数组长度虽然是 4, 但它只能保存 3 个字符, 因为要留一个位置给空字符 \0
重点:
std::cin.getline()同样是以行为单位输入, 但它会把输入缓冲队列末尾的换行符\n替换为空字符\n, 不会影响下一个输入语句- 如果输入的字符串长度大于
std::cin.getline()所指定的最大长度, 后续的输入语句会被阻断

浙公网安备 33010602011771号