C\C++文件操作对比

本文探讨了C与C++文件操作的区别,包括类型安全性、错误减少、可扩展性和继承性等方面。通过实例对比了stdio与iostream的不同,并讨论了不同文件读取方法的效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

开个话题,C\C++文件操作对比

网上多是介绍几种文件操作方式,很少见到比较两种操作的,开个话题有时间研究总结一下:

C与C++文件操作的对比,包括各自特点、效率,C++编程里面究竟应该使用stdio还是stream的文件操作?

搜集的一些材料:

1. http://www.parashift.com/c++-faq-lite/input-output.html

[15.1] Why should I use  <iostream>  instead of the traditional  <cstdio> ?

Increase type safety, reduce errors, allow extensibility, and provide inheritability.

printf()  is arguably not broken, and  scanf()  is perhaps livable despite being error prone, however both are limited with respect to what C++ I/O can do. C++ I/O (using  <<  and  >> ) is, relative to C (using  printf()  and  scanf() ):

  • More type-safe: With  <iostream> , the type of object being I/O'd is known statically by the compiler. In contrast,  <cstdio>  uses "%"  fields to figure out the types dynamically.
  • Less error prone: With  <iostream> , there are no redundant  "%"  tokens that have to be consistent with the actual objects being I/O'd. Removing redundancy removes a class   of errors.
  • Extensible: The C++  <iostream>  mechanism allows new user-defined types to be I/O'd without breaking existing code. Imagine the chaos if everyone was simultaneously adding new incompatible  "%"  fields to  printf()  and  scanf() ?!
  • Inheritable: The C++  <iostream>  mechanism is built from real classes such as  std::ostream  and  std::istream . Unlike  <cstdio> 's FILE* , these are real classes and hence inheritable. This means you can have other user-defined things that look and act like streams, yet that do whatever strange and wonderful things you want. You automatically get to use the zillions of lines of I/O code written by users you don't even know, and they don't need to know about your "extended stream" class.

[15.7] Should I end my output lines with  std::endl  or  '\n' ?

Using  std::endl  flushes the output buffer after sending a  '\n' , which means  std::endl  is more expensive in performance. Obviously if you need to flush the buffer after sending a  '\n' , then use  std::endl ; but if you don't need to flush the buffer, the code will run faster if you use  '\n' .

This code simply outputs a  '\n' :

     
void f() { std::cout << ...stuff... << '\n'; }

This code outputs a  '\n' , then flushes the output buffer:

     
void g() { std::cout << ...stuff... << std::endl; }

This code simply flushes the output buffer:

     
void h() { std::cout << ...stuff... << std::flush; }

Note: all three of the above examples require  #include <iostream>

[15.16] Why can't I open a file in a different directory such as  "..\test.dat" ?

Because  "\t"  is a tab character.

You should use forward slashes in your filenames, even on operating systems that use backslashes (DOS, Windows, OS/2, etc.). For example:

复制代码
    
#include <iostream> #include <fstream> int main() { #if 1 std::ifstream file("../test.dat"); // RIGHT! #else std::ifstream file("..\test.dat"); // WRONG! #endif ... }
复制代码

