1 C++的基本流类体系
整个流类体系是一个派生类体系。按ANSI C++标准,类ios是抽象类,它的析构函数是虚函数,它的构造函数为保护的,作为所有基本流类的基类。
ios提供了对流进行格式化输入输出和错误处理的成员函数。所有派生都是公有派生。istream类提供完成提取(输入)操作的成员函数,而ostream类提供完成插入(输出)操作的成员函数。iostream类是前两者的组合。
streambuf是一个独立的类,只是ios有一个保护访问限制的指针指向它。streambuf的作用是管理一个流的缓冲区。
2 标准输入输出
在流类库中,最重要的两部分功能为标准输入/输出(standard input/output)和文件处理。
在C++的流类库中定义了四个全局流对象:cin,cout,cerr和clog。可以完成人机交互的功能。
cin标准输入流对象,键盘为其对应的标准设备。
cout标准输出流对象,显示器为标准设备。
cerr和clog标准错误输出流,输出设备是显示器。
其中cin、cout和clog是带缓冲区的,缓冲区由streambuf类对象来管理。而cerr为非缓冲区流,一旦错误发生立即显示。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
int main()
{
int a = 0;
cin >> a; // 从键盘输入
cout << a << endl; // 输出到终端
cerr << a << endl; // 输出到终端
clog << a << endl; // 输出到终端
system("pause");
return 0;
}
标准设备输入是最不安全的,注意以下几点,可以避免错误:
- cin为缓冲流。键盘输入的数据保存在缓冲区中,当要提取时,是从缓冲区中拿。如果一次输入过多,会留在那儿慢慢用,如果输入错了,必须在回车之前修改,如果回车键按下就无法挽回了。只有把输入缓冲区中的数据取完后,才要求输入新的数据。不可能用刷新来清除缓冲区,所以不能输错,也不能多输!
- 输入的数据类型必须与要提取的数据类型一致,否则出错。出错只是在流的状态字state(枚举类型io_state)中对应位置位(置1),程序继续。所以要提高健壮性,就必须在编程中加入对状态字state的判断。
- 空格和回车都可以作为数据之间的分格符,所以多个数据可以在一行输入,也可以分行输入。但如果是字符型和字符串,则空格(ASCII码为32)无法用cin输入,字符串中也不能有空格。回车符也无法读入。
3 文件的输入输出
文件指的是磁盘文件。C++根据文件(file)内容的数据格式,可分为两类:二进制文件和文本文件。文本文件由字符序列组成,也称ASCII码文件,在文本文件中存取的最小信息单位为字符(character),而二进制文件中存取的最小信息单位为字节(Byte)。文件的使用步骤如下:
1.文件流对象
说明一个文件流对象,这又被称为内部文件:
ifstream ifile;// 只输入用, 即读文件
ofstream ofile;// 只输出用,即写文件
fstream iofile;// 既输入又输出用 ,即读写文件,一般不用
2.打开一个磁盘文件
使用文件流对象的成员函数打开一个磁盘文件。
这样在文件流对象和磁盘文件名之间建立联系。文件流中说明了三个打开文件的成员函数。
void ifstream::open(const char*,int =ios::in,int=filebuf::openprot);
void ofstream::open(const char *,int=ios::out, int=filebuf::opernprot);
void fstream::open(const char*,int, int=filebuf::openprot);
第一个参数为要打开的磁盘文件名。第二个参数为打开方式,有in(读文件),out(写文件)等,打开方式在ios基类中定义为枚举类型。第三个参数为指定打开文件的保护方式,一般取缺省。所以第二步可如下进行:
ifile.open("myfile.txt", ios::in);
ofile.open("myfile.txt", ios::out);
iofile.open(“myfile.txt”,ios::in | ios::out);
三个文件流类都重载了一个带缺省参数的构造函数,功能与open函数一样:
ifstream::ifstream(const char*,int=ios::in,int=filebuf::openprot);
ofstream::ofstream(const char*,int=ios::out, int=filebuf::openprot);
fstream::fstream(const char*,int,int=filebuf::operprot);
所以1,2两步可合成:
ifstream ifile("myfile.txt", ios::in);
ofstrema ofile("myfile.txt", ios::out);
fstream iofile(“myfile.txt”,ios::in|ios::out);
打开文件也应该判断是否成功,若成功,文件流对象值为非零值,不成功为0(NULL),文件流对象值物理上就是指它的地址。因此打开一个文件完整的程序为:
fstream iofile(”myfile.txt”,ios::in|ios::out);
if(!iofile)
{
cout << ”不能打开文件:” << ”myfile,txt” << endl;
exit(1); // 失败退回操作系统
}
文件打开方式是在ios类中定义的,公有枚举成员:
enum open_mode
{
in=0x01, // 打开文件用于输入操作(从文件读取),文件指针在文件头
// 打开文件用于写入文件。如文件不存在,则建立,但指定目 录必须存在,
// 否则建立文件失败。如文件存在,未同时设 app,ate,in则文件清空
out=0x02,
ate=0x04, // 打开文件用于读写,文件指针在文件尾,但新数据可写到任何位置
app=0x08, // 打开文件用于写,但从尾部添加,新数据只能添加在尾部
trunce=0x10, // 打开文件,并清空它,以建立新文件
nocreate=0x20, // 如文件存在则打开,不存在并不创建新文件
noreplace=0x40,// 如文件不存在则创建,如文件存在则只能设为ate及app方式
binary=0x80 // 以二进制方式打开文件
};
3. 文件的读写
3.1 文本文件的顺序读写。
顺序读写可用C++的提取运算符(>>)进行读文件和插入运算符(<<)进行写文件
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
char buf[128] = { 0 };
ofstream ofile("test.txt", ios::out);
if (!ofile)
{
cerr << "open test.txt fail" << endl;
exit(1);
}
// 写文件
ofile << "hello c++";
ofile.close();
ifstream ifile("test.txt", ios::in);
if (!ifile)
{
cerr << "open test.txt fail" << endl;
exit(1);
}
// 读文件,这里有一个问题,将“hello c++”写进文件后在读出时,空格之后的内容c++的提取运算符>> 读不到,这种情况可以考虑以二进制的方式读写
ifile >> buf;
cout << buf << endl;
ifile.close();
system("pause");
return 0;
}
3.2 二进制文件的读写
C++提供了对二进制文件进行读写的成员函数,注意这时要以二进制的方式打开文件
从二进制流提取,即读文件
// 第一个参数指定存放有效输入的变量地址,
// 第二个参数指定提取的字节数,
// 函数从与文件流对象相关联的磁盘文件中读取指定数量的字节存入到char*中
istream&istream::read(char *,int);
istream&istream::read(unsigned char*,int);
istream&istream::read(signed char *,int);
向二进制流插入,即写文件
// 第一个参数指定输出对象的内存地址,
// 第二个参数指定插入的字节数,
// 从char* 中指定数量的字节写入到与文件流对象想关联的磁盘文件中
ostream&ostream::write(const char *,int);
ostream&ostream::write(const unsigned char *,int);
ostream&ostream::write(const signed char *,int);
二进制方式读写文件
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
char buf[128] = "hello c++";
ofstream ofile("test.txt", ios::out | ios::binary);
if (!ofile)
{
cerr << "open test.txt fail" << endl;
exit(1);
}
ofile.write(buf, strlen(buf));
ofile.close();
ifstream ifile("test.txt", ios::in | ios:: binary);
if (!ifile)
{
cerr << "open test.txt fail" << endl;
exit(1);
}
memset(buf, 0, sizeof(buf));
// 以二进制的方式读文件,不会读不到空格,而且可以批量读写,效率更高
ifile.read(buf, 100);
cout << buf << endl;
ifile.close();
system("pause");
return 0;
}
使用二进制文件,可以控制字节长度,读写数据时不会出现二义性,可靠性高。同时不知格式是无法读取的,保密性好。文件结束后,系统不会再读(见eofbit的说明),但程序不会自动停下来,所以要判断文件中是否已没有数据。如写完数据后没有关闭文件,直接开始读,则必须把文件定位指针移到文件头。如关闭文件后重新打开,文件定位指针就在文件头。
3.3 文件的随机访问
在C++中可以由程序移动文件指针,从而实现文件的随机访问,即可读写流中任意一段内容。一般文本文件很难准确定位,所以随机访问多用于二进制文件。在ios类中说明了一个公有枚举类型:
enum seek_dir
{
beg=0, //文件开头
cur=1, //文件指针的当前位置
end=2 //文件结尾
};
istream类中提供了如下三个成员函数:
// 指针直接定位
istream&istream::seekg(streampos);
// 指针相对定位
istream&istream::seekg(streamoff, ios::seek_dir);
// 返回当前指针位置
long istream::tellg();
流的指针位置类型streampos和流的指针偏移类型streamoff定义为长整型,也就是可访问文件的最大长度为4G
// 表示将文件定位指针从当前位置向文件头部方向移20个字节。
datafile.seekg(-20L,ios::cur);
// 表示将文件定位指针从文件头向文件尾方向移20个字节。
datafile.seekg(20L,ios::beg);
表示将文件定位指针从文件尾向文件头方向移20个字节。
// datafile.seekg(-20L,ios::end);
ifstream datafile("test.txt", ios::in | ios::binary);
datafile.seekg(2); // 不从文件开头读,偏移两个字节读
datafile.read(buf, 100);
cout << buf << endl;
datafile.close();
system("pause");
return 0;
tellg()和seekg()往往配合使用。指针不可移到文件头之前或文件尾之后。
ostream类也提供了三个成员函数管理文件定位指针:
ostream&ostream::seekp(streampos);
ostream&ostream::seekp(streamoff,ios::seek_dir);
long ostream::tellp();
为了便于记忆,函数名中g是get的缩写,而p是put的缩写。对输入输出文件定位指针只有一个但函数有两个,这两个函数功能完全一样。
4.关闭文件。
三个文件流类各有一个关闭文件的成员函数
void ifstream::close();
void ofstream::close();
void fstream::close();
使用很方便,如:iofile.close();
关闭文件时,系统把该文件相关联的文件缓冲区中的数据写到文件中,保证文件的完整,收回与该文件相关的内存空间,可供再分配,把磁盘文件名与文件流对象之间的关联断开,可防止误操作修改了磁盘文件。如又要对文件操作必须重新打开。
关闭文件并没有取消文件流对象,该文件流对象又可与其他磁盘文件建立联系。文件流对象在程序结束时,或它的生命期结束时,由析构函数撤消。它同时释放内部分配的预留缓冲区。