前言
这一篇将介绍上一篇中cin、cout的更多重载运算补充。
关于这些内容对应的C语言解释:
还有些内容是接着上一章的,上一章:
Reference:
输入输出相关成员函数
我们知道,C++中的cin和cout是智能对象,它们对运算符进行了重载,还定义了几个成员函数。在上一章已经介绍了一些成员函数,这一节就来总结一下常用的。
输入
程序输入时有一个缓存区(buffer),在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区,显然缓冲区是具有一定大小的。当一次键盘输入结束时会将输入的数据存入输入缓冲区,而cin函数直接从输入缓冲区中取数据。正因为cin函数是直接从缓冲区取数据的,所以有时候当缓冲区中有残留数据时,cin函数会直接取得这些残留数据而不会请求键盘输入。
cin>>
cin 可以连续从键盘或硬盘中读取想要的数据,是一种输入流,其有以下性质:
- 当非第一位输入遇到Enter、Space、Tab键时结束输入。
- 若缓冲区中第一个字符是空格、tab或换行这些分隔符时,cin>> 会将其忽略并清除,继续读取下一个字符。
- 不想略过空白字符时,可以使用 noskipws 流输入。
一个简单的例子:
#include <iostream>
using namespace std;
int main()
{
int a,b,c;
cin>>a>>b>>c;
cout<<a<<" "<<b<<" "<<c<<" "<<endl;
return 0;
}
int get(); 与 istream& get (char& c);
首先,cin.get()
和cin.get(char & c)
是从流中读取一个字符,它没有参数或者是只包括了一个参数。它们有这样的性质:
- cin.get()的返回值是int类型,成功则返回读取字符的ASCII码值,遇到文件结束符时,返回EOF,即-1。Windows下命令行输入文件结束符的方式为Ctrl+Z,Linux为Ctrl+D。
- 因为cin.get()读入时从缓存区读入,即可以起到刷新缓存区的作用。例如单写:
cin.get();
- cin.get(char &c)如果成功返回的是cin对象,即参数的值。
- cin.get(char &c)可以支持链式操作,如cin.get(b).get(c)。
- cin.get(char &c)读取失败时(比如遇到EOF)返回0值,否则非0(就算是0,也是字符‘0’对应的ASCII码值)。
下面有一个例子:
#include <iostream>
using namespace std;
int main()
{
char a,b;
a=cin.get();
cin.get(b);
cout<<a<<b;
return 0;
}
例如我们输入:d
,然后回车。
那么就是读入的第一个字符变量a就是d,第二个字符变量b是一个换行符。
同理我们使用cin.get()清空队列的话,可以像C语言这样写:(这只是一种写法)
cin.clear();//清空输入状态,后面会讲
while(cin.get()!='\n')
continue;
cin.get读取一行
读取一行可以使用istream& get ( char* s, streamsize n )
或者istream& get ( char* s, size_t n, streamsize delim)
。n表示目标空间的大小,delim表示可以自定义的截止符。它们都有下面的性质:
- 从输入流中读取n-1字符,赋给字符数组或字符指针所指向的数组。如果在读取n-1个字符之前遇到终止字符,则提前结束。(因为会自动加一个'\0')
- cin.get(s,n);读取一行时,遇到换行符时结束读取,但是不对换行符进行处理,换行符仍然残留在输入缓冲区。
不过要注意的是,s是一个地址,即此处应该填数组名,即cin.get(str,size);读取一行时,只能将字符串读入C风格的字符串中,即char*。String类得用getline。看看下面这个例子:
#include <iostream>
using namespace std;
int main()
{
char a;
char array[20]={NULL};
cin.get(array,20);
cin.get(a);
cout<<array<<" "<<(int)a<<endl;
return 0;
}
例如输入:123456789[Enter]
那么就会输出:123456789 10
此时这个a读入的是换行符,它对应的ASCII码值是10.
cin.getline()
getline这个成员函数有两种,istream& getline (char* s, streamsize n );
和 istream& getline (char* s, streamsize n, char delim );
也是从标准输入设备键盘读取一串字符串,并以指定的结束符结束。没指定delim时,默认以换行符结束。
和前面的get读入一行差不多,例如:
#include <iostream>
using namespace std;
int main()
{
char array[20]={NULL};
cin.getline(array,20); //或者指定结束符,使用下面一行
//等价于cin.getline(array,20,'\n');
cout<<array<<endl;
return 0;
}
它和get读入一行的方法的区别:
- cin.getline不会将结束符或者换行符残留在输入缓冲区中。cin.get会保留那个结束符。
- cin.getline和cin.get一样,不能读入string类的变量。
cin的条件状态
使用cin读取键盘输入时,难免发生错误,一旦出错,cin将设置条件状态(condition state)。条件状态标识符号为:
- goodbit:无错误
- eofbit:已到达文件尾
- failbit:非致命的输入/输出错误,可挽回
- badbit:致命的输入/输出错误,无法挽回
与这些条件状态对应的就是设置、读取和判断条件状态的流对象的成员函数。他们主要有:
- s.eof():若流s的eofbit置位,则返回true;
- s.fail():若流s的failbit置位,则返回true;
- s.bad():若流s的badbit置位,则返回true;
- s.good():若流s的goodbit置位,则返回true;
- s.clear(flags):清空状态标志位,并将给定的标志位flags置为1,返回void。
- s.setstate(flags):根据给定的flags条件状态标志位,将流s中对应的条件状态位置为1,返回void。
- s.rdstate():返回流s的当前条件状态,返回值类型为strm::iostate。strm::iostate是与机器相关的整型类型,由ios_base类定义,用于定义条件状态。
例如我们读取完了,想重新读入,我们需要先清空队列,于是就要把原来的状态刷新复位,我们就使用clear。
cin清空输入缓冲区
上一次的输入操作很有可能是输入缓冲区中残留数据,影响下一次的输入。那么如何解决这个问题呢?自然而然,我们想到了在进行输入时,对输入缓冲区进行清空和状态条件的复位。条件状态的复位使用clear(),清空输入缓冲区。
我们在前面的介绍中有下面这种写法:
cin.clear();//清空输入状态
while(cin.get()!='\n')
continue;
当然,C++中给出了一个新的解决方案,cin.ignore。
它的原型是:istream &ignore( streamsize n, int delim=EOF );
表示跳过输入流中n个字符,或在遇到指定的终止字符时提前结束(此时跳过包括终止字符在内的若干字符)
- 我们一般不知道有多少个n字符,所以可以用一个很大的数,或者使用limit里面的写法,即
numeric_limits<std::streamsize>::max()
表示<limits>头文件定义的流使用的最大值。 - 单写cin.ignore();时,当输入缓冲区没有数据时,也会阻塞等待数据的到来。
例如:
#include <iostream>
using namespace std;
int main()
{
char str1[20]={NULL},str2[20]={NULL};
cin.getline(str1,5);
cin.clear(); // 清除错误标志
cin.ignore(numeric_limits::max(),'\n'); //清除缓冲区的当前行
cin.getline(str2,20);
cout<<"str1:"<<str1<<endl;
cout<<"str2:"<<str2<<endl;
return 0;
}
当然,网上还给出了cin.sync()的清空队列的方式,但是仅在VC++环境下可以做到,其他环境可能不行,不建议使用。
getline()读取一行
C++中定义了一个在std名字空间的全局函数getline,可以读入一行进入string类变量中。因为这个getline函数的参数使用了string字符串,所以声明在了<string>头文件中了。
getline利用cin可以从标准输入设备键盘读取一行,当遇到换行符、EOF或超过限制宽度则停止读入。
它的原型是:istream& getline ( istream& is, string& str);
//默认以换行符分隔行
或 istream& getline ( istream& is, string& str, char delim);
和之前cin.getline()不同的是,第一个参数是输入流,第二个参数才是输入对象。第三个参数同理是结束符,默认为换行符。
例如:
#include <string>
#include <iostream>
using namespace std;
int main()
{
string str;
getline(cin,str);
cout<<str<<endl;
return 0;
}
还要注意的是:getline()遇到结束符时,会将结束符一并读入指定的string中,再将结束符替换为空字符。
cin.getline()与getline()类似,但是因为cin.getline()的输出是char*,getline()的输出是string,所以cin.getline()属于istream流,而getline()属于string流,二者是不一样的函数。
输出
cout 用于在屏幕上显示消息,应该是 console output 的简写。它是 C++ 中 ostream 对象,该类被封装在 库中,该库定义的名称都放在命名空间 std 中,所以 cout 的全称是 std::cout 。cout在使用中经常会有一些格式控制。
cout<<
cout 被分类为流对象,这意味着可以使用数据流的。要在屏幕上显示消息,可以发送一串字符到 cout 。
这个就很简单了,例如:
#include <iostream>
using namespace std;
int main()
{
char a,b;
cin.get(a).get(b);
cout<<"a="<<a<<" "<<"b="<<b<<endl;
return 0;
}
cout.put()
它的原型是ostream& put (char c);
,与get()相对的,它的作用是输出一个字符。因为是流输出,所以该函数可以被连续调用。即cout.put().put();
这个就不给出例子了,很简单。
cout.write()
它的原型是ostream& write (const char* s, streamsize n);
它的作用是把一个字符串(指针s表示)的个字符插大输出流。n表示输出的最大长度,如果总长度小于n,全部输出不补充,如果大于n,则输出前n个字符。
不过同样的,根据定义,它不能输出string类的变量,只能输出字符数组的形式。
例如:
#include <iostream>
using namespace std;
int main()
{
char s[]="abcdefg";
cout.write(s,5);
cout<<endl;
cout.write(s,10);
return 0;
}
输出第一行:abcde
输出第二行:abcdefg
PS:至于为什么可能末尾会有个?结尾,我想可能是我的编译器把最后的空白符输出了(如果有错请指出),正常情况就是全部输出就结束了。
cout.width()
width()函数控制输出的宽度,原型是streamsize width (streamsize wide);
字段宽度决定了在某些输出表示中要输出的最小字符数,它的作用域是只能控制最近的下面一句 cout 输出。
这个就和printf中%nd类似,n是一个常数,表示最小宽度。不足这个宽度就补足。
cout.fill()
fill()函数可以控制占位填充字符,经常与width连用,进行空白占位填充。它的声明是:char fill (char fillch);
同理,它的作用域也是只能控制最近的下面一句 cout 输出。
下面展示一个width与fill连用的例子:
#include <iostream>
using namespace std;
int main()
{
int a=1,b=2;
cout.width(3);//最小宽度为3
cout.fill('0');//用0填充空白
cout<<a<<endl;
cout<<b<<endl;
return 0;
}
它的输出第一行是:001
第二行是:2
这样我们就可以看到空白的地方被0替换了,而对b变量不生效。如果我们想一直让它们生效,则每一句前面都要加cout.width();
此时无需再fill!
cout.flags()
它的作用是当前格式状态全部替换为 fmtfl。注意,fmtfl 可以表示一种格式,也可以表示多种格式。它的原型是fmtflags flags (fmtflags fmtfl);
我们常用它来进行一些格式输出,请见下面图表。
cout.setf()与unsetf()
setf(fmtfl, mask)
在当前格式的基础上,追加 fmtfl 格式,并删除 mask 格式。其中,mask 参数可以省略。
unsetf(mask)
在当前格式的基础上,删除 mask 格式。
和上面的flags()一样,只是作用的范围不同。下面是一个进制输出的例子:
#include <iostream>
using namespace std;
int main()
{
cout.flags(ios::oct);//八进制输出
cout<<100<<endl;
return 0;
}
cout控制输出精度
在printf中我们控制输出精度一般是在表达方式处增加限制,例如%5.2d
表示最小宽度为5,保留小数点后2位。在cout中,我们有cout.width()控制最小宽度,那小数点后的精度,我们则有两种方法控制。
cout.precision()
原型是streamsize precision (streamsize prec);
必须与cout.flags(ios::fixed);
连用,不然没有效果。例如:
#include <iostream>
using namespace std;
int main()
{
cout.precision(3);
cout.flags(ios::fixed);
float a=1.2345;
cout<<a<<endl;
return 0;
}
最后输出的就是:1.235
。因为四舍五入了,最后一位进位了。
如果不加flags的话,则为1.23
,相当于保留了宽度为3的数,即3位有效数字。这是不一样的效果。
setprecision()方法
cout还可以直接通过使用 setprecision 操作符来控制显示浮点数值的有效数的数量。setprecision被定义在库#include
<iomanip>
里,使用需要添加。
例如:
#include <iostream>
#include <iomanip>//必须添加
using namespace std;
int main()
{
float a=1.2345;
cout<<setprecision(3)<<a<<endl;
return 0;
}
它的输出也是:1.23,说明,setprecison(n)也是保留n位有效数字。
后记
当然,这些只是其中的一些常用的关于输出输出的操作,更多的操作请见说明官网:http://www.cplusplus.com/reference/iostream/
Comments NOTHING