IO流
- C语言的输入与输出
- 流是什么
- CppIO流
- stringstream的介绍
1.C语言的输入与输出
C语言中我们用到的最频繁的输入输出方式就是scanf ()与printf()。其他还有文件读写的接口,网络中API。
scanf(): 从标准输入设备(键盘)读取数据,并将值存放在变量中。
printf(): 将指定的文字/字符串输出到标准输出设备(屏幕)。注意宽度输出和精度输出控制。
C语言借助了相应的缓冲区来进行输入与输出。
IO:输入输出
设备:磁盘,网卡,显示器
IO接口函数屏蔽底层细节。
对输入输出缓冲区的理解:
1.可以屏蔽掉低级I/O的实现,低级I/O的实现依赖操作系统本身内核的实现,所以如果能够屏蔽这部分的差 异,可以很容易写出可移植的程序。
2.可以使用这部分的内容实现“行”读取的行为,对于计算机而言是没有“行”这个概念,有了这部分,就可以 定义“行”的概念,然后解析缓冲区的内容,返回一个“行”。
2.流是什么
“流”即是流动的意思,是物质从一处向另一处流动的过程,是对一种有序连续且具有方向性的数据( 其单位 可以是bit,byte,packet )的抽象描述。 C++流是指信息从外部输入设备(如键盘)向计算机内部(如内存) 输入和从内存向外部输出设备(显示器)输出的过程。这种输入输出的过程被形象的比喻为“流”。
比如
cin>>s.name>>s.age;//数据从设备(终端)流向对象(内存)
cout<<s.name<<s.age<<endl;//数据对象(内存)流向终端
3.c++的IO流
C++系统实现了一个庞大的类库,其中ios为基类,其他类都是直接或间接派生自ios。
但c++标准库并没提供网络的接口。
cin和cout是istream的全局对象。
本意是cout进行正常的输出,cerr进行错误的输出,clog是进行日志的输出。不过后两个基本没人用。
fstream是以流的方式进行读写文件。
sstream是进行字符串的序列化和反序列化,用于网络。
3.1c++标准IO流
C++标准库提供了4个全局流对象cin、cout、cerr、clog。
使用cout进行标准输出,即数据从内存流向控制台(显示器)。
使用cin进行标准输入即数据通过键盘输入到程序中,
同时C++标准库还提供了cerr用来进行标准错误的输出,以及clog进行日志的输出,从上图可以看出,cout、cerr、clog是ostream类的三个不同的对象,因此这三个对象现在基本没有区别,只是应用场景不同。
struct P{
string name="xxx";
int age=21;
};
int main(void){
P s;
cout<<"名字:"<<s.name<<" 年龄:"<<s.age<<endl;
printf("名字:%s 年龄:%d\n",s.name.c_str(),s.age);
return 0;
}
这样的输入看起来可以,但是实际上是不行的。
struct P{
string name="xxx";
int age=21;
};
int main(void){
P s;
cin>>s.name>>s.age;
scanf("%s%d",s.name.c_str(),&s.age);
return 0;
}
cin的时候不论输入的时候多长,我们自己当时实现的时候是push_back。
istream& operator>>(istream& in,const string&s){
s.clear();//库的string虽然初始化好了重新输入的时候是新的内容。
while(1){
char ch;
//in>>ch;///系统实现的cin>>ch,输入'\n'接收不到
in.get(ch);
if(ch=='\n'){
break;
}
s+=ch;
}
return in;
}
//s1+='a';
string& operator+=(char ch){
this->push_back(ch);
return *this;
}
string在第一次输入的时候是动态的,而后面scanf直接输入是在string的指针后面追加,并没有扩容,所以会出事。
再比如:
struct P{
string name="xxx";
int age=21;
};
int main(void){
P s;
cin>>s.name>>s.age;
cout<<"名字:"<<s.name<<" 年龄:"<<s.age<<endl;
scanf("%s%d",s.name.c_str(),&s.age);
printf("名字:%s 年龄:%d\n",s.name.c_str(),s.age);
cout<<"名字:"<<s.name<<" 年龄:"<<s.age<<endl;
return 0;
}
回想模拟string类的时候,string的cout我们是按照size来输出的,而自带的printf是按照’\0’输出的。直接从scanf输入并没有改变size。因此如果非要用scanf给string,那么提前对string使用resize把空间开好。
建议:
- C++尽量去用cin和cout,能用它就用它
- 用cout和cin不方便的地方,再去使用scanf和printf
- 比如输出浮点数的小数点位数,cout的代码比printf多很多。
注意:
cin为缓冲流,键盘输入的数据保存在缓冲区中,当要提取时,是从缓冲区中拿。如果一次输入过多, 会留在那儿慢慢用,如果输入错了,必须在回车之前修改,如果回车键按下就无法挽回了。只有把输入缓冲区中的数据取完后,才要求输入新的数据。
输入的数据类型必须与要提取的数据类型一致,否则出错。出错只是在流的状态字state中对应位置位 (置1),程序继续。
空格和回车都可以作为数据之间的分格符,所以多个数据可以在一行输入,也可以分行输入。但如果是字符型和字符串,则空格(ASCII码为32)无法用cin输入,字符串中也不能有空格。回车符也无法读入。读取一行要使用getline。
cin和cout可以直接输入和输出内置类型数据,原因:标准库已经将所有内置类型的输入和输出全部重 载
对于自定义类型,如果要支持cin和cout的标准输入输出,需要对<<和>>进行重载。
对于IO类型的算法,一般都需要循环输入,可以使用ctrl+c就退出了。
while(cin>>a){ //实际调用的是operator>>(cin,str)函数,函数的返回值类型是istream&,返回对象是cin。 cout<<a<<endl; } while(scanf("%d",&a)!=EOF){ }
后者好理解,scanf函数的返回值,前者难道也有返回值吗?
cin会强转,如果发生了错误(比如说ctrl+c使流停止)则返回空指针(相当于逻辑0),不然就是其他值。那么除了0以外其他仍被认为是true。
因此我们想要一个自定义类型对象可以去直接做条件逻辑判断,就可以去重载 operator void*()或者operator bool()。(看起来确实怪)。跟operator new很像,属于重载中的特例。不像其他的写法一样重载运算符。
- 假设我想把对象类型强转成bool,而类型强转()这个运算符场景不仅仅这里需要,其他地方也有。bool operator()()【那就要写成这样】,void* operator()()【那就要写成这样】。但是这样编译器就不好进行语法分析了。
- 重载了operator()的类叫仿函数,因为这个类的对象看起来可以像函数一样去使用。因此上面的形式编译器就不好判断是仿函数还是转成条件逻辑判断式了。
- 所以记住想要自定义类型对象做条件逻辑判断重载的方式是explict operator bool() const;可以理解为是类型强转的重载
3.2c++文件IO流
- ifstream是用来读的。
- ofstream是用来写的。
其析构的时候会调用close();
常用接口:
如标准IO流中提到的,这些流都重载了operator bool() const;都有返回值可以当逻辑判断。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+1000;
typedef long long LL;
inline LL read(){LL x=0,f=1;char ch=getchar(); while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;}
void readfile(){
ifstream ifs("IO流例子.cpp");
char ch;
/*
while( (ch=ifs.get())!=EOF){
cout<<ch;
}*/
/*while(ifs.get(ch)){
cout<<ch;
}*/
while(ifs){
cout<<(char)ifs.get();
}
}
int main(void){
readfile();
return 0;
}
void writefile(){
ofstream ofs("test.txt");
ofs.put('h');
ofs.write("hello world",11);
}
int main(void){
///readfile();
writefile();
return 0;
}
3.2.1二进制文件读写
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+1000;
typedef long long LL;
inline LL read(){LL x=0,f=1;char ch=getchar(); while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;}
struct Server{
string Ip;
int port;
};
struct ConfigManager{
ConfigManager(const string& Configaddress)
:_Configaddress(Configaddress)
{}
///以二进制形式写出
ConfigWbin(const Server& server){
ofstream ofs(_Configaddress);
ofs.write((char*)&server,sizeof(server));
}
///以二进制形式读入
ConfigRbin(const Server& server){
ifstream ifs(_Configaddress);
ifs.read((char*)&server,sizeof(server));
}
private:
string _Configaddress;
};
int main(void){
ConfigManager CM("IOtest.txt");
Server s={"192.168.0.1",8336};
CM.ConfigWbin(s);
Server p;
CM.ConfigRbin(p);
cout<<p.Ip<<" "<<p.port<<endl;
return 0;
}
可以发现二进制的好处就是方便,直接全部转化成字符给传出去,不过坏处就是我们看不了二进制文件,只能看到原本是ascii表示的。
3.2.2文本文件读写
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+1000;
typedef long long LL;
inline LL read(){LL x=0,f=1;char ch=getchar(); while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;}
struct Server{
string Ip;
int port;
};
struct ConfigManager{
public:
ConfigManager(const string& Configaddress)
:_Configaddress(Configaddress)
{}
///以二进制形式写出
ConfigWbin(const Server& server){
ofstream ofs(_Configaddress);
ofs.write((char*)&server,sizeof(server));
}
///以二进制形式读入
ConfigRbin(Server& server){
ifstream ifs(_Configaddress);
ifs.read((char*)&server,sizeof(server));
}
///以文本形式写出(全转成字符串)
ConfigWtxt(const Server& server){
ofstream ofs(_Configaddress);
ofs.write(server.Ip.c_str(),server.Ip.length());
ofs.put('\n');
string tmp=to_string(server.port);
ofs.write(tmp.c_str(),tmp.length());
}
///以文本形式读入
ConfigRtxt(Server& server){
ifstream ifs(_Configaddress);
char str[20];
ifs.getline(str,20);
server.Ip=(str);
ifs.getline(str,20);
string tmp(str);
server.port=stoi(tmp);
}
private:
string _Configaddress;
};
int main(void){
ConfigManager CM("IOtest.txt");
Server s={"192.168.0.3",8446};
CM.ConfigWtxt(s);
Server p;
CM.ConfigRtxt(p);
cout<<p.Ip<<" "<<p.port<<endl;
return 0;
}
3.2.3流读写
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+1000;
typedef long long LL;
inline LL read(){LL x=0,f=1;char ch=getchar(); while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;}
struct Server{
string Ip;
int port;
};
struct ConfigManager{
public:
ConfigManager(const string& Configaddress)
:_Configaddress(Configaddress)
{}
///以二进制形式写出
ConfigWbin(const Server& server){
ofstream ofs(_Configaddress);
ofs.write((char*)&server,sizeof(server));
}
///以二进制形式读入
ConfigRbin(Server& server){
ifstream ifs(_Configaddress);
ifs.read((char*)&server,sizeof(server));
}
///以文本形式写出(全转成字符串)
ConfigWtxt(const Server& server){
ofstream ofs(_Configaddress);
ofs.write(server.Ip.c_str(),server.Ip.length());
ofs.put('\n');
string tmp=to_string(server.port);
ofs.write(tmp.c_str(),tmp.length());
}
///以文本形式读入
ConfigRtxt(Server& server){
ifstream ifs(_Configaddress);
char str[20];
ifs.getline(str,20);
server.Ip=(str);
ifs.getline(str,20);
string tmp(str);
server.port=stoi(tmp);
}
///以流形式写出
ConfigCouttxt(const Server& server){
ofstream ofs(_Configaddress);
ofs<<server.Ip<<" "<<server.port;
}
///以流形式读入
ConfigCintxt(Server& server){
ifstream ifs(_Configaddress);
ifs>>server.Ip>>server.port;
}
private:
string _Configaddress;
};
int main(void){
ConfigManager CM("IOtest.txt");
Server s={"192.168.0.5",8446};
CM.ConfigCouttxt(s);
Server p;
CM.ConfigCintxt(p);
cout<<p.Ip<<" "<<p.port<<endl;
return 0;
}
3.3字符串的序列化与反序列化
- stringstream实际是在其底层维护了一个string类型的对象用来保存结果。
- 多次数据类型转化时,一定要用clear()来清空,才能正确转化,但clear()不会将stringstream底层的 string对象清空。
- 可以使用s. str("")方法将底层string对象设置为""空字符串。
- 可以使用s.str()将让stringstream返回其底层的string对象。
- stringstream使用string类对象代替字符数组,可以避免缓冲区溢出的危险,而且其会对参数类型进 行推演,不需要格式化控制,也不会出现格式化失败的风险,因此使用更方便,更安全。
3.3.1C语言的方式
可以发现C语言的方式需要开一个额外的数组,但是我们并不知道要开多大。所以这种方式实际上存在缺陷。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+1000;
typedef long long LL;
inline LL read(){LL x=0,f=1;char ch=getchar(); while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;}
struct Server{
string Ip;
int port;
};
int main(void){
Server s={"111111111111111111111111111111111111111111192.168.0.1",8843};
///序列化(C语言方式)
char str[256];
sprintf(str,"%s %d",s.Ip.c_str(),s.port);
///反序列化(C语言方式)
Server p;
p.Ip.resize(s.Ip.size());
sscanf(str,"%s%d",p.Ip.c_str(),&p.port);
printf("%s %d",p.Ip.c_str(),p.port);
return 0;
}
3.3.2C++的stringstream
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+1000;
typedef long long LL;
inline LL read(){LL x=0,f=1;char ch=getchar(); while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;}
struct Server{
string Ip;
int port;
};
int main(void){
Server s={"111111111111111111111111111111111111111111192.168.0.1",8843};
///序列化(C语言方式)
// char str[256];
// sprintf(str,"%s %d",s.Ip.c_str(),s.port);
// ///反序列化(C语言方式)
// Server p;
// p.Ip.resize(s.Ip.size());
// sscanf(str,"%s%d",p.Ip.c_str(),&p.port);
// printf("%s %d",p.Ip.c_str(),p.port);
///C++sstream
stringstream ssm;
ssm<<s.Ip<<s.port;
string buff=ssm.str();
Server rc;
ssm>>rc.Ip>>rc.port;
cout<<rc.Ip<<" "<<rc.port<<endl;
return 0;
}
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+1000;
typedef long long LL;
inline LL read(){LL x=0,f=1;char ch=getchar(); while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;}
struct Server{
string Ip;
int port;
};
int main(void){
Server s={"111111111111111111111111111111111111111111192.168.0.1",8843};
///序列化(C语言方式)
// char str[256];
// sprintf(str,"%s %d",s.Ip.c_str(),s.port);
// ///反序列化(C语言方式)
// Server p;
// p.Ip.resize(s.Ip.size());
// sscanf(str,"%s%d",p.Ip.c_str(),&p.port);
// printf("%s %d",p.Ip.c_str(),p.port);
///C++sstream
// stringstream ssm;
// ssm<<s.Ip<<s.port;
// string buff=ssm.str();
stringstream ssm;
ssm.str("192.168.0.1 8843");
Server rc;
ssm>>rc.Ip>>rc.port;
cout<<rc.Ip<<" "<<rc.port<<endl;
return 0;
}
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+1000;
typedef long long LL;
inline LL read(){LL x=0,f=1;char ch=getchar(); while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;}
struct Server{
string Ip;
int port;
};
int main(void){
Server s={"111111111111111111111111111111111111111111192.168.0.1",8843};
///序列化(C语言方式)
// char str[256];
// sprintf(str,"%s %d",s.Ip.c_str(),s.port);
// ///反序列化(C语言方式)
// Server p;
// p.Ip.resize(s.Ip.size());
// sscanf(str,"%s%d",p.Ip.c_str(),&p.port);
// printf("%s %d",p.Ip.c_str(),p.port);
///C++sstream
// stringstream ssm;
// ssm<<s.Ip<<s.port;
// string buff=ssm.str();
// stringstream ssm;
// ssm.str("192.168.0.1 8843");
stringstream ssm("192.168.0.1 8843");
Server rc;
ssm>>rc.Ip>>rc.port;
cout<<rc.Ip<<" "<<rc.port<<endl;
return 0;
}