【C++】 Input / Output Streams

在这里插入图片描述

《C++程序设计基础教程》——刘厚泉,李政伟,二零一三年九月版,学习笔记



更多有趣的代码示例,可参考【Programming】


1、流的概念

流就是流动

C++ 中流(Stream)是指信息从外部输入设备(如键盘、磁盘)向计算机内部(即内存)输入和从内存向外部输出设备(如显示器和磁盘)输出的过程,这种输入输出过程被形象地比喻为流

C++ 中的流是一种抽象的数据处理机制,用于统一处理输入(Input)和输出(Output)操作。它通过标准库中的 iostream 库实现,提供类型安全、可扩展的接口,支持从/向多种数据源(如控制台、文件、字符串等)读写数据。

				 +-----------------+
                 |    ios_base     |  // 所有流类的基类,定义基础功能(如错误处理、格式化标志)
                 +-----------------+
                         |
                         | 继承
                 +-----------------+
                 |      ios        |  // 中间层,扩展 ios_base,提供公共接口
                 +-----------------+
                   /           \
                  /             \
                 /               \
                /                 \
+----------------+       +----------------+       +-----------------+
|   istream      |       |   ostream      |       |    iostream     |  // 双向流基类(继承自 istream 和 ostream)
| (输入流基类)    |       | (输出流基类)    |       |                 |
+----------------+       +----------------+       +-----------------+
       |                      |                          |
       | 继承                 | 继承                     | 继承
       |                      |                          |
+----------------+   +----------------+       +-----------------+
|  ifstream      |   |  ofstream      |       |    fstream      |  // 文件双向流
| (文件输入流)    |   | (文件输出流)    |       |                 |
+----------------+   +----------------+       +-----------------+
       |                      |                          |
       | 继承                 | 继承                     | 继承
       |                      |                          |
+----------------+   +----------------+       +-----------------+
| istringstream  |   | ostringstream  |       |  stringstream   |  // 字符串双向流
| (字符串输入流)  |   | (字符串输出流)  |       |                 |
+----------------+   +----------------+       +-----------------+
                                                        |
                                                        | 实例化
                                           +-----------------+
                                           | 标准流对象      |
                                           | (cin, cout, cerr, clog) |
                                           +-----------------+

ios_base 是所有流类的基类,定义底层操作(如错误码、格式化标志)。

ios 作为中间层,扩展 ios_base 并提供公共接口。

istream(输入流)和 ostream(输出流)分别继承自 ios。

iostream 通过多重继承同时继承 istream 和 ostream,实现双向流。

所有流类均为模板类,默认使用 char 类型(如 basic_istream<char>)。

在这里插入图片描述
流的类型

(1)标准流

  • cin:标准输入流(从键盘读取)。
  • cout:标准输出流(输出到屏幕)。
  • cerr:标准错误流(无缓冲,直接输出错误信息)。
  • clog:标准日志流(缓冲输出日志信息)。

默认关联:cin 与键盘关联,cout/cerr/clog 与屏幕关联。

(2)文件流

  • ifstream:从文件读取数据。
  • ofstream:向文件写入数据。
  • fstream:同时读写文件。

(3)字符串流

  • istringstream:从字符串读取数据。
  • ostringstream:向字符串写入数据。
  • stringstream:同时读写字符串。

流的状态

  • good():流状态正常。
  • eof():到达流末尾。
  • fail():操作失败(如类型不匹配)。
  • bad():流损坏(如文件无法打开)。
  • clear():重置流状态。

在这里插入图片描述

2、标准输入输出流

流(Stream)的核心作用

  • 提供统一的接口(如 operator<< 和 operator>>)让用户以类型安全的方式读写数据,隐藏底层设备差异。

流与缓冲区(Buffer)的关联

  • 流本身不直接操作设备,而是通过关联的缓冲区对象(streambuf)完成实际的数据传输。

缓冲区的定义:

  • 缓冲区是内存中的一块临时存储区域,用于暂存待传输的数据。

缓冲区的核心作用:

  • 减少直接 I/O 操作:批量读写数据,提升性能(如写入文件时,先存到缓冲区,再一次性写入磁盘)。
  • 支持格式化操作:如 cout 的 endl 会触发缓冲区刷新,同时插入换行符。
  • 提供中间层:允许流对输入/输出进行预处理(如转换大小写、过滤特定字符)。

流通过缓冲区操作数据:

当用户执行 cout << “Hello”; 时:

  • 数据 “Hello” 先写入 cout 关联的缓冲区(ostream 的 streambuf 对象)。
  • 缓冲区暂存数据,直到满足刷新条件(如缓冲区满、遇到 endl、程序结束等)。
  • 缓冲区将数据同步到目标设备(如控制台)。

缓冲区的类型:

  • 全缓冲(Full Buffering):缓冲区满或显式刷新时写入(如文件流)。
  • 行缓冲(Line Buffering):遇到换行符 \n 时刷新(如 cout 关联的标准输出)。
  • 无缓冲(No Buffering):直接写入设备(如 cerr,用于紧急输出)。

总结

  • 流是数据通道,提供统一的 I/O 接口
  • 缓冲区是流背后的数据暂存区,通过批量操作优化性能
  • 流通过缓冲区实现数据的高效传输,但两者是协作关系而非等同关系。理解这一机制有助于优化 I/O 性能(如手动控制缓冲区刷新)或扩展流的功能(如自定义缓冲区)。

