前言

数据是怎么写道磁盘的?

代码里面我们一个读写调用似乎就能将数据从磁盘读写。仿佛代码是直接和磁盘打交道。以我们最常见的笔记本、或台式机的磁盘SATA盘为例,其受南桥上ATA控制器支配,这里面传输的ATA指令。至少我们代码里面是没有处理这部分的,所以我们代码直接操控磁盘说不通。现实情况是,C++通过IO流库和磁盘打交道。

IO流库

C++语言中并没有输入/输出语句,而是在标准库里包含了一个I/O流类库,它与标准模板库同为C++标准库中最重要的组成部分。数据从一个对象到另一个对象的传送被抽象为“流”。数据的输入/输出就是通过输入/输出流来实现的。 流是一种抽象的概念,负责在数据的产生者和数据的使用者之间建立联系,并管理数据的流动。

流类体系

整个流类体系是一个派生类体系。按ANSI C++标准,类ios是抽象类,它的析构函数是虚函数,它的构造函数为保护的,作为所有基本流类的基类。
  

ios提供了对流进行格式化输入输出和错误处理的成员函数。所有派生都是公有派生。

istream类提供完成提取(输入)操作的成员函数

ostream类提供完成插入(输出)操作的成员函数。

iostream类是前两者的组合。

streambuf是一个独立的类,只是ios有一个保护访问限制的指针指向它。streambuf的作用是管理一个流的缓冲区。

在流类库中,最重要的两部分功能为:

①标准输入/输出(standard input/output)

②文件处理

在C++的流类库中定义了四个全局流对象,可以完成人机交互的功能(要使用这四个功能,必须包含<iostream.h>文件。):

①cin 标准输入流对象,键盘为其对应的标准设备。带缓冲区

②cout 标准输出流对象,显示器为标准设备。带缓冲区,一旦错误发生立即显示。

③cerr   标准错误输出流,输出设备是显示器。非缓冲区

④clog   标准错误输出流,输出设备是显示器。带缓冲区

输入输出格式控制

C++在类ios中提供格式化输入输出。这些格式是对所有文本方式的输入输出流均适用。格式控制符定义为公有的无名的枚举类型:

 enum{ skipws=0x0001,    //跳过输入中的空白字符
left=0x0002, //输出左对齐
right=0x0004, //输出右对齐
internal=0x0008, //在输出符号或数制字符后填充
dec=0x0010, //在输入输出时将数据按十进制处理
oct=0x0020, //在输入输出时将数据按八进制处理
hex=0x0040, //在输入输出时将数据按十六进制处理
showbase=0x0080, //在输出时带有表示数制基的字符
showpoint=0x0100, //输出浮点数时,必定带小数点
uppercase=0x0200, //输出十六进制,用大写
showpos=0x0400, //输出正数时,加”+”号
scientific=0x0800, //科学数方式输出浮点数
fixed=0x1000, //定点数方式输出实数
unitbuf=0x2000, //插入后,立即刷新流
stdio=0x4000} //插入后,立即刷新stdout和stderr
 #include<iostream>
using namespace std; void main()
{
for (int i=; i <=; ++i)
{
for (int j = ; j <= ; ++j)
{
cout.width();
cout.flags(ios::left);
cout << i * j;
}
cout << endl;
}
}

该枚举量说明中每一个枚举量实际对应两字节数据(16位)中的一个位,所以可以同时采用几个格式控制,只要把对应位置1即可,这样既方便又节约内存。取多种控制时,用或“|”运算符来合成,合成为一个长整型数,在ios中为:

 protected:
long x_flags;

类ios中还设置了三个输入输出流格式控制标志:

 protected:
int x_precision; //标志浮点数精度,缺省为6位
int x_width; //输出域宽,缺省域宽为0,
//重设域宽只对其后第一输出项有效,如域宽不足,则不受限制
char x_fill; //标志域宽有富余时填入的字符

流操作子(setiosflags stream manipulator)

可代替流格式控制成员函数,但绝大多数流操作子VC++不支持

