1.5 数据输入与输出

程序执行的基本逻辑是:输入数据 → 处理数据 → 输出结果。数据的输入/输出几乎是每个程序不可避免的问题。

1.5.1 流的概念

在C++中,I/O(Input/Output,输入/输出)数据是一些从源设备到目标设备的字节序列,称为字节流。除了图像、声音数据外,字节流通常代表的都是字符,因此,在多数情况下的流(stream)都是从源设备到目标设备的字符序列,如图1-5所示。

图1-5 流示意图

流分为输入流和输出流两类。输入流(input stream)是指从输入设备流向内存的字节序列。例如,在图1-5中,若源设备是键盘,目标设备是内存,则表示的是输入流,表示通过键盘输入数据到内存变量中。输出流(output stream)是指从内存流向输出设备的字节序列。例如,在图1-5中,若源设备是内存,目标设备是显示器,就是输出流,表示把内存中的字符逐个输出到显示屏幕上。

在C++中,标准输入设备通常是指键盘,标准输出设备通常是指显示器。为了从键盘输入数据,或为了将数据输出到显示器屏幕上,程序中必须包含头文件iostream.h。这个头文件包括了输入流istream和输出流ostream两种数据类型,而且用这两种数据类型定义了两个变量“istream cin”和“ostream cout”。其中,cin(读作see-in)用于从键盘输入数据,cout(读作see-out)用于将内存数据输出到显示器。

1.5.2 cin和析取运算符>>

在C++程序中,常用cin输入数据。其用法如下:

cin>>x;

程序执行到cin语句时,就会停下来等待键盘数据的输入,输入数据被插入到输入流中,数据输完后按Enter键结束。当遇到运算符>>时,就从输入流中提取一个数据,存入内存变量x中。

① cin是在iostream.h中预定义的一个标准输入设备(一般代表键盘),>>是析取运算符,用于从输入流中析取数据(即从流中分析和提取数据),并存于其后的变量x中。x是程序中定义的变量名,原则上x应该是系统内置的简单数据类型,如int、char、double等。

② 在一条cin语句中可以同时为多个变量输入数据。在输入数据时应当输入与cin语句中变量个数相同的数据,各输入数据之间用一个或多个空白(包括空格、回车、Tab)作为间隔符,全部数据输入完成后,按Enter键结束。例如,下面的程序段:

int x1;
double x2;
char x3;
cin>>x1>>x2>>x3;

假设x1为5,x2为3.4,x3为'A',则下面的两种输入方式等效:

5 3.4 A

5
3.4
A

当一条cin语句中有多个运算符>>时,就需要从键盘输入多个数据到输入流中,每当遇到一个>>时,就从输入流中提取一个数据存入其后的变量中。

可以把一条cin语句分解为多条cin语句,也可以把多条cin语句合并为一条语句,所以上面的输入语句与下面的语句组等效:

cin>>x1;
cin>>x2;
cin>>x3;

③ 在>>后面只能出现变量名,这些变量应该是系统预定义的简单类型,否则将出现错误。下面的语句是错误的:

cin>>"x=">>x;   //错误,>>后面含有字符串"x="
cin>>12>>x;   //错误,>>后面含有常数12
cin>>'x'>>x;   //错误,>>后面含有字符'x'

④ cin具有自动识别数据类型的能力,析取运算符>>将根据其后变量的类型从输入流中为它们提取对应的数据。例如:

cin>>x;

假设输入数据2,析取运算符>>将根据其后的x的类型决定输入的2到底是数字还是字符。若x是char类型,则2就是字符;若x是int,float之类的类型,则2就是一个数字。

再如,若输入34,且x是char类型,则只有字符3被存储到x中,4将继续保存在流中;若x是int或float,则34就会存储到x中。

⑤ 数值型数据的输入。在读取数值型数据时,析取运算符>>首先略掉数据前面的所有空白符号,如果遇到正、负号或数字,就开始读入,包括浮点型数据的小数点,并在遇到空白符或其他非数字字符时停止。例如:

int x1;
double x2;
char x3;
cin>>x1>>x2>>x3;

假如输入“35.4A”并按Enter键,第1个析取运算符>>根据x1的类型int,从输入流中提取一个整数存储在x1中,这个整数只能是35。因为接下来的是“.”不是整数的有效数字,所以提取x1后,输入流中的数据是“.4A”。第2个析取运算符>>将从输入流中为x2提取数据,x2是double型,所以只能把“.4”存储到x2中,因为接在4后面的A不是有效的数字,所以x2的结果为0.4(0由系统产生);第3个析取运算符>>为x3提取数据,x3是char型,所以字符'A'就被输入到x3中。

这个结果或许不正确,却从另一方面说明了在输入数据时,一定要注意数据之间间隔符的正确输入。结合上述各种情况,来看一个数据输入的综合性例子。

例1-2】 假设有变量定义语句如下:

int a,b;
double z;
char ch;

下面的语句说明数据输入的含义。

语句 输入 内存变量的值