流(stream)是抽象的数据通道,缓冲区(Buffer)是流实现高效数据传输的关键机制,流通过缓冲区管理数据的流动。

2.1、标准输入流

在 C++ 中的 istream 类库为标准输入流

只有在输入完数据再按回车键后,该行数据才被送入键盘缓冲区,形成输入流,提取运算符 “>>” 才能从中提取数据

eg 13-1 通过测试 cin 的真值,判断流对象是否处于正常状态

#include <iostream>
using namespace std;

int main() 
{
    
    
    float grade;
    cout << "enter grade:";
    while(cin>>grade) //从 cin 流读取数据
    {
    
    
        if(grade>=85)
            cout << grade << "GOOD!" << endl;
        if(grade<60)
            cout << grade << "FAIL!" << endl;
        cout << "enter grade:";
    }
    cout << "The End!" << endl;
    return 0;
}

output

enter grade:67
enter grade:89
89GOOD!
enter grade:56
56FAIL!
enter grade:100
100GOOD!
enter grade:^D
The End!

^D是文件结束符

除了可以用 cin 输入标准类型的数据外,还可以用 istream 类流对象的一些成员函数,实现字符的输入


在这里插入图片描述

eg:

std::getline(std::cin, line); // 读取整行

eg:

while (std::cin.get(ch) && ch != '\n') {
    
     // 读取直到换行符
        std::cout << ch; // 逐个输出字符
    }

(1)用 get 函数读入一个字符

cin >> 会跳过空白符(如空格、换行符),直接读取有效数据。

cin.get 会读取所有字符,包括空白符。

cout << 通用输出操作符,可处理多种数据类型(如 int, string 等)。

cout.put专用于输出单个字符,性能略高(但通常可忽略)。

eg 不带参数的 get 函数

#include <iostream>
using namespace std;

int main() 
{
    
    
    int c;
    cout << "enter a sentence:" << endl;
    while ((c = cin.get()) != '\n' && c != EOF)
        cout.put(c);
    return 0;
}

回车键触发 \n结束,或者 ctrl + z + 回车触发 EOF 结束

output

enter a sentence:
I study C++ very hard.
I study C++ very hard.

eg

#include <iostream>
using namespace std;

int main() 
{
    
    
    int c;
    cout << "enter a sentence:" << endl;
    while ((c = cin.get()) != EOF)
        cout.put(c);
    return 0;
}

仅遇到 EOF 退出

enter a sentence:
I study C++ very hard.
I study C++ very hard.
^Z

EOF,end of file,文件结束符
在这里插入图片描述

cout << EOF << endl; 的值为 -1


eg 带一个参数的 get 函数

调用形式为 cin.get(ch),如果读取成功为真,否则为假

#include <iostream>
using namespace std;

int main() 
{
    
    
    char c;
    cout << "enter a sentence:" << endl;
    while (cin.get(c))  // 读取一个字符给字符变量 c, 如果读取成功, cin.get(c) 为真
        cout.put(c) << endl;
    cout << "endl" << endl;
    return 0;
}

output

enter a sentence:
I study C++ very hard.
I

s
t
u
d
y

C
+
+

v
e
r
y

h
a
r
d
.


^Z
endl

eg 有多个参数的 get 函数

调用形式为

cin.get(字符数组, 字符个数n, 终止字符)

cin.get(字符指针, 字符个数n, 终止字符)

其作用是从输入流中读取 n-1 个字符,赋给指定的字符数组(或字符指针指向的数组),如果在读取 n-1 个字符之前遇到指定的终止符,则提前结束读取。读取成功返回真,如果失败返回假。

#include <iostream>
using namespace std;

int main() 
{
    
    
    char ch[20];
    cout << "enter a sentence:" << endl;
    cin.get(ch, 10, '\n');  // 指定换行符为终止字符
    cout << ch << endl;
    return 0;
}

out

enter a sentence:
I study C++ very hard
I study C

get 函数总参数可以省略,此时默认为 \n

也即 cin.get(ch, 10, '\n'); 等价于 cin.get(ch, 10);

终止符也可以用其它字符,如 cin.get(ch, 10, 'h');

#include <iostream>
using namespace std;

int main() 
{
    
    
    char ch[30];
    cout << "enter a sentence:" << endl;
    cin.get(ch, 20, 'a');  // 指定换行符为终止字符
    cout << ch << endl;
    return 0;
}

output

enter a sentence:
I study c++ very hard.
I study c++ very h

(2)用成员函数 getline 函数读入一行字符

getline 函数的作用是从输入流中读取一行字符,其用法与带参数的 get 函数类似

cin.getline(字符数组(或字符指针), 字符个数 n, 终止标志字符)

eg

#include <iostream>
using namespace std;

int main() 
{
    
    
    char ch[20];
    cout << "enter a sentence:" << endl;
    cin >> ch;
    cout << "The string read with cin is:" << ch << endl;

    cin.getline(ch, 20, '/');  // 指定换行符为终止字符, '\n' 默认
    cout << "The second part is:" << ch << endl;

    cin.getline(ch, 20);
    cout << "The third part is:" << ch << endl;
    
    return 0;
}