#include<iostream>
using namespace std; void main()
{
int a = ;
cout << hex << a << endl;
}

文件的输入输出

这里的文件指的是磁盘文件。C++根据文件(file)内容的数据格式,可分为两类:二进制文件和文本文件。文本文件由字符序列组成,也称ASCII码文件,在文本文件中存取的最小信息单位为字符(character),而二进制文件中存取的最小信息单位为字节(Byte)。

文件的使用步骤

声明一个文件流对象,这又被称为内部文件:

ifstream ifile;//只输入用
ofstream ofile;//只输出用
fstream iofile;//既输入又输出用

使用文件流对象的成员函数打开一个磁盘文件。这样在文件流对象和磁盘文件名之间建立联系。文件流中说明了三个打开文件的成员函数。

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基类中定义为枚举类型。

文件打开方式是在ios类中定义的,公有枚举成员:

enum open_mode{
in=0x01, //打开文件用于输入操作(从文件读取),文件指针在文件头
out=0x02, /*打开文件用于写入文件。如文件不存在,则建立,但指定目录必须存在,否则建立文件失败。如文件存在,未同时设app,ate,in则文件清空*/
ate=0x04,//打开文件用于输入/输出,文件指针在文件尾,但新数据可写到任何位置
app=0x08, //打开文件用于输出,但从尾部添加,新数据只能添加在尾部
trunce=0x10, //打开文件,并清空它,以建立新文件
nocreate=0x20, //如文件存在则打开,不存在并不创建新文件
noreplace=0x40,//如文件不存在则创建,如文件存在则只能设为ate及app方式
binary=0x80 //以二进制方式打开文件
};

第三个参数为指定打开文件的保护方式,一般取缺省。

三个文件流类都重载了一个带缺省参数的构造函数,功能与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两步可合成:

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();
}

使用提取(>>)和插入(<<)运算符对文件进行读写操作,或使用成员函数进行读写

关闭文件。三个文件流类各有一个关闭文件的成员函数

void ifstream::close();
void ofstream::close();
void fstream::close();

使用很方便,如:iofile.close();

关闭文件时,系统把该文件相关联的文件缓冲区中的数据写到文件中,保证文件的完整,收回与该文件相关的内存空间,可供再分配,把磁盘文件名与文件流对象之间的关联断开,可防止误操作修改了磁盘文件。如又要对文件操作必须重新打开。关闭文件并没有取消文件流对象,该文件流对象又可与其他磁盘文件建立联系。文件流对象在程序结束时,或它的生命期结束时,由析构函数撤消。它同时释放内部分配的预留缓冲区。

代码演示

#include<iostream>
#include<fstream>
using namespace std;
int main()
{
int ar[] = {,,,,,,,,};
//
ofstream ofile;
//
ofile.open("test.txt",ios::out);
if (!ofile)
{
cerr << "Open File Fail!" << endl;
exit();
}
//
for (int i = ; i < sizeof(ar) / sizeof(int);++i)
{
//cout<<ar[i]<<" ";
ofile << ar[i] << " ";
}
ofile.close();
return ;
}
#include<iostream>
#include<fstream>
using namespace std;
int main()
{
int ar[] = {,,,,,,,,};
ofstream ofile("test.txt", ios::out);
if (!ofile)
{
cerr << "Open File Fail!" << endl;
exit();
}
//
for (int i = ; i < sizeof(ar) / sizeof(int);++i)
{
//cout<<ar[i]<<" ";
ofile << ar[i] << " ";
}
ofile.close();
return ;
}

再见文件内容读入到数组

#include<iostream>
#include<fstream>
using namespace std;
int main()
{
int ar[];
ifstream ifile("test.txt", ios::out);
if (!ifile)
{
cerr << "Open File Fail!" << endl;
exit();
}
//
for (int i = ; i < sizeof(ar) / sizeof(int);++i)
{
//cout<<ar[i]<<" ";
ifile >> ar[i];
}
ifile.close();
return ;
}