1 cin>>ch;  A  ch='A'
2 cin>>ch;  AB  ch='A',而'B'被保留在输入流中等待被读取
3 cin>>a;  32  a=32
4 cin>>a;  32.23 a=32,后面的.23被保留在输入流中等待被读取
5 cin>>z;  76.21 z=76.21
6 cin>>z;  65  z=65.0
7 cin>>a>>ch>>z 23 B 3.2 a=23,ch='B',Z=3.2
8 cin>>a>>ch>>z 23B3.2 a=23,ch='B',Z=3.2
9 cin>>a>>b>>z  23 32 a=23,b=32,计算机等待输入下一个数据存入z
10 cin>>a>>z  2 3.2 24 a=2,z=3.2,而24被保留在输入流中等待被读取
11 cin>>a>>ch  132  a=132,计算机等待输入 ch的值
12 cin>>ch>>a  132  ch='1',a=32

1.5.3 cout和插入运算符<<

在C++程序中,一般用cout输出数据,其用法如下:

cout<<x;

程序执行到cout语句时,将在显示屏幕上把x的值显示出来。x可以是字符串、变量或常量。

cout是在iostream.h中定义的标准输出设备(一般代表显示器),<<是插入运算符,用来将其右边的x的值插入到输出流中(cout是流向的目的地,所以最终是把x显示在屏幕上)。

(1)输出字符类型的数据

字符类型数据包括字符常量、字符串常量、字符变量和字符串变量。对于字符常量和字符串常量,cout将把它们原样输出在屏幕上;对于字符变量和字符串变量,cout将把变量的值输出到显示屏幕上。例1-3是一个字符输出示例程序。

例1-3】 用cout输出字符数据。

//Eg1-3.cpp
#include<iostream.h>
void main()
{
     char ch1='c';
     char ch2[]="Hellow C++!";
     cout<<ch1;
     cout<<ch2;
     cout<<"C";
     cout<<"Hellow everyone!";
}

程序的运行结果如下(这个结果是由程序中的4条cout语句共同输出的):

cHellow C++!CHellow everyone!

(2)连续输出

cout语句能够同时输出多个数据,其用法如下:

cout<<x1<<x2<<x3<<…;

其中,x1,x2和x3可以是相同或不同类型的数据,此命令将依次把x1、x2和x3的值输出到显示屏幕上。

cout的这种格式表明,可以把多条cout语句合并成一条语句。当然,也可以把一条cout语句分解为多条语句。将Eg1-3.cpp程序中的4条cout语句合并成一条命令,不会影响程序的功能,其运行结果完全相同:

cout<<ch1<<ch2<<"C"<<"Hellow everyone!";

与C语言一样,在C++程序中也可以将一条命令写在多行上。例如,上面的语句也可写成下面的形式:

cout<<ch1
    <<ch2
    <<"C"
    <<"Hellow everyone!";

(3)输出换行

例1-3的输出结果并不清晰,如果能够把它们输出在多行上,效果会更好。在cout语句中,可以通过输出换行符“\n”或endl操纵符将输出光标移动到下一行的开头处。例1-4是对例1-3的改写。

例1-4】 在例1-3的输出语句中增加换行符。

//Eg1-4.cpp
#include<iostream.h>
void main(){
     char ch1='c';
     char ch2[]="Hellow C++!";
     cout<<ch1<<endl;
     cout<<ch2<<"\n";
     cout<<"C"<<endl;
     cout<<"Hellow everyone!\n";
}

本程序的输出如下:

c
Hellow C++!
C
Hellow everyone!

endl和“\n”具有相同的功能,它们可以出现在cout语句中任何位置的<<的后面。“\n”还可以直接放在字符串常数的后面,如语句“cout<<"Hellow everyone!\n"”最后的“\n”。

(4)输出数值类型的数据

数值型常量数据可以利用cout直接输出,例如:

cout<<1<<2<<3<<endl;

将在屏幕上显示:123。数值变量的输出也是如此,如下面的程序段:

int x1=23;
float x2=34.1;
double x3=67.12;
cout<<x1<<x2<<x3<<900;

其中的cout语句将在屏幕上输出“2334.167.12900”。

从上面两条输出语句的结果可以看出:cout在输出多个数据时,不会在数据之间插入任何间隔符,其结果是使输出数据变得含混不清,如数值1,2,3被输出成了123。

针对这种情况,需要在cout输出语句中添加一些数据间隔符。例如,可将上面的语句改写为:

cout<<1<<" "<<2<<" "<<3<<endl;
cout<<"x1="<<x1<<" "<<"x2="<<x2<<" "<<"x3="<<x3<<endl<<900<<endl;

下面是这两条语句的输出结果,显然它比前面的输出结果更清晰。

1  2  3
x1=23  x2=34.1 x3=67.12
900

1.5.4 输出格式控制符

在程序运行过程中,常常需要按照一定的格式输出其运行结果,如设置数值精度、设置小数点的位置、设置输出数据宽度或对齐方式……数据输出格式的设置是程序设计的一个重要内容,影响到程序结果的清晰性。