cin 的局限性,只能读取到第一个空格前的字符串,剩余的内容会存放到缓存区

The second part 遇到终止符 '/' 停止

The third part 输入19 个字符满了

output

enter a sentence:
I like C++./I study C++./I am happy.
The string read with cin is:I
The second part is: like C++.
The third part is:I study C++./I am h

eg

#include <iostream>
using namespace std;

int main() 
{
    
    
    char ch[20];
    cout << "enter a sentence:" << endl;
    cin >> ch;
    cout << "The string read with cin is:" << ch << endl;

    cin.getline(ch, 20, '/');  // 指定换行符为终止字符, '\n' 不是默认
    cout << "The second part is:" << ch << endl;

    cin.getline(ch, 20);
    cout << "The third part is:" << ch << endl;
    
    return 0;
}

output

enter a sentence:
He likes C++./He studys C++./He is happy. 
The string read with cin is:He
The second part is: likes C++.
The third part is:He studys C++./He i

读取字符串推荐做法

cin.getline

#include <iostream>
using namespace std;

int main() 
{
    
    
    char buffer[100];
    cout << "Enter text (max 99 chars): ";
    cin.getline(buffer, 100, '\n');  // 以 '/' 为分隔符
    cout << "You entered: " << buffer << endl;
    return 0;
}

output

Enter text (max 99 chars): I love you.
You entered: I love you.

或者 getline

#include <iostream>
#include <string>
using namespace std;

int main() 
{
    
    
    string input;
    getline(cin, input);  // 安全读取整行
    cout << input << endl;
    return 0;
}

output

I love you.
I love you.

或者 cin.get

#include <iostream>
using namespace std;

int main()
{
    
    
    char buffer[100];
    cout << "Enter text (max 99 chars): ";
    cin.get(buffer, 100);  // 读取整行,包括空格
    cout << "You entered: " << buffer << endl;
    return 0;
}

output

Enter text (max 99 chars): I love you.
You entered: I love you.

(3) eof 函数

eof 是 end of file 的缩写,表示 “文件结束”,如果达到文件末尾为 true,否则为 false

window 中文件结束是 Ctrl+z + Enter

#include <iostream>
using namespace std;
 
int main() 
{
    
    
    char num[20];
    while (!cin.eof()) // 循环直到输入结束(如按 Ctrl+Z + Enter)
    {
    
      
        cin.getline(num, 19, '\n');
        cout << "Read: " << num << endl;
    }
    return 0;
}

output

I love you!
Read: I love you!
I love C++!
Read: I love C++!
hello wordl!
Read: hello wordl!
^Z
Read:

(4)peek 函数

peek 是观察的意思,作用是观测下一个字符,调用形式为

c=cin.peek()

cin.peek()

返回的是指针指向的当前字符,但是只是观测,指针仍停留在当前位置,并不后移。

如果要访问字符是文件结束符,则函数值是 EOF(-1)

核心性质总结

  • 非破坏性读取:peek() 仅查看字符,不移动流指针。
  • 条件检查:常用于判断输入类型(如数字、分隔符)。
  • 预处理输入:可跳过无效字符或提前终止读取。
  • 避免冗余读取:与 cin.get() 或 cin.ignore() 配合使用更高效。

eg,检查下一个字符是否为特定字符

#include <iostream>
using namespace std;

int main() 
{
    
    
    char c;
    cout << "Enter a character: ";
    c = cin.peek();  // 查看下一个字符,不消耗
    cout << "Next character is: " << c << endl;
    cin.get();       // 实际读取字符
    return 0;
}

output

Enter a character: S
Next character is: S

解释:peek() 仅查看字符 ‘A’,未将其从流中移除,后续 cin.get() 仍能读取到它。


eg:跳过所有前导指定字符

#include <iostream>
#include <string>
using namespace std;

int main() 
{
    
    
    string s;
    cout << "Enter text with spaces:";
    while (cin.peek() == ' ')
    {
    
      // 跳过所有前导空格
        cin.ignore();
    }
    getline(cin, s);
    cout << "Result:" << s << endl;
    return 0;
}

output

Enter text with spaces:  I love you!
Result:I love you!

注意是前导,也即输入的第一个非空格才会开始进入 buffer

解释:peek() 检查字符是否为空格,若是则用 ignore() 跳过,直到遇到非空格字符。


eg

#include <iostream>
using namespace std;

int main() {
    
    
    int num = 0;
    cout << "Enter digits (e.g., 123a): ";
    while (isdigit(cin.peek())) // 检查下一个字符是否为数字
    {
    
      
        num = num * 10 + (cin.get() - '0');
    }
    cout << "Parsed number: " << num << endl;
    return 0;
}

output

Enter digits (e.g., 123a): 123x
Parsed number: 123

解释:peek() 确保只读取数字字符,遇到非数字时停止。


(5)putback 函数

调用形式为

cin.putback(ch)

作用是将前面 get 或 getline 函数从输入流中读取的 ch 返回到输入流,插入到当前指针位置,以供后面读取

eg 13-5 peek 函数和 putback 函数的用法

#include <iostream>
using namespace std;

