C++流是指信息从外部输入设备(如键盘)向计算机内部(如内存)输入和从内存向外部输出设备(显示器)输出的过程。这种输入输出的过程被形象的比喻为“流”。
它的特性是:有序连续、具有方向性
为了实现这种流动,C++定义了I/O标准类库,这些每个类都称为流/流类,用以完成某方面的功能
c++实现了庞大的类库,< ios >为基类,其余的都是派生类或间接派生类。
这是我们常包的头文件,里面有cin,cout,cerr,clog。
cin,cout支持多种类型的输入输出,本质因为其重载了操作符<<
和<<
,我们可以以cout为例,查看一下:
就是函数重载,使得其如此的方便,比C语言还优化在哪里呢?那就是我们可以重载<<
,>>
。完成自定义类型的输入输出。
比如:
我定义一个日期类,并且重载<<
,>>
。
#include
using namespace std;
class Date
{
friend ostream& operator<<(ostream& ofs, Date& d);
friend istream& operator>>(istream& i, Date& d);
public:
Date(int year = 2022, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& o, Date& d)
{
o << d._year << "/" << d._month << "/" << d._day << endl;
return o;
}
istream& operator>>(istream& i, Date& d)
{
i >> d._year >> d._month >> d._day;
return i;
}
int main()
{
Date a;
cin >> a;
cout << a;
return 0;
}
重载这个<<和>>会使得向自定义类型输入输出非常舒服,这正是cout和cin的厉害之处。
运行一下:
利用的是引用返回,返回的依旧是那个istream或ostream对象,从而做到连续的输入输出。
在做题时,有可能会要求连续输入:
a是个字符串数组:
这样就完成了多组输入,如果想要中断C语言的输入,可以按下ctrl + z,利用信号来结束输入。我们来看下EOF是什么?
C++中,封装成了一个函数:eof()
也就是说,如果遇到非法输入,或是信号中断那么就会返回EOF或者eof(),所以我们分别看一下scanf()和cin的返回值。
scanf返回值是int,其实scanf返回的是在屏幕上打印的变量个数,EOF在C语言中是-1,在上图可以看到,所以输入流停止输入时,会返回值-1,也就是EOF。
cin重载了<<,返回值是一个istream对象,怎么能放到while里做判断呢?我想说的是istream里继承了bool(),我们可以看一下:
也就是说,while判断的是对象中的bool()。至于调到了对象的bool(),这是编译器做了优化。
注意:cin用的是空格或者换行,做的分割。所以空格是输入是无法读取的,所以要用到getline()。
类似于这样调用:
getline(cin,str);
文件的io流,向一个文件中写入或者读取数据。
C++根据文件内容的数据格式分为二进制文件和文本文件。
在将fstream前,我想聊聊C语言的文件IO,对比着学习,也能复习一下:
(1) 打开文件,关闭文件:
常用以下俩个函数接口:
fopen()的第一个参数是字符串,也就是文件名;第二个参数也是字符串,表示打开文件的操作模式。
fclose()的参数是一个文件指针,关闭文件。
- r 表示只读,这个文件必须已经存在,可以取出文件中的内容
- w 表示只写,向文件中写入内容,文件可以不存在,不存在的话可以创建;如果文件已经存在那么会清空文件的内容,进行重写
- a 打开文件附加写入,如果文件不存在创建新文件
- r+ 表示可读可写,文件必须已经存在
- w+ 创建新的文件供读取并写入,如果文件已经存在则清空
- a+ 打开文件可读取,附加写入,如果文件不存在创建新文件
如果是二进制读写,只需要第二个参数加上b,比如:wb,rb,ab……
FILE * fopen ( const char * filename, const char * mode );
int fclose ( FILE * stream );
(2) 文件的读写操作
// 二进制读写
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
// 文本读写
int fprintf ( FILE * stream, const char * format, ... );
int fscanf ( FILE * stream, const char * format, ... );
// 定义一个结构体
struct info
{
char a[20];
int s;
};
// 向文件中写
void test_wbin()
{
info a = { "hollow",10};
FILE* fi = fopen("test.bin", "wb");
fwrite(&a, sizeof(a), 1, fi);
fclose(fi);
}
// 读取文件的内容
void test_rbin()
{
info b;
FILE* fi = fopen("test.bin", "rb");
fread(&b, sizeof(b), 1, fi);
fclose(fi);
// 打印到屏幕上
cout << b.a << " " << b.s;
}
int main()
{
test_wbin();
test_rbin();
return 0;
}
这是向文件test.bin写入的内容,
读取文件的内容到b中,再打印到屏幕上
void test_W()
{
info a = { "holloweveryone",10 };
FILE* fi = fopen("test.txt", "w");
fprintf(fi, "%s %d", a.a, a.s);
fclose(fi);
}
void test_R()
{
info b;
FILE* fi = fopen("test.txt", "r");
fscanf(fi, "%s%d", b.a, &b.s);
fclose(fi);
printf("%s %d\n", b.a, b.s);
}
int main()
{
test_W();
test_R();
return 0;
}
向test.txt写入:
读取文件的内容到b中,再打印到屏幕上:
注意
: 我们在向文件中进行写操作时,fprintf(),格式控制时最好加上空格,或者 /n 。否则读取时,会出现问题。
write的参数,第一个是要往文件写入的指针,需要强转成 const char*,第二个参数是写入的大小
因为重载了<<
,所以直接往里面写就行了。
(2) ifstream类:读取文件
我简单的讲讲构造函数的第二个参数,打开的模式,每个模式都可以用 按位或 | 的方式组合使用。
为什么模式可以按位或呢?
每个模式都是整型且为2的倍数,也就是2,4,8,16……
所以二进制 表示就是010,100,1000。也就是说每个二进制位表示一种模式,如果按位或的话,对应的二进制位就会变成1,表明支持此模式。
read()的第一个参数,表示将文件的内容读取到某处的指针,需要强转成char*,第二个参数是读取的大小。
因为重载>>
,所以直接读取就行了。
#include
#include
#include
using namespace std;
struct info
{
char a[20];
int s;
};
class config
{
public:
config(const char* filename)
:_filename(filename)
{
}
// 二进制写
void write_bin(const info& w)
{
ofstream ofs(_filename.c_str(), ios_base::out | ios_base::binary);
ofs.write((const char*)&w, sizeof(info));
}
// 二进制读
void read_bin(info& r)
{
ifstream ifs(_filename.c_str(), ios_base::in | ios_base::binary);
ifs.read((char*)&r, sizeof(info));
}
// 文本写
void write(const info& w)
{
ofstream ofs(_filename.c_str());
ofs << w.a << " " << w.s;
}
// 文本读
void read(info& r)
{
ifstream ifs(_filename.c_str());
ifs >> r.a >> r.s;
}
private:
string _filename;
};
int main()
{ // 二进制写
info w = { "hollow" ,10 };
config wi ("test_bin.bin");
wi.write_bin(w);
// 二进制读
info r;
wi.read_bin(r);
cout << r.a << " " << r.s;
// 文本写
info ww = { "everyone",100 };
config wwi("test_ww.txt");
wwi.write(ww);
// 文本读
info rr;
wwi.read(rr);
cout << rr.a << rr.s;
}
以上我创建了一个类config,私有成员是文件名。里面封装了几个函数,用于文本读写。
sstream 用于将一段数据转化为字符串,这个用的很多;我们将数据变成字符串叫做序列化,将字符串再还原成数据,叫做反序列化。
我举个最简单的例子:
#include
#include
#include
#include
using namespace std;
struct PersonInfo
{
string _name;
int _age;
};
int main()
{
// 序列化
PersonInfo info = {"张三", 18};
ostringstream oss;
oss << info._name <<" "<< info._age ;
string str = oss.str();
cout << str;
// 反序列化
istringstream iss(str);
string name;
int age;
iss >> name >> age;
cout << name << age;
return 0;
}
上面的:
序列化就是将结构体info的数据,流入oss中,再使用接口str(),就转成了字符串。
反序列化就是将上面的字符串进行分割,将数据取出。
结尾语: 以上 就是 c++的IO流。