Files
张宗平 13b3ebbe14 课程代码仓库初始化:Part1 (144) + Part3 (29) 全量生成与验证
把川大 wiki「02.Qt方向」课程代码整理为 CMake 管理的可编译运行项目。

Part1 C/C++ 强化 (p01/, 144 目标, host gcc/g++ 构建, 全绿):
- 修复全部编译失败:补 <cstring>/<fstream>/<iomanip>/<vector> 等缺失头
  (common_fix + infer_stl_includes 自动检测注入)、修正 pause() 壳注入
  (改检测已转换后的 pause 调用)、namespace 提升出函数体、const 成员函数
  正确性、dedupe 不再破坏函数重载集、K&R 隐式 int / 动态异常规范等
  C++17 移除特性的可编译等价改写。
- 目录采用深缩写 p01/ch04/s03/ (原 part1_cpp/ch04_class_object/03_ctor_dtor)。

Part3 Qt 桌面 (p03/, 29 目标, 隔离 Qt 5.14.2 构建, 全绿):
- gen_part3.py: Qt 外壳模板 (main+QApplication+show, QTimer 自动退出便于
  offscreen 验证)、AUTOMOC + .moc include、rewrite_* 处理散文混入/重载/
  资源依赖、生成占位 png/gif + .qrc + rundata 样例数据。
- 修正 QLable 笔误、全角分号、.→->、Windows 路径、QApplication 阻塞事件
  循环改为 QCoreApplication 等。

任务1 概念/版本验证:
- tools/std_probe.py: 跨 -std= 编译探针 (--pedantic-errors 让已废除特性硬失败)。
- tools/demo_versions/: K&R 隐式 int、三目左值、void* 转换、enum 赋整、
  const 经指针、动态异常规范 共 6 个跨版本演示。
- docs/VERSION_NOTES.md: 每条「原文说法→C17/C++17 是否成立→何版本成立→已验证」。

文档: 顶层 README、docs/SOURCE_PAGES.md (页面→目录映射)、
docs/ERRATA.md (Part1 修正)、docs/ERRATA_part3.md (Part3 修正)。
另修复 tools/run.sh / run_qt.sh 的 ${1:?...} 引号语法 bug。

隔离 Qt 验证: ldd 0 系统 Qt 引用; 173/173 目标全绿; p03 offscreen 运行通过。
2026-06-30 14:16:55 +08:00

