输入输出与文件操作

输入输出与文件操作

目录

[TOC]


比较重要的内容

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#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报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ■ 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

1
2
3
4
5
6
7
8
9
10
11
12
13
// ■ 可以用 if(!cin.getline(...)) 判断输入是否结束

int main() {
char name[10];
cout << "请输入姓名: ";
if(!cin.getline(name, 10)) {
cout << "读取失败! " << endl;
cout << "可能原因: 输入过长或流出错" << endl;
} else {
cout << "读取成功, 姓名: " << name << endl;
}
return 0;
}
1
2
3
4
5
// 现代
#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' 就会返回

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
using namespace std;

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

输入:

1
12 abcd

输出:

1
abcd

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

输入:

1
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' 就会返回

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
using namespace std;

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

输入:

1
12 abcd

输出:

1
abcd

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

输入:

1
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&
      <!--code13-->
      
      
      
      

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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重定向到指定文件,向文件写入数据

最简示例

1
2
3
4
5
6
7
// 输入重定向:从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++

1
2
>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:按行读取字符串

函数原型
1
2
3
4
// 读到换行符\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)无/resetiosflagsn = 总有效数字位数
定点格式(fixed)setiosflags(ios::fixed) / fixedn = 小数点后保留位数
科学计数格式(scientific)setiosflags(ios::scientific) / scientificn = 小数点后保留位数

核心规则

  • 小数截短显示时,自动执行四舍五入
  • 格式取消: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&的函数,示例:

1
2
3
4
5
// 自定义制表符算子
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)打开成功判断

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

(4)文件关闭

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

1
2
fin.close();
fout.close();

2. 文件读写指针操作

指针类型适用流核心函数功能
读指针ifstream/fstreamtellg()获取读指针当前位置(字节数)
seekg(偏移量, 基准位置)移动读指针到指定位置
写指针ofstream/fstreamtellp()获取写指针当前位置(字节数)
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\n0x0a
Windows\r\n0x0d 0x0a
Mac OS\r0x0d

打开模式差异

系统文本模式(无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*,否则会编译报错