0
点赞
收藏
分享

微信扫一扫

C++11之IO流(8千字长文详解)

C++11之IO流

什么是流?

​ “流”即是流动的意思,是物质从一处向另一处流动的过程,是对一种有序连续且具有方向性的数 据( 其单位可以是bit,byte,packet )的抽象描述

C++流是指信息从外部输入设备(如键盘)向计算机内部(如内存)输入和从内存向外部输出设 备(显示器)输出的过程。这种输入输出的过程被形象的比喻为“流”。 它的特性是:有序连续、具有方向性

为了实现这种流动,C++定义了I/O标准类库,这些每个类都称为流/流类,用以完成某方面的功 能

image-20230605200800214

C++的IO流

C++系统实现了一个庞大的类库,其中ios为基类,其他类都是直接或间接派生自ios类

image-20230605201011124

C++的标准IO流

C++标准库提供了4个全局流对象cin、cout、cerr、clog,使用cout进行标准输出,即数据从内 存流向控制台(显示器)。

使用cin进行标准输入即数据通过键盘输入到程序中,同时C++标准库还 提供了cerr用来进行标准错误的输出,以及clog进行日志的输出,从上图可以看出,cout、 cerr、clog是ostream类的三个不同的对象,因此这三个对象现在基本没有区别,只是应用场景不同。

#include <iostream>
using namespace std;
int main()
{
       cout << "11111111111" <<endl;
       cerr << "22222222222" <<endl;
       clog<< "333333333333" <<endl;
       return 0;
}
//这三个实际用起来没有太多的区别!
  1. **cin为缓冲流。键盘输入的数据保存在缓冲区中,当要提取时,是从缓冲区中拿。如果一次输 入过多,会留在那儿慢慢用,如果输入错了,必须在回车之前修改,如果回车键按下就无法 挽回了。**只有把输入缓冲区中的数据取完后,才要求输入新的数据。

istream类型对象转换为逻辑条件判断值

//我们经常看到一下的写法!
#include <iostream>
using namespace std;
int main()
{
       string str;
       //流提取是一种阻塞操作!
       //当缓冲区里面有的时候就去缓冲区里面提取!没有就进行阻塞!
       while(cin >> str)
       {
           cout << str <<endl;
       }
       //那么这个程序是如何结束的?
       //正确的操作是ctrl+Z+换行!
       return 0;
}

cin>>str的返回值是一个istream! 因为本质就是>> 的重载实际调用函数operator>>(cin,str)

那么问题来了!istream是如何当做条件判断的?——一般来说我们只有bool,整形,指针可以来当做条件判断!

因为C++进行了一个隐式类型转换!

image-20230605203341507

image-20230605203432694

==这不是一个常规的重装!因为void*和bool都是一个类型!而不是运算符!==

sync_with_stdio