int main() 
{
    
    
    char c[20];
    int ch;
    cout << "please enter a sentence:" << endl;
    cin.getline(c, 15,'/');
    cout << "The first part is:" << c << endl;
    ch = cin.peek();
    cout << "The next character(ASCII) is:" << ch << endl;
    cin.putback(c[0]);
    cin.getline(c, 15,'/');
    cout << "The Second part is:" << c << endl;
    return 0;
}

output

please enter a sentence:
I am a boy./ am a student./
The first part is:I am a boy.
The next character(ASCII) is:32
The Second part is:I am a student

eg:预读并回退字符

#include <iostream>
using namespace std;

int main() 
{
    
    
    char c;
    cout << "Enter a character: ";
    c = cin.get();       // 读取字符
    cin.putback(c);      // 将字符放回
    c = cin.get();
    cout << "Read again: " << c << endl;  // 重新读取
    return 0;
}

output

Enter a character: a
Read again: a

eg:跳过非数字字符

#include <iostream>
using namespace std;

int main() 
{
    
    
    char c;
    cout << "Enter digits (e.g., a123): ";
    c = cin.get();       // 预读第一个字符
    if (!isdigit(c)) {
    
    
        cin.putback(c);  // 非数字则放回
        cout << "Skipping non-digit: " << c << endl;
        cin.ignore();    // 跳过该字符(实际已放回,需处理)
    } 
    else 
    {
    
    
        cin.putback(c);  // 数字则放回供后续读取
    }
    int num;
    cin >> num;
    cout << "Parsed number: " << num << endl;
    return 0;
}

output

Enter digits (e.g., a123): x456
Skipping non-digit: x
Parsed number: 456

核心性质总结

  • 回退单个字符:putback 允许将最近读取的一个字符放回输入流。
  • 预读与条件处理:常用于先检查字符类型(如数字、分隔符),再决定是否回退。
  • 多字符回退限制:需循环调用 putback,且只能放回有限次数(依赖实现)。
  • 错误处理:若尝试放回未读取的字符,可能导致未定义行为。

(6)ignore 函数

调用形式

cin.ignore(n, 终止字符)

函数作用是跳过输入流中 n 个字符,或在遇到指定的终止字符时提前结束(此时跳过包括终止符在内的若干字符)

ignore() // 默认值为 1,终止字符默认为 EOF

相当于

ignore(1, EOF)

eg 13-6 不用 ignore 函数的情况

#include <iostream>
using namespace std;

int main() 
{
    
    
    char ch[20];
    cin.get(ch, 20,'/');
    cout << "The first part is:" << ch << endl;
    cin.get(ch, 20,'/');
    cout << "The Second part is:" << ch << endl;
    return 0;
}

output

I like C++./I study C++./I am happy./
The first part is:I like C++.
The Second part is:

可以看到第二次 get 不到信息了,因为遇到了 '/'

可以 ignore 掉

#include <iostream>
using namespace std;

int main() 
{
    
    
    char ch[20];
    cin.get(ch, 20,'/');
    cout << "The first part is:" << ch << endl;
    cin.ignore();
    cin.get(ch, 20,'/');
    cout << "The Second part is:" << ch << endl;
    return 0;
}

output

I like C++./I study C++./I am happy./
The first part is:I like C++.
The Second part is:I study C++.

2.2、标准输出流

ostrem 类定义了输出流对象

(1)cout 流对象

cout 是 console output 的缩写,控制台(终端显示器)的输出

cout 是 ostream 流类的对象,在 io stream 中定义

(2)cerr 流对象

cerr 是标准错误流

作用是向标准错误设备(standard error device)输出有关出错信息。

用法和 cout 差不多,区别是 cout 流通常是传送到显示器输出,也可以被重定向输出到磁盘文件

cerr 流中的信息只能在显示器上及时输出

eg

#include <iostream>
#include <cmath>
using namespace std;

int main() 
{
    
    
    float a,b,c,disc;
    cout << "please input a,b,c:";
    cin >> a >> b >> c;
    if(a==0)
        cerr << "a is equal to zero, error!" << endl;
    else
    {
    
    
        if((disc=b*b-4*a*c)<0)
            cerr << "disc=b*b-4*a*c<0!" << endl;
        else
        {
    
    
            cout << "x1=" << (-b + sqrt(disc)) / (2*a) << endl;
            cout << "x2=" << (-b - sqrt(disc)) / (2*a) << endl;
        }
    }
    return 0;
}

output

please input a,b,c:0 2 3
a is equal to zero, error!

output

please input a,b,c:5 2 3
disc=b*b-4*a*c<0!

output

please input a,b,c:1 2.5 1.5
x1=-1
x2=-1.5

(3)clog 流对象

clog 也是标准错误流,console log 的缩写

作用与 cerr 相同,都是在终端显示器上显示出错误信息

区别:cerr 是不经过缓冲区,直接向显示器上输出有关信息,而 clog 中的信息存放在缓冲区中,缓冲区满后或遇 endl 时向显示器输出。

在这里插入图片描述
缓冲机制:

  • cout:默认使用行缓冲,遇到换行符(\n)或缓冲区满时自动刷新。
  • cerr:无缓冲(单位缓冲),每个字符直接输出,确保错误信息实时显示。
  • clog:通常使用行缓冲,行为与 cout 类似,但默认关联到日志文件。

