2.C++输入输出


基本

  • cin、cout、<<、>>
    • cincout都是对象。
    • <<>>此处是流插入、提取运算符,原来是位操作运算符,7级优先级,经过重载变成现在的功能。重载运算符现在不学。
    • cout << a;实际上是表达式语句。可以使用对表达式分析的方式进行解释。
    • <<在cout对象和a变量之间进行运算。运算的值是cout对象本身。
    • cout << a,b,c;实际上是一个逗号表达式,表达式的值是c,最终只会输出a的值。
  • 缓冲区
    • cout被插入数据后,存储在缓冲区内,当满足条件(缓冲区满/endl/‘\n’/使用cout.flush();刷新)才会集中输出一次。Windows系统下对这个规则的遵守不如Linux规范。
    • cin不会被直接提取数据,输入的数据会存储在缓冲区内,当满足条件(缓冲区满/‘\n’)才会集中被提取一轮数据。
  • 输入输出设备均可以更改,称为输入/输出重定向。
  • 输出会自动判断数据类型,选择合适的方法输出。char类型会自动按照字符输出。如果想要按数字或者按字符输出,强制类型转换即可。
  • 奇技淫巧:不用强制类型转换,但是让char以整数的方式输出:cout << ch+0 << endl;运算那里的涉及整型提升。
  • cin 不能把数据提取到常量、表达式里去

cin/cout格式控制符

  • 考试会给出
控制符 作用
dec 设置整数为 10 进制
hex 设置整数为 16 进制
oct 设置整数为 8 进制
setbase(n) 设置整数为 n 进制 (n=8,10,16)
setfill© 设置填充字符,c 可以是字符常量或字符变量
setprecision(n) 设置实数的精度为 n 位。在以一般十进制形式输出时,n 代表有效数字。在以 fixed (固定小数位) 形式和 scientific (指数) 形式输出时,n 为小数位数
setw(n) 设置字段宽度为 n
setiosflags(ios::fixed) 设置浮点数以固定的小数位数显示
setiosflags(ios::scientific) 设置浮点数以科学计数法(即指数形式)显示
setiosflags(ios::left) 输出数据左对齐
setiosflags(ios::right) 输出数据右对齐
setiosflags(ios::skipws) 忽略前导的空格
setiosflags(ios::uppercase) 在以科学计数法输出 E 和十六进制输出字母 X 时,以大写表示
setiosflags(ios::showpos) 输出正数时,给出 “+” 号
resetiosflags(*) 终止已设置的输出格式状态,括号内为具体内容 (本处用 * 替代)

cin

  • cin的输入终止条件是回车、空格、非法输入。
  • cin的读值遵循“合理且最长原则”,或者可以描述为“贪心”。先把可以读的读到,然后才去看对不对。这个原则和之前字符常量8进制转义的读取逻辑是很像的。详细规则见下方cin读入规则
  • 读数值的时候,如果直接上非法字符或数值超过目标变量的上下限,那么实际的读入量将会不可信,此时讨论这个读入的东西没有意义,不同编译器都不一样。此时cin.good()为0.
  • 要判断读入的值可不可信,可以使用cin.good()或者cin.fail(),按照字面意思,大部分情况下是互斥的。会有少部分情况出现矛盾。(查阅得知这个东西是用于检查当前输入流是否正常,所以能拿来检查输入正不正常。)
  • 一旦cin.good()变成0,后面所有的cin读入都不会成功执行。
  • 无符号错误规律和有符号不一样,有符号的超出范围就会good变0,而无符号整数输入绝对值超范围才会good变0.负数但绝对值没抄是当作正常操作的。有符号的整数这里和赋值超范围的规则是不一样的。有符号赋值超范围只是会警告截断。但这里直接让输入流异常。
  • cin永远无法读取空格、换行,因为把这些当作分隔符。
  • cin读取浮点数的时候,输入".3"可以用于代表"0.3"。
  • cin读取字符的时候,只能读图形字符,不能输入换行,不能输入空格。转义符方式无法输入控制字符。‘ " \ 都当单字符。
  • cin不能跟endl。
  • cin读东西,如果缓冲区没读完,就继续读缓冲区里的东西。
  • ctrl+c表示程序结束,这也会导致cin.good()变成0,之后发生的事都不可信
  • ctrl+z表示输入结束,这也会导致cin.good()变成0,之后cin后变量的数据都不可信
  • cin进bool,可以输0和1,不能输true false不能输除了0和1以外的任何数字,否则都会cin.good()变0.
  • cin连续输入多个int时,碰到非法字符,下一个是0,再下面才是随机值
  • cin >>str;不能读有空格的字符串,输入多个的时候,空格是分隔符,与scanf不一样的是,超出长度不会报错,会截取能保证数组尾部有尾0的部分,比scanf安全,最后没有换行符。