二进制文件读写

C++提供了对二进制文件进行读取的成员函数:

istream&istream::read(char *,int); //从二进制流提取
istream&istream::read(unsigned char*,int);
istream&istream::read(signed char *,int);

第一个参数指定存放有效输入的变量地址
第二个参数指定提取的字节数

C++提供了对二进制文件进行写入的成员函数:

ostream&ostream::write(const char *,int);
ostream&ostream::write(const unsigned char *,int);
ostream&ostream::write(const signed char *,int);

第一个参数指定输出对象的内存地址
第二个参数指定插入的字节数

之前用循环每次像数组读取一个字符,速度很慢。可以采用二进制读取,在读取二进制文件之前需要构造二进制文件。

 #include<iostream>
#include<fstream>
using namespace std;
int main()
{
int ar[] = { ,,,,,,,, };
int br[];
ofstream ofile("test.txt", ios::out|ios::binary);
if (!ofile)
{
cerr << "Open File Fail!" << endl;
exit();
}
ofile.write((char*)ar,sizeof(ar));
ofile.close();
ifstream ifile("test.txt", ios::in | ios::binary);
if (!ifile)
{
cerr << "Open File Fail!" << endl;
exit();
}
ifile.read((char*)br, sizeof(br));
return ;
}

读函数并不能知道文件是否结束,可用状态函数int ios::eof()来判断文件是否结束。必须指出系统是根据当前操作的实际情况设置状态位,如需根据状态位来判断下一步的操作,必须在一次操作后立即去调取状态位,以判断本次操作是否有效。

使用二进制文件,可以控制字节长度,读写数据时不会出现二义性,可靠性高。同时不知格式是无法读取的,保密性好。
文件结束后,系统不会再读,但程序不会自动停下来,所以要判断文件中是否已没有数据。
如写完数据后没有关闭文件,直接开始读,则必须把文件定位指针移到文件头。
如关闭文件后重新打开,文件定位指针就在文件头。

文件随机访问

C++把每一个文件都看成一个有序的字节流,每一个文件或者以文件结束符(end of file marker)结束,或者在特定的字节号处结束。

当打开一个文件时,该文件就和某个流关联起来了。对文件进行读写实际上受到一个文件定位指针(file position pointer)的控制。
输入流的指针也称为读指针,每一次提取操作将从读指针当前所指位置开始,每次提取操作自动将读指针向文件尾移动。
输出流指针也称写指针,每一次插入操作将从写指针当前位置开始,每次插入操作自动将写指针向文件尾移动。

在C++中可以由程序移动文件指针,从而实现文件的随机访问,即可读写流中任意一段内容。一般文本文件很难准确定位,所以随机访问多用于二进制文件。

在ios类中说明了一个公有枚举类型:

enum seek_dir{
beg=, //文件开头
cur=, //文件指针的当前位置
end= //文件结尾
};

istream类中提供了如下三个成员函数:

istream&istream::seekg(streampos); //指针直接定位
istream&istream::seekg(streamoff, ios::seek_dir); //指针相对定位
long istream::tellg(); //返回当前指针位置

流的指针位置类型streampos和流的指针偏移类型streamoff定义为长整型,也就是可访问文件的最大长度为4G

datafile.seekg(-20L,ios::cur);表示将文件定位指针从当前位置向文件头部方向移20个字节。
datafile.seekg(20L,ios::beg);表示将文件定位指针从文件头向文件尾方向移20个字节。
datafile.seekg(-20L,ios::end);表示将文件定位指针从文件尾向文件头方向移20个字节。
tellg()和seekg()往往配合使用。指针不可以移到文件头之前或文件尾之后。

ostream类也提供了三个成员函数管理文件定位指针,它们是:

ostream&ostream::seekp(streampos);
ostream&ostream::seekp(streamoff,ios::seek_dir);
long ostream::tellp();

为了便于记忆,函数名中g是get的缩写,而p是put的缩写。

借助二进制读写优化文件读取,代码演示

