加载中

输入输出与文件操作

输入输出与文件操作

比较重要的内容

int x;
while(cin>>x){}

可见istream在其基类内重载了operator bool

#include <iostream>
using namespace std;
class MyCin{
    bool stop;
public:
    // 重要的,初始值不停
    MyCin() : stop(false) {}
    // 重载bool
    operator bool(){
        return !stop;
    }
    // 传入i引用,重载>>读取这个i
    MyCin& operator>>(int &i){
        if(stop) return *this;
        cin>>i;
        if(i==100) stop=true;
        return *this;
    }
};
int main(){
    MyCin m;
    int x;
    while(m>>x){
        cout<<x<<endl;
    }
    return 0;
}

getline

istream & getline(char* buf,int bufsize,char delim);

delim缺省,默认为\n,从输入流中读取(bufsize-1)个字符到缓冲区,或读到delim

  • delim不会读入缓冲区,但会被输入流取走
  • 达到或超出bufsize报错
// ■ getline读入字符个数超过了bufSize个
int main() {
    char buf1[5], buf2[5];
    // 第一次读取
    cin.getline(buf1, 5, '\n');  // 输入 "abcdef\n"
    // 结果: buf1 = "abcd", 流的状态标志 failbit 被置位
    // 第二次读取 - 会失败!
    cin.getline(buf2, 5, '\n');
    // 因为 failbit 已设置, 这次读取立即返回, 不读取任何内容
    // buf2 内容不变, 可能保持未定义状态
    cout << buf1 << endl << buf2 << endl;
    return 0;
}

// 输入:
// abcdef
// 输出:
// abcd

// 输入:
// abc
// def
// 输出:
// abc
// def
// ■ 可以用 if(!cin.getline(...)) 判断输入是否结束

int main() {
    char name[10];
    cout << "请输入姓名: ";
    if(!cin.getline(name, 10)) {
        cout << "读取失败! " << endl;
        cout << "可能原因: 输入过长或流出错" << endl;
    } else {
        cout << "读取成功, 姓名: " << name << endl;
    }
    return 0;
}
// 现代
#include <string>
std::string line;
std::getline(cin, line);           // 没有长度限制,自动管理内存
std::getline(cin, line, ',');      // 自定义分隔符

istream 类的成员函数

  • bool eof(); — 判断输入流是否结束
  • int peek(); — 返回下一个字符,但不从流中去掉
  • istream & putback(char c); — 将字符 c 放回输入流
  • istream & ignore(int nCount = 1, int delim = EOF); — 从流中删掉最多 nCount 个字符,遇到 EOF 时结束

getline 读到留在流中的 '\n' 就会返回

#include <iostream>
using namespace std;

int main() {
    int x;
    char buf[100];
    cin >> x;
    cin.getline(buf, 90);
    cout << buf << endl;
    return 0;
}

输入:

12 abcd

输出:

abcd

(前面有一个空格,因为 >> 留下的空格还在流里)

输入:

12

