参考书目:《C++面向对象程序设计》—— 谭浩强 《C++程序设计:思想与方法》—— 翁惠玉
在C++中,输入输出是通过流完成的。C++的输出操作将一个对象的状态转换成一个字符序列,输出到某个地方。输入操作则是从某个地方接收一个字符序列,然后将其转换成一个对象的状态所要求的格式。
把接收存放输出数据的地方叫做目标,把输入数据来自的地方叫做源,输入和输出操作可以看成字符序列在源、目标和对象之间的流动。执行输入和输出操作的类体系叫做流类,提供流类实现的系统叫做流类库。
注:此图是简化的流类库的基本类等级图,而不是直接的继承关系图。其实它们都是模板类,箭头表示的是类等级关系。
这个等级关系在头文件iostream.h中说明。在图中,ios类中一个指针成员指向streambuf类的对象。Streambuf类管理流的缓冲区。由于数据隐蔽和封装的需要,普通用户只使用ios、istream和ostream类提供的公有接口,完成流的提取和插入操作。
ios类是istream类和ostream类的虚基类,提供对流的格式化I/O操作和错误处理的成员处理的成员函数。从ios类公有派生istream类和ostream类分别提供对流进行提取和插入操作的成员函数,而iostream类通过组合istream类和ostream类支持对一个流进行双向操作,它并没有提供新成员函数。
iostream中预定义四个流对象,它们分别是cin、cout、cerr、clog。事实上可以将cin视为istream的一个对象,而cout视为ostream的一个对象。流是一个抽象的概念,当实际进行I/O操作时,必须将流和一个具体的物理设备连接起来。
C++的流类库预定义的四个流所连接的设备如表所示:
流 | 设备 |
---|---|
cin | 标准输入设备 |
cout | 标准输出设备 |
cerr | 标准错误输出设备(非缓冲方式) |
clog | 标准错误输出设备(缓冲方式) |
与iostream类库有关的头文件:iostream类库中不同的类的声明被放在不同的头文件中,用户在程序中用#include命令包含所需的头文件就相当于在程序中声明了所需的类。
iostream 输入输出流操作
fstream 管理文件的I/O操作
strstream 字符流的I/O操作
stdiostream 混合使用C和C++
iomanip 使用格式化I/O操作
注:已在iostream头文件中重载了>> 和<<运算符。用于标准类型数据的输入和输出。
ostream类定义了cout、cerr和clog三个流对象。
例:编写程序,从键盘输入a,b,c的值求解一元二次方程 ax2+bx+c=0。如果a=0或判别式的值b2-4ac<0,输出出错信息。
int main()
{
float a, b, c, disc;
cout << "请输入 a,b,c:";
cin >> a >> b >> c;
if (a == 0)
cerr << "a 等于0,错误!" << endl;
else if ((disc = b * b - 4 * a*c) < 0)
cerr << "判别式b*b-4*a*c<0"<<endl;
else
{
cout << "x1=" << (-b + sqrt(disc)) / (2 * a) << endl;
cout << "x2=" << (-b - sqrt(disc)) / (2 * a) << endl;
}
return 0;
}
例:用控制符控制输出格式。
#include
#include
#include
using namespace std;
int main()
{
int a;
cout << "input a:";
cin >> a;
cout << "dec:" << dec << a << endl;
cout << "hex:" << hex << a << endl;
cout << "oct:" << setbase(8) << a << endl;
char *pt = (char *)"China";
cout << setw(10) << pt << endl;
cout << setfill('*') << setw(10) << pt << endl;
double pi = 22.0 / 7.0; cout << setiosflags(ios::scientific) << setprecision(8);
cout << "pi=" << pi << endl;
cout << "pi=" << setprecision(4) << pi << endl;
cout << "pi=" << setiosflags(ios::fixed) << pi << endl;
return 0;
}
用于控制输出格式的常用成员函数见表7.4:
流成员函数setf和控制符setiosflags括号中参数是格式标志,在类ios中定义它是枚举值。所以在引用这些格式标志时要以ios::开始,格式标志列于表7.5:
例:用流控制成员函数输出数据。
int main()
{
int a = 21;
cout.setf(ios::showbase);
cout << "dec:" << a << endl;
cout.unsetf(ios::dec);
cout.setf(ios::hex);
cout << "hex:" << a << endl;
cout.unsetf(ios::hex);
cout.setf(ios::oct);
cout << "oct:" << a << endl;
char *pt =(char *) "China";
cout.width(10);
cout << pt << endl;
cout.width(10);
cout.fill('*');
cout << pt << endl;
double pi = 22.0 / 7.0;
cout.setf(ios::scientific);
cout << "pi=";
cout.width(14);
cout << pi << endl;
cout.unsetf(ios::scientific);
cout.setf(ios::fixed);
cout.width(12);
cout.setf(ios::showpos);
cout.setf(ios::internal);
cout.precision(6);
cout << pi << endl;
return 0;
}
cin是istream类的对象,从标准输入设备读取数据。流提取运算符>>在流中提取数据时通常跳过流中的空格、tab键、换行符等字符。只有输入回车键时输入的数据才进入键盘缓冲区,形成输入流,提取运算符才能从其中提取数据。
当遇到无效字符(与变量数据类型不一致)或文件结束符时,输入流cin就处于出错状态,此时对cin流的所有操作都被终止。当输入流出错时,cin的值是false,所以可以根据cin的值判断流对象是否处于正常状态。
例:测试cin的值,判断流对象的状态。
int main()
{
float grade;
cout << "enter grade:";
while (cin >> grade)
{
if (grade >= 85)
cout << grade << " GOOD!" << endl;
if (grade < 85 && grade >= 60)
cout << grade << " Just so so!" << endl;
if (grade < 60)
cout << grade << " fail!" << endl;
cout << "enter grade:";
}
cout << "The end." << endl;
return 0;
}
int main()
{
char c;
cout << "enter a sentence:" << endl;
while ((c = cin.get()) != EOF)
cout.put(c);
return 0;
}
int main()
{
char c;
cout << "enter a sentence:" << endl;
while (cin.get(c)) { cout.put(c); }
cout << "end" << endl;
return 0;
}
void main()
{
char ch[20];
cout << "enter a sentence:" << endl;
cin.get(ch, 10, '\n');
cout << ch << endl;
}
int main()
{
char ch[20];
char c1;
cout << "输入一句话:" << endl;
cin >> ch;
cout << "第一次 cin 提取的字符串是:" << ch << endl;
c1 = cin.get();
cout << "第二次 cin.get() 提取的字符串是:" << c1 << endl;
cin.get(c1);
cout << "第三次 cin.get(c1) 提取的字符串是:" << c1 << endl;
cin.get(ch, 20, '/');
cout << "第四次 cin.get( ch, 20, '/') 提取的字符串是:" << ch << endl;
cin.getline(ch, 20, '/');
cout << "第五次 cin.getline (ch,20,'/')提取的字符串是:" << ch << endl;
cin.getline(ch, 20, '/');
cout << "第六次 cin.getline (ch,20,'/')提取的字符串是:" << ch << endl;
return 0;
}
注:cin和cin.getline都具有从键盘缓冲区按指针所指提取字符串的功能。它们有以下区别:
1.cin忽略起始的空白字符;而cin.getline不忽略起始的空白字符。
2.cin当提取到非空白字符后,遇到空白字符时就终止提取,指针就停留在空白字符处;而cin.getline是提取到规定的终止字符或规定的字符个数后终止提取,指针停留在提取的最后一个字符后面相邻的字节。
3.通过对>> 的重载,cin可以提取其他类型的数据;而cin.getline只能输入字符串。
eof()函数
当输入缓冲区的指针遇到文件结束符时函数值为真,否则显假。从键盘用ctrl+z输入文件结束符。
例:从键盘输入字符串,以文件结束符作为结束标志,逐个输出非空格字符。
void main()
{
char c;
while (!cin.eof())
if ((c = cin.get()) != ' ')
//cout.put(c);
cout << c;
}
文件是指存储在存储介质上的数据集合。操作系统把存储介质上的相关数据抽象为文件,用标识符为其取名并由文件系统管理文件。只要用户指出文件名,操作系统就可以按名存取文件信息。根据文件中数据的表示形式,文件分为ASCII文件和二进制文件。ASCII文件就是文本文件,每个字节表示一个字符。二进制文件是把内存中的数据、指令按其在内存的格式存放在磁盘上。
字符信息在内存也是以ASCII码形式存放,所以字符在ASCII码文件和在二进制文件中形式是一样的。对于数值数据,两者是不一样的。如,一个十进制整数100000,用二进制表示时用四个字节;而用ASCII码表示时用六个字节。
文件流是以外存文件为输入输出对象的数据流。输出文件流是从内存流向外存文件的数据流,输入文件流是从外存文件流向内存的数据流。为了弥补访问内存和访问外存的速度差,每个文件流都有一个内存缓冲区。
在C++的I/O类库里定义了几种文件类,专门用于文件的输入和输出操作。
从图中看到C++从标准输入、输出流类派生出三个文件流类。
要对文件进行输入输出,必须定义一个文件流类对象,用对象调用类的成员函数对文件操作。
ASCII码文件也是文本文件,文件中一个字节存放一个字符。对ASCII码文件操作包括向文件写入字符和从文件读取字符。
读写ASCII码文件有用文件流对象与提取、插入运算符和用文件流对象调用类的成员函数put, get, getline 两种方法。
例:定义一个有十个元素的整型数组,从键盘输入十个整数,将它们放入数组,同时用插入运算符将它们写入当前目录下的f1.txt文件。
#include
#include
#include
using namespace std;
int main()
{
int a[10];
ofstream outfile("f1.txt");
if (!outfile)
{
cerr << "open error!" << endl;
exit(1);
}
cout << "enter 10 integer numbers:" << endl;
for (int i = 0; i < 10; i++)
{
cin >> a[i];
outfile << a[i] << " ";
}
outfile.close();
return 0;
}
注:程序中用文件流类ofstream,它是在头文件fstream中定义的,用vc6.0时要把这个头文件包含进来。
二进制文件是按内存中的数据存储形式写入磁盘文件,因此又称为内存数据的映象文件。
对二进制文件操作与对文本文件操作相似的是先定义文件流对象,然后打开文件,使用完要关闭文件。在打开时必须指定文件的存储形式是二进制形式,二进制文件即可以作为输入文件也可以作为输出文件,还可以作为既能输入又能输出的文件。这是与ASCII文件不同的地方。
例:把一批数据以二进制形式写入磁盘文件,并从文件中读数据并显示到屏幕上。
struct student
{ char name[20];
int num;
int age;
char sex;
};
int main()
{
student stud[3]={ "Li", 1001,18,'f', "Fun",1002,19,'m', "Wang",1004,17,'f'};
ofstream outfile("stud.dat",ios::binary);
if(!outfile) { cerr<<"open error!"<<endl; abort(); }
outfile.write( (char *)&stud,sizeof(stud));
outfile.close();
return 0;
}
int main()
{
student stud[3]; int i;
ifstream infile("stud.dat", ios::binary);
if (!infile) { cerr << "open error!" << endl; abort(); }
infile.read((char*)stud, sizeof(stud));
infile.close();
for (i = 0; i < 3; i++)
{
cout << "NO." << i + 1 << endl;
cout << "姓名:" << stud[i].name << endl;
cout << "学号:" << stud[i].num << endl;
cout << "年龄:" << stud[i].age << endl;
cout << "性别:" << stud[i].sex << endl << endl;
}
return 0;
}
例:有五个学生的数据,要求:
1)把它们写入磁盘文件
2)从磁盘文件读第1,3,5学生数据并显示
3)修改第3 个学生的数据并保存到原来位置
4)从磁盘文件读入修改过的5个学生数据并显示
struct student
{
int num; char name[20]; float score;
};
int main()
{
int i;
student stud[5] = { 1001,"Li",85,1002,"Fun",97.5,1004,"Wang",54,1006,"Tan",76.5,1010,"ling",96 };
fstream iofile("stud.dat", ios::in | ios::out | ios::binary);
if (!iofile)
{
cerr << "open error!" << endl; abort();
}
for (i = 0; i < 5; i++)
iofile.write((char *)&stud[i], sizeof(stud[i]));
student stud1[5];
for (i = 0; i < 5; i = i + 2)
{
iofile.seekg(i * sizeof(stud[i]), ios::beg);
iofile.read((char *)&stud1[i / 2], sizeof(stud1[i]));
cout << stud1[i / 2].num << " " << stud1[i / 2].name << " "<< stud1[i / 2].score << endl;
}
cout << endl;
stud[2].num = 1012;
strcpy(stud[2].name, "Wu");
stud[2].score = 60;
iofile.seekp(2 * sizeof(stud[0]), ios::beg);
iofile.write((char *)&stud[2], sizeof(stud[2]));
iofile.seekg(0, ios::beg);
for (i = 0; i < 5; i++)
{
iofile.read((char *)&stud[i], sizeof(stud[i]));
cout << stud[i].num << " " << stud[i].name << " "<< stud[i].score << endl;
}
iofile.close();
return 0;
}
字符串流以内存中用户定义的字符数组(字符串)为输入输出对象,即将数据写入内存数组,或从内存字符数组读取数据。
字符串流也需要缓冲区,读取或写入时,流缓冲区中的数据不断增加,待缓冲区满或遇到换行符时,缓冲区中数据一起写入字符数组或赋予指定变量。
例:在一个字符数组c中存放10个整数,以空格为分隔符,要求将它们放到整型数组中排升序,然后再写入原来的字符数组中。
#include
#include
#include
#include
using namespace std;
int main()
{
char c[50] = "12 34 65 -23 -32 33 61 99 321 32";
int a[10], i, j, t;
cout << "array c:" << c << endl;
istrstream strin(c, sizeof(c));
for (i = 0; i < 10; i++)
strin >> a[i];
cout << "array a:";
for (i = 0; i < 10; i++)
cout << a[i] << " ";
cout << endl;
for (i = 0; i < 9; i++)
for (j = 0; j < 9 - i; j++)
if (a[j] > a[j + 1])
{
t = a[j]; a[j] = a[j + 1]; a[j + 1] = t;
}
ostrstream strout(c, sizeof(c));
for (i = 0; i < 10; i++)
strout << a[i] << " ";
strout << ends;
cout << "array c:" << c << endl;
return 0;
}