cin读入规则

  • cin读数据的流程:
    1. 按照合理且最长原则的某一条规则读取一次数据。
    2. 读取到非法的东西,或者到空格和换行了,结束本次读取,开始将读好的东西转成目标形式:
      1. 能够成功转换(类型不同,只要是读入的东西占用内存小于等于目标类型,都是可以成功的):成功完成本次输入。
      2. 转换失败(没有读到东西,读入的东西占用内存小于等于目标类型):good()置0.
  • 大的数据读进小的空间,会出现越界写的危险行为,所以不可以。
第一个字符 后续字符
整型 +、-、数码 数码
浮点型 +、-、数码、小数点 数码、第一次出现的小数点、第一次出现的e
字符 一个非空格非回车的字符 /
字符串 待后续补充 待后续补充

此外的都是非法情况。如果读到了非法的字符,数据是不会从缓冲区拿出来的,非法字符还在那里面。
提示:浮点的情况,读到.3,得值0.3

  • 读到无符号整数的时候,由于使用的规则不区分有没有符号,所以负数是合法的,然后如果和目标类型又是兼容的,那可以读进来,并发生自然而然的类型转换(二进制没变)。这时good()是1.对cin来说这属于可信范畴。

cin读一些整数

  • signed整数,以short(-32768~32767)为例:
    • 输入在[-32768,32767]内的数字,都正常
    • 输入小于-32768大于32767的,会置错误标记。
  • unsigned整数,以unsigned short (0~65535)为例:
    • 输入在[-65536,65535]内的数字,都不会触发错误标记,其中的负数,是低位截断了
    • 输入小于-65536大于65535的,会置错误标记。

cin格式化

  • hex/oct/dec/setbase(n)可按照对应的进制输入数据。
  • setiosflags(ios::skipws)忽略前导空格
    • "忽略前导空格"的意思,把数据间的空格忽略,作为分割符
    • 这个东西默认有效的,即不设置也生效,是不需要手动setiosflags的
    • 想取消"忽略前导空格"的设置,把空格作为输入数据,应使用cin.unsetf(ios::skipws);此时空格不作为分隔符,而是做为输入数据(如果输入整数遇到空格会当作非法字符,直接置异常标记)

cin部分成员函数

  • cin.get()
       功能:从输入流中读一个字符并返回该字符,可获取’ ‘和’\n’
  • cin.get(字符变量)
       功能:同上,从输入流中读一个字符给字符变量,返回cin(流对象自身)
  • cin.get(字符数组,字符个数n,中止字符)
       功能:从输入流中读n-1个字符,若遇到中止字符,则提前结束,返回cin(流对象自身)
       不写中止字符也不会输进来’\n’
       某种程度上这个n代表最后这个字符串被写入n个字符,包含了最后的尾零。

  • cin.getline(字符数组,字符个数n,中止字符)
       功能:同三个参数的cin.get()
  • cin.eof()
       功能:判断是否遇到了文件结束符EOF,返回逻辑值 (遇到EOF为真)
       EOF(End Of File)是宏定义
  • cin.peek()
      功能:返回输入流中的下一个字符(不提取)(遇见文件结束符则返回EOF)
  • cin.putback(字符变量/字符常量)
      功能:将字符变量/常量插入到输入流的头部
  • cin.ignore(字符个数n,中止字符)
      功能:跳过n个字符,或遇到中止字符时提前结束。这个中止字符也会被跳过。