#include<iostream>
#include<fstream>
using namespace std;
int main()
{
int ar[] = {,,,,,,,,,};
ofstream ofile("test.txt", ios::out|ios::binary);
if (!ofile)
{
cerr << "Open File Fail!" << endl;
exit();
}
ofile.write((char*)ar, sizeof(ar));
ofile.close(); ifstream ifile;
ifile.open("test.txt", ios::in | ios::binary);
if (!ifile)
{
cerr << "Open File Fail!" << endl;
exit();
}
int pos;
int value;
while ()
{
cout << "请输入位置:>" << endl;
cin >> pos;
ifile.seekg((long)(pos * sizeof(int)), ios::beg);
ifile.read((char*)&value, sizeof(int));
cout << "value = " << value << endl;
} ifile.close();
return ;
}

文件与对象

在面向对象的程序设计中,信息总是放在对象的数据成员里。这些信息最终应该保存到文件中。当程序开始运行时,就要根据打开的文件重新创建对象。在运行过程中,放在对象的数据成员里的信息得到利用和修改。运行结束时必须把这些信息重新保存到文件中,然后关闭文件。
磁盘文件打开时建立的内部文件流是在运行时动态分配的资源。在面向对象的C++程序设计中,文件应该在构造函数中打开,并创建对象;而在析构函数中保存和关闭文件,并撤销对象。这样,与动态安排的其他资源一样,当撤销对象时,能自动释放资源。特别在程序发生异常时,C++系统有异常处理机制,该机制要求动态分配的资源是在类对象中,这样C++保证退出异常点时资源得到完整的释放。
对文件而言,释放资源同时包括将对象中的信息再次存入磁盘文件。在程序运行过程中,应该将信息适时保存到相应的磁盘文件中,以免数据意外丢失。

代码演示

 #include<iostream>
#include<fstream>
using namespace std; class Complex;
ostream& operator<<(ostream &out, const Complex &c);
class Complex
{
friend ostream& operator<<(ostream &out, const Complex &c);
public:
Complex() :m_real(), m_img()
{
ifstream ifile;
ifile.open("data.dat",ios::in);
if (!ifile)
{
cerr << "Open File Fail" << endl;
exit();
}
ifile >> m_real >> m_img;
ifile.close();
}
Complex(int real, int imag)
{
m_real = real;
m_img = imag;
}
~Complex()
{
ofstream ofile;
ofile.open("data.dat", ios::in);
if (!ofile)
{
cerr << "Open File Fail" << endl;
exit();
}
ofile << m_real <<" "<< m_img;
ofile.close();
}
void SetComplex(int real, int imag)
{
m_real = real;
m_img = imag;
}
private:
int m_real;
int m_img;
}; ostream& operator<<(ostream &out, const Complex &c)
{
out << "(" << c.m_real << "," << c.m_img << ")" << endl;
return out;
} int main()
{
Complex c;
cout << c << endl;
c.SetComplex(, );
cout << c << endl;
return ;
}