总结:

  • cout:高效缓冲,适合常规输出。
  • cerr:无缓冲,确保错误信息实时性。
  • clog:缓冲日志输出,适合记录程序行为。

数据按指定的格式输出,有如下两种方法

(1)使用控制符控制输出格式

iomanip 是 “I/O Manipulators” 的缩写,全称为 Input/Output Manipulators(输入输出操作符库)。

setiosflags

  • set:表示“设置”(Set)。
  • ios:是 “Input/Output Stream”(输入/输出流)的缩写。
  • flags:表示“标志”(Flags)。

setprecision(4) 默认4位有效数字,包含整数部分和小数部分,四舍五入

  • fixed 固定小数点后几位

  • scientific 科学计数法

eg 13-8 用控制符控制输出格式

#include <iostream>
#include <iomanip>
using namespace std;

int main() 
{
    
    
    int a;
    cout << "input a:";
    cin >> a;
    cout << "dec:" << dec << a << endl;  //十进制输出
    cout << "dec:" << setbase(10) << a << endl;
    cout << "hex:" << hex << a << endl;  //十六进制输出
    cout << "hex" << setbase(16) << a << endl;
    cout << "oct:" << setbase(8) << a << endl; // 八进制输出
    cout << "oct:" << oct << a << endl;

    char *pt = "China";
    cout << setw(10) << pt << endl;  // 指定域宽
    cout << setfill('*') << setw(10) << pt << endl; // 指定域宽,空白处填 * 
    cout << setfill('*') << setw(6) << pt << endl; // 指定域宽,空白处填 * 

    double pi=22.0/7.0;  // 计算 pi
    cout << "pi=" << setprecision(4) << pi << endl; // 4 位有效数字
    cout << "pi=" << setiosflags(ios::fixed) << setprecision(4) << pi << endl;
    cout << resetiosflags(ios::fixed); // 注意前面的 ios 设置,不然后面会乱
    cout << "pi=" << setiosflags(ios::scientific) << setprecision(4) << pi << endl; // 科学计数法,4位小数
    return 0;
}

output

input a:34
dec:34
dec:34
hex:22
hex22
oct:42
oct:42
     China
*****China
*China
pi=3.143
pi=3.1429
pi=3.1429e+00

注意显式重置格式标志,resetiosflags,不重置可能导致输出的结果和预期不一样

(2)用流对象成员函数控制输出格式

cout.setf() 是 C++ 中用于控制输出格式的关键函数,属于 <ios> 头文件。它通过修改流(如 cout)的格式标志(Format Flags)来调整输出行为。

在这里插入图片描述
总结

  • cout.setf() 是控制输出格式的核心工具。
  • 通过组合标志和掩码,可以灵活调整输出行为(进制、对齐、浮点数格式等)。
  • 注意标志的粘性和清除操作,避免意外影响后续输出。

eg 13-9 用流控制成员函数输出数据

#include <iostream>
#include <iomanip>
using namespace std;

int main() 
{
    
    
    int a = 21;
    cout.setf(ios::showbase);  // 显示基数符号,十六进制 0x 和 八进制 0
    cout << "dec:" << a << endl; 
    cout.unsetf(ios::dec);  // 终止十进制的格式设置

    cout.setf(ios::hex);  // 设置十六进制的输出状态
    cout <<"hex:" << a << endl;
    cout.unsetf(ios::hex);  // 终止十六进制的格式设置

    cout.setf(ios::oct);  // 设置八进制的输出状态
    cout <<"oct:" << a << endl;
    cout.unsetf(ios::oct);  // 终止八进制的格式设置

    char *pt = "China";
    cout.width(10);  // 指定域宽
    cout << pt << endl;

    cout.width(10);
    cout.fill('*');  // 填充
    cout << pt << endl;

    double pi=22.0/7.0;
    cout.setf(ios::scientific);  // 指定科学计数法
    cout <<"pi=";
    cout.width(14);
    cout << pi << endl;
    cout.unsetf(ios::scientific); // 终止科学计数法状态

    cout.setf(ios::fixed);  // 指定定点形式
    cout.width(12);
    cout.setf(ios::showpos); // 正数输出 +
    cout.setf(ios::internal);  // 数符+出现在左侧
    cout.precision(6);  // 保留小数
    cout << pi << endl;
    return 0;
}

output

dec:21
hex:0x15
oct:025
     China
*****China
pi=**3.142857e+00
+***3.142857

代码解释

注释掉 cout.setf(ios::internal); 的话,输出从 +***3.142857 变成了 ***+3.142857

cout.setf(ios::showbase); 是 C++ 中用于控制输出格式的标志操作,属于 <ios> 头文件。它的作用是强制显示数值的进制前缀(如十六进制 0x、八进制 0),但需配合进制标志(如 ios::hexios::oct)使用。

默认行为:

  • 十进制(ios::dec)默认不显示前缀。
  • 十六进制(ios::hex)默认不显示 0x,启用 showbase 后会显示。
  • 八进制(ios::oct)默认不显示 0,启用 showbase 后会显示。

清除标志

  • 使用 cout.unsetf(ios::showbase); 可关闭前缀显示。

临时生效:

  • showbase 是“粘性”标志,一旦启用,会持续影响后续输出,直到被显式关闭。

3、文件流

文件流分类

  • ifstream:输入文件流(读取文件)。
  • ofstream:输出文件流(写入文件)。
  • fstream:同时支持读写操作。