cout

  • 使用格式化输出要用iomanip头文件
  • setiosflags(ios::left/right)调整左右对齐。setiosflags(ios::fixed/scientific)调整输出是小数还是科学记数法。这两个在转变前一定要resetiosflags(xx)一下,否则会坏掉。
  • 为什么要求C++里必须用cout而不能用printf:用printf那个OI对速度的追求。但在实际的工程里面,printf没有安全检查,cout在后面可以重载。在工程的应用里printf没法和cout打。
  • 实际上,输出这个行为非常非常的慢,程序运行的大部分时间都花在输入输出,普通的这种纯计算对CPU来说是很快的。
  • cout输出字符数组的时候,如果没有尾零会继续越界

cout格式化

  • 想要让 cout 的输出按照不同的进制,可以使用前导进制转换 cout << [jinzhi] << ...,其中,hex是16,dec是10,oct是8.这个不需要iomanip。这种输出只会输出最基本的数字表示,不会带 0x 之类的标记。
  • 2 进制没有直接的输出方法。
  • hex/oct/dec在使用以后,是对之后输出的所有整数生效,对浮点数不生效。
  • setbase(n)
    • 允许的合法值有8,10,16
    • 当出现非法值时,处理方法是忽略输入值,以默认的10进制输出
    • 对后面的所有整数有效,直到用另一个setbase去改变为止
  • setiosflags(ios::uppercase)
    • 用来在16进制形式的输出中,让a~f的字母改用大写的形式,不会改变其他字符输出的小写字母
    • 对后面的所有数据有效
    • 恢复小写,具体的做法是输出前进行格式控制:resetiosflags(ios::uppercase)进行重置
  • setiosflags(ios::showpos)
    • 用于设置数字显示符号。无论这个东西设不设置,负数都会显示负号,所以相当于就是在调整正数显不显示正号
    • 对后面的所有整数(无论进制)和浮点数都生效
    • 取消,具体的做法是输出前用resetiosflags(ios::showpos)重置
  • setprecision(n)
    • 设置浮点数输出的有效位数/小数点后位数
    • 对后面的所有浮点数生效
    • 单独使用,指定的输出位数是有效位数
    • 如果显示精度到达得到规定精度,就正常输出10进制整数(进制标记不影响他),如果精度不能到达个位,则按照科学记数法(1.23e+05这种)输出。
    • 如果在保留有效位数的过程中有效位数以内的末尾出现了0,则都忽略掉,不输出0.
  • setiosflags(ios::fixed)
    • 对后面的所有浮点数生效
    • ios::fixed,指定的输出位数是小数点后位数
    • 不采用科学记数法,直接输出小数,设置保留多少位小数则输出多少位小数,尾0不忽略也输出。
  • setiosflags(ios::scientific)
    • 对后面的所有浮点数生效
    • 指定的输出位数是尾数的小数点后位数
    • 使用科学计数法输出,保留的是尾数的小数点后位数,且不忽略尾0
  • 上面两个标志原则上是互斥的,标准规定,同时使用ios::scientific 和 ios::fixed 会被解释为十六进制浮点格式(0x1.abcdefp+04这种)
  • setw(n)
    • setw()的意义就是,输出后面这个东西,无论长度是什么,一定要占满多少位,不够用填充字符填充,默认空格
    • setw指定的宽度,是总宽度(对于实型数据,包含小数点),当总宽度n大于数据宽度时,显示规律为在数据前面/后面补足用于填充的字符到总长达到设定总宽度为止;当总宽度n小于数据宽度时,显示规律为按照原样数据宽度输出
    • 对后面的仅一个数据有效
  • setfill(c)
    • 设置用于补足填充的字符
    • 对后面的所有数据有效
  • setiosflags(ios::left)
    • 设置数据左对齐,在右边补齐填充符号
    • 不设置,默认是右对齐
  • 同时使用ios::left/ios::right,右对齐优先级高
  • boolalpha可以让输出布尔值的时候不是输出0/1,而是true/false

