常规输入、输出

c++输入和输出概述

多数计算机语言的输入和输出是以语言本身为基础实现的。但C和C++都没有将输入和输出建立在语言中。
C语言最初包I/O留给编译器实现人员。目的为了让实现人员能够自由设计I/O函数。使之最合适于目标计算机的硬件要求。实际上,多数实现人员都把I/O建立在最初为UNIX环境开发的库函数的基础上。ANSI C正式承认这个I/O软件包时,将其称为标准输入/输出包。

C++依赖于C++的解决方案,而不是C语言的I/O解决方案,前者在头文件iostream和fstream中定义一组类。这个类库不是正式语言定义的组成部分(cin和istream不是关键字);正如C实现自带一个标准函数库一样,C++也自带一个标准类库。首先,标准类库是一个非正式的标准,只是由头文件iostream和fstream中定义的类组成。ANSI/ISO C++委员会决定把这个类正式作为一个标准类库。

流和缓冲区

c++程序把输入和输出看作字节流。输入时,程序从输入流中抽取字节;输出时,程序将字节插入到输出流中。流充当了程序和流源或流目标之间的桥梁。使得c++可以以相同的方式对待来自键盘的输入和输出。使用流,c++程序处理输出的方式将独立于其去向,因此管理包含两步:

  1. 将流与输入去向的程序关联起来。
  2. 将流与文件连接起来。
    在这里插入图片描述

缓冲区是用作中介的内存块它是将信息从设备传输到程序或从程序传输给设备的临时存储工具。通过使用缓冲区可以更高效的处理输入和输出。
在这里插入图片描述

流、缓冲区和iostream文件

管理流和缓冲区的工作比较复杂,但iostream文件中包含一些专门设计用来实现、管理流和缓冲区的类。c++98版本C++I/O定义了一些类模板,以支持char和wchar_t数据;c++11添加char16_t和char32_t具体化。通过使用typedef工具,c++使得这些模板char具体化能够模仿传统的非模板I/O实现。例如:
在这里插入图片描述

  1. streambuf类为缓冲区提供了内存,并提供了用于填充缓冲区、访问缓冲区内容、刷新缓冲区和管理缓冲区内存的类方法;
  2. iso_base类表示流的一般特征,如是否可读取、二进制流还是文本流等;
  3. ios类基于ios_base,其中包括了一个指向streambuf对象的指针成员;
  4. ostream类从ios类派生而来的,提供了输出方法;
  5. istream类也是从ios类派生而来的,提供了输入方法;
  6. iostream类是基于istream和ostream类的,因此继承了输入方法和输出方法;

要使用这些工具,必须使用适当的类对象。例如,使用ostream对象(如cout)来处理输出。创建这样的对象将打开一个流,自动创建缓冲区,并将其与流关联起来,同时使得能够使用类成员函数。

重定义I/O

ISO/ANSI标准c++98对I/O作了两方面的修订。首先是从ostream.h到ostream的变化,用ostream将类放到std名称空间中。其次,I/O类被重新写入编写。c++必须处理需要16位的国际字符集或更宽的字符类型。因此,该语言在传统的8位char类型添加了wchar_t字符类型;而c++11添加了类型char16_t和char32_t。每种类型都需要自己的I/O工具。只有一套I/O类模板,其中包括basic_istream<charT,traits>和basic_ostream<charT,traits>。traits模板是个模板类,为字符类型定义了具体特性。c++11标准提供了I/O的char和wchar_t具体化。例如,istream和ostream都是char具体化的typedef。同样,wistream和wostream都是wchar_t具体化。例如:wcout对象用于输出宽度字符流。头文件ostream中包含这些定义。

ios基类中的一些独立于类型的信息被移动到新的ios_base类中,这包括各种格式化常量,例如:ios_base::fixed。另外,ios_base还包含一些老式ios中没有的选项。

c++的iostream类库管理很多细节。例如:在程序中包含iostream文件将自动创建8个流对象(4个用于窄字符流,4个用于宽字符流)。

  1. cin对象对应于标准输入流。在默认情况下,这个流被关联到标准输入设备,wcin对象于此相似。但处理的是wchar_t类型。
  2. cout对象于标准输出流相对应。在默认情况下,这个流被关联到标准输出设备,wcout对象与此相似,但处理的是wchar_t类型。
  3. cerr对象于标准错误流相对应,可用于显示错误信息,在默认情况下,这个流被关联到标准输出设备。这个流没有被缓冲到意味着信息将被发送给屏幕,而不会等缓冲区填满或新的换行符。wcerr对象与此相似,但处理的是wchar_t类型。
  4. clog对象也对应着标准错误流。在默认情况下,这个流被关联到标准输出设备,这个流被缓冲。wclog对象与此类似,但处理的是wchar_t类型。
  5. 对象代表流——当iostream文件为程序声明一个cout对象时,该对象将包含存储了与输出相关的信息的数据成员,如显示数据时使用的字段宽度、小数位数、显示整数时采用的计数方法以及描述用来处理输出流的缓冲区streambuf对象地地址。ostream类定义了上述语句中使用的operator<<()函数,ostream类还支持其他大量的类方法。