3.1、文件流的概念

cin 和 cout 也是流对象,由 iostream 事先定义,无须用户自己定义

在用磁盘文件时,由于情况各异,无法事先统一定义,所以文件流对象必须由用户自己定义

eg

#include<fstream.h>
ofstream outfile;

3.2、文件流的操作

文件流操作可以分为以下步骤:

  • 建立文件流对象

  • 打开或建立文件

  • 进行文件读写操作

  • 关闭文件

成员函数 open 的一般形式为(显示调用)

文件流对象.open(文件名, 模式)

eg

ifstream inFile;
inFile.open("a.txt", iso::in);

还可以利用构造函数直接 open

ifstream inFile("a.txt", iso::in);

区别如下:

在这里插入图片描述

模式如下

在这里插入图片描述

如果文件需要用两种或多种方式打开,则用 | 来分隔组合在一起

关闭文件

流对象.close()

流类库中的 IO 操作 <<>>、put、get、getline、read 和 write 都可以用于文件的输入输出

详细介绍下 read 和 write 函数常用格式

文件流对象.read(char *buf, int len);
文件流对象.write(const char *buf, int len);

eg 13-10 写入文本文件的例子

#include <iostream>
#include <fstream>
using namespace std;

int main() 
{
    
    
    ofstream file("file.txt", ios::out | ios::ate); // 打开文件
    if(!file)
    {
    
    
        cout << "不可以打开文件。" << endl;
        exit(1);
    }

    // 写文件
    file << "hello c++!\n";
    char ch;
    while(cin.get(ch))
    {
    
    
        if(ch=='\n')
            break;
        file.put(ch);
    }
    file.close();
    return 0;
}

输入

234234

output

生成 file.txt

在这里插入图片描述


eg 13-11 读文件 file.txt

#include <iostream>
#include <fstream>
using namespace std;

int main() 
{
    
    
    ifstream file("file.txt", ios::in); // 打开文件
    if(!file)
    {
    
    
        cout << "不可以打开文件。" << endl;
        exit(1);
    }

    // 读文件
    char str[100];
    file.getline(str, 100, '\n'); 
    cout << str << endl;

    char rch;
    while(file.get(rch))  // '\n' 终止
    {
    
    
        cout.put(rch);
    }
    cout << endl;
    file.close();
    return 0;
}

output

hello c++!
234234

代码中 ios::inios::out 可省略,因为 ifstream 和 ofstream 中分别默认了相应的模式


eg 13-12,编写一个程序,从一个文本文件 source.txt 中读入若干整数,将升序排序后的结果写入另一个文本文件 target.txt 中

#include <iostream>
#include <fstream>
#include <stdlib.h>
using namespace std;

void sort(int *a, int n)
{
    
    
    for(int i=0; i<n-1; i++)
    {
    
    
        for(int j=i+1; j<n; j++)
        {
    
    
            if(a[i]>a[j])
            {
    
    
                int tmp;
                tmp = a[i];
                a[i] = a[j];
                a[j] = tmp;
            }
        }
    }
}

int main() 
{
    
    

    int a[100], i, n;
    fstream in, out;

    in.open("source.txt", ios::in); // 打开文件
    if(!in)
    {
    
    
        cout << "不可以打开文件 source.txt。" << endl;
        exit(1);
    }
    
    out.open("target.txt", ios::out); // 打开文件
    
    if(!out)
    {
    
    
        cout << "不可以打开文件 target.txt." << endl;
        exit(2);
    }

    i =0;
    while(in>>a[i])
    {
    
    
        i++;
    }
    sort(a, i);

    n = i;

    for(i=0; i<n; i++)
        out << a[i] << endl;

    in.close();
    out.close();

    return 0;
}

准备好 source.txt,内容如下

在这里插入图片描述

output

在这里插入图片描述

注意,这个例子中写用的是 <<


eg 13-13 读入文件 data.txt 中的数据,写入二进制文件 data.bin 中

#include <iostream>
#include <fstream>
using namespace std;


int main() 
{
    
    

    fstream in, out;

    in.open("data.txt", ios::in); // 打开文件

    if(!in)
    {
    
    
        cout << "不可以打开文件 data.txt。" << endl;
        exit(1);
    }

    int a[100], n;

    int i=0;
    
    while(in>>a[i])
    {
    
    
        i++;
    }
    in.close();

    n = i;

    for(i=0; i<n; i++)
        cout << a[i] << endl;

    out.open("data.bin", ios::out | ios::binary); // 打开文件
    
    if(!out)
    {
    
    
        cout << "不可以打开文件 data.dat." << endl;
        exit(2);
    }

    out.write((char *)&n, sizeof(n));
    out.write((char *)a, n*sizeof(int));

    out.close();

    return 0;
}

输入的 data.txt 内容如下

在这里插入图片描述

output

2
3
10
45
33
8
9
20
45
67
888
3
7
2
2
-2
0
-1

生成的二进制文件 data.bin

在这里插入图片描述

该例子中写用的是 文件流对象.write


eg 13-14 编写一个程序对二进制文件进行读写。本程序的功能是,从键盘输入若干学生的信息,写入二进制文件,再从二进制文件中读出学生的信息,输出到屏幕

