输入/输出流
C++中,提供了两套输入/输出方法
与C语言相兼容的输入/输出函数,如
printf
和scanf
函数输入/输出流库ios:可实现类对象数据流的操作
流类和流对象
在C++中,输入/输出是由”流”来处理的
流:数据从一个位置到另一个位置的流动抽象为流
输入流:当数据从键盘或磁盘文件流入到程序中时,这样的流成为输入流
输出流:当数据从程序中流向屏幕或磁盘文件时,这样的流成为输出流
当流被建立后,可以使用一些特性的操作从流中获取数据或向流中添加数据.从流中获取数据成为提取操作,向流中添加数据成为插入操作
- 上图中,
ios
类是用来提供一些关于对流状态进行设置的功能,它是一个虚基类,其他类都是由这个类派生的 streamfuf
不是ios
类的派生类在类ios
中只有一个指针成员,指向streambuf
类的一个对象.streambuf
类是用来为ios类及其派生类提供对数据的缓冲支持- 缓冲:指系统中在主存开辟一个专用的区域来临时存放输入/输出信息,这个区域成为缓冲区
- 刷新:只有当缓冲区满了或当前送入的数据为新的一行时,系统才对流中的数据进行处理,成为刷新.
istream
和ostream
类均是ios
类的公有派生类,前者提供了向流中插入数据的有关操作,后者提供了从流中提取数据的有关操作.iostream
类是istream
和ostream
类公有派生的,仅仅是将istream
和ostream
类综合在一起,提供方便- C++提供了4个预定义的标准流对象(为了方便用户对基本输入/输出流进行操作):
cin
,cout
,cerr
和clog
cin
:用于处理标准输入,即键盘输入cout
:用于处理标准输出,即屏幕输出cerr
和clog
:用来处理标准出错信息,并将信息显示在屏幕上.
- 标准流通常使用插入运算符”<<”和提取运算符”>>”来进行输入/输出操作,而且系统还会自动地完成数据类型的转换.
流的格式控制和错误处理
C++标准的输入/输出流提供了两种格式的控制模式:
使用
ios
类的相关成员函数,如width
,precision
和fill
等可以直接使用的格式操作算子,如
oct
,hex
和dec
等
1.使用格式控制成员函数
在ios
类中控制输入/输出的成员函数有:
函数 | 作用 |
---|---|
int ios::width(); | 返回当前的宽度设置 |
int ios::width(int); | 设置宽度并返回上一次的设置 |
int ios::precision(); | 返回当前的精度设置 |
int ios::precision(int); | 设置精度并返回上一次的设置 |
char ios::fill(); | 返回当前空位填充的字符 |
char ios::fill(char); | 设置空位填充的字符并返回上一次的设置 |
long ios::setf(long); | 设置状态标志并返回上一次的状态标志 |
long ios::unsetf(long); | 消除状态标志并返回上一次的状态标志 |
long ios::flags(); | 返回当前的状态标志 |
long ios::flags(long); | 设置状态标志并返回上一次的状态标志 |
* 状态标志:各个状态值之间通过|
组合而成,在ios
类中是一个公共的枚举类型,各个标志代表的含义如下:
标志 | 含义 |
---|---|
ios::skipws | 跳过输入中的空白符 |
ios::left | 输出数据按输入域左对齐 |
ios::right | 输出数据按输入域右对齐(默认) |
ios::internal | 数据的符号左对齐,数据本身右对齐,符号和数据之间为填充字符 |
ios::dec | 转换为十进制形式 |
ios::oct | 转换为八进制形式 |
ios::hex | 转换为十六进制形式 |
ios::showbase | 输出的数值前面带有基数符号(0或0x) |
ios::showpoint | 显示浮点数的小数点和后面的0 |
ios::showpos | 显示证书前面的”+”号 |
ios::uppercase | 用大写字母(A~F)输出十六进制数 |
ios::scientific | 按科学计数法显示浮点数 |
ios::fixed | 用定点格式显示浮点数 |
ios::unitbuf | 输出操作完成后立即刷新缓冲区 |
ios::stdio | 每次输入操作完成后刷新stdout和stderr |
#include <iostream>
using namespace std;
int main()
{
int nNum = 12345;
double dNum = 12345.6789;
char *str[] = {"This","is","a Test"};
// 设置标志:八进制,显示基,正号
cout.setf(ios::oct|ios::showbase|ios::showpos);
cout << nNum << "\t" << dNum << endl;
// 设置十六进制,科学计数法,大写标志
cout.setf(ios::hex|ios::scientific|ios::uppercase);
cout << nNum << "\t" << dNum << endl;
// 设置填充符号为*
cout.fill('*');
for(int i=0;i<3;i++)
{
cout.width(12);
cout << str[i] << " ";
}
cout << endl;
// 设置标志:左对齐
cout.setf(ios::left);
for(int i=0;i<3;i++)
{
cout.width(12);
cout << str[i] << " ";
}
cout << endl;
return 0;
}
/**
+12345 +12345.7
+12345 +1.234568E+04
********This **********is ******a Test
This******** is********** a Test******
*/
2.使用格式算子
- 格式算子是一个对象,可以直接用插入符或提取符来操作.C++提供的预定义格式的算子如下:
格式算子 | 功能 | 输入(I)/输出(O) |
---|---|---|
dec | 设置为十进制 | I/O |
hex | 设置为十六进制 | I/O |
oct | 设置为八进制 | I/O |
ws | 提取空白字符 | I |
endl | 插入一个换行符 | O |
ends | 插入一个空字符 | O |
flush | 刷新输出流 | O |
setbase(int) | 设置转换基数,参数值可以是0,8,16和10,0代表默认基数 | I/O |
resetiosflags(long) | 取消指定的标志 | I/O |
setiosflags(long) | 设置指定的标志 | I/O |
setfill(int) | 设置填充字符 | O |
setprecision(int) | 设置浮点数的精度 | O |
setw(int) | 设置输出宽度 | O |
* 注意,从resetiosflags
一直到后面的格式算子,需要包含头文件–iomanip
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
int nNum = 12345;
double dNum = 12345.6789;
char *str[] = {"This","is","a Test"};
// 设置标志:八进制,显示基,正号
cout << setiosflags(ios::oct|ios::showbase|ios::showpos);
cout << nNum << "\t" << dNum << endl;
// 设置十六进制,科学计数法,大写标志
cout<< setiosflags(ios::hex|ios::scientific|ios::uppercase);
cout << nNum << "\t" << dNum << endl;
// 设置填充符号为*
cout << setfill('*');
for(int i=0;i<3;i++)
cout << setw(12) << str[i] << " ";
cout << endl;
// 设置标志:左对齐
cout << setiosflags(ios::left);
for(int i=0;i<3;i++)
cout << setw(12) << str[i] << " ";
cout << endl;
return 0;
}
/**
+12345 +12345.7
+12345 +1.234568E+04
********This **********is ******a Test
This******** is********** a Test******
*/
3.流的错误处理
在输入/输出过程中,一旦发现操作错误,C++流就会将发生的错误记录下来.用户可以使用C++提供的错误检查功能,检测和查明错误发生的原因和性质,然后调用
clear
函数清除错误状态,使流能够恢复处理在
ios
类中,定义了一个公有枚举成员io_state
来记录各种错误的性质:
enum io_state
{
goodbit = 0x00; // 正常
eofbit = 0x01; // 已达到文件尾
failbit = 0x02; // 操作失败
badit = 0x03; // 非法操作
};
ios
类中又定义了检测上述流状态的成员函数,如下:
函数 | 含义 |
---|---|
int ios::rdstate(); | 返回当前的流状态,它等于io_state的枚举值 |
int ios::bad(); | 如果badbit位被置1,返回非0 |
void ios::clear(int); | 清楚错误状态 |
int ios::eof(); | 返回非0表示提取操作已达文件尾 |
int ios::fail(); | 如果failbit位被置1,返回非0 |
int ios::good(); | 操作正常,返回非0 |
// 检测流的错误
#include <iostream>
using namespace std;
int main()
{
int i,s;
char buf[80];
cout << "输入一个整数: ";
cin >> i;
s = cin.rdstate();
cout << "流的状态为: " << hex << s << endl;
while(s)
{
// cin有缓冲流,必须先清空
cin.clear();
cin.getline(buf,80);
cout << "非法输入,重新输入一个整数: ";
cin >> i;
s = cin.rdstate();
}
return 0;
}
使用输入/输出成员函数
1.输入操作的成员函数
- 数据的输入/输出可以分为三大类:字符类,字符串和数据
1.使用get
和getline
函数
get
函数:用于输入字符或字符串.函数原型:
int get();//从输入流中提取一个字符,并转换成整型数值
istream& get(char& rch);//从输入流中提取字符到`rch`
istream& get(char* pch,int nCount,char delim = '\n');// 从输入流中提取一个字符串并由pch返回,nCount指定提取字符的最多个数,delim指定结束字符
getline
函数的函数原型:
istream& getline(char* pch,int nCount,char delim = '\n');
// 用来从输入流中提取一个输入行,并把提取的字符串由pch返回
// get和getline的使用
#include <iostream>
using namespace std;
int main()
{
char s1[80], s2[80], s3[80];
cout << "请输入一个字符: ";
cout << cin.get() << endl;
cin.get(); // 提取换行符
cout << "请输入一行字符串: ";
for (int i = 0; i < 80; i++)
{
cin.get(s1[i]);
if (s1[i] == '\n')
{
s1[i] = '\0';
break;
}
}
cout << s1 << endl;
cout << "请输入一行字符串: ";
cin.get(s2, 80);
cout << s2 << endl;
cin.get(); // 提取换行符
cout << "请输入一行字符串: ";
cin.getline(s3, 80);
cout << s3 << endl;
system("pause");
return 0;
}
/*
请输入一个字符: A
65
请输入一行字符串: This is a test!
This is a test!
请输入一行字符串: Computer
Computer
请输入一行字符串: 你今天过得好吗?
你今天过得好吗?
*/
- 注意:
get
函数遇到换行符就会结束提取,换行符仍然在缓冲区中,而getline
函数则会连换行符都提取.
2.使用read
函数
read
函数不仅可以读取字符或字符串(称为文本流),而且可以读取字节流.函数原型如下:
// 以下几种形式都是从输入流中读取由nCount指定数目的字节并将它们放在由pch或puch或psch指定的数组中
istream& read(char* pch,int nCount);
istream& read(unsigned char* puch,int nCount);
istream& read(signed char* psch,int nCount);
// read函数的使用
#include <iostream>
using namespace std;
int main()
{
char data[80];
cout << "请输入: " << endl;
cin.read(data, 80);
data[cin.gcount()] = '\0';
cout << endl << data << endl;
system("pause");
return 0;
}
/*
请输入:
12345
ABCDE
This is a test!
^Z
12345
ABCDE
This is a test!
*/
^Z
表示按下[Ctrl+Z]组合键,表示数据输入提前结束,gcount
函数是istream
类的一个成员函数,用来返回上一次提取的字符个数.当使用read
函数读取数据时,不会因为换行符而结束读取,因此它的读取多行的字符串
2.输出操作的成员函数
ostream
类中用于输出单个字符或字节的成员函数是put
和write
.它们的原型如下:
ostream& put(char ch);
ostream& write(const char* pch,int nCount);
ostream& write(const unsigned char* puch,int nCount);
ostream& write(const signed char* psch,int nCount);
提取和插入运算符重载
- C++中允许用户重载”
>>
“和”<<
“运算符,以便用户利用标准的输入/输出流来输入/输出自己定的数据类型(包括类),实现对象的输入/输出.重载时,最好将重载声明为类的友元函数,以便访问类中的私有成员.
#include <iostream>
using namespace std;
class CStudent
{
public:
friend ostream& operator << (ostream& os, CStudent& stu);
friend istream& operator >> (istream& is, CStudent& stu);
private:
char strName[10]; // 姓名
char strID[10]; // 学号
float fScore[3]; // 三门成绩
};
ostream& operator << (ostream& os, CStudent& stu)
{
os << endl << "学生信息如下: " << endl;
os << "姓名: " << stu.strName << endl;
os << "学号: " << stu.strID << endl;
os << "成绩: " << stu.fScore[0] << ",\t" << stu.fScore[1] << ",\t" << stu.fScore[2] << endl;
return os;
}
istream& operator >> (istream& is, CStudent& stu)
{
cout << "请输入学生信息" << endl;
cout << "姓名: ";
is >> stu.strName;
cout << "学号: ";
is >> stu.strID;
cout << "三门成绩:";
is >> stu.fScore[0] >> stu.fScore[1] >> stu.fScore[2];
return is;
}
int main()
{
CStudent one;
cin >> one;
cout << one;
system("pause");
return 0;
}
/*请输入学生信息
姓名: LiMing
学号: 123456
三门成绩:80 90 75
学生信息如下:
姓名: LiMing
学号: 123456
成绩: 80, 90, 75
*/
经过重载后的提取和插入运算符,实现了对象的直接输入和输出.
文件流及其处理
1.文件流概述
- C++将文件看成由连续的字符(字节)的数据顺序组成的.
- 根据文件中数据的组织方式,可分为 文本文件(ASCII文件) 和 二进制文件
- 文本文件:文件中的每一个字节用以存放一个字节的ASCII码值
- 二进制文件:将数据以二进制存放在文件中,它保持里数据在内存中存放的原有格式
- 无论是文本文件还是二进制文件,都需要用文本指针来操纵.一个文件指针总是和一个文件所关联的.当文件每一次打开时,文件指针指向文件的开始,随着文件的处理,文件指针不断地在文件中移动,并一直指向最新处理的字符(字节)位置.
- 文件处理有两种方式
- 文件的顺序处理:从文件的第一个字符(字节)开始顺序处理到文件的最后一个字符(字节),文件指针也相应地从文件的开始位置到文件的结尾.这种方式,文件称为顺序文件
- 文件的随机处理:在文件中通过C++相关的函数移动文件指针,并指向所要处理的字符(字节)位置.这种方式,文件称为随机文件
- C++提供的文件操作的文件流库,体系结构如下图:
ifstream
类是从istream
类公有派生而来的,用来支持从输入文件中提取数据的各种操作ofstream
类是从ostream
类公有派生而来的,用来实现把数据写入文件中的各种操作fstream
类是从iostream
类公有派生而来的,用来提供从文件中提取数据或把数据写入文件的各种操作filebuf
类是从streambuf
类派生而来的,用来**管理磁盘文件的缓冲区,应用程序一般不涉及该类- 上述类的成员函数在进行文件操作时,需要包括头文件—
fstream
.文件操作一般是按打开文件,读写文件,关闭文件这三个步骤进行的.
2.顺序文件操作
- 在C++打开或创建一个指定的文件需要下列两个步骤:
1.声明一个ifstream
,ofstream
或fstream
类对象,如
ifstream infile; // 声明一个输入(读)文件流对象
ofstream outfile; // 声明一个输出(写)文件流对象
fstream iofile; // 声明一个可读可写的文件流对象
2.使用文件流类的成员函数打开或创建一个指定的文件,使得该文件与声明的文件流对象联系起来,这样,对流对象的操作也是对文件的操作.如:
infile.open("file1.txt");
outfile.open("file2.txt");
iofile.open("file3.txt",ios::in|ios::out);
3.以上两部可合为一步进行,即在声明对象时指定文件名,如:
ifstream infile("file1.txt");
ofstream outfile("file2.txt");
fstream iofile("file3.txt",ios::in|ios::out);
- 在
ifstream
,ofstream
或fstream
类构造函数中,总有一种原型和它的成员函数open
功能相同,它们的函数原型如下:
ifstream(const char* szName,int nMode = ios::in,int nProt = filebuf::openprot);
void ifstream::open(const char* szName,int nMode = ios::in,int nProt = filebuf::openprot);
ofstreawm(const char* szName,int nMode = ios::out,int nProt = filebuf::openprot);
void ofstream::open(const char* szName,int nMode= ios::out,int nProt = filebuf::openprot);
fstream(const char* szName,int nMode,int nProt = filebuf::openprot);
void fstream::open(const char* szName,int nMode,int nProt = filebuf::openprot);
其中,szName
用来指定要打开的文件名,包括路径和扩展名.Mode
指定文件的访问方式,Prot
指定文件的共享方式,默认是filebuf::openprot
,表示DOS兼容的方式
* 文件访问方式
方式 | 含义 |
---|---|
ios::app | 打开一个文件使新的内容始终添加在文件的末尾 |
ios::ate | 打开一个文件使新的内容添加在文件的末尾,但下一次添加时,却在当前位置处进行 |
ios::in | 为输入(读)打开一个文件,若文件存在,不清除文件原有内容 |
ios::out | 为输出(写)打开一个文件 |
ios::trunc | 若文件存在,清除文件原有内容 |
ios::nocreate | 打开一个已有的文件,若文件不存在,则打开失败 |
ios::noreplace | 若打开的文件已经存在,则打开失败 |
ios::binary | 二进制文件方式(默认是文本文件方式) |
* 注意:
* nMode
指定文件的访问方式通过"|"
运算组合而成
* ios::trunc
方式通常与ios::out
,ios::ate
,ios::app
和ios::in
进行组合
* ios::binary
是二进制文件方式,通常组合如下
ios::in | ios::binary 表示打开一个只读的二进制文件
ios::out | ios::binary 表示打开一个可写的二进制文件
ios::in | ios::out | ios::binary 表示打开一个可读可写的二进制文件
- 在文件使用结束后,要及时调用
close
函数关闭 - 文件打开后,就可以对文件进行读写操作
- 读:
get
,getline
,read
函数以及提取符”>>
” - 写:
put
,write
函数以及插入符”<<
”
- 读:
// 将文件内容保存在另一个文件中,并把内容显示在屏幕上
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
fstream file1; // 定义一个fstream类的对象用于读
file1.open("Ex_DataFile.txt",ios::in);
if(!file1)
{
cout << "Ex_DataFile.txt不能打开!\n";
return -1;
}
fstream file2; // 定义一个fstream类的对象用于写
file2.open("Ex_DataFileBak.txt",ios::out | ios::trunc);
if(!file2)
{
cout << "Ex_DataFileBak不能被创建!\n";
file1.close();
return -2;
}
char ch;
while(!file1.eof())
{
file1.read(&ch,1);
cout << ch;
file2.write(&ch,1);
}
file2.close();
file1.close();
return 0;
}
3.随机文件操作
- 随机文件提供在文件中来回移动文件指针和非顺序地读写文件的能力,这样读写磁盘文件某一数据以前无需读写其前面的数据,从而能快速地检索,修改和删除文件中的信息
- C++顺序文件和随机文件间的差异不是物理的,这两种文件都是以顺序字符流的方式将信息写在磁盘等介质上,其区别仅仅在于文件的访问和更新的方法.以随机的方式访问文件时,文件中的信息在逻辑上组织成定长的记录格式. 这样就可以通过逻辑的方法,将文件指针直接移动到所读写数据的起始位置,来读取数据或者将数据直接写到文件的这个位置上.
- 定长的记录格式:指文件中的数据被解释成C++的同一类型的信息的集合.例如,都是整型数或者都是用户所定义的某一种结构的数据等.
- 在以随机的方式读写文件时,同样必须首先打开文件,且随机方式和顺序方式打开文件所用的函数也完全相同,但随机方式的文件流的打开模式必须同时有
ios::in | ios::out
- 在文件打开的时候,文件指针指向文件的第一个字符(字节),可以用C++提供的
seekg
和seekp
函数将文件指针移动到指定的位置,它们的原型如下:
istream& seekg(long pos);
istream& seekg(long off,ios::seek_dir dir);
ostream& seekp(long pos);
ostream& seekp(long off,ios::seek_dir dir);
其中,pos
用来指定文件指针的绝对位置,而off
用来指定文件指针的相对偏移时,文件指针的最后位置还依靠dir
的值,dir
的值可以为
ios::beg 从文件流的头部开始
ios::cur 从当前的文件指针位置开始
ios::end 从文件流的尾部开始