sync_with_stdio是一个C++标准库函数,它用于设置C++标准流(如cin和cout和C标准流(如stdin和stdout)之间的同步关系。

默认情况下,这两种流是同步的,也就是说,每次在C++流上进行输入或输出操作时,都会同步更新C流的缓冲区。这样可以保证混合使用C和C++风格的输入输出时得到正确和预期的结果。

但是,这种同步也会带来一定的性能开销,因为每次同步都需要刷新缓冲区。

如果调用sync_with_stdio(false),

那么就会关闭这种同步,让C++流拥有自己独立的缓冲区,这可能会提高一些执行速度,但也会带来一些风险。

例如,如果在关闭同步后混合使用C和C++风格的输入输出,那么可能会出现数据不一致或者顺序错乱的情况。另外,关闭同步后,C++流也不再保证线程安全。

#include <iostream>
#include <cstdio>
int main ()
{
    std::ios::sync_with_stdio(false);//关闭同步可以提高程序效率!
    std::cout << "a\n";
    std::printf("b\n");
    std::cout << "c\n";
}

image-20230605211915273

==如果不关闭==

image-20230605211936713

IO的文件流

C++根据文件内容的数据格式分为二进制文件和文本文件。

ostream/ifstream

==成员函数==

image-20230606104030077

#include <iostream>
#include <string>
#include <fstream>
struct ServerInfo
{
    char _address[32];
    int _port;
};

struct ConfigManage
{
    ConfigManage(const char* filename)
            :_filename(filename)
    {}

    void WriteBin(const ServerInfo& info)//这个是二进制读
    {
        ofstream ofs(_filename,std::ofstream::out|std::ofstream::binary);
        //因为out/binary这些在基类里面就定义了!所以也可以怎么写 ofstream ofs(_filename,std::ios_base::out,std::ios_binary);
        ofs.write((char*)&info,sizeof(info));//虽然是const char*类型,但是只要强转一下就可以了!
    }

    void ReadBin(ServerInfo& info)
    {
        ifstream ifs(_filename,std::ifstream::in|std::ifstream::binary);
        ifs.read((char*)&info,sizeof(info));
    }
private:
    string _filename;//配置文件
};
int main()
{
    ConfigManage cm("test.txt");
    ServerInfo info{"192.0.0.0",8080};
    cm.WriteBin(info);

    ServerInfo rinfo;
    cm.ReadBin(rinfo);

    return 0;
}

==如果是string我们就要谨慎的用二进制读写!==

#include <iostream>
#include <string>
#include <fstream>
struct ServerInfo
{
    string _address;
    int _port;
};

struct ConfigManage
{
    ConfigManage(const char* filename)
        :_filename(filename)
        {}

    void WriteBin(const ServerInfo& info)//这个是二进制读
    {
        ofstream ofs(_filename,std::ofstream::out|std::ofstream::binary);
        ofs.write((char*)&info,sizeof(info));
    }

    void ReadBin(ServerInfo& info)
    {
        ifstream ifs(_filename,std::ifstream::in|std::ifstream::binary);
        ifs.read((char*)&info,sizeof(info));
    }
    private:
    string _filename;//配置文件
};
int main()
{
    ConfigManage cm("test.txt");
    ServerInfo info{"192.0.0.0",8080};
    cm.WriteBin(info);

    ServerInfo rinfo;
    cm.ReadBin(rinfo);

    return 0;
}

image-20230606172806997

所以当使用二进制读写的时候要不可以使用string!(二进制读写就是将数据原模原样的写出去!)如果我们用的是数组,数组的内容会被原模原样的写出去,所以就不会出错!

C++文本读写会更加好一些

#include <iostream>
#include <string>
#include <fstream>
struct ServerInfo
{
    string _address;
    int _port;
};

struct ConfigManage
{
    ConfigManage(const char* filename)
            :_filename(filename)
    {}

    void WriteBin(const ServerInfo& info)//这个是二进制读
    {
        ofstream ofs(_filename,std::ofstream::out|std::ofstream::binary);
        ofs.write((char*)&info,sizeof(info));
    }

    void ReadBin(const ServerInfo& info)
    {
        ifstream ifs(_filename,std::ifstream::in|std::ifstream::binary);
        ifs.read((char*)&info,sizeof(info));
    }

    void WriteText(const ServerInfo& info)//文本读写!就无论是数组还是string都是可以的!
    {
        ofstream ofs(_filename,std::ofstream::out);
        //ofstream ofs(_filename);//直接怎么写也可以,因为ofstream第二个参数的缺省就是std::ofstream::out
        ofs << info._address <<endl;
        ofs << info._port <<endl;
        //因为重载了 << 我们可以直接怎么进行写入到文件里面!
        //后面一定要加上空格或者换行!因为!文本是以空格或者换行作为多个值的分割!
    }

    void ReadText(ServerInfo& info)
    {
        ifstream  ifs(_filename);
        ifs >> info._address;
        ifs >> info._port;
        //>> 同理!
    }
private:
    string _filename;//配置文件
};
int main()
{
    ConfigManage cm("test.txt");
    ServerInfo info{"192.0.0.0",8080};
    //cm.WriteText(info);
    //可以先写后然后屏蔽上面写

    ServerInfo rinfo;
    cm.ReadText(rinfo);
    cout << rinfo._address << " " <<rinfo._port <<endl;
    return 0;
}

如果不在后面加上换行我们读取到的结果是这样的!

image-20230606201210825

加了换行后的正常结果

image-20230606201603361

如果我们里面有一个自定义类型应该办?

#include <iostream>
#include <string>
#include <fstream>

class A
{
    friend istream& operator>>(istream& in,A& a);
    friend ostream& operator<< (ostream& out,A& a);
public:
    A(int a,int a2)
    :_a(a1),
    _a2(a2)
    {}

    A()
    {}

    operator bool()
    {
        if(_a == 0)
        {
            return false;
        }
        return true;
    }
private:
    int _a = 0;
    int _a2 = 0;
};
istream& operator>> (istream& in ,A& a)
{
   in >> a._a >> a._a2;
   return in;
}
ostream& operator<< (ostream& out,const A& a)//这里的A要加上const否则会报错!
{
   out << a._a << " "<<a._a2;
   return out;
}

struct ServerInfo
{
    string _address;
    int _port;
    A _a;//有一个自定义类型该怎么办?
};


struct ConfigManage
{
    ConfigManage(const char* filename)
            :_filename(filename)
    {}

    void WriteBin(const ServerInfo& info)//这个是二进制读
    {
        ofstream ofs(_filename,std::ofstream::out|std::ofstream::binary);
        ofs.write((char*)&info,sizeof(info));
    }

    void ReadBin(const ServerInfo& info)
    {
        ifstream ifs(_filename,std::ifstream::in|std::ifstream::binary);
        ifs.read((char*)&info,sizeof(info));
    }

    void WriteText(const ServerInfo& info)//文本读写!
    {
        ofstream ofs(_filename,std::ofstream::out);
        ofs << info._address << endl;
        ofs << info._port <<endl;

        //因为我们上面已经重装了流插入和流提取!
        //所以我们这里也可以使用!
        ofs << info._a << endl;
        //这个将任何类型都转换为文本类型的行为我们一般称之为序列化!
    }

    void ReadText(ServerInfo& info)
    {
        ifstream  ifs(_filename);
        ifs >> info._address;
        ifs >> info._port;
        ifs >> info._a;
        //只要我们重装了 >> 这里也可以使用!
    }
private:
    string _filename;//配置文件
};
int main()
{
    ConfigManage cm("test.txt");
    ServerInfo info{"192.0.0.0",8080 ,{1,2}};
    //cm.WriteText(info);

    ServerInfo rinfo;
    cm.ReadText(rinfo);
    cout << rinfo._address << " " << rinfo._port <<" " << rinfo._a;
    return 0;
}

image-20230606203435782

image-20230606204122006

sstream

这个设计的是用来完成字符串的序列化和反序列化——不过因为这个sstream设计太简单,所以实际上只能对一些简单的类型来使用

总结
  1. stringstream实际是在其底层维护了一个string类型的对象用来保存结果。
  2. 多次数据类型转化时,一定要用clear()来清空,才能正确转化,但clear()不会将 stringstream底层的string对象清空。
  3. 可以使用s. str("")方法将底层string对象设置为""空字符串。
  4. 可以使用s.str()将让stringstream返回其底层的string对象。
  5. stringstream使用string类对象代替字符数组,可以避免缓冲区溢出的危险,而且其会对参 数类型进行推演,不需要格式化控制,也不会出现格式化失败的风险,因此使用更方便,更 安全。
举报

相关推荐

0 条评论