目录
前言
1. C语言中的IO
2. 如何理解流
3. C++中的IO流
3.1 C++中的标准IO
3.2 C++中的文件IO
4. stringstream
总结
前言
C语言中的I/O接口十分强大,但使用起来有些繁琐。好在C++中的I/O方式为我们解决了这些问题,让数据的读写操作变得更加简洁和便捷; 在本文中,我们将探讨C++中的I/O流;
1. C语言中的IO
C语言中最常用的输入输出的方式就是 scanf 和printf ; scanf(): 从标准输入设备(键盘)读取数据,并将值存放在变量中。printf(): 将指定的文字/字符串输出到标准输出设备(屏幕)。注意宽度输出和精度输出控制;
C语言借助了相应的缓冲区(内部封装)来进行输入与输出;
如何理解:
- 屏蔽掉低级I/O的实现, 低级IO需要依赖底层操作系统的实现, 屏蔽掉底层细节便于程序的可移植性;
- 可以实现行读取的操作, 在计算机内部是没有行这个概念的, 有了缓冲区就可以根据行进行数据解析, 更为方便
2. 如何理解流
“流”即是流动的意思,是物质从一处向另一处流动的过程,是对一种有序连续且具有方向性的数据( 其单位可以是bit,byte,packet )的抽象描述
C++流是指信息从外部输入设备(键盘)向计算机内部(内存)输入和从内存向外部输出设备(显示器)输出的过程。这种输入输出的过程被形象的比喻为“流”。
为了实现这种流动,C++定义了I/O标准类库,这些每个类都称为流/流类,用以完成某方面的功能;
3. C++中的IO流
C++系统实现了一个庞大的类库,其中ios为基类,其他类都是直接或间接派生自ios类
从图中结构可以看出它使用了菱形继承; istream 主要是进行输入, ostream主要是进行输出, 然而如果使用输入输出需要包含不同的类那就有点不便了, 所以它有采用了一个iostream的类去继承 istream 和 ostream ; 在这样在使用时只需用iostream就可以既可以输入又可以输出;
3.1 C++中的标准IO
C++标准库提供了4个全局流对象cin、cout、cerr、clog;
- cout进行标准输出,即数据从内存流向控制台(显示器)。
- cin进行标准输入即数据通过键盘输入到程序中,
- cerr用来进行标准错误的输出
- clog进行日志的输出
在使用时候必须要包含文件并引入std标准命名空间
注意:
- cin为缓冲流。键盘输入的数据保存在缓冲区中,当要提取时,是从缓冲区中拿。如果一次输入过多,会留在那儿慢慢用,如果输入错了,必须在回车之前修改,如果回车键按下就无法挽回了。只有把输入缓冲区中的数据取完后,才要求输入新的数据。
- 输入的数据类型必须与要提取的数据类型一致,否则出错。出错只是在流的状态字state中对应位置位(置1),程序继续。
- 空格和回车都可以作为数据之间的分格符,所以多个数据可以在一行输入,也可以分行输入。但如果是字符型和字符串,空格和回车也依然无法无法读入;
- cin和cout可以直接输入和输出内置类型数据,原因:标准库已经将所有内置类型的输入和输出全部重载了;
- OJ中的输入和输出:
常见的几种:
// 单个元素循环输入
while(cin>>a)
{// ...
}// 多个元素循环输入
while(c>>a>>b>>c)
{// ...
}// 整行接收
while(cin>>str)
{// ...
}
使用while(cin>>i)去流中提取对象数据时,调用的是operator>>,返回值是应该是 istream 类型的对象,这里为什么可以做逻辑条件值?
因为 istream的对象又调用了operator bool,operator bool调用时如果接收流失败,或者有结束标志,则返回false;
- 对于自定义类型,如果要支持cin和cout的标准输入输出,需要对<<和>>进行重载
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;elsereturn true;}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;
}int main()
{Date d(2022, 4, 10);cout << d;
}
3.2 C++中的文件IO
文件的格式主要有两种: 文本文件 和 二进制文件;
在使用时也非常简单, 文件流对象有三种:
- ifstream ifile(只输入用)
- ofstream ofile(只输出用)
- fstream iofile(既输入又输出用)
文本形式读写文件:
// 写
ofstream ofs(_filename); // 默认以文本形式打开一个文件不需要添加任何选项(默认为覆盖写)
ofs << value1 << " " << value2 << " " << value3;// 读
ifstream ifs(_filename);
ofs >> value1 >> value2 >> value3; // 当有多个值时,流提取时默认将换行和空格视为分割符
二进制形式进行读和写:
// 写
ofstream ofs(_filename, ios_base::out | ios_base::binary);// 覆盖写 + 二进制;
ofs.write((const char*)&info, sizeof(info)); // info为结构化字段, 取结构体的地址,取的字节个数// 读
ifstream ifs(_filename, ios_base::in | ios_base::binary);
ifs.read((char*)&info, sizeof(info)); // //读到这个结构体中
在使用时可以根据自己的需求封装一下, 这样进行文件读写时也会更加方便简洁;
写入方式:
- 追加写 选项: ios_base::app
- 覆盖写 选项: ios_base::out (默认)
另外特别需要注意:
二进制读写一个特别坑的地方, 文本读写并不会存在问题;
使用string类型存储数据, 然后把string类型的数据以二进制形式写入到文件中时;
我的测试环境是VS2022, 感兴趣的小伙伴可以去测试一下:
struct ServerInfo
{/*char _address[132];*/string _address;double _x;Date _date;
};class BinIO
{
public:BinIO(const char* filename = "info.bin"):_filename(filename){}//写void Write(const ServerInfo& winfo){ofstream ofs(_filename, ofstream::out | ofstream::binary);ofs.write((char*)&winfo, sizeof(winfo));}// 读void Read(ServerInfo& rinfo){ifstream ifs(_filename, ofstream::in | ofstream::binary);ifs.read((char*)&rinfo, sizeof(rinfo));//读到这个结构体中}private:string _filename;
};int main()
{ServerInfo winfo = { "https://legacy.cplusplus.com", 12.13, { 2024, 1, 1 } };BinIO bin;bin.Write(winfo);ServerInfo info;bin.Read(info);cout << info._address << endl;cout << info._x << endl;cout << info._date << endl;return 0;
}
当ServerInfo 使用 string 类型时, 读数据出来的时候程序就会异常崩溃;
主要分为两种情况:
读写在一个进程中时,读是可以读取到,但最后程序还是会崩,这是由于浅拷贝的问题
- string内部存在一个指针、大小、容量
- 写进去时会将指针地址、大小容量什么都写入到了文件当中
- 当读取时,将所有数据读给一个对象
- 这就导致两个对象的指针指向同一块空间,进而导致程序崩溃
读和写不在一个进程中:
- 不同的进程读取数据的对象中的string内部变成了野指针
- 因为读进来的string中它的指针指向的空间已经被销毁
- 这里导致string内容读取不到
- 所有使用二进制读写时要特别注意容器
最好的避免方法就是不使用容器;
4. stringstream
这里作为拓展, 只需要知道 stringstream怎么使用即可;
在C语言中,如果想要将一个整形变量的数据转化为字符串格式,如何去做?
- 使用itoa()函数
- 使用sprintf()函数
这两个接口以及非常的好用了, 尤其是sprintf, 在一些限制只能使用C的情况下, 格外好用;
但是使用时就比较繁琐;
两个函数在转化时,都得需要先给出保存结果的空间,那空间要给多大呢,就不太好界定,
而且转化格式不匹配时,可能还会得到错误的结果甚至程序崩溃;
在C++中,可以使用stringstream类对象来避开此问题。
在程序中如果想要使用stringstream,必须要包含头文件。在该头文件下,标准库三个类:
istringstream、ostringstream 和 stringstream,分别用来进行流的输入、输出和输入输出操
作,本文主要介绍stringstream;
当需要拼接多个字符串时C++中string的方式就有点显得僵硬;
string sql2;sql2 += "select * from t_scroe where name = '";sql2 += name;sql2 += "'";cout << sql2 << endl;
而stringstream就很好的解决了这些问题;
stringstream的功能非常强大:
- 将数值类型数据格式化为字符串
#include<sstream>
int main()
{int a = 12345678;string sa;stringstream s;s << a;s >> sa;cout << sa << endl; // 12345678s.str(""); // 将底层管理的string对象置为""s.clear(); // 清空s, 不清空会转化失败double d = 12.34;s << d;s >> sa;string sValue;sValue = s.str(); // str()方法:返回stringsteam中管理的string类型cout << sValue << endl; // 12.34return 0;
}
- 字符串拼接
int main()
{stringstream sstream;// 将多个字符串放入 sstream 中sstream << "first" << " " << "string,";sstream << " second string";cout << sstream.str() << endl; // first string, second string// 清空 sstreamsstream.str("");sstream << "third string";cout << sstream.str() << endl; // third stringreturn 0;
}
- 序列化和反序列化结构数据
struct ChatInfo
{string _name; // 名字int _id; // idDate _date; // 时间string _msg; // 聊天信息
};int main()
{ChatInfo winfo = { "张三", 135246, { 2024, 2, 24 }, "xxxxxx" };stringstream oss;//stringstream继承了ostringstream和istringstreamoss << winfo._name << endl;oss << winfo._id << endl;oss << winfo._date << endl;oss << winfo._msg << endl;cout << oss.str() << endl;// 网络输出ChatInfo rinfo;string str = oss.str();stringstream iss(str);iss >> rinfo._name;iss >> rinfo._id;// 注意这里流提取默认空格和换行作为分割符,date自己实现的ostream分隔符应该使用空格或换行// 否则会提取出错iss >> rinfo._date;iss >> rinfo._msg;cout << "-------------------------------------------------------" << endl;cout << "姓名:" << rinfo._name << "(" << rinfo._id << ") ";cout << rinfo._date << endl;cout << rinfo._name << ":>" << rinfo._msg << endl;cout << "-------------------------------------------------------" << endl;return 0;
}
在学习时, 进行序列化和反序列化操作时一般使用的是Json;
使用注意:
- stringstream实际是在其底层维护了一个string类型的对象用来保存结果。
- 多次数据类型转化时,一定要用clear()来清空,才能正确转化,但clear()不会将stringstream底层的string对象清空。
- 可以使用s. str("")方法将底层string对象设置为""空字符串。
- 可以使用s.str()将让stringstream返回其底层的string对象。
- stringstream使用string类对象代替字符数组,可以避免缓冲区溢出的危险,而且其会对参数类型进行推演,不需要格式化控制,也不会出现格式化失败的风险,因此使用更方便,更安全
总结
C++中的IO方式解决了C语言IO接口使用繁琐的问题, 让数据的读写操作变得更加简洁; 同时C++的IO接口在使用时也有许多的注意点 , 学习C++中的IO , 在后续无论是工作, 还是学习都非常的重要; 好了以上便是本文的全部内容, 希望对你有所帮助, 感谢阅读!