程序立即结束,无输出。(getline 直接读到了 12 后面的 '\n'

istream 类的成员函数

  • bool eof(); — 判断输入流是否结束
  • int peek(); — 返回下一个字符,但不从流中去掉
  • istream & putback(char c); — 将字符 c 放回输入流
  • istream & ignore(int nCount = 1, int delim = EOF); — 从流中删掉最多 nCount 个字符,遇到 EOF 时结束

getline 读到留在流中的 '\n' 就会返回

#include <iostream>
using namespace std;

int main() {
    int x;
    char buf[100];
    cin >> x;
    cin.getline(buf, 90);
    cout << buf << endl;
    return 0;
}

输入:

12 abcd

输出:

abcd

(前面有一个空格,因为 >> 留下的空格还在流里)

输入:

12

程序立即结束,无输出。(getline 直接读到了 12 后面的 '\n'

  • cin.width(): 最大读取n-1个字符

  • cout默认右对齐

  • 设置宽度是一次性的

  • 流操纵器:

    • cout << tab 不是把 tab 的”返回值”传给 cout,而是把 tab 这个”函数本身”传给 cout,让 cout 内部再调用它。

    • ostream& tab(ostream& output) {
          return output << '\t';
      }
      //重载内部等价于return (*p)(*this);   // 也就是 tab(cout)
      ostream& operator<<(ostream& (*p)(ostream&));
      //                     ^^^^^^^^^^^^^^^^^
      //                     函数指针:接受ostream&,返回ostream&
      
      
        - `cout << "aa" << tab << "bb";`
      
      - 默认是总有效位数, fixed是小数点后有效位数, scientific是科学计数法上小数点后有效位数
      
      ## 总览
      
      
      ```mermaid
      graph LR
          Root[C++ 输入输出与文件操作] --> S[标准IO流]
          Root --> M[流操纵算子]
          Root --> F[文件操作]
          
          %% 标准IO流
          S --> C1[流类体系]
          S --> C2[标准流对象]
          S --> C3[重定向]
          C1 --> C11[ios -> istream/ostream]
          C1 --> C12[cin ifstream / cout ofstream]
          C2 --> C21[cin  cout  cerr  clog]
          C3 --> C31[freopen 输入/输出重定向]
          
          %% 流操纵算子
          M --> M1[头文件 iomanip]
          M --> M2[进制: dec hex oct]
          M --> M3[精度: fixed setprecision]
          M --> M4[域宽: setw 单次有效]
          
          %% 文件操作
          F --> F1[头文件 fstream]
          F --> F2[打开模式: in out app binary]
          F --> F3[指针操作: seekg tellg seekp tellp]
          F --> F4[文本文件: >> << 读写]
          F --> F5[二进制文件: read write 读写]

一、C++ IO流类体系(基础核心)

1. 流类继承关系(根类:ios)

graph TD
    ios["ios(流基类)"]
    istream["istream(输入流类)"]
    ifstream["ifstream(文件输入流类,专用于读文件)"]
    ostream["ostream(输出流类)"]
    ofstream["ofstream(文件输出流类,专用于写文件)"]
    iostream["iostream(输入输出流类,同时继承istream+ostream)"]
    fstream["fstream(文件读写流类,可同时读写文件)"]
    
    ios --> istream
    istream -->|"全局对象cin"| ifstream
    ios --> ostream
    ostream -->|"全局对象cout/cerr/clog"| ofstream
    ios --> iostream
    iostream --> fstream

2. 标准流对象核心说明

对象 绑定设备 缓冲机制 核心用途 能否重定向
cin 标准输入(键盘) 有缓冲 常规数据输入 可重定向到文件读取
cout 标准输出(屏幕) 有缓冲 常规数据输出 可重定向到文件写入
cerr 标准错误输出 无缓冲 紧急错误信息(程序崩溃也能正常输出) 不可重定向
clog 标准错误输出 有缓冲 非紧急运行日志(减少IO次数,性能更高) 不可重定向

二、标准IO核心操作

1. 输入/输出重定向

核心函数:freopen(头文件<iostream>

用法 核心功能
freopen("文件名", "r", stdin); 将标准输入cin重定向到指定文件,从文件读取数据
freopen("文件名", "w", stdout); 将标准输出cout重定向到指定文件,向文件写入数据

最简示例

// 输入重定向:从in.txt读取数据
freopen("in.txt", "r", stdin); 
cin >> x >> y;

// 输出重定向:向out.txt写入数据
freopen("out.txt", "w", stdout);
cout << x << " " << y;

2. 输入流结束判断

标准写法:while(cin >> x)

  • 底层原理:>>运算符返回istream对象,istream类重载了operator bool,流无效/结束时返回false
  • 结束触发条件:
    1. 键盘输入:单独一行输入Ctrl+Z,标记输入流结束
    2. 文件输入:读取到文件末尾(EOF),流自动标记为结束
  • 扩展:可通过重载operator>>operator bool实现自定义流的结束判断
1. 变量引用的本质
  • 地址传递int &n 传入的是内存地址。函数获得了修改 n1n2权限,但并不具备预知未来输入内容的能力。
  • 初始值陷阱:在执行 cin >> n 这一行指令之前,n 指向的内存空间里存放的是旧数据(上一次循环留下的值或随机值)。
2. 旧版逻辑失效原因:时序错位

C++

>if(n != -1)  // 步骤 A:判断旧数据
   cin >> n; // 步骤 B:覆盖新数据
  • 逻辑断层:当输入 -1 时,由于步骤 A 使用的是旧值(非 -1),程序会执行步骤 B 将 -1 读入内存。但此时判断已经结束,else 分支里的 stop = true 无法被触发。
  • 滞后效应stop 标志位的更新比输入流的变化慢了一个周期
3. 链式调用的“惯性”与“熔断”

while(m >> n1 >> n2) 执行时:

  • 惯性读取:读入 n1 = -1 后,因为 stop 仍为 false,程序会顺着指令链继续执行 m >> n2
  • 输出溢出:当这一轮 while 条件执行完毕时,n1n2 都已被新值覆盖,随后被 cout 打印,导致输出了本该拦截的 -1 和随后的数字。
4. 正确的逻辑构造:先做后看

严谨的实现必须确保:修改动作发生在状态判定之前。

  • 逻辑顺序
  1. 检查熔断if(stop) return *this;(若前一步已停止,立即拦截)。
  2. 执行修改cin >> n;(更新内存中的值)。
  3. 同步状态if(n == -1) stop = true;(根据刚读入的值更新标志)。

3. istream核心成员函数(高频考点)

(1)getline:按行读取字符串

函数原型
// 读到换行符\n停止
istream& getline(char* buf, int bufSize);
// 读到自定义分隔符delim停止
istream& getline(char* buf, int bufSize, char delim);
核心规则
  • 最多读取bufSize-1个字符,自动在缓冲区末尾添加字符串结束符’\0’
  • 分隔符\n/delim会从流中移除,但不会存入缓冲区
  • 若读取字符数超过bufSize,会置位流的failbit后续所有读操作全部失效
错误判断:if(!cin.getline(...)) → 读取失败/流结束时返回true

(2)其他常用成员函数

函数 核心功能
bool eof(); 判断输入流是否到达文件末尾(EOF)
int peek(); 返回流中下一个字符,不从流中移除(仅“偷窥”)
istream& putback(char c); 将字符c放回输入流的开头
istream& ignore(int n=1, int delim=EOF); 跳过流中最多n个字符,遇到delim提前停止

(3)getline高频坑点

cin >> x 读取完成后,会在流中残留换行符\n,后续调用getline会直接读取空行,需先用ignore()清除残留换行。


三、流操纵算子(格式化输入输出)

必须包含头文件:<iomanip>

1. 整数基数控制

算子 功能
dec 十进制输出(默认格式)
oct 八进制输出
hex 十六进制输出
setbase(n) 指定输出基数(仅支持8/10/16)

2. 浮点数精度控制(核心考点)

核心工具

  • 成员函数:cout.precision(n);
  • 流操纵算子:cout << setprecision(n);
  • 两者功能完全一致,算子支持链式连续输出

精度含义规则(与输出格式强绑定)

输出格式 控制算子 setprecision(n)的含义
默认格式(defaultfloat) 无/resetiosflags n = 总有效数字位数
定点格式(fixed) setiosflags(ios::fixed) / fixed n = 小数点后保留位数
科学计数格式(scientific) setiosflags(ios::scientific) / scientific n = 小数点后保留位数

核心规则

  • 小数截短显示时,自动执行四舍五入
  • 格式取消:resetiosflags(ios::fixed/ios::scientific)

3. 域宽设置(setw / width)

核心用法

  • 成员函数:cin.width(n); / cout.width(n);
  • 流操纵算子:cin >> setw(n); / cout << setw(n);

核心规则

  • 域宽设置仅对下一次读写操作有效,每次读写前必须重新设置
  • 输入时:最多读取n-1个字符(预留\0的存储位置)
  • 无参width():返回当前的域宽值

4. 用户自定义流操纵算子

定义规则

返回值和参数均为ostream&的函数,示例:

// 自定义制表符算子
ostream& tab(ostream& output){
    return output << '\t';
}
// 使用方式:cout << "a" << tab << "b" << endl;

底层原理

ostream重载了<<运算符,支持接收函数指针,会自动调用传入的函数,并将流对象本身作为参数传入。


四、文件IO操作(核心应用)

必须包含头文件:<fstream>

1. 文件打开与关闭

(1)两种打开方式

方式 示例
构造对象时直接打开 ofstream outFile("test.txt", ios::out|ios::binary);
先创建对象,再用open打开 ofstream fout; fout.open("test.txt", ios::out);

(2)常用打开模式

模式 核心功能
ios::in 只读模式打开(ifstream默认模式)
ios::out 只写模式打开,清空原有内容(ofstream默认模式)
ios::app 追加模式,保留原有内容,始终在文件尾部写入
ios::ate 打开后文件指针定位到尾部,可在文件任意位置写入
ios::binary 二进制模式打开,不执行换行符自动转换

(3)打开成功判断

if(!fout){ // 流对象返回false,代表打开失败
    cerr << "文件打开失败!" << endl;
    return 1;
}

(4)文件关闭

文件使用完毕必须显式关闭,释放系统资源:

fin.close();
fout.close();

2. 文件读写指针操作

指针类型 适用流 核心函数 功能
读指针 ifstream/fstream tellg() 获取读指针当前位置(字节数)
seekg(偏移量, 基准位置) 移动读指针到指定位置
写指针 ofstream/fstream tellp() 获取写指针当前位置(字节数)
seekp(偏移量, 基准位置) 移动写指针到指定位置

基准位置参数

  • ios::beg:文件开头(默认基准)
  • ios::cur:指针当前位置
  • ios::end:文件尾部
  • 偏移量支持负数(代表向前移动)

3. 文本文件读写

  • 文本文件流的用法与cin/cout完全一致,所有流操纵算子、>>/<<运算符均适用
  • 典型场景:从文本文件读取数据、处理后写入另一个文本文件(如整数排序、文本拷贝)

4. 二进制文件读写(核心考点)

核心函数

函数 标准用法
write(写) fout.write((const char*)&变量地址, sizeof(变量类型));
read(读) fin.read((char*)&变量地址, sizeof(变量类型));

辅助函数

gcount():返回最近一次read操作实际读取的字节数

核心特性

  • 直接读写内存中的二进制数据,无格式转换,记事本打开为乱码
  • 适用于结构体/类对象的批量读写,效率高、无精度损失
  • 必须配合ios::binary模式打开文件

5. 文本文件 vs 二进制文件(高频考点)

换行符系统差异

系统 换行符 ASCII码
Linux/Unix \n 0x0a
Windows \r\n 0x0d 0x0a
Mac OS \r 0x0d

打开模式差异

系统 文本模式(无ios::binary) 二进制模式(ios::binary)
Linux/Unix 无任何转换,与二进制模式无区别 无任何转换,按原始字节读写
Windows 读:自动将\r\n转为\n(少读1字节)
写:自动将\n转为\r\n(多写1字节)
无任何转换,按原始字节读写

五、注意点

  1. getline坑点cin >>后残留的\n会导致getline直接读取空行,需用cin.ignore()清除换行符
  2. setw单次有效:域宽设置仅对下一次读写生效,每次使用前必须重新设置
  3. 流failbit置位:getline读取溢出、读取出错会置位failbit,后续所有流操作全部失效,需用cin.clear()重置流状态
  4. Windows换行转换:二进制读写必须添加ios::binary,否则会因换行符自动转换导致文件读写异常
  5. 文件关闭要求:文件打开使用后必须显式调用close(),否则会导致资源泄漏,其他程序无法操作该文件
  6. cerr不可重定向:错误信息必须用cerr输出,避免cout重定向后,错误信息无法在屏幕显示
  7. 二进制读写强制转换:write/read的第一个参数必须强制转为char*/const char*,否则会编译报错

本文作者:flowwalker之码艺Blog

本文链接:/coding-notes-blog/posts/a1e2/

版权声明:本文采用 CC BY-NC-SA 4.0 许可协议