1 line
32 KiB
JSON
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"id":"58954476","type":"page","status":"current","title":"08.C++输入输出流","version":{"by":{"type":"known","username":"ethanliu","userKey":"2c90931b6cfb654f016d5c69becc0001","profilePicture":{"path":"/images/icons/profilepics/default.svg","width":48,"height":48,"isDefault":true},"displayName":"刘浩","_links":{"self":"https://wiki.suncaper.net/rest/api/user?key=2c90931b6cfb654f016d5c69becc0001"},"_expandable":{"status":""}},"when":"2022-07-09T01:51:24.000Z","message":"","number":1,"minorEdit":false,"hidden":false,"_links":{"self":"https://wiki.suncaper.net/rest/experimental/content/58954476/version/1"},"_expandable":{"content":"/rest/api/content/58954476"}},"body":{"storage":{"value":"<ac:layout><ac:layout-section ac:type=\"two_right_sidebar\"><ac:layout-cell><h2><span>8.1 </span>流的概念和流类库的结构</h2><p>程序的输入指的是从输入文件将数据传送给程序,程序的输出指的是从程序将数据传送给输出文件。</p><p>C++输入输出包含以下三个方面的内容:</p><p>    对系统指定的标准设备的输入和输出。即从键盘输入数据,输出到显示器屏幕。这种输入输出称为标准的输入输出,简称标准<span>I/O</span>。</p><p>    以外存磁盘文件为对象进行输入和输出,即从磁盘文件输入数据,数据输出到磁盘文件。以外存文件为对象的输入输出称为文件的输入输出,简称文件<span>I/O</span>。</p><p>    对内存中指定的空间进行输入和输出。通常指定一个字符数组作为存储空间<span>(</span>实际上可以利用该空间存储任何信息<span>)</span>。这种输入和输出称为字符串输入输出,简称串<span>I/O</span>。</p><p>       C++编译系统提供了用于输入输出的<span>iostream</span>类库。<span>iostream</span>这个单词是由<span>3</span>个部 分组成的,即<span>i-o-stream</span>,意为输入输出流。在<span>iostream</span>类库中包含许多用于输入输出的 类。常用的见表</p><p><ac:image><ri:attachment ri:filename=\"image2021-5-14_14-37-13.png\" /></ac:image></p><p>ios是抽象基类,由它派生出<span>istream</span>类和<span>ostream</span>类,两个类名中第<span>1</span>个字母<span>i</span>和<span>o</span>分别代表输入<span>(input)</span>和输出<span>(output)</span>。<span> istream</span>类支持输入操作,<span>ostream</span>类支持输出操作,<span> iostream</span>类支持输入输出操作。<span>iostream</span>类是从<span>istream</span>类和<span>ostream</span>类通过多重继承而派生的类。其继承层次见上图表示。</p><p>C++对文件的输入输出需要用<span>ifstrcam</span>和<span>ofstream</span>类,两个类名中第<span>1</span>个字母<span>i</span>和<span>o</span>分别代表输入和输出,第<span>2</span>个字母<span>f</span>代表文件<span> (file)</span>。<span>ifstream</span>支持对文件的输入操作,<span> ofstream</span>支持对文件的输出操作。类<span>ifstream</span>继承了类<span>istream</span>,类<span>ofstream</span>继承了类<span>ostream</span>,类<span>fstream</span>继承了 类<span>iostream</span>。见图</p><p><ac:image><ri:attachment ri:filename=\"image2021-5-14_14-37-46.png\" /></ac:image></p><p>I/O类库中还有其他一些类,但是对于一般用户来说,以上这些已能满足需要了。</p><p><strong>与<span>iostream</span>类库有关的头文件 </strong></p><p>iostream类库中不同的类的声明被放在不同的头文件中,用户在自己的程序中用<span>#include</span>命令包含了有关的头文件就相当于在本程序中声明了所需 要用到的类。可以换 —种说法:头文件是程序与类库的接口,<span>iostream</span>类库的接口分别由不同的头文件来实现。常用的有</p><ul><li>iostream  包含了对输入输出流进行操作所需的基本信息。</li><li>fstream  用于用户管理的文件的<span>I/O</span>操作。</li><li>strstream  用于字符串流<span>I/O</span>。</li><li>stdiostream  用于混合使用<span>C</span>和<span>C + +</span>的<span>I/O</span>机制时,例如想将<span>C</span>程序转变为<span>C++</span>程序。</li><li>iomanip  在使用格式化<span>I/O</span>时应包含此头文件。</li></ul><p><strong>在<span>iostream</span>头文件中定义的流对象</strong></p><p>在<span> iostream </span>头文件中定义的类有<span> ios</span><span>istream</span><span>ostream</span><span>iostream</span><span>istream </span>等。</p><p>在<span>iostream</span>头文件中不仅定义了有关的类,还定义了<span>4</span>种流对象,</p><table><colgroup><col /><col /><col /><col /><col /></colgroup><tbody><tr><td><p><strong>对象</strong></p></td><td><p><strong>含义</strong></p></td><td><p><strong>对应设备</strong></p></td><td><p><strong>对应的类</strong></p></td><td><p><strong>c</strong><strong>语言中相应的标准文件</strong></p></td></tr><tr><td><p>cin</p></td><td><p>标准输入流</p></td><td><p>键盘</p></td><td><p>istream_withassign</p></td><td><p>stdin</p></td></tr><tr><td><p>cout</p></td><td><p>标准输出流</p></td><td><p>屏幕</p></td><td><p>ostream_withassign</p></td><td><p>stdout</p></td></tr><tr><td><p>cerr</p></td><td><p>标准错误流</p></td><td><p>屏幕</p></td><td><p>ostream_withassign</p></td><td><p>stderr</p></td></tr><tr><td><p>clog</p></td><td><p>标准错误流</p></td><td><p>屏幕</p></td><td><p>ostream_withassign</p></td><td><p>stderr</p></td></tr></tbody></table><p><strong> </strong></p><p>在<span>iostream</span>头文件中定义以上<span>4</span>个流对象用以下的形式(以<span>cout</span>为例):<span><br />    ostream cout ( stdout);<br />       </span>在定义<span>cout</span>为<span>ostream</span>流类对象时,把标准输出设备<span>stdout</span>作为参数,这样它就与标准输出设备<span>(</span>显示器<span>)</span>联系起来,如果有<span><br />    cout &lt;&lt;3;<br /></span>就会在显示器的屏幕上输出<span>3</span>。</p><p><strong>在<span>iostream</span>头文件中重载运算符</strong></p><p>“<span>&lt;&lt;</span>”和“<span>&gt;&gt;</span>”本来在<span>C++</span>中是被定义为左位移运算符和右位移运算符的,由于在<span>iostream</span>头文件中对它们进行了重载, 使它们能用作标准类型数据的输入和输出运算符。所以,在用它们的程序中必须用<span>#include</span>命令把<span>iostream</span>包含到程序中。</p><p>    #include &lt;iostream&gt;</p><ul><li>&gt;&gt;a表示将数据放入<span>a</span>对象中。</li><li>&lt;&lt;a表示将<span>a</span>对象中存储的数据拿出。</li></ul><p><span> </span></p><p><span> </span></p><h2><span>8.2 </span>标准<span>I/O</span>流</h2><p>标准<span>I/O</span>对象<span>:cin</span><span>cout</span><span>cerr</span><span>clog</span></p><p><strong>cout</strong><strong>流对象</strong></p><p>cout是<span>console output</span>的缩写,意为在控制台(终端显示器)的输出。强调几点。</p><p>1)   cout不是<span>C++</span>预定义的关键字,它是<span>ostream</span>流类的对象,在<span>iostream</span>中定义。 顾<span>       </span>名思义,流是流动的数据,<span>cout</span>流是流向显示器的数据。<span>cout</span>流中的数据是用流插入<span>     </span>运算符“<span>&lt;&lt;</span>”顺序加入的。如果有<span>:<br />    cout&lt;&lt;&quot;I &quot;&lt;&lt;&quot;study C++ &quot;&lt;&lt;&quot;very hard. &lt;&lt; </span>“<span>hello world !&quot;;</span></p><p>按顺序将字符串<span>&quot;I &quot;, &quot;study C++ &quot;, &quot;very hard.&quot;</span>插人到<span>cout</span>流中,<span>cout</span>就将它们送<span>   </span>到显示器,在显示器上输出字符串<span>&quot;I study C++ very hard.&quot;</span>。<span>cout</span>流是容纳数据的载<span>    </span>体,它并不是一个运算符。人们关心的是<span>cout</span>流中的内容,也就是向显示器输出什么。</p><p><br />2)    用“<span>cout&lt;&lt;</span>”输出基本类型的数据时,可以不必考虑数据是什么类型,系统会判断数<span>  </span>据的类型,并根据其类型选择调用与之匹配的运算符重 载函数。这个过程都是自动的,<span>  </span>用户不必干预。如果在<span>C</span>语言中用<span>prinf</span>函数输出不同类型的数据,必须分别指定相应<span>    </span>的输出格式符,十分麻烦,而且容易出错。<span>C++</span>的<span>I/O</span>机制对用户来说,显然是方便<span>       </span>而安全的。</p><p><br /></p><ul><li>cout流在内存中对应开辟了一个缓冲区,用来存放流中的数据,当向<span>cout</span>流插人一个<span>endl</span>时,不论缓冲区是否已满,都立即输出流中所有数据,然后插入一个换行符, 并刷新流(清空缓冲区)。注意如果插人一个换行符”<span>\\n</span>“(如<span>cout&lt;&lt;a&lt;&lt;&quot;\\n&quot;</span>),则只输出和换行,而不刷新<span>cout </span>流<span>(</span>但并不是所有编译系统都体现出这一区别)。</li></ul><p><br /></p><ul><li>在<span>iostream</span>中只对<span>&quot;&lt;&lt;&quot;</span>和<span>&quot;&gt;&gt;&quot;</span>运算符用于标准类型数据的输入输出进行了重载,但未对用户声明的类型数据的输入输出进行重载。如果用户声明了新的类型,并希望用<span>&quot;&lt;&lt;&quot;</span>和<span>&quot;&gt;&gt;&quot;</span>运算符对其进行输入输出,按照重运算符重载来做。</li></ul><p><strong>cerr</strong><strong>流对象</strong></p><p>cerr流对象是标准错误流,<span>cerr</span>流已被指定为与显示器关联。cerr的 作用是向标准错误设备<span>(standard error device)</span>输出有关出错信息。<span>cerr</span>与标准输出流<span>cout</span>的作用和用法差不多。但有一点不同:<span>cout</span>流通常是传送到显示器输出,但也可以被重定向输出到磁盘文件,而<span>cerr</span>流中的信息只能在显示器输出。当调试程序时,往往不希望程序运行时的出错信息被送到其他文件,而要求在显示器上及时输出,这时 应该用<span>cerr</span>。<span>cerr</span>流中的信息是用户根据需要指定的。</p><p><strong>clog</strong><strong>流对象</strong></p><p>clog流对象也是标准错误流,它是<span>console log</span>的缩写。它的作用和<span>cerr</span>相同,都是在终端显示器上显示出错信息。区别:<span>cerr</span>是不经过缓冲区,直接向显示器上输出有关信息,而<span>clog</span>中的信息存放在缓冲区中,缓冲区满后或遇<span>endl</span>时向显示器输出。</p><p><br /></p><p>缓冲区的概念<span>:</span></p><p><span><ac:image><ri:attachment ri:filename=\"image2021-5-14_14-38-33.png\" /></ac:image></span></p><h2><span>8.3 </span>标准输入流</h2><p>标准输入流对象<span>cin</span>,重点掌握的函数</p><p>cin.get() //一次只能读取一个字符</p><p>cin.get(一个参数<span>) //</span>读一个字符</p><p>cin.get(两个参数<span>) //</span>可以读字符串</p><p>cin.getline()</p><p>cin.ignore()</p><p>cin.peek()</p><p>cin.putback()</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"a0df37f4-31b9-4a44-95eb-938fd75eea89\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[//cin.get\nvoid test01(){\n#if 0\n\tchar ch = cin.get();\n\tcout << ch << endl;\n\n\tcin.get(ch);\n\tcout << ch << endl;\n\n\n\t//链式编程\n\tchar char1, char2, char3, char4;\n\tcin.get(char1).get(char2).get(char3).get(char4);\n\n\tcout << char1 << \" \" << char2 << \"\" << char3 << \" \" << char4 << \" \";\n#endif\n\n\tchar buf[1024] = { 0 };\n\t//cin.get(buf.1024);\n\tcin.getline(buf,1024);\n\tcout << buf;\n}\n\n//cin.ignore\nvoid test02(){\n\n\tchar buf[1024] = { 0 };\n\tcin.ignore(2); //忽略缓冲区当前字符\n\tcin.get(buf,1024);\n\tcout << buf << endl;\n}\n\n//cin.putback 将数据放回缓冲区\nvoid test03(){\n\n\t//从缓冲区取走一个字符\n\tchar ch = cin.get();\n\tcout << \"从缓冲区取走的字符:\" << ch << endl;\n\t//将数据再放回缓冲区\n\tcin.putback(ch);\n\tchar buf[1024] = { 0 };\n\tcin.get(buf,1024);\n\tcout << buf << endl;\n\n}\n\n//cin.peek 偷窥\nvoid test04(){\n\t\n\t//偷窥下缓冲区的数据\n\tchar ch = cin.peek();\n\tcout << \"偷窥缓冲区数据:\" << ch << endl;\n\tchar buf[1024] = { 0 };\n\tcin.get(buf, 1024);\n\tcout << buf << endl;\n}\n\n//练习 作业 使用cin.get和putback完成类似功能\nvoid test05(){\n\t\n\tcout << \"请输入一个数字或者字符串:\" << endl;\n\tchar ch = cin.peek();\n\tif(ch >= '0' && ch <= '9'){\n\t\tint number;\n\t\tcin >> number;\n\t\tcout << \"数字:\" << number << endl;\n\t}\n\telse{\n\t\tchar buf[64] = { 0 };\n\t\tcin.getline(buf, 64);\n\t\tcout << \"字符串:\" << buf << endl;\n\t}\n}]]></ac:plain-text-body></ac:structured-macro><h2><span>8.4 </span>标准输出流</h2><h3><span>8.4.1 </span>字符输出</h3><p>cout.flush() //刷新缓冲区<span> Linux</span>下有效</p><p>cout.put() //向缓冲区写字符</p><p>cout.write() //从<span>buffer</span>中写<span>num</span>个字节到当前输出流中。</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"8c757647-6172-4aeb-a82c-64af1aa84edc\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[//cout.flush 刷新缓冲区,linux下有效\nvoid test01(){\t\n\tcout << \"hello world\";\n\t//刷新缓冲区\n\tcout.flush(); \n}\n\n//cout.put 输出一个字符\nvoid test02(){\n\t\n\tcout.put('a');\n\t//链式编程\n\tcout.put('h').put('e').put('l');\n}\n\n//cout.write 输出字符串 buf,输出多少个\nvoid test03(){\n\t\n\t//char* str = \"hello world!\";\n\t//cout.write(str, strlen(str));\n\tchar* str = \"*************\";\n\tfor (int i = 1; i <= strlen(str); i ++){\n\t\tcout.write(str, i);\n\t\tcout << endl;\n\t}\n\n\tfor (int i = strlen(str); i > 0; i --){\n\t\tcout.write(str, i);\n\t\tcout << endl;\n\t}\n\n}]]></ac:plain-text-body></ac:structured-macro><h3><span>8.4.2 </span>格式化输出</h3><p>在输出数据时,为简便起见,往往不指定输出的格式,由系统根据数据的类型采取默认的格式,但有时希望数据按指定的格式输出,如要求以十六进制或八进制形式输出一个整数,对输出的小数只保留两位小数等。有两种方法可以达到此目的。</p><p>1)使用控制符的方法;</p><p>2)使用流对象的有关成员函数。</p><p><span>4.4.2.1 </span>使用流对象的有关成员函数</p><p>通过调用流对象<span>cout</span>中用于控制输出格式的成员函数来控制输出格式。用于控制输出格式的常用的成员函数如下:</p><p><ac:image><ri:attachment ri:filename=\"image2021-5-14_14-40-5.png\" /></ac:image></p><p>流成员函数<span>setf</span>和控制符<span>setiosflags</span>括号中的参数表示格式状态,它是通过格式标志来指定的。格式标志在类<span>ios</span>中被定义为枚举值。因此在引用这些格式标志时要在前面加上类名<span>ios</span>和域运算符“<span>::</span>”。格式标志见表<span>13.5</span>。</p><p><ac:image><ri:attachment ri:filename=\"image2021-5-14_14-40-40.png\" /></ac:image></p><p><span>8.4.2.2 </span><strong>控制符格式化输出</strong></p><p>C++提供了在输入输出流中使用的控制符<span>(</span>有的书中称为操纵符<span>)</span>。</p><p><ac:image><ri:attachment ri:filename=\"image2021-5-14_14-41-14.png\" /></ac:image></p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"b55faa77-0fa7-45b2-9ea3-9dcf5cbcee0d\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[//通过流成员函数\nvoid test01(){\n\t\n\tint number = 99;\n\tcout.width(20);\n\tcout.fill('*');\n\tcout.setf(ios::left);\n\tcout.unsetf(ios::dec); //卸载十进制\n\tcout.setf(ios::hex);\n\tcout.setf(ios::showbase);\n\tcout.unsetf(ios::hex);\n\tcout.setf(ios::oct);\n\tcout << number << endl;\n\n}\n\n//使用控制符\nvoid test02(){\n\n\tint number = 99;\n\tcout << setw(20)\n\t\t<< setfill('~')\n\t\t<< setiosflags(ios::showbase)\n\t\t<< setiosflags(ios::left)\n\t\t<< hex\n\t\t<< number\n\t\t<< endl;\n\n}]]></ac:plain-text-body></ac:structured-macro><p><span>8.4.2.3 </span><strong>对程序的几点说明</strong></p><p>1) 成员函数<span>width(n)</span>和控制符<span>setw(n)</span>只对其后的第一个输出项有效。如:</p><p>cout. width(6);<br />    cout &lt;&lt;20 &lt;&lt;3.14&lt;&lt;endl;<br />       输出结果为<span> 203.14</span></p><p>在输出第一个输出项<span>20</span>时,域宽为<span>6</span>,因此在<span>20</span>前面有<span>4</span>个空格,在输出<span>3.14</span>时,<span>width (6)</span>已不起作用,此时按系统默认的域宽输出(按数据实际长度输出)。如果要求在输出数据时都按指定的同一域宽<span>n</span>输出,不能只调用一次<span>width(n)</span> 而必须在输出每一项前都调用一次<span>width(n&gt;</span>,上面的程序中就是这样做的。</p><p><br /></p><p>2) 在表<span>13.5</span>中的输出格式状态分为<span>5</span>组,每一组中同时只能选用一种(例如<span>dec</span>、<span>hex</span>和<span>oct</span>中只能选一,它们是互相排斥的)。在用成员函数<span>setf</span>和 控制符<span>setiosflags</span>设置输出格式状态后,如果想改设置为同组的另一状态,应当调用成员函数<span>unsetf</span>(对应于成员函数<span>self</span>)或<span> resetiosflags</span>(对应于控制符<span>setiosflags</span>),先终止原来设置的状态。然后再设置其他状态,大家可以从本程序中看到这点。程序在开 始虽然没有用成员函数<span>self</span>和控制符<span>setiosflags</span>设置用<span>dec</span>输出格式状态,但系统默认指定为<span>dec</span>,因此要改变为<span>hex</span>或<span>oct</span>,也应当先 用<span>unsetf </span>函数终止原来设置。如果删去程序中的第<span>7</span>行和第<span>10</span>行,虽然在第<span>8</span>行和第<span>11</span>行中用成员函数<span>setf</span>设置了<span>hex</span>和<span>oct</span>格式,由于未终止<span>dec</span>格式,因 此<span>hex</span>和<span>oct</span>的设置均不起作用,系统依然以十进制形式输出。</p><p>同理,程序倒数第<span>8</span>行的<span>unsetf </span>函数的调用也是不可缺少的。</p><p><br /></p><p>3) 用<span>setf </span>函数设置格式状态时,可以包含两个或多个格式标志,由于这些格式标志在<span>ios</span>类中被定义为枚举值,每一个格式标志以一个二进位代表,因此可以用位或运算符“<span>|</span>”组合多个格式标志。如倒数第<span>5</span>、第<span>6</span>行可以用下面一行代替:</p><p>    cout.setf(ios::internal I ios::showpos);  //包含两个状态标志,用<span>&quot;|&quot;</span>组合</p><p><br /></p><ul><li>可以看到:对输出格式的控制,既可以用控制符<span>(</span>如例<span>2)</span>,也可以用<span>cout</span>流的有关成员函数<span>(</span>如例<span>13.3)</span>,二者的作用是相同的。控制符是在头文件<span>iomanip</span>中定义的,因此用控制符时,必须包含<span>iomanip</span>头文件。<span>cout</span>流的成员函数是在头文件<span>iostream </span>中定义的,因此只需包含头文件<span>iostream</span>,不必包含<span>iomanip</span>。许多程序人员感到使用控制符方便简单,可以在一个<span>cout</span>输出语句中连续使用多种控制符。</li></ul><h2><span>8.4 </span>文件读写</h2><h3><span>8.4.1</span>文件流类和文件流对象</h3><p>输入输出是以系统指定的标准设备(输入设备为键盘,输出设备为显示器)为对象的。在实际应用中,常以磁盘文件作为对象。即从磁盘文件读取数据,将数据输出到磁盘文件。</p><p>和文件有关系的输入输出类主要在<span>fstream.h</span>这个头文件中被定义,在这个头文件中主要被定义了三个类,由这三个类控制对文件的各种输入输出操作,他们分别是<span>ifstream</span>、<span>ofstream</span>、<span>fstream</span>,其中<span>fstream</span>类是由<span>iostream</span>类派生而来,他们之间的继承关系见下图所示:</p><p><ac:image><ri:attachment ri:filename=\"image2021-5-14_14-42-13.png\" /></ac:image></p><p>由于文件设备并不像显示器屏幕与键盘那样是标准默认设备,所以它在<span>fstream</span>头文件中是没有像<span>cout</span>那样预先定义的全局对象,所以我们必须自己定义一个该类的对象。<span>ifstream</span>类,它是从<span>istream</span>类派生的,用来支持从磁盘文件的输入。<span>ofstream</span>类,它是从<span>ostream</span>类派生的,用来支持向磁盘文件的输出。</p><p><br /></p><p>fstream类,它是从<span>iostream</span>类派生的,用来支持对磁盘文件的输入输出。</p><h3><span>8.4.2 C++</span>打开文件</h3><p>所谓打开<span>(open)</span>文件是一种形象的说法,如同打开房门就可以进入房间活动一样。 打开文件是指在文件读写之前做必要的准备工作,包括:</p><p>1)为文件流对象和指定的磁盘文件建立关联,以便使文件流流向指定的磁盘文件。</p><p>2)指定文件的工作方式,如:该文件是作为输入文件还是输出文件,是<span>ASCII</span>文件还是二进制文件等。</p><p>以上工作可以通过两种不同的方法实现<span>:</span></p><p>1)   调用文件流的成员函数<span>open</span>。如</p><p>           ofstream outfile;  //定义<span>ofstream</span>类<span>(</span>输出文件流类<span>)</span>对象<span>outfile</span></p><p>           outfile.open(&quot;f1.dat&quot;,ios::out);  //使文件流与<span>f1.dat</span>文件建立关联</p><p>第<span>2</span>行是调用输出文件流的成员函数<span>open</span>打开磁盘文件<span>f1.dat</span>,并指定它为输出文件,<span>   </span>文件流对象<span>outfile</span>将向磁盘文件<span>f1.dat</span>输出数据。<span>ios::out</span>是<span>I/O</span>模式的一种,表示<span>    </span>以输出方式打开一个文件。或者简单地说,此时<span>f1.dat</span>是一个输出文件,接收从内存 输出的数据。</p><p>磁盘文件名可以包括路径,如<span>&quot;c:\\\\new\\\\f1.dat&quot;</span>,如缺省路径,则默认为当前目录下的文件。</p><p><br /></p><p>2)   在定义文件流对象时指定参数</p><p>在声明文件流类时定义了带参数的构造函数,其中包含了打开磁盘文件的功能。因此,<span>       </span>可以在定义文件流对象时指定参数,调用文件流类的构造函数来实现打开文件的功能。</p><p><ac:image><ri:attachment ri:filename=\"image2021-5-14_14-42-46.png\" /></ac:image></p><p>几点说明:<span><br />1) </span>新版本的<span>I/O</span>类库中不提供<span>ios::nocreate</span>和<span>ios::noreplace</span>。<span><br /><br />2) </span>每一个打开的文件都有一个文件指针,该指针的初始位置由<span>I/O</span>方式指定,每次读写都从文件指针的当前位置开始。每读入一个字节,指针就后移一个字节。当文件指针移到最后,就会遇到文件结束<span>EOF</span>(文件结束符也占一个字节,其值为<span>-1)</span>,此时流对象的成员函数<span>eof</span>的值为非<span>0</span>值<span>(</span>一般设为<span>1)</span>,表示文件结束 了。<span><br /><br />3) </span>可以用“位或”运算符“<span>|</span>”对输入输出方式进行组合,如表<span>13.6</span>中最后<span>3</span>行所示那样。还可以举出下面一些例子:<span><br />    ios::in | ios:: noreplace  //</span>打开一个输入文件,若文件不存在则返回打开失败的信息<span><br />    ios::app | ios::nocreate  //</span>打开一个输出文件,在文件尾接着写数据,若文件不存在,则返回打开失败的信息<span><br />    ios::out l ios::noreplace  //</span>打开一个新文件作为输出文件,如果文件已存在则返回打开失败的信息<span><br />    ios::in l ios::out I ios::binary  //</span>打开一个二进制文件,可读可写<span><br /></span>但不能组合互相排斥的方式,如<span> ios::nocreate l ios::noreplace</span>。<span><br /><br />4) </span>如果打开操作失败,<span>open</span>函数的返回值为<span>0(</span>假<span>)</span>,如果是用调用构造函数的方式打开文件的,则流对象的值为<span>0</span>。可以据此测试打开是否成功。如<span><br />    if(outfile.open(&quot;f1.bat&quot;, ios::app) ==0)<br />        cout &lt;&lt;&quot;open error&quot;;<br /></span>或<span><br />    if( !outfile.open(&quot;f1.bat&quot;, ios::app) )<br />        cout &lt;&lt;&quot;open error&quot;;</span></p><p><br /></p><h3><span>8.4.3 C++</span>关闭文件</h3><p>在对已打开的磁盘文件的读写操作完成后,应关闭该文件。关闭文件用成员函数<span>close</span>。如:<span>outfile.close( );  //</span>将输出文件流所关联的磁盘文件关闭<span><br /></span>所谓关闭,实际上是解除该磁盘文件与文件流的关联,原来设置的工作方式也失效,这样,就不能再通过文件流对该文件进行输入或输出。此时可以将文件流与其他磁盘文件建立关联,通过文件流对新的文件进行输入或输出。如<span>:<br />    outfile.open(&quot;f2.dat&quot;,ios::app|ios::nocreate)</span></p><p>   此时文件流<span>outfile</span>与<span>f2.dat</span>建立关联,并指定了<span>f2.dat</span>的工作方式。</p><p><span> </span></p><p><span> </span></p><h3><span>8.4.4 C++</span>对<span>ASCII</span>文件的读写操作</h3><p>如果文件的每一个字节中均以<span>ASCII</span>代码形式存放数据<span>,</span>即一个字节存放一个字符<span>,</span>这个文件就是<span>ASCII</span>文件<span>(</span>或称字符文件<span>)</span>。程序可以从<span>ASCII</span>文件中读入若干个字符<span>,</span>也可以向它输出一些字符。</p><ul><li>用流插入运算符“<span>&lt;&lt;</span>”和流提取运算符“<span>&gt;&gt;</span>”输入输出标准类型的数据。“<span>&lt;&lt;</span>”和“<span> &gt;&gt;</span>”都巳在<span>iostream</span>中被重载为能用于<span>ostream</span>和<span>istream</span>类对象的标准类型的输入输出。由于<span>ifstream</span>和<span> ofstream</span>分别是<span>ostream</span>和<span>istream</span>类的派生类;因此它们从<span>ostream</span>和<span>istream</span>类继承了公用的重载函数,所以在对磁盘文件的操作中,可以通过文件流对象和流插入运算符“<span>&lt;&lt;</span>”及 流提取运算符“<span>&gt;&gt;</span>”实现对磁盘 文件的读写,如同用<span>cin</span>、<span>cout</span>和<span>&lt;&lt;</span>、<span>&gt;&gt;</span>对标准设备进行读写一样。</li><li>用文件流的<span>put</span>、<span>get</span>、<span>geiline</span>等成员函数进行字符的输入输出,:用<span>C++</span>流成员函数<span>put</span>输出单个字符、<span>C++ get()</span>函数读入一个字符和<span>C++ getline()</span>函数读入一行字符。</li></ul><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"ac68ece5-ce33-4348-ae26-4d6f2d8608b6\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[int main(){\n\n\tchar* sourceFileName = \"./source.txt\";\n\tchar* targetFileName = \"./target.txt\";\n\t//创建文件输入流对象\n\tifstream ism(sourceFileName, ios::in);\n\t//创建文件输出流对象\n\tofstream osm(targetFileName,ios::out);\n\n\tif (!ism){\n\t\tcout << \"文件打开失败!\" << endl;\n\t}\n\n\twhile (!ism.eof()){\n\t\tchar buf[1024] = { 0 };\n\t\tism.getline(buf,1024);\n\t\tcout << buf << endl;\n\t\tosm << buf << endl;\n\t}\n\n\t//关闭文件流对象\n\tism.close();\n\tosm.close();\n\n\tsystem(\"pause\");\n\treturn EXIT_SUCCESS;\n}]]></ac:plain-text-body></ac:structured-macro><h3><span>8.4.5 C++</span>对二进制文件的读写操作</h3><p>二进制文件不是以<span>ASCII</span>代码存放数据的,它将内存中数据存储形式不加转换地传送到磁盘文件,因此它又称为内存数据的映像文件。因为文件中的信息不是字符数据,而是字节中的二进制形式的信息,因此它又称为字节文件。</p><p>对二进制文件的操作也需要先打开文件,用完后要关闭文件。在打开时要用<span>ios::binary</span>指定为以二进制形式传送和存储。二进制文件除了可以作为输入文件或输出文件外<span>,</span>还可以是既能输入又能输出的文件。这是和<span>ASCII</span>文件不同的地方。</p><p><br /></p><p><strong>用成员函数<span>read</span>和<span>write</span>读写二进制文件</strong></p><p>对二进制文件的读写主要用<span>istream</span>类的成员函数<span>read</span>和<span>write</span>来实现。这两个成员函数的原型为<span><br />    istream&amp; read(char *buffer,int len);<br />    ostream&amp; write(const char * buffer,int len);<br /></span>字符指针<span>buffer</span>指向内存中一段存储空间。<span>len</span>是读写的字节数。调用的方式为:<span><br />    a. write(p1,50);<br />    b. read(p2,30);<br />       </span>上面第一行中的<span>a</span>是输出文件流对象,<span>write</span>函数将字符指针<span>p1</span>所给出的地址开始的<span>50</span>个字节的内容不加转换地写到磁盘文件中。在第二行中,<span>b</span>是输入文件流对象,<span>read </span>函数从<span>b</span>所关联的磁盘文件中,读入<span>30</span>个字节<span>(</span>或遇<span>EOF</span>结束),存放在字符指针<span>p2</span>所指的一段空间内。</p><ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\" ac:macro-id=\"5585d598-a91b-45a9-9737-be5064ef71b9\"><ac:parameter ac:name=\"language\">cpp</ac:parameter><ac:plain-text-body><![CDATA[class Person{\npublic:\n\tPerson(char* name,int age){\n\t\tstrcpy(this->mName, name);\n\t\tthis->mAge = age;\n\t}\npublic:\n\tchar mName[64];\n\tint mAge;\n};\n\nint main(){\n\n\tchar* fileName = \"person.txt\";\n\t//二进制模式读写文件\n\t//创建文件对象输出流\n\tofstream osm(fileName, ios::out | ios::binary);\n\n\tPerson p1(\"John\",33);\n\tPerson p2(\"Edward\", 34);\n\n\t//Person对象写入文件\n\tosm.write((const char*)&p1,sizeof(Person));\n\tosm.write((const char*)&p2, sizeof(Person));\n\n\t//关闭文件输出流\n\tosm.close();\n\n\t//从文件中读取对象数组\n\tifstream ism(fileName, ios::in | ios::binary);\n\tif (!ism){\n\t\tcout << \"打开失败!\" << endl;\n\t}\n\t\n\tPerson p3;\n\tPerson p4;\n\n\tism.read((char*)&p3, sizeof(Person));\n\tism.read((char*)&p4, sizeof(Person));\n\n\tcout << \"Name:\" << p3.mName << \" Age:\" << p3.mAge << endl;\n\tcout << \"Age:\" << p4.mName << \" Age:\" << p4.mAge << endl;\n\n\t//关闭文件输入流\n\tism.close();\n\n\tsystem(\"pause\");\n\treturn EXIT_SUCCESS;\n}]]></ac:plain-text-body></ac:structured-macro></ac:layout-cell><ac:layout-cell><p><br /></p></ac:layout-cell></ac:layout-section></ac:layout>","representation":"storage","_expandable":{"content":"/rest/api/content/58954476"}},"_expandable":{"editor":"","view":"","export_view":"","styled_view":"","anonymous_export_view":""}},"extensions":{"position":"none"},"_links":{"webui":"/pages/viewpage.action?pageId=58954476","edit":"/pages/resumedraft.action?draftId=58954476","tinyui":"/x/7JKDAw","collection":"/rest/api/content","base":"https://wiki.suncaper.net","context":"","self":"https://wiki.suncaper.net/rest/api/content/58954476"},"_expandable":{"container":"/rest/api/space/2CT","metadata":"","operations":"","children":"/rest/api/content/58954476/child","restrictions":"/rest/api/content/58954476/restriction/byOperation","history":"/rest/api/content/58954476/history","ancestors":"","descendants":"/rest/api/content/58954476/descendant","space":"/rest/api/space/2CT"}}