C++——流类库和输入/输出的更多相关文章

  1. C++——流类库与输入/输出

    I/O流的概念 当程序与外界环境进行信息交换时,存在着两个对象,一个是程序中的对象,另一个是文件对象,流是一种抽象,它负责在数据的生产者和数据的消费者之间建立连接,并管理数据的流动.程序建立一个流对象 ...

  2. 第15章-输入/输出 --- 理解Java的IO流

    (一)理解Java的IO流 JAVA的IO流是实现输入/输出的基础,它可以方便地实现数据的输入/输出操作,在Java中把不同的输入/输出(键盘.文件.网络连接等)抽象表述为"流"( ...

  3. JAVA里面的IO流(一)分类1(字节/字符和输入/输出)

      java.io包中定义了多个流类型(流或抽象类)来实现输入/输出功能:可以从不同的角度对其进行分类: 按数据流的方向不同可以分为输入流和输出流 从文件读数据为输入流:往文件写数据为输出流 按处理数 ...

  4. (19)IO流之字符流FileReader和FileWriter,缓冲字符流---缓冲输入字符流BufferedReader和缓冲输出字符流BufferedWriter

    字符流,读取的文件是字符的时候,有两个基类一个是Reader,一个是Writer这有点拟人的感觉,人直接看懂的是文字 字符流 字节流:读取的是文件中的二进制字节流并不会帮你转换成看的懂得字符 字符流: ...

  5. Java 输入/输出——处理流(BufferedStream、PrintStream、转换流、推回输入流)

    关于使用处理流的优势,归纳起来就是两点:(1)对于开发人员来说,使用处理流进行输入/输出操作更简单:(2)使用处理流执行效率更高. 1.BufferedInputStream/BufferedOutp ...

  6. I/O(输入/输出)---字节流与字符流

    流: 分为输入流和输出流,输入/输出是相对计算机内存来说的,数据输入到内存是输入流,数据从内存中输出是输出流. 流对象构造的时候会和数据源联系起来. 数据源分为:源数据源和目标数据源.输入流联系的是源 ...

  7. 在java程序中,对于数据的输入/输出操作以“流”(stream)方式进行

    在java程序中,对于数据的输入/输出操作以“流”(stream)方式进行

  8. USB限流IC,输入5V,输出5V,最大3A限流

    USB限流芯片,5V输入,输出5V电压,限流值可以通过外围电阻进行调节,PWCHIP产品中可在限流范围0.4A-4.8A,并具有过压关闭保护功能. 过压关闭保护: 如芯片:PW1555,USB我们一半 ...

  9. [linux] 输入&输出&错误流

    输入&输出&错误流 Linux中有三种标准输入输出,分别是STDIN,STDOUT,STDERR,对应的数字分别是0,1,2. 标准 数字 含义 STDIN 0 标准输入,默认从键盘读 ...

随机推荐

  1. C# WinForm程序中使用Unity3D控件 (转)

    https://www.cnblogs.com/cnxkey/articles/5394378.html 最近在自学Unity3D,打算使用这个时髦.流行.强大的游戏引擎开发一个三维业务展示系统,不过 ...

  2. Python - Django - simple_tag 和 inclusion_tag

    simple_tag: simple_tag 和自定义 filter 类似,但可以接收更多更灵活的参数 在 app01/templatetags/ 目录下创建 mysimple_tag.py mysi ...

  3. bootstrap 自带字体颜色

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  4. linux用户解锁

    pam_tally2 --user=username  #查看 pam_tally2 --user=username --reset #重置

  5. Js把对象数组列表转换成数组

    今天写组件的时候遇到一个问题,就是当我需要对获取到的对象列表进行删减的时候,发现没有合适的方法,比如: //获取图片列表 var imgs = document.getElementsByTagNam ...

  6. js 验证手机号

    <script> var reg = /^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\d{8}$/; function ver ...

  7. 第07组 Alpha冲刺(2/4)

    队名:秃头小队 组长博客 作业博客 组长徐俊杰 过去两天完成的任务:完成人员分配,初步学习Android开发 Github签入记录 接下来的计划:继续完成Android开发的学习,带领团队进行前后端开 ...

  8. Tomcat 服务器介绍和使用

    服务器的概念和作用: 问题: 学习了 java 编程之后,java 代码的一个很重要的作用就是进行数据的处理,但是目前来说我们运行编写的代码,只有一次性,也就是运行完毕后,如果需要再次运行则需要再次手 ...

  9. 《ucore lab1》实验报告

    资源 ucore在线实验指导书 我的ucore实验代码 练习1:理解通过make生成执行文件的过程 详见<ucore lab1 exercise1>实验报告 练习2:使用qemu执行并调试 ...

  10. LeetCode 179. 最大数(Largest Number) 21

    179. 最大数 179. Largest Number 题目描述 给定一组非负整数,重新排列它们的顺序使之组成一个最大的整数. 每日一算法2019/5/24Day 21LeetCode179. La ...