• C++流概述


    C++流概述
    在程序设计中,数据输入/输出(I/O)操作是必不可少的,C++语言的数据输入/输出操作是通过I/O流库来实现的。C++中把数据之间的传输操作称为流,流既可以表示数据从内存传送到某个载体或设备中,即输出流,也可以表示数据从某个载体或设备传送到内存缓冲区变量中,即输入流。


    C++流涉及以下概念:

    标准I/O流:内存与标准输入输出设备之间信息的传递;
    文件I/O流:内存与外部文件之间信息的传递;
    字符串I/O流:内存变量与表示字符串流的字符数组之间信息的传递。
    STL中定义的流类:

    流类分类 流类名称 流 类 作 用
    流基类 ios 所有流类的父类,保存流的状态并处理错误
    输入流类 istream 输入流基类,将流缓冲区中的数据作格式化和非格式化之间的转换并输入
      ifstream 文件输入流类
      istream_withassign cin输入流类,即操作符>>输入流
      istrstream 串输入流类, 基于C类型字符串char*编写
      istringstream 串输入流类, 基于std::string编写
    输出流类 ostream 输出流基类,将流缓冲区中的数据作格式化和非格式化之间的转换。并输出
      ofstream 文件输出流类
      ostream_withassign Cout、cerr、clog的输出流类,即操作符<<输出流
      ostrstream 串输入流类, 基于C类型字符串char*编写
      ostringstream 串输入流类, 基于std::string编写
    输入/输出流类 iostream 多目的输入/输出流类的基类
      fstream 文件流输入/输出类
      strstream 串流输入/输出类, 基于C类型字符串char*编写
      stringstream 串流输入/输出类, 基于std::string编写
    注,对于串流,提供了两套类,一个基于C类型字符串char *编写(定义于头文件strstream),一个基于std::string编写(定义于sstream), 后者是C++标准委员会推荐使用的。另外看一些资料,都说还定义了文件流和串流的基类fstreambase和strstreambase,而且描述ofstream继承于ostream和fstreambase,但没有找到,于是省略了。可搜索关键字(fstreambase或strstreambase)查看详情。

    预定义标准流对象:

    istream &cin; //键盘 
    ostream &cout; //屏幕 
    ostream &cerr; //屏幕 
    ostream &clog; //打印机 
    wistream &wcin; 
    wostream &wcout; 
    wostream &wcerr; 
    wostream &wclog;

    C++中读取string对象
    1.标准输入读取:cin >> string

    a.忽略开头所有的空白字符(空格、换行、制表符等);

    b.读取字符直至再次遇到空白字符,读取终止;

    2.读取整行文本:getline(istream, string)

    a.不忽略开头的空白字符;

    b.读取字符直至遇到换行符,如果第一个字符是换行符,则返回空string;

    c.返回时丢弃换行符,换行符不存储在string中。

    STL C++中string、ifstream、stringstream的使用
    1、从标准输入接受字符串,然后进行相关处理

    #include<iostream> 
    #include<sstream> 
    #include<string> 
    using namespace std; 
    int main() 

    string s; //定义一个stirng对象,从标准输入接受一个字符串 
    cout<<"请输入一行字符串:"<<endl; 
    getline(cin,s); 
    stringstream ss(s); //定义一个string流(使用s实例化) 
    cout<<"处理后的字符串为:"<<endl; //将string流里的东西输出 
    for(string s1;ss>>s1;cout<<s1<<endl); 
    return 0; 
    }

    运行结果如下:

    请输入一行字符串: 
    you are a good boy 
    处理后的字符串为: 
    you 
    are 

    good 
    boy

    根据前文所说“忽略开头空白字符,读取字符直至再次遇到空白字符为止”,这样的结果不难理解。

    2、从文件读入字符串,然后进行相关处理

    #include<iostream> 
    #include<sstream> 
    #include<string> 
    #include<fstream> 
    using namespace std; 
    int main() 

    ifstream fin("test.in"); //定义一个文件输入流,从文件中读取数据 
    if(!fin) 

    cout<<"can not open the input file"; 
    return -1; 

    string s; //定义一个string,获取文件输入流中的一行 
    while(getline(fin,s)) 

    stringstream ss(s); //定义一个string流(使用一个string对象填充) 
    int a,b; 
    ss>>a;ss>>b; //将string流中的两个值分别读入a、b中 
    cout<<"该行数据和为:"<<a+b<<endl; 

    return 0; 
    }

    test.in文件内容如下:

    1 10 
    2 100 
    3 8 
    4 9

    运行结果为:

    该行数据和为:11 
    该行数据和为:102 
    该行数据和为:11 
    该行数据和为:13

    其中,getline函数原型如下:

    template<class E, class T, class A> 
    basic_istream<E, T>& getline( 
    basic_istream <E, T>& is, 
    basic_string<E, T, A>& str); 
    template<class E, class T, class A> 
    basic_istream<E, T>& getline( 
    basic_istream <E, T>& is, 
    basic_string<E, T, A>& str, 
    E delim);


    第二个重载函数很有意思,结尾是char, 或wchar型的一个分隔符,如果设为’ ’,则为以换行符为分隔,如果设为’,',则为以逗号为分隔。由此,虽然C++中的字符串没有分割函数,如果是从文件中读取出被特定分隔符分隔的文本,那么就可以用此方法,如:

    std::ifstream file; 
    file.open("tt.txt"); 
    std::string s,t; 
    while(std::getline(file,s)) //按行读取 

    std::stringstream strs(s); //把行装到另一个流中 
    while(std::getline(strs,t,’,')) //把流按分隔符实现分割 
    std::cout<<t<<std::endl; 

    file.close();

    上面的程序相当于将整个文本先按行分割,再按分隔符分割,也可以变换一下,只按分隔符分割,然后过滤掉按行符,换行符与某元素连在了一起,并处于开头。

    C++的流操作复制文件
    写.wrl, .obj文件格式转换时用到,记录一下相关方法

    使用C++标准程序库的输入输出流(I/O Stream)复制文件,存在许多的方法,

    方法一:逐个字符复制

    #include < fstream > 
    std::ifstream input("in",iOS::binary); 
    std::ofstream output("out",ios::binary); 
    char ch; 
    while (input.get(ch)) output << ch;

    注意:如果使用input>>ch读取字符,则必须先调用input.unsetf(ios::skipws)取消输入流默认的跳过空白符(空格、换行、制表符等)的输入格式,因为换行符是空白符的一种。另外,对于ofstream对象,默认的操作方式是ios_base::out | ios_base::trunc,即输出和文件清空!

    方法二:逐行复制

    #include < fstream > 
    #include < string > 
    std::ifstream input("in",ios::binary); 
    std::ofstream output("out",ios::binary); 
    std::string line; 
    while (getline(input,line)) output << line << " ";

    注意:这里的代码有一个小小的缺陷,如果文件不是纯文本格式的文件,或者文本文件的最后没有换行符,那么会导致复制后的文件末尾添加了一个多余的换行符。

    方法三:迭代器复制

    #include < fstream > 
    #include < iterator > 
    #include < algorithm > 
    std::ifstream input("in",ios::binary); 
    std::ofstream output("out",ios::binary); 
    input.unsetf(ios::skipws); 
    copy(istream_iterator(input),istream_iterator(),ostream_iterator(output,""));

    同样这里也有一个小技巧,输入流的格式默认为跳过空白字符,因此调用unsetf取消这个格式,才可保证正确的复制。见后方中字符串缓冲与文件缓冲类的定义,文件缓冲类没有定义自己的迭代器,所以就用了

    方法四:缓冲区复制

    #include < fstream > 
    std::ifstream input("in",ios::binary); 
    std::ofstream output("out",ios::binary); 
    output << input.rdbuf();

    这里直接使用了输入流的缓冲区,因此没有引入额外的临时对象。

    很显然,上述四种方法中,最后一种方法最简洁,由于直接操作输入流的缓冲区,从运行效率上来说,也比其他方法有着略微的优势(当然,由于操作系统可能提供了 额外的基于设备的文件缓冲机制,也许你无法证实这一点)。因此,除非要对输入内容进行处理,直接复制文件推荐最后一种方法,既不容易出错,又能获得良好的性能。

    另外,对文件进行更改、删除、插入等操作,可以直接用以上方法也可以先把文件读入vector<string>,处理后再输出,不过当文件很大的时候,vector占用内存空间较大,而且输出时会破坏原文件格式,尽量不使用。

    以上是几种同种流(文件流)之间的数据复制的方式,字符串流与文件流之间也可以以此方式进行复制。另外,再看一下get函数:

    int_type get(); 
    basic_istream& get(E& c); 
    basic_istream& get(E *s, streamsize n); 
    basic_istream& get(E *s, streamsize n, E delim); 
    basic_istream& get(basic_streambuf<E, T> *sb); 
    basic_istream& get(basic_streambuf<E, T> *sb, E delim);

    delim表示结束符,前文中讨论的流分割函数还记得吧?又多了种方法,估计get与全局函数getline(不是那个成员函数,成员函数要求给出streamsize,而全局的就不用)实现得差不多。默认的也是’ ’,按行分隔。

    C++流缓冲区的应用——输出文件内容的方法举例
    简单讨论C++流对象的底层缓冲区,并举例介绍如何使用该缓冲区进行文件内容的输出

    C++标准库封装了一个缓冲区类streambuf,以供输入输出流对象使用。每个标准C++输出输出流对象都包含一个指向streambuf的指针,用户可以通过调用rdbuf()成员函数获得该指针,从而直接访问底层streambuf对象。因此,可以直接对底层缓冲区进行数据读写,从而跳过上层的格式化输入输出操作。

    template <class Elem, class Traits> class basic_ios 
    : public ios_base { 
    basic_streambuf<_Elem, _Traits>*_Mystrbuf; 
    //C++标准库封装了一个缓冲区类streambuf。 
    _Mysb * rdbuf() const 
    { // return stream buffer pointer 
    return (_Mystrbuf); 

    //使调用者与参数(流缓冲指针)关联, 
    //返回自己当前关联的流缓冲区指针, 可用来重定向等 
    _Mysb * rdbuf(_Mysb *_Strbuf) 
    { // set stream buffer pointer 
    _Mysb *_Oldstrbuf = _Mystrbuf; 
    _Mystrbuf = _Strbuf; 
    return (_Oldstrbuf); 

    };

    对象通过调用rdbuf()获得了底层streambuf对象的指针,也就可以通过该指针调用streambuf支持你各种操作进行输入输出。在这里主要介绍如何利用该指针实现文件内容的输出。

    对于文件流类和字符串流类,分别派生了相应的流缓冲区类型:

    template<class _Elem, 
    class _Traits> class basic_streambuf; typedef basic_streambuf<char, char_traits<char> > streambuf; typedef basic_streambuf<wchar_t, char_traits<wchar_t> > wstreambuf; 
    template <class Elem, class Tr = char_traits<Elem>, class Alloc = allocator<Elem> > class basic_stringbuf : 
    public basic_streambuf<Elem, Tr> 
    typedef basic_stringbuf<char, char_traits<char>, allocator<char> > stringbuf; typedef basic_stringbuf<wchar_t, char_traits<wchar_t>, allocator<wchar_t> > wstringbuf; 
    template <class Elem, class Tr = char_traits<Elem> > class basic_filebuf : public basic_streambuf<Elem, Tr> 
    typedef basic_filebuf<char, char_traits<char> > filebuf; 
    typedef basic_filebuf<wchar_t, char_traits<wchar_t> > wfilebuf;

    输出流提供了一个重载版本operator<<,它以streambuf指针为参数,实现把streambuf对象中的所有字符输出到输出流出中;输入流也提供了一个对应的operator>>重载版本,把输入流对象中的所有字符输入到streambuf对象中。输入流的get成员重载版本中还有以streambuf指针为参数的版本,也可以用来把输入流的东西写入到输出流缓冲区中。

    下面用三种方法实现把一个文件的内容输出标准输出(当然还可以通过普通的标准格式化输入输出完成):

    方法一:通过operator<<

    #include <iostream> 
    #include <fstream> 
    using namespace std; 
    int main() 

    ifstream fin("source.dat"); 
    cout<<fin.rdbuf(); 
    return 0; 
    }

    方法二:利用get成员函数

    ifstream fin("source.dat"); 
    while (!fin.get(*cout.rdbuf()).eof()) { // extract a line input 
    if (fin.fail()) // blank line 
    fin.clear(); 
    cout<<char(fin.get()); // extract ' ' 
    }

    代码解释:由于上面代码中的get版本在遇到’ ’字符时,结束提取,所以需要用循环实现整个文件内容的输出。另外,当此版本get函数遇到空行时,因为没有提取到任何字符(注意:get不提取回车符),注意会设置失败标志ios::failbit,所以此时应当调用clear()函数清除错误标志。同样,因为该版本get不会提取回车符,所以需要用另一版本的get()提取回车符。不同版本的Get函数参见前文。此处还使用了*cout.rdbuf(),cout是ostream类的对象,当然就有缓冲区,可以用rdbuf返回缓冲区对象的指针。最后,关于fin.clear, 需要特别注意的是:要清空流类对象的内存,不能用clear方法,那只是设置了错误标志位;

    方法三:利用重载的get函数

    ifstream fin("main.cpp"); 
    fin.get(*cout.rdbuf(), EOF);

    代码解释:这个版本的get成员函数可以自定义提取终止符。这里通过设置为文件结束符(EOF)来达到一下提取整个文件的目的。

    当然,你可以把上面的cout换成任意的输出流,比如文件输出流,从而可以实现文件的拷贝功能。

    另外,上面代码中并没有使用输入流的>>操作符,因为>>和<<是相对的,只是把操作数交换一下位置罢了。因此,你可以把上面代码中用out<<in.rdbuf()的地方换成in>>out.rdbuf(),代码的功能完全一样,效果也一样。

    关于流缓冲区
    1. 缓冲类型。

    标准库提供缓冲是为了减少对read和write的调用。提供的缓冲有三种类型(整理自APUE):

    全缓冲。 在这种情况下,实际的I/O操作只有在缓冲区被填满了之后才会进行。对驻留在磁盘上的文件的操作一般是有标准I/O库提供全缓冲。缓冲区一般是在第一次对流进行I/O操作时,由标准I/O函数调用malloc函数分配得到的。flush描述了标准I/O缓冲的写操作。缓冲区可以由标准I/O函数自动flush(例如缓冲区满的时候);或者我们对流调用fflush函数。
    行缓冲。 在这种情况下,只有在输入/输出中遇到换行符的时候,才会执行实际的I/O操作。这允许我们一次写一个字符,但是只有在写完一行之后才做I/O操作。一般的,涉及到终端的流–例如标注输入(stdin)和标准输出(stdout)–是行缓冲的。

    无缓冲。 标准I/O库不缓存字符。需要注意的是,标准库不缓存并不意味着操作系统或者设备驱动不缓存。

    ISO C要求:

    当且仅当不涉及交互设备时,标准输入和标准输出是全缓存的。

    标准错误绝对不是全缓存的。

    但是,这并没有告诉我们当标准输入/输出在涉及交互设备时,它们是无缓存的还是行缓存的;也没有告诉我们标准错误应该是行缓存的还是无缓存的。不过,大多数实现默认的缓存类型是这样的:

    标准错误总是无缓存的。

    对于所有的其他流来说,如果它们涉及到交互设备,那么就是行缓存的;否则是全缓存的。

    2. 改变默认缓存类型,即自定义缓冲区

    a. 对于C中文件操作:

    可以通过下面的函数改变缓存类型(摘自APUE):

    void setbuf(FILE *restrict fp, char *restrict buf); 
    int setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);

    这些函数必须在流打开之后、但是未对流做任何操作之前被调用(因为每个函数都需要一个有效的文件指针作为第一个参数)。

    利用setbuf,可以打开或者关闭缓存。为了打开缓存,buf参数必须一个大小为BUFSIZ的缓存,BUFSIZ是定义在stdio.h中的常量。<ISO/IEC 9899>要求:BUFSIZ至少为256。如果要关闭缓存,可以将buf设成NULL。

    利用setvbuf,我们可以设定缓存类型。这是通过mode参数指定的。

    b. 对于C++中流:

    virtual basic_streambuf *setbuf(E *s, streamsize n);

    第一个参数指向自定义缓冲区空间,第二个为缓冲区大小。

    格式化输出
    查看《C++流操作》

    摘要:控制符是在头文件iomanip.h中定义的对象。 使用前必须把iomanip.h包含进来

    摘选自:

    (1)《C++流总结》

    (2)《关于流和缓冲区的理解》
    ---------------------
    作者:erzr_zhang
    来源:CSDN
    原文:https://blog.csdn.net/erzr_zhang/article/details/52770789
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    IntelliJ Idea和IntelliJ webstrm 常用快捷键
    mybatis基础学习2---(resultType和resultMap的用法和区别)和setting的用法
    使用观察者模式观察线程的生命周期
    观察者设计模式介绍
    java的内存模型及缓存问题
    一个解释volatile关键字作用的最好的例子
    多线程的waitset详细介绍
    浅谈单例模式
    SimpleThreadPool给线程池增加自动扩充线程数量,以及闲时自动回收的功能
    SimpleThreadPool给线程池增加拒绝策略和停止方法
  • 原文地址:https://www.cnblogs.com/lixuejian/p/10821730.html
Copyright © 2020-2023  润新知