C++提供了许多控制数据输入输出格式的函数和操纵符(也称为操纵函数或操纵算子),如setprecision、setw、right等,它们都是在iomanip.h中定义的,应用它们时要包含该头文件。

(1)设置浮点数的精度

在需要设置输出数据的精度时,可以用操纵函数setprecision()。其用法如下:

setprecision(n)

其中,n代表有效数位,包括整数的位数和小数的位数。如setprecision(3)将所有数值的输出精度都指定为3位有效数字,直到再次用setprecision()改变输出精度为止。setprecision()是在iomanip.h中定义的,在使用时要包含该头文件。例如,语句

cout<<setprecision(3)<<3.1415926<<"  "<<2.4536<<endl;

将输出“3.14 2.45”。

(2)设置输出域宽和对齐方式

在C++中,可以用操纵函数setw()设置输出数据占用的列数(域宽,即占用的字符个数)。setw()的用法如下:

setw(n)

其中,n是输出数据占用屏幕宽度的字符个数,在默认情况下,输出数据按右对齐。若输出数据的位数比n小,则左边留空。若输出数据的实际位数比n大,则输出数据将自动扩展到所需占用的列数。例如:

cout<<"1234567812345678"<<endl;   //L1
cout<<setw(8)<<23.27<<setw(8)<<78<<endl;  //L2
cout<<setw(8)<<"Abc"<<78<<endl;   //L3

上述语句的输出结果如下:

1234567812345678
   23.27 78
    Abc78

setw()只对紧随其后的一个输出数据有效,语句L3中的setw(8)只对跟在其后的字符串"Abc"有效,所以最后的“78”就按默认方式输出,紧接在"Abc"的后边。

(3)设置对齐方式

操纵函数setiosflags()和resetiosflags()可用于设置或取消输入/输出数据的各种格式,包括改变数制基数、设置浮点数的精度、转换字母大小写、设置对齐方式等。它们的用法如下:

setiosflags(long f);
resetiosflags(long f);

在iostream.h中还定义了两个表示对齐方式的常数,表示左对齐的常数值是ios::left,表示右对齐的常数值是ios::right,它们可作为setiosflags( )和resetiosflags( )操纵符的参数,用于设置输出数据的对齐方式。

在默认方式下,C++按右对齐方式输出数据。当用setiosflags()设置输出对齐方式成功后,将一直有效,直到用resetiosflags()取消它。

例1-5】 用setiosflags()和resetiosflags()设置和取消输出数据的对齐方式。

//Eg1-5.cpp
#include<iostream.h>        //L1
#include<iomanip.h>      //L2
void main(){         //L3
  cout<<"123456781234567812345678"<<endl;      //L4
  cout<<setiosflags(ios::left)<<setw(8)<<456<<setw(8)<<123<<endl; //L5
  cout<<resetiosflags(ios::left)<<setw(8)<<123<<endl;     //L6
}

这个程序的输出结果如下:

123456781234567812345678
456     123
     123

输出结果的第1行是语句行L4输出的;第2行是语句行L5输出的,输出的两个数据各占8位,且设置了左对齐方式;第3行是语句行L6的输出,输出数据占8位,由于在输出之前用resetiosflags(ios::left)操纵符取消了左对齐,使数据输出又成了默认的右对齐方式,所以输出数据的左边留了5个空白。

1.5.5 数制基数

C++在iostream.h中预定义了hex、oct、dec等操纵符。分别表示十六进制数、八进制数和十进制数。在默认方式下,C++按照十进制数形式输入、输出数据。当要按其他进制输入、输出数据时,就需要在cin和cout语句中指定数据的基数。在用键盘输入数据时:

十进制整数:直接输入数据本身,如78。

十六进整数:在要输入的数据前加0x或0X,如0x1A(对应的十进制数是26)。

八进制整数:在输入的数据前加0,如043(代表十进制数35)。

例1-6】 输入、输出不同进制的数据。

//Eg1-6.cpp
#include<iostream.h>
void main(){
  int x=34;
  cout<<hex<<17 <<" "<<x<<" "<<18<<endl;
  cout<<17 <<" "<<oct <<x<<" "<<18<<endl;
  cout<<dec<<17 <<" "<<x<<" "<<18<<endl;
  int x1, x2, x3, x4;
  cout<<"输入 x1(oct), x2(oct), x3(hex), x4(dec):"<<endl;
  cin>>oct>>x1;    //八进制数
  cin>>x2;     //八进制数
  cin>>hex>>x3;    //输入十六进制数
  cin>>dec>>x4;    //输入十进制数
  cout<<"x1="<<x1<<"\tx2="<<x2<<"\tx3="<<x3<<"\tx4="<<x4<<endl;
}

设置数制基数后,它将一直有效,直到遇到下一个基数设置。本程序运行结果如图1-6所示。

图1-6 程序运行结果

其中,第1行和第2行的11是十六进制数,第2行的42和22是八进制数,第3行是十进制数。第5行是从键盘输入的数据,013、034是八进制数,0x2a是十六进制数,18是十进制数。最后一行是按十进制输出的数据。