C++11之IO流
什么是流?
“流”即是流动的意思,是物质从一处向另一处流动的过程,是对一种有序连续且具有方向性的数 据( 其单位可以是bit,byte,packet )的抽象描述。
C++流是指信息从外部输入设备(如键盘)向计算机内部(如内存)输入和从内存向外部输出设 备(显示器)输出的过程。这种输入输出的过程被形象的比喻为“流”。 它的特性是:有序连续、具有方向性
为了实现这种流动,C++定义了I/O标准类库,这些每个类都称为流/流类,用以完成某方面的功 能
C++的IO流
C++系统实现了一个庞大的类库,其中ios为基类,其他类都是直接或间接派生自ios类
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;
}
//这三个实际用起来没有太多的区别!
- **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++进行了一个隐式类型转换!
==这不是一个常规的重装!因为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";
}
==如果不关闭==
IO的文件流
C++根据文件内容的数据格式分为二进制文件和文本文件。
ostream/ifstream
==成员函数==
#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;
}
所以当使用二进制读写的时候要不可以使用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;
}
如果不在后面加上换行我们读取到的结果是这样的!
加了换行后的正常结果
如果我们里面有一个自定义类型应该办?
#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;
}
sstream
这个设计的是用来完成字符串的序列化和反序列化——不过因为这个sstream设计太简单,所以实际上只能对一些简单的类型来使用
总结
- stringstream实际是在其底层维护了一个string类型的对象用来保存结果。
- 多次数据类型转化时,一定要用clear()来清空,才能正确转化,但clear()不会将 stringstream底层的string对象清空。
- 可以使用s. str("")方法将底层string对象设置为""空字符串。
- 可以使用s.str()将让stringstream返回其底层的string对象。
- stringstream使用string类对象代替字符数组,可以避免缓冲区溢出的危险,而且其会对参 数类型进行推演,不需要格式化控制,也不会出现格式化失败的风险,因此使用更方便,更 安全。