C++ IO流(转)

转载:
https://segmentfault.com/a/1190000010266504?utm_source=tag-newest
https://blog.csdn.net/z961968549/article/details/79954632

当程序与外界进行信息交换时,存在两个对象:一个是程序中的对象,另一个是文件对象。

流是信息流动的一种抽象,它负责在数据的生产者和数据的消费者之间建立联系,并管理数据的流动。

流对象与文件操作

  1. 程序建立一个流对象
  2. 指定这个流对象与某个文件对象建立连接
  3. 程序操作流对象
  4. 流对象通过文件系统对所连接的文件对象产生作用

提取与插入

  1. 读操作在流数据抽象中被称为(从流中)提取
  2. 写造作被称为(向流中)插入
类名 说明 包含文件
抽象流基类
ios 流基类 ios
输入流类
istream 通用输入流类和其他输入流的基类 istream
ifstream 文件输入流类 fstream
istringstream 字符串输入流类 sstream
输出流类
ostream 通用输出流类和其他输出流的基类 ostream
ofstream 文件输出流类 fstream
ostringstream 字符串输出流类 sstream
输入/输出流类
iostream 通用输入/输出流类和其他输入/输出流的基类 iostream
fstream 文件输入/输出流类 fstream
stringstream 字符串输入/输出流类 sstream
流缓冲区类
streambuf 抽象流缓冲区基类 streambuf
filebuf 磁盘文件的流缓冲区类 fstream
stringbuf 字符串的流缓冲区类 sstream

一 、 基于控制台的I/O:istream, ostream, iostream

头文件 类型
iostream istream从流中读取
iostream ostream写到流中去
iostream iostream对流进行读写,从istream和ostream派生

开始前先偏点题,首先讲讲网上搜索常常可以看到的一个问题:

<iostream>和<iostream.h>的区别?

#include <iostream.h>非标准输入输出流
#include <iostream>标准输入输出流

C++中为了避免名字定义冲突,特别引入了“名字空间的定义”,即namespace。

1. 当代码中用<iostream.h>时,输出可直接引用cout<<x; // <iostream.h> 继承C语言的标准库文件,未引入名字空间定义,所以可直接使用。

2. 当代码中用<iostream>时,输出需要引用std::cout<<x; // 如果还是按原来的方法就会有错。

使用<iostream>时,引入std::有以下方法:
1.
using namespace std;
cout<<x;
2.
using std::cout;
cout<<x;
3.
最基本的std::cout<<x;
这回你该知道为什么通常用#include <iostream>时,要用using namespace std;了吧。如果你不用这个,就要在使用cout时,用后两种方法了。
其他头文件也是同样的道理。(有“.h”的就是非标准的,C的标准库函数,无“.h”的,就要用到命令空间,是C++的。还有一部分不完全是有“.h”和没“.h”的差别。例如:math.h和cmath)

为什么需要iostream:
C++的输入输出是由iostream库提供的,它与C语言的 stdio库不同,它从一开始就是用多重继承与虚拟继承实现的面向对象的层次结构,作为一个c++的标准库组件提供给程序员使用。
iostream为内置类型对象(int, char, …)提供了输入输出支持,同时也支持文件的输入输出,类的设计者可以通过对iostream库的扩展,来支持自定义类型的输入输出操作。

为什么说要扩展才能提供支持呢?我们来一个示例。

#include <stdio.h> 
#include <iostream> 
using namespace std;

class Test
{
    public:
    Test(int a=0,int b=0)
    { 
            Test::a=a;
            Test::b=b;
    }
    int a;
    int b;
};
int main() 
{ 
        Test t(100,50); 
        printf("%???",t);//不明确的输出格式 
        scanf("%???",t);//不明确的输入格式 
        cout<<t<<endl;//同样不够明确 
        cin>>t;//同样不够明确 
        system("pause"); 
}

   由于自定义类的特殊性,在上面的代码中,无论你使用c风格的输入输出,还是c++的输入输出都是不明确的一个表示,由于c语言没有运算符重载机制,导致stdio库的不可扩充性,让我们无法让printf()和scanf()支持对自定义类对象的扩充识别,而c++是可以通过运算符重载机制扩充iostream库的,使系统能能够识别自定义类型,从而让输入输出明确的知道他们该干什么,格式是什么。

   在上例中我们之所以用printf与cout进行对比目的是为了告诉大家,C与C++处理输入输出的根本不同,我们从c的输入输出可以很明显看出是函数调用方式,而c++的则是对象模式,cout和cin是ostream类和istream类的对象。

iostream库定义了以下三个标准流对象:

cin:表示标准输入的istream类对象。cin使我们可以从设备读入数据。
cout:表示标准输出的ostream类对象。cout使我们可以向设备输出或者写数据。
cerr:表示标准错误的osttream类对象。cerr是导出程序错误消息的地方,它只能允许向屏幕设备写数据。
 