#include <iostream>
#include <fstream>
#include <string.h>
using namespace std;

struct student  // 学生结构体
{
    
    
    char name[10];  // 姓名
    char id[10];  // 学号
    int score;  // 分数
};

#define LEN sizeof(struct student)  // 结构体内存大小


int main() 
{
    
    
    student st;
    fstream file;

    file.open("stud.dat", ios::out | ios::binary); // 打开文件
    
    if(!file)
    {
    
    
        cout << "不可以打开文件 stud.dat" << endl;
        exit(1);
    }

    cin >> st.name;  // 输入姓名
    while(strcmp(st.name, "#")!=0)  // 输入姓名以 # 结束
    {
    
    
        cin>>st.id>>st.score;  // 输入学号和成绩
        file.write((char *)&st, LEN);  // 写入文件中
        cin>>st.name;
    }
    file.close();
    
    student sts[100];  // 结构体数组,存储多位学生的信息
    
    int i=0, j;
    
    file.open("stud.dat", ios::in | ios::binary); // 打开文件,复用 file 流对象

    if(!file)
    {
    
    
        cout << "不可以打开文件 stu.dat" << endl;
        exit(2);
    }
    
    while(file.read((char *)(sts+i), LEN))  // 一次性读入 LEN 字节的数据,存入内存指定地址
    {
    
    
        i++;
    }
    file.close();

    for(j=0; j<i; j++)  // 循环向屏幕输出学生信息
        cout << sts[j].name << '\t' << sts[j].id << '\t' << sts[j].score << endl;
    return 0;
}

注意,输入 # 表示结束,读和写 open 的时候记得都要用二进制模式 ios::binary

这个例子中写入文件用的是 对象流.write

output

Zhangsan 
001 90
Lisi
002 98
Wangwu
003 78
#
Zhangsan        001     90
Lisi    002     98
Wangwu  003     78

3.3、文件类型

C++ 程序中使用的保存数据的文件按存储格式分为两种类型

  • 字符文件,ASCII 码文件,文本文件(Text File)

  • 字节文件,二进制文件(Binary File)

文本文件特点

  • 以字符序列存储,每个字符对应一个字节(ASCII 或 Unicode)。
  • 可被文本编辑器直接打开和编辑。
  • 换行符在不同系统中可能不同(如 Windows 用 \r\n,Linux 用 \n)。

适用场景

  • 存储字符串、数字等可读数据。
  • 需要与其他文本工具(如 Excel、记事本)交互的场景。

eg

#include <iostream>
#include <fstream>
using namespace std;

int main() {
    
    
    // 写入文本文件
    ofstream outFile("text.txt");
    outFile << "Hello World\n";
    outFile << 42 << "\n";
    outFile.close();

    // 读取文本文件
    ifstream inFile("text.txt");
    string line;
    while (getline(inFile, line)) 
    {
    
    
        cout << line << endl;
    }
    inFile.close();
    return 0;
}

output

Hello World
42

在这里插入图片描述


二进制文件特点

  • 直接存储内存中的二进制数据(字节流)。
  • 不可被文本编辑器直接阅读,但读写效率更高。
  • 换行符和特殊字符(如 \0)按原样存储。

适用场景

  • 存储非文本数据(如图片、音频、视频)。
  • 高效读写结构体、数组等复杂数据类型。

eg

#include <iostream>
#include <fstream>
using namespace std;

struct Data {
    
    
    int id;
    double value;
};

int main() {
    
    
    // 写入二进制文件
    ofstream outFile("data.bin", ios::binary);
    Data d = {
    
    1, 3.14};
    outFile.write(reinterpret_cast<char*>(&d), sizeof(Data));
    outFile.close();

    // 读取二进制文件
    ifstream inFile("data.bin", ios::binary);
    Data readData;
    inFile.read(reinterpret_cast<char*>(&readData), sizeof(Data));
    cout << "ID: " << readData.id 
         << ", Value: " << readData.value << endl;
    inFile.close();
    return 0;
}

output

ID: 1, Value: 3.14

在这里插入图片描述


对比

在这里插入图片描述

注意,当向字符文件输出一个换行符 ‘\n’ 时,则被看作为输出了回车 ‘\r’ 和换行 ‘\n’ 两个字符

相反,当从字符文件中读取回车和换行两个连续字符时,也被看作为一个换行符读取。


选择建议

  • 文本文件:需要人类可读性或与其他工具交互时。
  • 二进制文件:追求性能或处理非文本数据时(如游戏存档、图像)。

eg 13-15 txt 文件读取和写入

#include <iostream>
#include <fstream>
#include <iomanip>
using namespace std;

int main() 
{
    
    
    char buffer[256];

    ifstream inFile;
    inFile.open("a.txt");

    ofstream outFile;
    outFile.open("b.txt");

    if(!inFile.is_open())
    {
    
    
        cout << "不可以打开文件 a.txt" << endl;
        exit(1);
    }

    if(!outFile.is_open())
    {
    
    
        cout << "不可以打开文件 b.txt" << endl;
        exit(2);
    }

    int a,b;
    int i=0;
    int data[6][2];
    
    while(!inFile.eof())
    {
    
    
        inFile.getline(buffer, sizeof(buffer), '\n');
        sscanf(buffer, "%d %d", &a, &b);
        cout << "read:" << a << " " << b << endl;
        data[i][0] = a;
        data[i][1] = b;
        i ++;
    }
    inFile.close();


    for(int k=0; k<i; k++)
    {
    
    
        outFile << data[k][0] << " " << data[k][1] << endl;
        cout << "write:" << data[k][0] << " " << data[k][1] << endl;
    }
    outFile.close();
    return 0;
}

