前言

这一篇将介绍上一篇中cin、cout的更多重载运算补充。

关于这些内容对应的C语言解释:

C语言学习笔记

还有些内容是接着上一章的,上一章:

【C++学习笔记】C与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():若流seofbit置位,则返回true
  • s.fail():若流sfailbit置位,则返回true
  • s.bad():若流sbadbit置位,则返回true
  • s.good():若流sgoodbit置位,则返回true
  • s.clear(flags):清空状态标志位,并将给定的标志位flags置为1,返回void
  • s.setstate(flags):根据给定的flags条件状态标志位,将流s中对应的条件状态位置为1,返回void
  • s.rdstate():返回流s的当前条件状态,返回值类型为strm::iostatestrm::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/

这里的一切都有始有终,却能容纳所有的不期而遇和久别重逢。
最后更新于 2024-01-14