目录
前言:
一,流的概念
二,c++的io流
输入输出流
缓冲区的同步
文件流
文件的打开
文件读写自定义类型数据
字符流
1. 将数值类型数据格式化为字符串
2. 字符串拼接
3. 序列化和反序列化结构数据
在了解c++的输入输出流之前,我们先来看看c语言的io流,c语言中是利用printf与scanf进行输入输出,中间利用缓冲区实现对数据的高效处理,
对于缓冲区这个我们在linux中已经了解到,所谓的printf与scanf都是底层函数open和write的封装,目的就是提供有缓冲区,使得更加高效的进行读写。
所谓流就是流动的意思,物质从一方向流动到另一方向,是对一种有序连续且具有方向性的数
对于c++,c++在兼容c语言的输出输出的情况下,自己也实现了一套完整的输入输入流库,在这个库中ios_base为基类,派生出了许多子类也就是输入,输出等。
其功能与c语言的输入输出功能基本一致,不过c++利用自己面向对象的特点搞了一套,因此我们学习c++的io流可以对标c语言来看:
ostream类是继承了父类basic_ios,其中也包含了需要成员方法,不过我们一般就用一个重载的<<,进行输出。
ostream对象的定义:
ostream对象不能直接实例,上面是其构造的参数。
在输出流中,定义了三个输出对象:
以输出流为例:
int main()
{
//输出流类 ostream
ostream out(nullptr);
//我们用的cout cerr clog 都是ostream的对象
cout << "hello c++"<
对于ostream与istream我们最多也都是用的他的流插入(<<)与流提取(>>), 当然他们也有很多成员方法,在涉及输入输出的详细操作时,大家可以搜索来看。
int main()
{
string str;
while (cin >> str)
{
cout << str << endl;
}
}
其次还有一点就是我们在算法题时,可能会遇到多组输入数据的情况,在这里涉及到输入流的返回值的问题,实际上,cin>>str的返回值还是一个cin输入流对象每次是他要作为bool判断就会调用operator bool来实现类型的转换。
在c++中,ostream是输入,istream是输出,通过继承输出输出,还得到了既可以输入也可以输出的输入输出流类iostream。
由于c++兼容c语言,二c++与c语言都有自己各自一套的输入输出流,两者也有各自的缓冲区,在使用时,有时候我们不管你是谁的缓冲区的,我们希望他们自己能相互兼容,比如输入整数,用cin与scanf都去输入,我们希望cin输入一个数,scanf输入一个数,他们是不一样的,之后在利用不同的输出,输出数据,不论谁的缓冲区,相互都可以输出:
int main()
{
int i, j;
cin >> i;
scanf("%d", &j);
printf("%d ",i);
cout << j << endl;
return 0;
}
因此c++为了兼容c的输入输出,兼容了c的缓冲区。当然我们也可以关闭掉这个缓冲区的兼容,
通过调用sync_with_stdio(false)关闭缓冲区的兼容,在vs的平台下没什么问题,但不同的平台处理是不一样的。
在c语言中主要用三组函数,fputc/fgetc像文件读写字符,fread/fwrite,读完文件与写文件,fscanf/fprintf格式化输入输出。
对于文件部分的读写也是与输入输出类似的:
文件输入类 ifstream ,文件输出类 ofstream, 文件输出输出类 fstream。与输入输出流类似,文件流也有对应的文件缓冲区filebuf.
在c++中,由于面向对象的特性,这里都是类,主要的两个类文件输入ifstream与文件输出ofstream这两个类,其它向文件读写字符,字符串,格式化读写都是成员方法,c++提供了许多接供我们使用。
不过最基本的我们还是从创建文件来看,文件的打开有两种方式:
1.通过文件输入流或者文件输出流的构造函数来选择性地打开文件,
2.通过调用open方法代开文件,
文件打开的方式如下图:
in以读的方式打开文件,out以写的方式打开文件,binary以二进制方式读写文件,ate在文件末尾出输出,app在文件末尾处追加,trunk,截断,在打开前清空文件内容。
通过将两项|(或)的方式,就可以实现和c语言的r,w,a的方式打开文件。
以ifstream为例:
int main()
{
ifstream fp("test.txt", ifstream::out |ifstream::app);
ofstream fd("test.txt", ofstream::out|ofstream::app);
fd.write("hello c++", 10);
char* str=new char(10);
fp.read(str,10);
cout << str << endl;
return 0;
}
在c语言中可以使用fscanf与fprintf进行数据的读写,通过%d,%s等识别数据类型来向文件读写,在c++中没有对标的方法,而是全都放到fstream的流插入(<<)与流提取(>>)当中,通过不同的构造参数,调用不同的函数进行文件读写。
当然这也是为了自定义类型向文件读写时,通过重载<<与>>实现文件读写,也统一了文件读写数据可以都用 fstream的 流插入与流提取。
对于c++还是c语言向文件里读写数据其实都只有两种方式:
1.二进制读写。(字节流读写)
2.文本读写。
先以二进制读写为例:
class Date
{
friend ostream& operator << (ostream& out, const Date& d);
friend istream& operator >> (istream& in, Date& d);
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
operator bool()
{
// 这里是随意写的,假设输入_year为0,则结束
if (_year == 0)
return false;
else
return true;
}
private:
int _year;
int _month;
int _day;
};
//我们向文件读写的自定义数据
struct severInfo
{
severInfo()
{}
severInfo(char str[], double x, Date tmp) :address(str), x(x), date(tmp)
{}
char* address;
double x;
Date date;
};
//进行读写
class IO
{
public:
IO(const string file)
{
filename = file;
}
void write(const severInfo& tmp)
{
//向文件以二进制方式写入
ofstream fp(filename, ofstream::binary | ofstream::out);
fp.write((char*)&tmp, sizeof(tmp));//取地址转为char* 二进制写入
}
void read(const severInfo& tmp)
{
//以读的二进制方式打开文件
ifstream fp(filename, ifstream::binary | ifstream::in);
fp.read((char*)&tmp, sizeof(severInfo));
}
private:
string filename;
};
int main()
{
IO test("test.txt");
severInfo tmp((char*)"192.168.1.1", 10.5, Date(2023, 1, 1));
//此时写入二进制数据到文件当中
test.write(tmp);
severInfo info;
//将读来的二进制数据存放会info中
test.read(info);
return 0;
}
通过存入数据的二进制(地址),在以二进制方式读取出来,实现自定义类型的读写。
但文件中是二进制数据。
其次二进制读写数据要注意不要用,数据不要用容器,因为如果时容器的话,一般封装起来不仅仅是我们要的数据,如string 里面还有capacity, size等参数,一般二进制读取数据就是内置类型的数据,或者封装起来的内置类型的数据。
文本读写
文本读写的本质就是转化成字节流,即字符串。因此我们需要很多的类型转化,好处就是文件可以看得见,坏处就是更加麻烦。
在c语言我们可以使用sprintf,sscanf将其他类型转化为字符串,c++中使用to_string来转化。
class Date
{
friend ostream& operator << (ostream& out, const Date& d);
friend istream& operator >> (istream& in, Date& d);
//friend ofstream& operator << (ofstream& out, const Date& d);
//friend ifstream& operator >> (ifstream& in, Date& d);
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
operator bool()
{
// 这里是随意写的,假设输入_year为0,则结束
if (_year == 0)
return false;
else
return true;
}
private:
int _year;
int _month;
int _day;
};
istream& operator >> (istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
//ofs << winfo._date;
ostream& operator << (ostream& out, const Date& d)
{
out << d._year << " " << d._month << " " << d._day;
return out;
}
//我们向文件读写的自定义数据
struct severInfo
{
severInfo()
{}
severInfo(char str[], double x, Date tmp) :address(str), x(x), date(tmp)
{}
char* address;
double x;
Date date;
};
class TEXTIO
{
public:
TEXTIO(const string file)
{
filename = file;
}
void write(const severInfo& tmp)
{
//向文件文本方式写入
ofstream fp(filename, ofstream::out | ofstream::trunc);
fp << tmp.address<> 和 <> (istream& in, Date& d)
//这里的ofstream与ifstream都是继承istream与ostream
void read(const severInfo& tmp)
{
ifstream fp(filename);
fp >> tmp.address;
fp >> tmp.x;
fp >> tmp.date;
}
private:
string filename;
};
int main()
{
severInfo info((char*)"192.168.1.1", 1.1, Date(2023, 1, 1));
TEXTIO test("test.txt");
//文本写
test.write(info);
severInfo tmp;
//文本读
test.read(tmp);
}
对标于c语言的sprintf与sscanf,字节流的输入输出,c++也提供了字节流的输入与输出。
istringstream为字符输入,ostringstream为字符输出,stringstream是继承了输入输出,stringbuf为字符缓冲区。
使用方法与上面的文件流类似,当然字符流中的输入输出也都提供了一系列的成员方法,
如str,=等。除了自身的成员函数,当然继承的也可以使用。
int main()
{
stringstream str;
int a = 10;
string tmp;
//将其他类型的数据输出为字符型
str << a;
//再将该数据输入到string对象中
str >> tmp;
cout << tmp << endl;
cout<
int main()
{
stringstream str;
// 将多个字符串放入 str 中
//先将两个字符连接起来,即都输出到str当中
str << 20;
str << "个苹果";
str << 10;
str << "个香蕉";
cout << "开始" << str.str() << endl;
str.str("");//修改副本为空字符
str << 5;
str << "个橘子";
cout << "最后" << str.str() << endl;
return 0;
}
可以看到对于stringstream,它自身对象就是一个缓冲区,临时字符串,可以拼接我们输出的字符
class Date
{
friend ostream& operator << (ostream& out, const Date& d);
friend istream& operator >> (istream& in, Date& d);
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
istream& operator >> (istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
ostream& operator << (ostream& out, const Date& d)
{
out << d._year << " " << d._month << " " << d._day;
return out;
}
struct ChatInfo
{
string name;
int id;
Date date;
string msg;
};
int main()
{
// 结构信息序列化为字符串
ChatInfo winfo = { "张三", 135246, { 2022, 4, 10 }, "晚上一起看电影吧"};
ostringstream oss;
//序列化转为字符串
oss << winfo.name << " " << winfo.id << " " << winfo.date << " "
<< winfo.msg;
string str = oss.str();
cout << str << endl << endl;
//反序列化
// 字符串解析成ChatInfo对象
ChatInfo tmp;
istringstream iss(str);
iss >> tmp.name >> tmp.id >> tmp.date >> tmp.msg;
return 0;
}
注意:1. stringstream 实际是在其底层维护了一个 string 类型的对象用来保存结果 。2. 多次数据类型转化时,一定要用 clear() 来清空,才能正确转化 ,但 clear() 不会将stringstream 底层的 string 对象清空。3. 可以 使用 s. str("") 方法将底层 string 对象设置为 "" 空字符串 。4. 可以 使用 s.str() 将让 stringstream 返回其底层的 string 对象 。5. stringstream 使用 string 类对象代替字符数组,可以避免缓冲区溢出的危险,而且其会对参数类型进行推演,不需要格式化控制,也不会出现格式化失败的风险 ,因此使用更方便,更安全。