输出主要由重载的左移操作符(<<)来完成,输入主要由重载的右移操作符(>>)完成:
>>a表示将数据放入a对象中。
<<a表示将a对象中存储的数据拿出。 

那么原理上C++有是如何利用cin/cout对象与左移和右移运算符重载来实现输入输出的呢?下面我们以输出为例,说明其实现原理:

  1. cout是ostream类的对象,因为它所指向的是标准设备(显示器屏幕),所以它在iostream头文件中作为全局对象进行定义。
  2. ostream cout(stdout);//其默认指向的C中的标准设备名,作为其构造函数的参数使用。
  3. 在iostream.h头文件中,ostream类对应每个基本数据类型都有其友元函数对左移操作符进行了友元函数的重载。
ostream& operator << (ostream &temp,int source);
ostream& operator << (ostream &temp,char *ps);
#include <iostream>
using namespace std;

class Test
{
public:
    Test(int a=0,int b=0)
    : a(0)
     , b(this->a) // 可以使用this
    // , this->b(a) // 不可以使用this.对象没有被完全被构造出来出前,最好不要用this指针。因此只有构造函数执行完后,对象才是完整的。
    { 
        this->a=a; // 构造函数体内使用this指针,可以。
        this->b=b;
    }
    friend ostream & operator<<(ostream &os, const Test & test);
    friend istream & operator>>(istream &is,Test & test);
private:
    int a;
    int b;

};

ostream & operator<<(ostream &os, const Test & test)
{
    os << test.a << endl;
    return os;
}

istream & operator>>(istream &is, Test & test)
{
    int m;
    cin >> m;
    test.a = m;
    return is;
}

int main() 
{ 
        Test t(100,50); 
        cin>>t;//同样不够明确 
        cout<<t;//同样不够明确 
}
// 结果:
// 22 (输入)
// 22 (输出)
// 因为没有办法修改 ostream 类和 istream 类,所以只能将<<和>>重载为全局函数的形式。由于这个函数需要访问 Complex 类的私有成员,因此在 Complex 类定义中将它们声明为友元。
// 参照 http://c.biancheng.net/view/242.html

二 、 基于文件的I/O:fstream, ifstream, ofstream

fstream类是由iostream类派生而来
ifstream类是由istream类派生而来
ofstream类是由ostream类派生而来

由于文件设备并不像显示器屏幕与键盘那样是标准默认设备,所以它在fstream.h头文件中是没有像cout那样预先定义的全局对象,所以我们必须自己定义一个该类的对象,要以文件作为设备向文件输出信息(也就是向文件写数据),那么就应该使用ofstream类。

ofstream类的默认构造函数原形为
ofstream::ofstream(constchar *filename,int mode = ios::out,int penprot = filebuf::openprot);

* filename:  要打开的文件名
* mode:   要打开文件的方式
* prot:    打开文件的属性

mode属性表

属性 含义
ios::app 以追加的方式打开文件
ios::ate 文件打开后定位到文件尾,ios:app就包含有此属性
ios::binary 以二进制方式打开文件,缺省的方式是文本方式。两种方式的区别见前文
ios::in 文件以输入方式打开
ios::out 文件以输出方式打开
ios::trunc 如果文件存在,把文件长度设为0

可以用“或”把以上属性连接起来,如ios::out|ios::binary。

openprot属性表

属性 含义
0 普通文件,打开访问
1 只读文件
2 隐含文件
4 系统文件

可以用“或”或者“+”把以上属性连接起来 ,如3或1|2就是以只读和隐含属性打开文件。

【实例代码1】

#include <fstream>
using namespace std;

int main()
{
    ofstream myfile("c:\\1.txt",ios::out|ios::trunc,0);
    myfile<<"white"<<endl<<"中文:"<<"白色"<<endl;
    myfile.close();// 文件使用完后可以使用close成员函数关闭文件。
}

【实例代码2】

// ios::app为追加模式,在使用追加模式的时候同时进行文件状态的判断是一个比较好的习惯。
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
    ofstream myfile("c:\\1.txt",ios::app,0);
    if(!myfile)//或者写成myfile.fail()
    { 
            cout<<"文件打开失败,目标文件状态可能为只读!";
            system("pause");
            exit(1);
    } 
    myfile<<"white"<<endl<<"中文:"<<"白色"<<endl;
    myfile.close();
}

【实例代码3】

//在定义ifstream和ofstream类对象的时候,我们也可以不指定文件。以后可以通过成员函数open()显式的把一个文件连接到一个类对象上。
#include <iostream>
#include <fstream>
using namespace std;
int main()
{ 
    ofstream myfile;
    myfile.open("c:\\1.txt", ios::out|ios::app);
    if(!myfile)//或者写成myfile.fail()
    { 
            cout<<"文件创建失败,磁盘不可写或者文件为只读!";
            system("pause");
            exit(1);
    } 
    myfile<<"white"<<endl<<"中文:"<<"白色"<<endl;
    myfile.close();
}

【实例代码4】