Remember, the backslash ("\") is used in string literals to create special characters: "\n" is a newline, "\b" is a backspace, and "\t" is a tab,"\a" is an "alert", "\v" is a vertical-tab, etc. Therefore the file name "\version\next\alpha\beta\test.dat" is interpreted as a bunch of very funny characters. To be safe, use "/version/next/alpha/beta/test.dat" instead, even on systems that use a "\" as the directory separator. This is because the library routines on these operating systems handle "/" and "\" interchangeably.

Of course you could use  "\\version\\next\\alpha\\beta\\test.dat" , but that might hurt you (there's a non-zero chance you'll forget one of the "\" s, a rather subtle bug since most people don't notice it) and it can't help you (there's no benefit for using  "\\"  over  "/" ). Besides  "/"  is more portable since it works on all flavors of Unix, Plan 9, Inferno, all Windows, OS/2, etc., but  "\\"  works only on a subset of that list. So  "\\" costs you something and gains you nothing: use  "/"  instead.

2. 读取效率

http://www.byvoid.com/blog/fast-readfile/

为确保准确性,我又换到Windows平台上测试了一下。结果如下表:

方法/平台/时间(秒) Linux gcc Windows mingw Windows VC2008
scanf 2.010 3.704 3.425
cin 6.380 64.003 19.208
cin取消同步 2.050 6.004 19.616
fread 0.290 0.241 0.304
read 0.290 0.398 不支持
mmap 0.250 不支持 不支持
Pascal read 2.160 4.668

从上面可以看出几个问题

  1. Linux平台上运行程序普遍比Windows上快。
  2. Windows下VC编译的程序一般运行比MINGW(MINimal Gcc for Windows)快。
  3. VC对cin取消同步与否不敏感,前后效率相同。反过来MINGW则非常敏感,前后效率相差8倍。
  4. read本是linux系统函数,MINGW可能采用了某种模拟方式,read比fread更慢。
  5. Pascal程序运行速度实在令人不敢恭维。

3. 提高速度

(1)内存映射

(2)使用WINAPI

(3)#优化算法 (这才是王道)

http://dev.firnow.com/course/3_program/c++/cppjs/20090403/163891.html

FILE自己维护了一套缓存机制
FILE会使用默认的一个缓存值作为io缓存(4k),或者也可以通过setbuf来设置这个缓存的大小

假 设你fread 1字节 会导致ReadFile 4k,然后fread再将要读取的数据copy到指定的缓冲区中。以后访问只要不过这个边界,就一直从该io缓存中读取,fwrite也是,直到超过io 缓存边界才真正的调用WriteFile。可以调用flush主动强制刷新从而调用WriteFile 或者fclose被动刷新调用WriteFile(这时fclose会阻塞)。

再说一下硬盘的 硬盘的cache由硬盘控制器管理和使用 就像处理器的cache没法直接操作一样 写硬盘的时候会先写入cache 然后硬盘内部会把数据慢慢写入磁盘 这个过程中没有优化 也就是说硬盘驱动按什么顺序写的 写入磁盘就是什么顺序
而实际上 硬盘是个随机访问设备 先写哪个后写哪个无所谓 所以一般在把应用层的io访问转化为底层的io请求后 内核层会做io请求优化排序 

假 设一个io队列上目前挂着10个请求 内核层会事先计算每个请求在物理上的位置 然后进行排序 以保证磁头转动一周,尽量让10个请求中的多个在一周内完成,想像一下 最好的情况 10个请求都在一个盘面上 磁头旋转1周 10个请求全部完成 最坏的情况 要转10周 10周的原因是一次只能操作一个磁头 而10个请求可能不幸的在10个盘面上(这时候内核也不会再排序了)

因此让自己的io操作尽可能维持在连续的磁盘空间 且在物理上不跨越盘面 这样效果最好。为此你可能需要硬盘的准确的参数 并精确计算。

缓 存的优势在高强度的io操作会被抵消 因为硬盘的写入速度始终跟不上处理器的请求 cache只能帮助缓冲一下 cache越大 缓冲的时间越长 当cache填满 硬件上ready信号为无效 硬盘驱动不能再写了 只能挂起内核的io队列 这时候上层还在不停的请求 内核层要么继续往io请求队列上挂装请求 要么阻塞发起io的进程 等到cache有空间了 硬件使能ready信号 驱动重新从内河的io请求队列上摘取io请求 再填cache 又满 。。。。 也就是说cache的优势只在一开始的缓存时间上 这个优势对于小的io请求特别有好处 因为能在填满cache之前不会遭到阻塞或挂起 

纵上所述 软件上其实做的很有限而且也很累 何必呐 orz。。。。

C、传统 C++ #include     //设定插入点 #include      //字符处理 #include      //定义错误码 #include      //浮点数处理 #include     //文件输入/输出 #include     //参数化输入/输出 #include    //数据流输入/输出 #include     //定义各种数据类型最值常量 #include     //定义本地化函数 #include      //定义数学函数 #include      //定义输入/输出函数 #include     //定义杂项函数及内存分配函数 #include     //字符串处理 #include    //基于数组的输入/输出 #include      //定义关于时间的函数 #include      //宽字符处理及输入/输出 #include     //宽字符分类 ////////////////////////////////////////////////////////////////////////// 标准 C++ (同上的不再注释) #include     //STL 通用算法 #include      //STL 位集容器 #include #include #include #include #include      //复数类 #include #include #include #include #include       //STL 双端队列容器 #include     //异常处理类 #include #include    //STL 定义运算函数(代替运算符) #include #include       //STL 线性列表容器 #include        //STL 映射容器 #include #include        //基本输入/输出支持 #include      //输入/输出系统使用的前置声明 #include #include      //基本输入流 #include      //基本输出流 #include       //STL 队列容器 #include        //STL 集合容器 #include      //基于字符串的流 #include       //STL 堆栈容器     #include     //标准异常类 #include     //底层输入/输出支持 #include      //字符串类 #include      //STL 通用模板类 #include      //STL 动态数组容器 #include #include using namespace std; ////////////////////////////////////////////////////////////////////////// C99 增加 #include    //复数处理 #include     //浮点环境 #include   //整数格式转换 #include    //布尔环境 #include    //整型环境 #include    //通用类型数学宏 --------------------------------------------------------------------------------------------------------- 补充: 经常在优快云以及其他之类的技术论坛上问关于C++ 头文件的问题。提出这些问题的往往就是那些刚学C++的新手。当初我是菜鸟的时候也问过类似的问题。 现在来看看下面两个include: #include // 这个就是1998年标准化以后的标准头文件 #include // 这个就是标准化以前的头文件 更本质上的区别就是iostream把标准C++库的组件放在一个名位std的namespace里面。而相对的iostream.h则将这些标准组件放在全局空间里,同时在标准化以后旧有的C标准库也已经经过改造了。 看看下面这两个头文件 // 标准化后经过改造的C的标准库,所有的组件都放在了std中 #include // 标准化以前C++中的C标准库 #include // 在看看这个头文件C标准库下 基于char* 的字符处理函数库 #include // 在标准化以后他变成了这样 #include // 但是很多朋友还看见过这个字符串处理函数库,他包含了新的string class #include 经过了标准委员会如此大规模手术后,在98年以前出品的C++编译器(BC3.0,BC5.0)上能顺利通过编译的源文件,在支持新标准的编译器上可能无法顺利通过编译也就是很正常的事了。 [起因] 在回过头来看看标准程序库,这个程序库涵盖范围相当广大,提过了许许多多好用的功能。正是因为这样标准程序库中class的名称和函数名与第三方提供的程序库中的class名或是函数名发生名字冲突的可能性大大增大。为了避免这个问题的发生,标准委员会决定将标准程序库中每一样东西都放在namespace std中。但是这么做同时有引来了一个新的问题。很多C++程序代码依赖那些已经存在很多年的C++ “准”标准程序库(C++迟迟未标准化才导致这些情况的发生),例如iosteam.h,complex.h等等。 为了解决这个新出现的问题,标准化委员会决定设计一些新的头文件名,给那些穿上std外衣的组件所使用。把C++头文件的.h去掉,于是就有前面出现的iostream,同样C的头文件也做了相同的处理,同时在前面加上了一个字母c,以表示是C的头文件(感觉上有中种族歧视的感觉)。同时标准化委员会声明就有的C++头文件将不再列于被支持的名单之中了,而旧有的C头文件为了满足“对C的兼容性”这个古老契约,仍然将继续存活下去。 但是,那些编译器厂商不可能去推翻他们客户的旧有编译器(也跟本不会去这么做),所以那些旧有的C++头文件仍然苟延残喘的活了下来,并不断的扰乱那些C++新兵的心智。 下面就是现在大多数C++开发工具表示头文件的组织状态: 1. 旧的C++头文件 比如iostream.h,他们虽然被标准化委员会所抛弃,但由于各大厂商为了各自的商业利益仍然将继续存活下去,这些头文件的内容将不处于namespace std中。 2. 新的C++头文件如iostream虽然提供了和旧有头文件相同的功能,但他的内容都并入了namespace std中,从而有效避免了名字污染的问题。 3. 标准C的头文件如stdio.h继续获得支持,这类文件的内容并未放在std中。 4. C函数库的技能也有对应的新式C++版本,起名称类似cstdio,这类头文件的内容也有幸穿上了std的外衣。 其实标准化以后的标准程序库的改动并不只有这些而已,很多的标准化组件都被“tamplate化”。其中就有元老级人物iostream。标准程序库的问题并不是用一篇,两篇文章就可以说清楚的。如果你像进一步的了解C++的标准程序库的话,你可以看看侯先生的《C++标准程序库》。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值