cout部分成员函数

  • cout.put(字符常量/字符变量)
     功能:向标准输出设备输出一个字符
  • cout.write(字符串常量/变量,输出长度)
     功能:向标准输出设备输出n个字符(如果n超过串长,则输出串长)
  • 下面这些和之前cout格式化那里的一样的
  • cout.setf(控制标记)
      功能:设置指定的控制标记(常用控制标记就是之前那些ios::下的东西)(setiosflags())
  • cout.unsetf(控制标记)
      功能:清除指定的控制标记(resetiosflags())
  • cout.width(宽度)
      功能:设置指定的输出宽度(setw(n)
  • cout.fill(字符常量/字符变量)
      功能:设置填充字节(setfill(c)
  • cout.precision(精度)
      功能:设置浮点数的输出精度(setprecision(n)

输入错误处理

  • 这里面包含了c的输入输出。但是不可能拆开,就放在C++这里了

基本原则

基本范式:

1
2
3
4
5
6
7
8
9
while(1){
//提示并输入x
if (输入错误){
清除错误标记
清空缓冲区
}
else if (值在要求的合理范围内)
break;
}

一些原则:
多选择少输入:用户的行为都是不可控的,尽量让用户选择你给定的值,而不要让用户输入。
多个输入值,一个值cin一次,cin.fail()则清空缓冲区,否则不清空。(剩余字符留在缓冲区等下一轮处理,所以如果输入"123abc",会发现程序进行了两次输入的行为,这是对的。)
除非有规定要一次处理多个输入,那就按具体规定来。

cin判断输入错误

cin本身在输入错误的时候会返回一个false值,也可以用来判断输入错误

cin.good()

反映输入完成后能否进行下一次读取

cin.fail()

反映本次输入是否错误

可能出现同为0的情况,不会出现同为1的情况。
同为0的情况:输入文件的末尾没有换行

1
2
12 34 56
78

不推荐使用cin.good()进行判断,推荐使用cin.fail(),这是一个仅专注于数据本身输入是否正确的判据

cin清除错误标记

cin.clear()

把异常标记全都清除,恢复正常状态
上面这些cin.good()cin.fail()都会回到正确的情况下去
一定要先clear再ignore!!!!

cin清除缓冲区

cin.ignore(n, ch)

清理缓冲区,最大清空n个字符,一直遇到ch字符为止。ch一般填’\n’。
这里写一个随便想出来的大数显然是不合适的。目前最好的方法是#include <climits>(C++)或#include <limits.h>(C++/C),填INT_MAX(就是2147483647)进去。
在这个地方填其他的:
cin.rdbuf() -> in_avail()
释义:查询输入流缓冲区中可立即读取的字符数
能不能用:不可以用,MSVC可以过,gcc不能用

numeric_limits<streamsize>::max
释义:用于获取 streamsize 类型的最大值(不是输入流缓冲区容纳字符数上限,而是一个逻辑上的上限,非常非常大,在我这里是9223372036854775807
能不能用:可以用,有限制,当你在Visual Studio下#include <Windows.h>的时候会和那个库里的一个max冲突。如果要在这个情况下强行共存,可以定义一个宏#define NOMINMAX。但这样一个代码就不适用多个编译器,不好。

getchar()循环

C++能用这个,但是还是ignore成员函数更好。
见下面的scanf清除缓冲区

cin输入错误示例

1
2
3
4
5
6
7
8
9
10
while(1){
cout << "请输入xxx:";
cin >> x;
if(cin.fail()){
cin.clear();
cin.ignore(INT_MAX, '\n');
}
else if (x >= 0 && x <= 100)
break;
}

scanf判断输入错误

没有状态位,对于输入超限这种情况没有处理能力!

核心方法:看scanf函数返回值是不是等于输入数据的个数。
把输入x和判读输入错误合到一起:

1
2
3
if(scanf("%d", &a) == 0){
……
}

scanf不需要清除错误标记,因为没有错误标记

scanf清除缓冲区

getchar()循环

直接用这个

1
2
while(getchar()!='\n')
;

这一种也可以:

1
2
while((ch = getchar())!='\n' && ch != EOF)
;

其他方案:

scanf("%*[^\n]")

释义:读取字符并丢弃,直到’\n’。这个*是丢弃的意思,中括号是一个匹配表达式,^是取反的意思,代表匹配不是’\n’的字符。
能不能用:可以

setbuf(stdin, NULL)

释义:关闭输入缓冲区。
可不可以用:错误的东西,不能用

rewind(stdin)

释义:将缓冲区光标移到开头,重新开始写,覆盖之前的内容。
可不可以用:不推荐。在目前的情况下没什么问题,但是到linux上会不支持。

scanf输入错误处理示例

1
2
3
4
5
6
7
8
9
while(1){
printf("请输入xxx:");
if(scanf("%d", &x) == 0){
while(getchar()!='\n')
;
}
else if (x >= 0 && x <= 100)
break;
}