// 利用ifstream类对象,将文件中的数据读取出来,然后再输出到标准设备中的例子。
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{ 
    ifstream myfile;
    myfile.open("c:\\1.txt", ios::in);
    if(!myfile) 
    { 
            cout<<"文件读错误";
            system("pause");
            exit(1);
    } 
    char ch;
    string content;
    while(myfile.get(ch))
    { 
            content+=ch;
            cout.put(ch);//cout<<ch;这么写也是可以
    } 
    myfile.close();
    cout<<content;
    system("pause");
}
利用成员函数get(), 逐一的读取文件中的有效字符。再利用put()函数,将文件中的数据通过循环逐一输出到标准设备(屏幕)上,get()函数会再文件读到末尾的时候返回false,所以可以作为while循环的终止条件。

【实例代码5】

// 我们在简单介绍过ofstream类和ifstream类后,我们再来看一下fstream类,fstream类是由iostream派生而来,fstream类对象可以同对文件进行读写操作。
#include <iostream>
#include <fstream>
using namespace std;
int main()
{ 
    fstream myfile;
    myfile.open("c:\\1.txt", ios::out|ios::app);
    if(!myfile)
    { 
            cout<<"文件写错误,文件属性可能为只读!"<<endl;
            system("pause");
            exit(1);
    } 
    myfile<<"white"<<endl<<"中文:"<<"白色"<<endl;
    myfile.close();
    
    myfile.open("c:\\1.txt", ios::in);
    if(!myfile)
    { 
            cout<<"文件读错误,文件可能丢失!"<<endl;
            system("pause");
            exit(1);
    }
    char ch;
    while(myfile.get(ch))
    {
            cout.put(ch);
    } 
    myfile.close();
    system("pause");
}

三 、 基于字符串的I/O:istringstream, ostringstream, strstream

头文件

#include<sstream>

作用

istringstream类用于执行C++风格的字符串流的输入操作。 
ostringstream类用于执行C++风格的字符串流的输出操作。 
strstream类同时可以支持C++风格的串流的输入输出操作。

为什么我们要学

如果你已习惯了<stdio.h>风格的转换,也许你首先会问:为什么要花额外的精力来学习基于的类型转换呢?也许对下面一个简单的例子的回顾能够说服你。假设你想用sprintf()函数将一个变量从int类型转换到字符串类型。为了正确地完成这个任务,你必须确保证目标缓冲区有足够大空间以容纳转换完的字符串。此外,还必须使用正确的格式化符。如果使用了不正确的格式化符,会导致非预知的后果。下面是一个例子:

int n=10000;
char s[10];
sprintf(s, "%d", n);// s中的内容为“10000”

到目前为止看起来还不错。但是,对上面代码的一个微小的改变就会使程序崩溃:

int n=10000;
char s[10];
sprintf(s,"%f",n);// 看!错误的格式化符

在这种情况下,程序员错误地使用了%f格式化符来替代了%d。因此,s在调用完sprintf()后包含了一个不确定的字符串。要是能自动推导出正确的类型,那不是更好吗?

istringstream类

描述:从流中提取数据,支持 >> 操作
这里字符串可以包括多个单词,单词之间使用空格分开

istringstream的构造函数原形:  
istringstream::istringstream(string str);  

初始化:使用字符串进行初始化

istringstream istr("1 56.7");  
istr.str("1 56.7");//把字符串"1 56.7"存入字符串流中

使用:我们可以使用分解点获取不同的数据,完成字符串到其他类型的转换
常用成员函数:

str():使istringstream对象返回一个string字符串

【实例代码1】

#include <fstream>
#include <cstring>
#include <iostream>
#include <sstream>
using namespace std;
template <class T>
inline  T fromString(const string& str) {
    istringstream is(str);  //创建字符串输入流
    T v;
    is >> v;  //从字符串输入流中读取变量v
    return v; //返回变量v
}

int main(){
    int v1 = fromString<int>("5");
    cout << v1 << endl;
    double v2 = fromString<double>("1.2");
    cout << v2 << endl;
    return 0;
}

// 结果
// 5
// 1.2

ostringstream类

描述:把其他类型的数据写入流(往流中写入数据),支持<<操作

ostringstream的构造函数原形:  
ostringstream::ostringstream(string str);  

初始化:使用字符串进行初始化

ostringstream ostr("1234");  
ostr.str("1234");//把字符串"1234"存入字符串流中 

stringstream类

描述:是对istringstream和ostringstream类的综合,支持<<, >>操作符,可以进行字符串到其它类型的快速转换

stringstream的构造函数原形如下:  
stringstream::stringstream(string str);  

初始化:使用字符串进行初始化

stringstream str("1234");  
str.str("1234");//把字符串"1234"存入字符串流中  

作用:

1. stringstream通常是用来做数据转换的
2. 将文件的所有数据一次性读入内存
posted on 2020-04-14 17:51  JJ_S  阅读(125)  评论(0编辑  收藏  举报