输入文件

在这里插入图片描述

程序打印

read:2 3
read:33 8
read:45 67
read:7 2
read:0 -1
write:2 3
write:33 8
write:45 67
write:7 2
write:0 -1

输出结果

在这里插入图片描述

代码解析:

注意 scanfsscanf 的区别

scanf:

  • 从标准输入(通常是键盘)读取格式化的数据。
  • 适用于从用户输入或重定向的输入流中获取数据。

sscanf:

  • 从字符串中读取格式化的数据。
  • 适用于从内存中的字符串解析数据。

在这里插入图片描述

改的更规范一些的话如下

#include <iostream>
#include <fstream>
#include <iomanip>
using namespace std;

int main() 
{
    
    
    char buffer[256];

    ifstream inFile;
    inFile.open("a.txt");

    ofstream outFile;
    outFile.open("b.txt");

    if(!inFile.is_open())
    {
    
    
        cout << "不可以打开文件 a.txt" << endl;
        exit(1);
    }

    if(!outFile.is_open())
    {
    
    
        cout << "不可以打开文件 b.txt" << endl;
        exit(2);
    }

    int a,b;
    int i=0;
    int data[6][2];

    while(!inFile.eof())
    {
    
    
        inFile.getline(buffer, sizeof(buffer), '\n');

        if (sscanf(buffer, "%d %d", &a, &b) == 2) // Parse integers from the buffer
        {
    
    
            if (i >= 6) // Prevent array overflow
            {
    
    
                cout << "数据超出存储限制,忽略后续行" << endl;
                break;
            }
            cout << "read:" << a << " " << b << endl;
            data[i][0] = a;
            data[i][1] = b;
            i++;
        }
        else
        {
    
    
            cout << "无法解析行: " << buffer << endl; // Handle invalid lines
        }
    }
    inFile.close();


    for(int k=0; k<i; k++)
    {
    
    
        outFile << data[k][0] << " " << data[k][1] << endl;
        cout << "write:" << data[k][0] << " " << data[k][1] << endl;
    }
    outFile.close();
    return 0;
}

修改点

  • 增加了对数组越界的检查,防止 i 超过 data 的大小。
  • 增加了对无法解析的行的处理,避免程序崩溃。
  • 增加了对空行或格式错误行的提示。

eg 13-16 从键盘读入一行字符,把其中的字母依次放在磁盘文件 fat1.dat 中,再把它从磁盘文件中读入程序,其中的小写字母改成大写字母,再存入磁盘文件 fat2.dat

#include<fstream>
#include<iostream>
#include<cmath>
using namespace std;

void read_save()
{
    
    
    char c[80];
    ofstream outfile("fat1.dat", ios::out);
    if(!outfile)
    {
    
    
        cerr << "open error!" << endl;
        exit(3);
    }
    cout << "please enter a string:";
    cin.getline(c, 80);

    cout << "write to fat1.dat:";
    for(int i=0; c[i]!=0; i++)  // 遇到 /0 为止
    {
    
    
        if((c[i]>=65 && c[i]<=90) || (c[i]>=97)&&c[i]<=122)
        {
    
    
            outfile.put(c[i]);

            cout << c[i];
        }
    }
    cout << endl;
    outfile.close();
}

void create_data()
{
    
    
    char ch;
    ifstream infile("fat1.dat", ios::in);
    if(!infile)
    {
    
    
        cerr << "open error!" << endl;
        exit(1);
    }

    ofstream outfile("fat2.dat", ios::out);
    if(!outfile)
    {
    
    
        cerr << "open error!" << endl;
        exit(2);
    }

    cout<<"write to fat2.dat:";
    
    while(infile.get(ch))
    {
    
    
        if(ch<=122&&ch>=97)
            ch-=32;
        outfile.put(ch);
        cout << ch;
    }
    cout << endl;
    infile.close();
    outfile.close();
}

int main()
{
    
    
    read_save();
    create_data();
    return 0;
}

output

please enter a string:SaedaeiuASDF
write to fat1.dat:SaedaeiuASDF
write to fat2.dat:SAEDAEIUASDF

在这里插入图片描述

又比如

please enter a string:S1p2Q3d4aa5
write to fat1.dat:SpQdaa
write to fat2.dat:SPQDAA

代码解析

ASCII 65 到 90 是大写 A 到 Z,97 到 122 是小写字母 a 到 z

【附录】

获取当前工作路径 windows

#include <iostream>
#include <unistd.h> // 包含 getcwd
 
int main() {
    
    
    char buffer[PATH_MAX];
    if (getcwd(buffer, sizeof(buffer)) != nullptr) {
    
    
        std::cout << "Current working directory: " << buffer << std::endl;
    } else {
    
    
        std::cerr << "Error getting current directory." << std::endl;
    }
    return 0;
}

output

Current working directory: C:\window64\bin

更多有趣的代码示例,可参考【Programming】