重定向

标准输入和输出通常连接键盘和屏幕。但很多操作系统都支持重定向,这个工具使得能够改变标准输入和输出。例:名为counter.exe的、可执行的Windows命令提示符c++程序,能够计算输入中的字符数,并报告结果(在大多数Windows系统中,可以选择“开始”>"程序”,在单击“命令提示符”来打开命令提示窗口):
C>counter
Hello
and goodbye!
Control-z <<simulsted end-of-file
Input contained 19 characters.
C>
其中的输入来自键盘,输出的被显示到屏幕上。
通过输入重定向(<)和输出重定向(>),可以使用程序计算文件oklahoma中的字符数,并将结果放到cow_cnt文件中:
cow_cnt file:
C>countercow_cnt
C>
命令行中的将标准输入与oklahoma文件关联起来,使cin从该文件读取输入。换句话说,操作系统改变了输入流的流入段,而流出段仍然与程序相连。

cout代表得标准输出流是程序输出的常用通道。标准错误流(由cerr和clog代表)用于程序的错误消息。默认情况下,3个对象都被发送给显示器,但对标准输出重定向并不会影响cerr和clog,如果,使用其中一个对象打印错误消息,程序将在屏幕上显示错误消息,即使常规的cout输出被重定向到其他地方。

if(success)
cout<<"Here come the goodies!\n";
else
{
cerr<<"Something horrible has happened.\n";
exit(1);
}

如果重定义没有起作用,则选定的信息都将被显示在屏幕上。然而,如果程序输出被重定向到一个文件,则第一条消息(如果没有被选中)将被发送到文件中,而第二条信息(如果没有被选中)将被发送到屏幕。有些操作系统也允许对标准错误进行重定义。

使用cout进行输出

c++将输出看作字节流,但在程序中,很多数据被组织成比字节更大的单位。例如:int类型由16位或32位的二进制值表示;double值由64位的二进制数据表示。但在字节流发送给屏幕时,希望每个字节表示一个字符值。因此,ostream类最重要的任务之一是将数值类型(如int或float)转换为以文本形式表示的字符流。也就是,ostream类将数据内部表示(二进制位模式)转换为字符字节组成的输出流。

1.重载<<运算符
经常结合使用cout和<<运算符:

int clients=22;
cout<<clients;

在c++,与c一样,<<运算符的默认含义是按位左移运算符。表达式x<<3的意思,将x的二进制表示中所有的位向左移动3位。显然,这与输出的关系不大。但ostream类重新定义了<<运算符,方法是将其重载为输出。在这种情况下,<<叫作插入运算符,而不是左移运算符。插入运算符被重载。使之能够识别c++中所有的基本类型:

  1. unsigned char;
  2. signed char;
  3. char;
  4. short;
  5. unsigned short;
  6. int;
  7. unsiged int;
  8. long;
  9. unsigned long;
  10. long long(c++11);
  11. unsigned long long(c++11);
  12. float;
  13. double;
  14. long double;

对于上述每种数据类型,ostream类都提供了ostream<<()函数的定义。因此,如果使用下面这样一条语句,而value是前面列出的类型之一,则c++程序将其对应的特征标的运算符函数:

cout<<value;

例如:表达式cout<<88对应与下面的方法原型:

ostream & operator<<(int);

该原型表明,operator<<()函数接受一个int参数,与上述中的88匹配,该原型还表明,函数返回一个指向ostream对象的引用,这使得可以将输出连接起来,如下所示:

cout<<"I'm feeling sedimental over"<<boundary<<"\n";

2.输出和指针

ostream类还为下面的指针类型定义了插入运算符函数:

  1. const signed char*;
  2. const unsigned char*;
  3. const char*;
  4. void*;

c++用指向字符串存储位置的指针来表示字符串。指针的形式可以是char数组名、显示的char指针或引号括起来的字符串。因此,下面所有的cout语句都显示字符串:

char name[20]="Dudly Diddlemore";
char * pn="Violet D'Amore”;
cout<<"Hello";
cout<<name;
cout<<pn;

方法使用字符串中的终止空字符来确定何时停止显示字符。
对于其他类型的指针,c++将其对应于void *,并打印地址的数值表示。如果要获得字符串的地址,则必须将其强制转换为其他类型:

char name[20]="Dudly Diddlemore”;
char *pn ="Violet D'Amore";
cout<<"Hello!"
cout<<name;
cout<<pn;

方法使用字符串中的终止空字符来确定何时停止显示字符。
对于其他类型的指针,c++将其对应与void *,并打印地址的数值表示。如果要获得字符串的地址,则必须将其强制转换为其他类型:

int eggs=12;
char * amount="dozen";
cout<<&eggs;//打印变量的地址
cout<<amount;//打印dozen
cout<<(void *)amount;//打印dozen地址

3.拼接输出

插入运算符的所有化身的返回类型都是ostream&。原型格式如下:

ostream & operator<<(type);

(type是要显示的数据类型)返回类型ostream &意味着使用该运算符将返回一个指向ostream对象的引用。引用将指向用于调用该运算符的对象。换句话说,运算符的返回值为调用运算符的对象。例如:cout<<"potluck”返回的是cout对象。这种特征使得能够通过插入来连接输出。例如,请看下面的语句:

cout<<"We have"<<count<<"unhatchad chickens.\n";

表达式cout<<"We have"将显示字符串,并返回cout对象。上述语句将变成:

cout<<count<<"unhatched chickens.\n";

表达式cout<<count将显示count变量的值,并返回cout。然后cout将处理语句中的最后一个参数。
在这里插入图片描述

其他ostream方法

除了各种operator<<()函数外,ostream类还提供了put()方法和write()方法,前者用于显示字符,后者用于显示字符串。
put()方法的原型:

ostream & put(char);

当前标准与此相同,但被模板化,以适用于wchar_t。可以用于类方法表示来调用它:

cout.put('W');//显示W

cout调用方法的对象,Put()是类成员函数。和<<运算符函数一样,该函数也返回一个指向调用对象的引用,因此可以用它将拼接输出:

cout.put('I').put('t');//打印It调用put()两次

函数调用cout.put(‘I’)返回cout,cout然后被用作put(‘t’)调用的调用对象。

可以将数值类型参数(如Int)用于put(),让函数原型自动将参数转换为正确char值。:

cout.put(65);//将int值转换为一个char值
cout.put(66.3);//将double值66.3转换为char值66,并显示对应的字符。

一些老式的编译器错误地为char、unsigned char和signed char 3种参数类型重载了put()。这使得将int参数用于put()时具有二义性,因为int可被转换为这3中的任何一种.
write()方法显示整个字符串,其模板原型如下:

basic_ostream<charT,traits>& write(const char_type* s,streamsize n);

write()的第一个参数提供了要显示的字符串的地址,第二个参数指出要显示多少个字符。使用cout调用write()时,将调用char具体化,因此返回类型为ostream&。下面程序演示write()方法如何工作:

#include <iostream>
#include<string.h>
using namespace std;

int main()
{
    const char * state1="Florrida";
    const char * state2="Kansas";
    const char * state3="Euphoria";
    int len=strlen(state2);
    cout<<"Increasing loop index:\n";
    int i;
    for(i=1;i<=len;i++)
    {
    //cout.write()调用返回cout对象。这是因为write()返回一个指向调用它的对象的引用,调用它对象时cout。
    //这使得可以将输出拼接起来,因为cout.write()将被其返回值cout替换:
        cout.write(state2,i);
        cout<<endl;
    }
    //连接输出
    cout<<"Decreasing loop index:\n";
   for(i=len;i>0;i--)
       cout.write(state2,i)<<endl;

   //超过字符串长度
   cout<<"Exceeding string length:\n";
   cout.write(state2,len+5)<<endl;
   return 0;

}

在这里插入图片描述
注:write()方法并不会在遇到空字符时自动停止打印字符,即使超过字符串的边界。

write()方法也可用于数值数据,可以将数字的地址强制转换为char*,然后传递给它:

long val=560031841;//作为4个独立字节被传输
cout.write((char *)&val,sizeof(long));

这不会将数字转换为相应的的字符,而是传输内存中存储的位表示。

刷新输出缓冲区

使用cout将字节发送给标准输出,由于ostream类对cout对象处理的输出进行缓存,所以输出不会立即发送到目标地址,而是被存储在缓冲区,直到缓冲区填满。然后,程序将刷新缓冲区,把内容发送出去,并清空缓冲区,以存储新的数据。

可以直接调用flush()来刷新cout缓冲区:

flush(cout);//事实上,控制符也是函数

然而,ostream 类对<<插入运算符进行重载,使得下述表达式将替换为函数调用flush(cout):

cout<<flush;

用cout进行格式化

ostream插入运算符将值转换为文本格式。在默认情况下,格式化值的方式如下:

  1. 对于char值,如果他代表的是可打印字符,则将被作为一个字符显示在宽度为一个字符的字段中。
  2. 对于数值整型,将以十进制方式显示在一个刚好容纳该数字及负号的字段中。
  3. 字符串被显示在宽度等于该字符串长度的字段中。

浮点数的默认行为也有变化:

  1. 新式:浮点类型被显示为6位,末尾的0不显示。
  2. 旧式:浮点类型显示为带6位小数,末尾的0不显示
#include <iostream>
using namespace std;
int main()
{
    cout<<"12345678901234567890\n";
    char ch='K';
    int t=273;
    cout<<ch<<":\n";
    cout<<t<<":\n";
    cout<<-t<<"\n";

    double f1=1.200;
    cout<<f1<<":\n";
    cout<<(f1+1.0/9.0)<<":\n";//1.0/9.0生成一个无穷小数,以便知道打印多少位

    double f2=1.67E2;
    cout<<f2<<":\n";
    f2+=1.0/9.0;
    cout<<f2<<":\n";
    cout<<(f2*1.0e4)<<":\n";

    double f3=2.3e-4;
    cout<<f3<<":\n";
    cout<<f3/10<<":\n";
    return 0;

}

在这里插入图片描述
注:1.200末尾的0没有显示出来,但末尾不带0的浮点值后面将有6个空格。另外,该实现将指数显示为3位,而其他实现可能为两位。

修改显示时使用的计数系统

ostream类是从ios类派生而来的,而后者是从ios_base类派生而来的。ios_base类存储了描述格式状态的信息。例如:一个类成员中某些位决定了使用的计数系统,而另一个成员则决定了字段宽度。通过使用控制符,控制显示整数时使用的计数系统。通过使用ios_base的成员函数,控制字段宽度和小数位数。由于ios_base类是ostream的间接基类,因此可以将其方法用于ostream对象,如cout。
注:ios_base类中的成员和方法以前位于ios类中,现在,ios_base是ios的基类。在新系统中,ios是包含char和wchar_t具体化的模板,而ios_base包含非模板特性。

设置小数整数时使用的计算系统。要控制整数以十进制、十六进制还是八进制显示,可以使用dec、hex和oct控制符。例如:将cout对象的计数系统格式状态设置为十六进制:

hex(cout);
#include <iostream>
using namespace std;
int main()
{
    cout<<"Enter an integer:";
    int n;
    cin>>n;
    cout<<"n   n*n\n";
    cout<<n<<"  "<<n*n<<"(decimal)\n";

    //十六进制
    cout<<hex;
    cout<<n<<"   ";
    cout<<n*n<<"(hexadecimal)\n";

    //八进制
    cout<<oct<<n<<"   "<<n*n<<" (octal)\n";

    //调用操作器的替代方法
    dec(cout);
    cout<<n<<"   "<<n*n<<" (decimal)\n";
    return 0;
}

在这里插入图片描述
注:虽然控制符实际上是函数,但它们通常的使用方式为:

cout<<hex;

ostream类重载了<<运算符,使得上述用法与函数调用hex(cout)等价。控制符位于名称空间std中。

调整字符长度

使用width成员函将长度不同的数字放到宽度相同的字段中,方法:

int width();//返回字段长度当前位置
int width(int i);//将字段设置为i个空格,并返回以前的字段宽度值

width()方法只影响将显示的下一个项目,然后字段宽度将恢复默认值。:

cout<<'#';
cout.width(12);
cout<<12<<"#"<<24<<"#\n";

由于width()是成员函数,因此必须使用对象(指cout)来调用它。输出如下:

#      12#24#

width()方法只影响接下来显示的一个项目,然后字段宽度将恢复为默认值。
c++不会截断数据,因此如果试图在宽度为2的字段打印一个7位值,c++将增宽字段,以容纳该数据(在有些语言中,如果数据长度与字段宽度不匹配,将用星号填充字段。C/C++的原则是:显示所有的数据比保持列的整洁更重要,c++视内容重与形式)。

#include <iostream>
using namespace std;
int main()
{
    int w =cout.width(30);
    cout<<"defautl filed witdth="<<w<<":\n";
    cout.width(5);
    cout<<"N"<<":";
    cout.width(8);
    cout<<"N*N"<<":\n";
    for(long i=1;i<=100;i*=10)
    {
        cout.width(5);
        cout<<i<<':';
        cout.width(8);
        cout<<i*i<<":\n";
    }
    return 0;
}

在这里插入图片描述

填充字符

在默认情况下,cout用空格填充字段中未被使用的部分,可以用fill()成员函数来改变填充字符。对于检查打印结果,防止接收放添加数字很有用。例如:下面函数调用填充字符改为星号:

cout.fill('*');
#include <iostream>
using namespace std;
int main()
{
  cout.fill('*');
  const char * staff[2]={"waldo whipsnade","wilmarie wooper"};
  long bonus[2]={900,1350};
  for(int i=0;i<2;i++)
      {
      cout<<staff[i]<<":$";
      cout.width(7);
      cout<<bonus[i]<<"\n";
  }
  return 0;
}

在这里插入图片描述

设置浮点数的显示精度

浮点数精度的含义取决于输出模式。在默认情况下,它指的是显示的总位数。在定点模式和科学模式下,精度指的是小数点后面的位数。c++的默认精度为6位。precision()成员函数使得能够选择其他值。例如:
下面语句将cout的精度设置为2:

cout.precision(2);

和width()的情况不同,但与fill()类似,新的精度设置将一直有效,直到被重新设置。

#include <iostream>
using namespace std;
int main()
{
  float price1=20.40;
  float price2=1.9+8.0/9.0;

  cout<<"\"Furry Friends\" is $"<<price1<<"!\n";
  cout<<"\Fiery Fiends\" is $"<<price2<<"!\n";

  cout.precision(2);
  cout<<"\"Furry Friends\" is $"<<price1<<"!\n";
  cout<<"\Fiery Fiends\" is $"<<price2<<"!\n";
  return 0;

}

在这里插入图片描述

打印末尾的0和小数点

ios_base类提供了一个setf()函数,调用cout小数末尾小数点:

cout.setf(ios_base::showpoint);//showpoint是ios_base类声明中定义的类级静态常量。在外面使用必须加上作用域(::)。因此iso_base::showpoint指的是在ios_base类中定义的常量。

使用默认的浮点数时,上述语句还将导致末尾的0被显示出来。例如:如果使用默认精度(6位)时,cout不会将2.00显示为2,而是将它显示为2.000000。

#include <iostream>
using namespace std;
int main()
{
  float price1=20.40;
  float price2=1.9+8.0/9.0;

  cout.setf(ios_base::showpoint);
  cout<<"\"Furry Friends\" is $"<<price1<<"!\n";
  cout<<"\Fiery Fiends\" is $"<<price2<<"!\n";

  cout.precision(2);
  cout<<"\"Furry Friends\" is $"<<price1<<"!\n";
  cout<<"\Fiery Fiends\" is $"<<price2<<"!\n";
  return 0;

}

在这里插入图片描述

再谈setf()

ios_base类有一个受保护的数据成员,其中的标记分别控制着格式化的各个方面,例如计算系统,是否显示未尾的0等。打开一个标记称为设置标记(或位),并意味着相应的位被设置为1。例如,hex、dec和oct控制符调整计数系统的3个标记位。setf()函数提供了另一个调整标记的途径:

fmtflags setf(fmtflags);

fmtflags是bitmask类型的typedef名,用于存储格式标记。该名称是在iso_base类中定义的。这个版本的setf()用来设置单个控制的格式信息。参数是个fmtflags值,指出设置哪一位。返回值是类型为fmtflags的数字,指出所有标记以前的设置。如果打算以后恢复原始设置,则可以保存这个值。iso_base类定义了代表位值的常量。例:
在这里插入图片描述
注:bitmask类型是一种用来存储各个位值的类型。可以是整型、枚举,也可以是STLbitset容器。这里的主要思想是,每一位都是可以单独访问的,都有自己的含义。iostream软件包使用bitmask来存储状态信息。

使用格式常量必须加上作用域运算符(::)。

#include <iostream>
using namespace std;
int main()
{
  int temperature=63;
  cout<<"Today's water tempersture:";
  cout.setf(ios_base::showpos);
  cout<<temperature<<endl;

  cout<<"For our programming friends,that's\n";
  cout<<hex<<temperature<<endl;
  cout.setf(ios_base::uppercase);
  cout.setf(ios_base::showbase);
  cout<<"or\n";
  cout<<temperature<<endl;
  cout<<"How"<<true<<"! oops--How";
  cout.setf(ios_base::boolalpha);
  cout<<true<<"!\n";
  return 0;
}

在这里插入图片描述

第二个setf()原型接受两个参数,并返回以前的设置:

fmtflages setf(fmtflags,fmtflags);

使用函数setf()时,要用第二个参数指出要清除那些位,用第一位参数指出要设置哪位。ios_base类为此定义了常量。具体说,要修改基数,可以将常量ios_base_basefield用作第二参数,将ios_base::hex用作第一参数。也就是说,下面的函数调用与使用十六进制控制符的作用相同:

cout.setf(ios_base:;hex,ios_base::basefield);

在这里插入图片描述
在这里插入图片描述
ios_base类定义了可按这种方式处理的3组格式标记。每组标记都由一个可用作第二个参数的常量和两三个可用作第一参数的常量组成。第二参数清除一批相关的位,然后第一参数将其中一位设置为1。

在c++标准中,定点表示法和科学表示法都有下面两个特征:
1.精度指的是小数位数,而不是总位数。
2.显示末尾的0。

#include <iostream>
#include<cmath>
using namespace std;
int main()
{
    cout.setf(ios_base::left,ios_base::adjustfield);
    cout.setf(ios_base::showpos);
    cout.setf(ios_base::showpoint);
    cout.precision(3);


    ios_base::fmtflags old=cout.setf(ios_base::scientific,ios_base::floatfield);
    cout<<"Left Justification:\n";
    long n;
    for(n=1;n<=41;n+=10)
    {
        cout.width(4);
        cout<<n<<"|";
        cout.width(12);
        cout<<sqrt(double(n))<<"|\n";

    }

    cout.setf(ios_base::internal,ios_base::adjustfield);
    cout.setf(old,ios_base::floatfield);

    cout<<"Internal Justification:\n";
    for(n=1;n<=41;n+=10)
    {
        cout.width(4);
        cout<<n<<"|";
        cout<<sqrt(double(n))<<"|\n";
    }
    cout.setf(ios_base::right,ios_base::adjustfield);
    cout.setf(ios_base::fixed,ios_base::floatfield);
    cout<<"Right Justification:\n";
    for(n=1;n<=41;n+=10)
    {
        cout.width(4); 
        cout<<n<<"|";
        cout.width(12);
        cout<<sqrt(double(n))<<"|\n";
    }
    return 0;

 }

在这里插入图片描述

标准控制法

使用setf()不是进行格式化的、对用户最友好的方法,c++提供了多个控制法,能够调用setf(),并自动提供正确的参数。前面介绍过dec、hex和oct,这些控制法(多数都不适合老式c++实现)的工作方式都与hex相似。例如:下面的语句打开左对齐和定点选项:

cout<<left<<fixed;

下表列出这些控制法以及其他一些控制法:

在这里插入图片描述

在这里插入图片描述

头文件iomanip

为了方便设置一些格式值,c++在头文件iomanip中提供了其他一些控制符,表示起来更方便。三个最常用的控制符分别是setprecision()、setfill()和setw(),分别来设置精度、填充字符和字段宽带。与前面讨论的控制符不同是:

  1. 三个控制符带参数。
  2. setprecision()控制符接受个指定精度的整数参数。
  3. setfill()控制符接受个指定填充字符的char参数。
  4. setw()控制符接受个指定字段的整数参数。
  5. 都是控制符可以用cout语句连接起来。
#include <iostream>
#include<iomanip>
#include<cmath>
using namespace std;

int main()
{
    //使用命名空间操作
    cout<<fixed<<right;//使用fixed控制符导致显示末尾的0

    //使用iomanip头文件操作
    cout<<setw(6)<<"N"<<setw(14)<<"square root"
       <<setw(15)<<"fourth root\n";
    double root;
    for(int n=10;n<=100;n+=10)
    {
        root=sqrt(double(n));
        cout<<setw(6)<<setfill('.')<<n<<setfill(' ')
           <<setw(12)<<setprecision(3)<<root
          <<setw(14)<<setprecision(4)<<sqrt(root)
         <<endl;
    }
    return 0;
}

在这里插入图片描述

使用cin进行输入

cin对象将标准输入表示为字节流。通过键盘获得字符流。cin从对象从输入流中抽取字符,输入的字符可以是int值、float值,也可以是其他类型。抽取还涉及了其他类型转换,cin对象根据接受值的变化量的类型,使用其方法将字符序列转换所需的类型.。

通常,这样使用cin:

cin>>value_holder;

其中,value_holder为存储输入的内存单元,可以是变量、引用、被解除引用的指针,也可以是类或结构的成员。cin解释输入的方式取决于value_holder的数据类型。istream类(在iosatream头文件中定义)重载了抽象运算符>>,使之能够识别下面这些基本类型:

  1. signed char &;
  2. unsigned char &;
  3. char &;
  4. short &;
  5. unsigned short &;
  6. int &;
  7. unsigned int &;
  8. long &;
  9. unsigned long &;
  10. long long &(c++11);
  11. unsigned long long&(c++11);
  12. float &;
  13. double &;
  14. long double &;

这些运算符函数被称为格式化输入函数,因为它们可以将输入数据转换为目标指定的格式。

典型的运算符函数的原型如下:

istream & operator>>(int &);

参数和返回值都是引用。意味着下面这样的语句将导致operator>>()函数处理变量staff_szie本身,而不是像常规参数那样处理它的副本:

cin>>staff_size;
 

由于参数类型为引用,cin能够直接修改用作参数的变量的值。例如,上述语句将直接修改变量的staff_size的值。

抽取运算符的类型转换,对于各种类型的参数,抽取运算符将字符输入转换为指定类型的值。例如:
假设staff_size的类型为int,则编译器将:

cin>>staff_szie;

与下面原型匹配:

istream & operator>>(int &);

上述原型的函数将读取发送给程序的字符流(假设为字符2、3、1、8和4)。使用2字节的int,函数将把这些字符转换为整数23184的2字节二进制表示;如果staff_size的类型为double,则使用operator>>(double &)将上述输入转换为值23184.0的8字节浮点表示。

注:可以将hex、oct和dec控制符与cin一起使用,来指定十六进制、八进制还是十进制。例如:将输入12或0x12解释为十六进制的12或十进制的18。而将ff或FF解释为十进制的255:

cin>>hex;

istream类还为下列字符指针类型重载了>>抽取运算符:

  1. signed char*;
  2. char *;
  3. unsigned char*;

对于这种类型的参数,抽取运算符将读取输入中的下一个单词,将它放置到指定的位置,并加上一个空值字符,使之成为一个字符串。

每个抽取运算符都返回调用对象的引用,这使得能够将输入拼接起来,就像拼接那样:

char name[20];
float fee;
int group;
cin>>name>>fee>>group;

其中,cin>>name返回的cin对象成了处理fee的对象。

cin>>如何检查输入

不同版本的抽取运算符查看输入流的方法相同的。它们跳过空白(空格、换行符和制表符),直到遇到非空白字符。即使对于单字符模式(参数类型为char、unsigned char或signed char),情况也是如此。但C语言的字符输入函数,却不一样。在单字符模式下,>>运算符将读取该字符,将它放置到指定的位置。在其他模式下,>>运算符将读取指定类型的数据。也就是说,它读取从非空白字符开始,到与不匹配第一个字符之间内容。

在这里插入图片描述
例如:对于下面的代码:

int n;
cin>>n;

输入字符:-123Z;读取到3,因为都是整数。Z字符不是有效字符,所以最后一个可接受字符是3。Z保留在输入流,下一个cin语句将从这里开始读取。同时,运算符将字符序列-123转换为一个整数值,并将它赋给n。
如果输入Zcar,而不是-123Z。这时抽取运算符将不会修改n的值,并返回0。返回值false让程序能够检查输入是否满足要求。

#include <iostream>
using namespace std;

int main()
{
  cout<<"Enter numbers:";
    int sum=0;
    int input;
    while(cin>>input)
    {
        sum+=input;
    }
    cout<<"Last value entered="<<input<<endl;
    cout<<"Sum="<<sum<<endl;
    return 0;
}

在这里插入图片描述
由于输入被缓冲。键盘输入的第二行在按回车之前,不会被发送给程序。然而,循环在字符Z处停止了对输入的处理,因此它不与任何一种浮点格式匹配。输入与预期格式不匹配反过来将导致表达式cin>>operator的结果为false,因此while循环被终止。

流状态

下面列出了改变流状态的ios_base方法。
在这里插入图片描述

设置状态、:

上表中两种方法——clear()和setstate()很相似。都重置状态,DNA采取的方式不同。
clera():将状态设置为它的参数。所以下面的调用将使用默认参数0,这将清除全部3个状态位(eofbit、badbit和failbit):

clear();

下面的调用状态设置为eofbit;另外两个状态被清除:

clear(eofbit);

而setstate()方法只影响其参数中已设置的位。因此不会影响其他:

setstate(eofbit);

因此,如果faibit被设置,则仍将被设置。

设置状态流原因:在输入不匹配或到达文件尾时,需要使用不带参数的clear()重新打开输入。setstate()的主要用途是为输入和输出函数提供一种修改状态的途径。例如:如果num是个int,则下面的调用将可能导致operator>>(int &)使用setstate()设置faibit或eofbit:

cin>>num;
I/O和异常

可以使用exceptions()方法来控制异常如何被处理。exceptions()方法返回一个位字段,它包含3位,分别对应eofbit、failbit和badbit。修改流状态涉及clear()或setstate(),这都使用clear()。修改流状态后,clear()方法将当前的流状态与exceptions返回的值进行比较。如果返回值中某一位倍设置,而当前状态中的对应位也被设置,则lclear()将引发ios_base::failure异常。如果两个值都设置了badbit,将发生这种情况。如果exceptions()返回goodit,则不会引发异常。

exceptions()的默认设置为goodbit,没有引发异常。但重载的exceptions(iostate)函数使得能够控制其行为:

cin.exceptions(badbit);//设置badbit会引发异常

位运算符OR使得能够指定多位。例如,如果badbit或eofbit随后被设置,下面语句将引发异常:

cin.exception(badbit | eofbit);
#include <iostream>
#include<exception>
using namespace std;

int main()
{
  cin.exceptions(ios_base::failbit);
  cout<<"Enter numbers:";
  int sum=0;
  int input;
  try
  {
      while(cin>>input)
      {
          sum+=input;
      }
  }catch(ios_base::failure &bf)
  {
      cout<<bf.what()<<endl;
      cout<<"O! the horror!\n";
  }
  cout<<"Last value entered ="<<input<<endl;
  cout<<"Sum="<<sum<<endl;
  return 0;
}

在这里插入图片描述

流状态的影响

只有在流状态良好的情况下,下面的测试才返回true:

while(cin>>input)

如果失败,可以使用上面的成员函数来判断可能的原因。例如,将程序修改为:

while(cin>>input)
{
	sum+=input;
	}
	if(cin.eof())
	cout<<"Loop terminated because EOF encount\n";

设置流状态有个非常重要的后果:流将对后面的输入或输出关闭,直到位被清除。下面代码不行:

while(cin>>input)
{
sum+=input;
}
cout<<"Last value entered="<<input<<endl;
cout<<"Sum="<<sum<<endl;
cout<<"Now enter a new number:";
cin>>input;//不行

如果希望程序在流状态位被设置后读取后面的输入,必须将流状态重置为良好。这可以通过调用clear()方法实现:

while(cin>>input)
{
sum+=input;
}
cout<<"Last value entered ="<<input<<endl;
cout<<"Sum="<<sum<endl;
cout<<"Now enter a new number:";
cin.clear();//重置流状态
while(!isspace(cin.get()))
continue;//去掉不好的输入
cin>>input;//可以

这样不足重新设置流状态。导致输入循环终止的不匹配输入仍留在输入队列中,程序必须跳过它。方法1:一直读取字符,直到到达空白为止。isspace()函数是个cctype函数,在参数是空白字符返回true。方法2:丢弃行中的剩余部分,而不仅仅是下一个单词:

while(cin.get() !='\n')
continue;//去掉剩余的部分

这个例子假设循环由于不恰当的输入而终止。可以使用fail()非法检测假设是否正确,来修复问题。fail()在failbit或eofbit被设置时返回true,因此代码必须排除后一种情况,比如下面:

while(cin>>input)
{
sum+=input;
}
cout<<"Last value entered="<<input<<endl;
cout<<"Sum="<<sum<<endl;
if(cin.fail() && !cin.eof())//输入不匹配失败
{
cin.clear();//重置流状态
while(!isspace(cin.get()))
continue;//去掉不好的输入
}
else
{
cout<<"I cannot go on!\n";
exit(1);
}
cout<<"Now enter a new number:";
cin>>input;//可以

其他istream类方法

get()和getline()方法,它们提供下面的输入功能:
1.get(char&)和get(void)提供不跳过空白的单字符输入功能,get(void)还读取空白。
2.函数get(char*,int,char)和getline(char*,int,char)在默认情况下读取整行而不是一个单词。
它们倍称为非格式化输入函数,因为它们只是读取字符输入,而不会跳过空白,也不进行数据转换。

1.单字符输入
在使用char参数或没有参数的情况下,get()方法读取下一个输入字符,即使该字符是空格、制表符或换行符。get(char & ch)版本将输入字符赋给其参数,而get(void)版本将输入字符转换为整型(int)并返回。
在这里插入图片描述

2.采用哪种单字符输入形式

假设可以选择>>、get(char&)或get(void)应使用哪个?

  1. 行为跳出空白,则使用抽取运算符>>。
  2. 希望程序检查每个字符,使用get()方法。在get()方法中,get(char&)的接口更佳。get(void)优点是,与标准C语言中的getchar()函数极其类似,意味着可以通过包含iostream(而不是stdio.h),并用cin.get()替换所有的getchar(),用cout.put(ch)替换所有的putchar(ch),来将C程序转换为C++程序。

3.字符串输入

  1. getline()。
  2. get()。
  3. ignore()。

下面程序演示getline()和get()是如何工作的,还介绍ignore()成员函数。该函数接受两个参数:一个是数字,指定要读取的最大字符数;另一个时数字,用作输入分解符。

#include <iostream>
#include<exception>
using namespace std;
const int Limit=255;

int main()
{
  char input[Limit];
  cout<<"Enter a string for getline() processing:\n";
  cin.getline(input,Limit,'#');
  cout<<"Here is your input:\n";
  cout<<input<<"\nDone with phase1\n";

  char ch;
  cin.get(ch);
  cout<<"The next input character is"<<ch<<endl;

  if(ch !='\n')
      cin.ignore(Limit,'\n');//读取并丢弃剩余的行,直到第一个换行符

  cout<<"Enter a string for get() processing:\n";
  cin.get(input,Limit,'#');
  cout<<"Here is your input:\n";
  cout<<input<<"\nDone with phase 2\n";

  cin.get(ch);
  cout<<"The next input character is"<<ch<<endl;
  return 0;
}

在这里插入图片描述
注:ignore()两个参数的默认值为1和EOF,该函数的返回类型为istream &:

istream & ignore(int =1,int =EOF);

默认参数值EOF导致ignore读取指定数目的字符或读取到文件尾。该函数返回调用对象,使得能够拼接函数调用:

cin.ignore(255,'\n').ignore(255,'\n');

第一个ignore()读取并丢弃一行,第二个读取并丢弃另一行,因此一共读取了两行。

4.意外字符串输入

get(char*,int)和getline()的某些输入形式将影响流状态。与其他输入函数一样,这两个函数在遇到文件时将设置eofblt,遇到流被破坏时将设置badbit。另外两种特殊情况是无输入以及输入到达或超过函数调用的最大字符数。
在这里插入图片描述

其他istream方法

istremam方法包括:read()、peek()、gcount()和putback()。

  1. read()函数读取指定数目的字节,并将它们存储在指定的位置。
  2. peek()函数返回输入中的下一个字符,但不抽取输入流中的字符。也就是它能够查看下一个字符。
  3. gcount()方法返回最后非格式化抽取方法读取的字符数。意味着字符是由get()、getline()、ignore()或read()方法读取,不是由抽取运算符>>读取的,抽取运算符对输入进行格式化,使之与特定的数据类型匹配。
  4. putback()函数将一个字符插入到输入字符串中,被插入的字符将是下一个输入语句读取的第一个字符。
#include <iostream>
using namespace std;


int main()
{
    char ch;
    while(cin.get(ch))//达到文件尾时,将返回false
    {
        if(ch!='#')
            cout<<ch;
        else
        {
            cin.putback(ch);//重新插入字符
            break;
        }
    }
    if(!cin.eof())
    {
        cin.get(ch);
        cout<<endl<<ch<<"is next input characte.\n";
    }
    else
    {
        cout<<"End of file reached.\n";
        exit(0);
    }

    while(cin.peek()!='#')
    {
        cin.get(ch);
        cout<<ch;
    }
    if(!cin.eof())
    {
        cin.get(ch);
        cout<<endl<<ch<<"is next input character.\n";
    }
    else
        cout<<"End of file reached.\n";
    return 0;
 }

在这里插入图片描述

下面程序使用peek()来确定是否读取了整行。如果一行中只有部分内容被加入到输入组中,程序将删除余下内容。

#include <iostream>
using namespace std;

const int SLEN=10;
inline void eatline()
{
    while(cin.get()!='\n')//确定是否为整行将保留换行符
        continue;
}

int main()
{
   char name[SLEN];
   char title[SLEN];
   cout<<"Enter your name:";
   cin.get(name,SLEN);
   if(cin.peek()!='\n')
       cout<<"Sorry,we only have enough room for"
          <<name<<endl;
   eatline();
   cout<<"Dear"<<name<<",enter your title:\n";
   cin.get(title,SLEN);
   if(cin.peek()!='\n')
       cout<<"We were forced to truncate your title.\n";
   eatline();
   cout<<"Name:"<<name
      <<"\nTitle:"<<title<<endl;
   return 0;
 }

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值