目录
关于C++IO流
C/C++中的日期输入
连续输入的问题
C++文件IO流
运算符>>的运用
二进制读写
文本读写
stringstream
关于C++IO流
C++系统中ios为基类,其他类都是直接或间接派生自ios类
C++标准库提供了4个全局流对象cin、cout、cerr、clog (在使用时候必须要包含文件并引入std标准命名空间)
使用cout进行标准输出,即数据从内存流向控制台(显示器)
使用cin进行标准输入即数据通过键盘输入到程序中
使用cerr用来进行标准错误的输出
使用clog进行日志的输出
C/C++中的日期输入
例如要求输入的是20231125,C/C++分别是如何输入年月日的:
C语言:
采用scanf输入,%nd可以用来表示输入n个数据:
通过调试可以清楚的发现,year, month, day可以满足要求
C++:
C++的cout并不支持%nd的语法,但是C++有string,可以将string中的substr,stoi结合使用:
其中substr(0, 4)表示的是从0位置开始,取出4个数据,stoi(str.substr(0, 4))则表示将取出的4个数据转为整型,year, month, day都是使用这种方式读入的
连续输入的问题
连续输入的问题也就是有多行测试用例的问题,在有些题目中,需要有多行测试用例
其中C语言是以下方式进行的:
scanf如果没有获取成功就返回EOF,EOF是宏定义的-1,运行结果为:
而C++是下面这种方式:
运行结果与C语言是一样的:
C++之所以可以在while的判断语句中用cin>>str来判断是否结束,是因为istream类型的cin对象调用operator bool,operator bool会根据输入的内容来返回true或false,所以可以用于连续输入的判断
这两种方式如果想结束连续输入,只需要Ctrl + C即可终止,这种是比较暴力的终止,在Linux信号部分也说到过,Ctrl + C相当于直接发送一个终止信号来终止进程
C++文件IO流
C++中ifstream是读文件,C++中cin是流提取,cin是从终端提取,所以ifstream就是从文件中提取,也就是我们所说的读文件
下面简单演示用法:
例如我们当前VS的文件名叫做test.cc,它可以不指定打开方式,因为有缺省参数,默认读文件,如下图:
get函数就类似于C语言文件中的fgetc函数
所以上述例子运行的结果就是将test.cc文件中的代码全部打印出来
运算符>>的运用
C语言能用fscanf将文件中的内容打印出来,C++自然也能做到,并且相比于C语言也是有一定优势的,因为C++重载了运算符>>,内置类型可以调用库中的operator >>,ifstream继承的istream,而istream重载了下面的这些内置类型,所以可以直接使用运算符>>读取普通文件的内容:
下面简单演示:
首先在当前打开的VS的路径下,创建一个tmp.txt的普通文件,在其中写几个数据:
然后使用运算符>>,调用库中的运算符重载>>,直接读取:
而C++文件操作的优势,例如自定义类型的类如Date日期类,如果在tmp.txt中有日期2024 10 24,C语言由于各种方面的原因,读取时可能会有些问题,而C++的运算符重载可以完美的解决这种情况,tmp.txt中多了一行日期:
代码中可以直接使用运算符>>提取日期类对象dt:
而可以支持ifs提取日期类对象dt的前提,就是我们在Date类中必须要重载运算符>>,所以自定义类型Date的对象dt才可以使用
而Date类中重载的运算符>>中,参数是istream类型的,由于ifstream是istream的子类,所以ifstream类型的对象ifs在调用运算符>>,依然可以调用:
结果如下,非常轻松的就能够提取出tmp.txt文件中的日期:2024 10 24
二进制读写
二进制读写:在内存如何存储,就如何写到磁盘文件
优点:快;缺点:写出去的内容看不见
下面看代码示例:
有一个Server类,里面内容是IP和端口
有一个Manager类,里面包含二进制的读写函数,还有配置文件_filename
二进制的写操作:
此时运行代码,在当前路径下就会出现一个server.config的普通文件,内容如下:
观察文件内容,IP可以清楚读出来是192.0.0.1,而端口却是乱码的形式,这是因为IP是字符串存储的,字符串是编码的方式存储的,所以写出去以后只要文件打开的编码方式和磁盘相同,自然就能够看见内容
而端口是整型,存储是和字符串的存储不同的,是按照原反补等方式计算的,而文件中并没有整型的概念,所以我们是看不见的
二进制的读操作
main函数中改变如下部分代码,即可进行二进制的读操作:
在磁盘中什么内容,读回来就是什么内容,结果如下:
这里需要注意的一点是:二进制读写有一定的局限性,需要注意,不可用包含string、vector等类的数据进行读写,以string类举个例子,string在Windows中如果存储较长的数据就会开辟在堆上,这时写入文件中的就是一个指向这个开辟在堆上的字符串的地址,也就是一个指针,写入结束后,调用析构函数会进行销毁
而接下来如果进行读取,因为此时是一个新的进程,之前的地址已经被销毁了,此时读取之前存储的指针时,就变为了野指针,因为之前存储的指针是对应的上一个进程在堆上开辟的,上一个进程结束时已经销毁,此时新进程读取自然会出错
文本读写
文本读写:对象数据序列化字符串写出来,读回来也是字符串,反序列化转成对象数据
优点:可以看见写出去是什么;缺点:存在一个转换的过程,要慢一些
文本的写操作:
代码如下:
写操作的代码在_add和_port中间加了换行符,一遍更好的读取数据,也为了接下来的文本读操作更加方便
运行代码后,在当前路径下就会出现一个server.config的普通文件,内容如下:
与二进制写不同的是,文本写操作我们可以清楚看到内容
文本的读操作:
main函数如下:
运行结果为:
也可以很好地打印出来
上面这种文本的读写方式还是有些麻烦,最简便的方式还是会用到重载的运算符>>与<<
文本读和文本写的实现变为下图的方式,如下所示:
运行结果与上面的实现方式是一样的
当有自定义类型,例如日期类时,只要日期类中重载了运算符>>与<<即可照常使用
stringstream
像上面单个对象,可以使用string中的to_string、stoi、stod等函数转换,但是如果多种类型的转换,比如说一个结构体中有各种类型的对象,既有内置类型,又有自定义类型,比较复杂,这时就可以考虑使用stringstream了,比较方便,具体如下所示:
结构体中,有多中不同类型的变量:
此时就可以考虑使用stringstream,需要包头文件sstream,使用方法如下:
其中函数str()是可以拿到序列化的字符串,调试观察str这个字符串是这样的:
打印结果如图:
上面这种过程就叫做序列化,即不论是什么类型的对象,都转化为字符串
既然有序列化,也就有反序列化,反序列化就是将上面转化出来的字符串解析出来
这时就可以运用istringstream,将上面的str字符串解析出来:
创建一个对象rex,将上面的字符串str传入istringstream类型得对象iss,然后就可以反序列化str了
通过监视窗口可以看到:
也可以将rex的各个对象打印出来,或是显示在某个界面上
stringstream的优势就是不仅仅可以对内置类型处理,也可以对自定义类型进行处理(例如上图的_date),只要自定义类型重载了>>和<<运算符即可
当然也有一种更为简便的方式,不去区分istringstream和ostringstream,直接使用stringstream即可,因为stringstream既继承了istringstream,又继承了ostringstream的内容: