C++学习笔记之输入输出流

目录

[5.1 C++的输入输出](#5.1 C++的输入输出)

[5.1.1 输入输出的含义](#5.1.1 输入输出的含义)

[5.1.2 类型安全和可扩展性](#5.1.2 类型安全和可扩展性)

[5.1.3 C++的输入输出流](#5.1.3 C++的输入输出流)

C++的流库

与流类库有关的头文件

在iostream头文件中定义的流对象

在iostream头文件中重载运算符

[5.2 标准输出流](#5.2 标准输出流)

[5.2.1 cout、cerr和clog流](#5.2.1 cout、cerr和clog流)

cout流对象

cerr流对象

clog流对象

[5.2.2 标准类型数据的格式输出](#5.2.2 标准类型数据的格式输出)

[5.2.3 用流成员函数put输出字符](#5.2.3 用流成员函数put输出字符)

[5.3 标准输入流](#5.3 标准输入流)

[5.3.1 cin流](#5.3.1 cin流)

[5.3.2 用于字符输入的流成员函数](#5.3.2 用于字符输入的流成员函数)

用get函数读入一个字符

[5.3.3 istream类的其他成员函数](#5.3.3 istream类的其他成员函数)

eof函数

peek函数

putback函数

ignore函数

[5.4 对数据文件的操作与文件流](#5.4 对数据文件的操作与文件流)

[5.4.1 文件的概念](#5.4.1 文件的概念)

[5.4.2 文件流泪与文件流对象](#5.4.2 文件流泪与文件流对象)

[5.4.3 文件的打开与关闭](#5.4.3 文件的打开与关闭)

打开磁盘文件

调用文件流的成员函数open

在定义文件流对象时指定参数

关闭磁盘文件

[5.4.4 对ASCII文件的操作](#5.4.4 对ASCII文件的操作)

[5.4.5 对二进制文件的操作](#5.4.5 对二进制文件的操作)

用成员函数read和write读写二进制文件

与文件指针有关的流成员函数

随机访问二进制数据文件

[5.5 字符串流](#5.5 字符串流)

[5.5.1 字符串流的本质](#5.5.1 字符串流的本质)

[5.5.2 工作流程与数据转换](#5.5.2 工作流程与数据转换)

[5.5.3 类体系与继承关系](#5.5.3 类体系与继承关系)

[5.5.4 与文件流的核心差异](#5.5.4 与文件流的核心差异)

数据流向

无需文件操作

结束标志

[5.5.5 应用场景](#5.5.5 应用场景)

[5.5.6 代码示例:传统与现代用法](#5.5.6 代码示例:传统与现代用法)

[5.6 整理与总结](#5.6 整理与总结)


5.1 C++的输入输出

5.1.1 输入输出的含义

  • 程序的输入:从输入文件将数据传送到内春单元

  • 程序的输出:从程序把内存单元中的数据传送给输出文件

C++的输入输出包含以下三方面内容:

  1. 标准的输入输出(标准I/O):对系统指定的标准设备的输入和输出。即从键盘输入数据,输出到显示器屏幕。

  2. 文件的输入输出(文件I/O):以外存(磁盘、光盘)为对象进行输入和输出,例如从磁盘文件输入数据,数据输出到磁盘文件。

  3. 字符串输入输出(串I/O)对内存中指定的空间进行输入和输出。通常指定一个字符数组作为存储空间(实际上可以利用该控件存储任何类型的信息)。

5.1.2 类型安全和可扩展性

在C++的输入输出中,编译系统对数据类型进行严格检查,凡是类型不正确的数据是不可能通过编译的。因此C++的输入输出操作是类型安全的。

C++的类机制使得它能建立一套可扩展的I/O系统,可以通过修改和扩充,能用于用户自己声明的类型的对象的输入输出。可扩展性是C++输入输出的重要特点之一,他能提高软件的重用性,加快软件的开发过程。

5.1.3 C++的输入输出流

C++的输入输出流是指由若干字节组成的字节序列,这些字节中的数据按顺序从一个对象传送到另一个对象。

缓冲区中的数据就是流

在C++中,输入输出流被定义为类。C++的I/O库中的类称为流类。用流类定义的对象称为 流对象

C++的流库

C++提供了一些类,专门用于输入输出。这些类组成一个流类库。这个流类库是用继承方法建立起来的用于输入输出的类库。 这些类由两个基类:ios类streambuf类,所有其他类都是从它们直接或间接派生出来的。

  • ios类是输入输出操作在用户端的接口,为用户的输入输出提供服务。

  • streambuf类是处理"缓冲流区"的类,包括缓冲区起始地址,读写指针和对缓冲区的读写操作,是数据在缓冲区中的管理和数据输入输出缓冲区的实现。streambuf是输入输出操作在物理设备一方的接口。

    可以说:ios负责高层操作,streambuf负责低层操作,为ios提供低级(物理级)的支持。

I/O类库中的常用流类

类别 类名 说明 头文件
标准输入流 istream 基本输入流类,提供输入操作的基础接口 <iostream>
cin istream的对象,用于标准输入(通常是键盘) <iostream>
标准输出流 ostream 基本输出流类,提供输出操作的基础接口 <iostream>
cout ostream的对象,用于标准输出(通常是显示器) <iostream>
cerr ostream的对象,用于标准错误输出(无缓冲,即时显示错误信息) <iostream>
clog ostream的对象,用于标准日志输出(有缓冲,类似cerr但可缓冲输出) <iostream>
文件流 ifstream 输入文件流类,用于从文件读取数据(继承自istream <fstream>
ofstream 输出文件流类,用于向文件写入数据(继承自ostream <fstream>
fstream 文件流类,既可读又可写(继承自iostream <fstream>
字符串流 istringstream 输入字符串流类,用于从字符串读取数据(继承自istream <sstream>
ostringstream 输出字符串流类,用于向字符串写入数据(继承自ostream <sstream>
stringstream 字符串流类,既可从字符串读取也可向字符串写入(继承自iostream <sstream>
基类 ios 所有 I/O 流类的基类,提供流的状态标志和格式化控制 <ios>
iostream 输入输出流基类,由istreamostream多重继承而来 <iostream>
  • 由抽象类ios之直接派生出四个派生类,即istream(输入流类)ostream(输出流类)fstreambase(文件流基类)strstreambase(串流基类)

    • fstreambase(文件流类基类)再派生出ifstream(输入文件流类)ofstream(输出文件流类)fstream(输入输出文件流出串流类)strstream(输入输出串流类)

      • strstreambase(字符串流类基类)再派生出istream(输出串流类)、``
与流类库有关的头文件

常用的有:

类名 说明
iostream 包含了对输入输出流进行操作所需的基本信息。
fstream 用于用户管理的文件的I/O操作。
strstream 用于字符串流I/O。
stdiostream 用于混合使用C和C++的I/O机制是,例如想将C程序转变为C++程序。
iomanip 在使用格式化I/O时应包含此头文件。
在iostream头文件中定义的流对象

在iostream头文件中声明的类有iosistreamostreamiostreamistream_withassignostream_withassigniostream_withassign等。

iostream头文件中定义的4种流对象

对象 含义 对应设备 对应的类 C语言中相应的标准文件
cin 标准输入流 键盘 istream_withassign stdin
cout 标准输出流 屏幕 ostream_withassign stdout
cerr 标准错误流 屏幕 ostream_withassign stderr
clog 标准错误流 屏幕 ostream_withassign stderr
在iostream头文件中重载运算符

"<<"和">>"本来是C++定义为左移运算符和右移运算符的,由于在iostream头文件中对它们进行重载,使它们能用作标准类型数据的输入和输出运算符。所以,在用它们的程序中必须用#include指令把iostream包含到程序中。即#include<iostream>

5.2 标准输出流

标准输出流是流向标准输出设备(显示器)的数据。

5.2.1 cout、cerr和clog流

ostream定义了3个输出流对象,即coutcerrclog

cout流对象

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

  1. cout不是C++预定义的关键字,他是ostream流派生类的对象,在iostream头文件中定义。

  2. 用"cout"和"<<"输出标准类型的数据时,由于系统已经进行了定义,可以不必考虑数据是什么类型,系统会判断数据的类型并根据其类型选择调用与之匹配的运算符重载函数。

  3. cout流在内存中对应开辟了一个缓冲区,用来存放流中的数据,当向cout流插入一个endl时,不论缓冲区是否已满,都立即输出流中所有的数据,然后处插入一个换行符,并刷新cout流(清空缓存区)。 如果插入一个换行符"\n",则只输出数据并换行,不刷cout流。

  4. iostream中只对"<<"和">>"运算符用于标准类型数据的输入输出进行了重载,但未对用户声明的类型数据的输入输出进行重载。

cerr流对象

cerr是console error的缩写,意为"在控制台(显示器)显示出错信息"。

cerr流已被指定为与显示器关联。

cerr的作用是向标准出错设备输出有关出错信息。

cerr与cout有一点不同:

cout流通常是传送到显示器输出,但也可以被重定向输出到磁盘文件,而cerr流中的信息只能在显示器输出。 当调试程序时,往往不希望程序运行时的出错信息被送到其他文件,而要求在显示器上及时输出,这是应该用cerr。

cerr流中的信息是用户根据需要指定的。

示例:

复制代码
#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;
}

运行结果:

clog流对象

clog流对象也是标准出错流,他是console log的缩写。

它的作用和cerr相同,都在终端显示器上显示出错信息

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

5.2.2 标准类型数据的格式输出

C++提供预定义类型的输入输出系统,用来处理标准类型数据的输入输出。

有两种输入输出方式:

  • **无格式输入输出:**对于简单的程序和数据,为简便起见,往往不指定输出的格式,由系统根据数据类型采取默认的格式。

  • **有格式输入输出:**有时希望数据按用户指定的格式输出,如要求以十六进制或八进制形式输出一个整数。 有两种方法可以达到此目的:

    1. 使用控制符控制输出格式
    控制符 作用
    dec 设置整数的基数为 10
    hex 设置整数的基数为 16
    oct 设置整数的基数为 8
    setbase(n) 设置整数的基数为 n(n 只能是 8,10,16 三者之一)
    setfill(c) 设置填充字符 c,c 可以是字符常量或字符变量
    setprecision(n) 设置实数的精度为 n 位。在以一般十进制小数形式输出时 n 代表有效数字。在以 fixed(固定小数位数)形式和 scientific(指数)形式输出时 n 为小数位数
    setw(n) 设置字段宽度为 n 位
    setiosflags(ios::fixed) 设置浮点数以固定的小数位数显示
    setiosflags(ios::scientific) 设置浮点数以科学记数法(即指数形式)显示
    setiosflags(ios::left) 输出数据左对齐
    setiosflags(ios::right) 输出数据右对齐
    setiosflags(ios::skipws) 忽略前导的空格
    setiosflags(ios::uppercase) 在以科学记数法输出 E 和以十六进制输出字母 X 时以大写表示
    setiosflags(ios::showpos) 输出正数时给出 "+" 号
    resetioflags() 终止已设置的输出格式状态,在括号中应指定内容

注意:这些控制符是在头文件iomanip中定义的,因而程序中应当包含头文件

复制代码
#include<iostream>   // 包含输入输出流库
#include<iomanip>    // 包含格式化输出库(提供setw、setprecision等操纵符)
using namespace std;
​
int main()
{
    int a;  // 声明整数变量a,用于存储用户输入的值
    
    // 提示用户输入一个整数,并读取输入值到变量a中
    cout << "input a:";
    cin >> a;
    
    // 演示整数的不同进制输出
    // dec:设置整数以十进制形式输出(默认也是十进制)
    cout << "dec:" << dec << a << endl;
    
    // hex:设置整数以十六进制形式输出(默认小写字母)
    cout << "hex:" << hex << a << endl;
    
    // setbase(8):设置整数以八进制形式输出,等价于使用oct操纵符
    // 注意需要输出变量a才能显示结果
    cout << "oct:" << setbase(8) << a << endl;
    
    // 声明字符指针并指向字符串"China"
    char* pt = "China";
    
    // setw(10):设置输出字段宽度为10个字符
    // 当输出内容长度小于10时,默认在左侧补空格(右对齐)
    cout << setw(10) << pt << endl;
    
    // setfill('*'):设置填充字符为'*',配合setw使用
    // 此处输出"China"(5个字符),会在左侧补5个'*'凑满10个字符宽度
    cout << setfill('*') << setw(10) << pt << endl;
    
    // 计算圆周率近似值(22/7是一个简单近似)
    double pi = 22.0 / 7.0;
    
    // setiosflags(ios::scientific):设置浮点数以科学计数法输出
    // setprecision(8):设置有效数字为8位(科学计数法中包括整数部分和小数部分)
    cout << setiosflags(ios::scientific) << setprecision(8);
    cout << "pi=" << pi << endl;  // 输出:pi=3.1428571e+00
    
    // 修改精度为4位有效数字(科学计数法下生效)
    cout << "pi=" << setprecision(4) << pi << endl;  // 输出:pi=3.143e+00
    
    // resetiosflags(ios::scientific):取消科学计数法设置
    // setiosflags(ios::fixed):设置浮点数以固定小数位数形式输出
    // setprecision(6):在fixed模式下表示保留6位小数
    cout << resetiosflags(ios::scientific) 
         << setiosflags(ios::fixed) << setprecision(6);
    cout << "pi=" << pi << endl;  // 输出:pi=3.142857
    
    return 0;
}
  1. 用流对象的数据成员函数控制输出格式

除了可以用控制符来输出格式外,还可以通过调用流对象cout中用于控制输出格式的成员函数来控制输出格式。

流成员函数 与之作用相同的控制符 作用
precision(n) setprecision(n) 设置实数的精度为n位
width(n) setw(n) 设置字段宽度位为n位
fill(c) setfill(c) 设置填充字符c
setf() setiosflags() 设置输出格式状态,括号中应给出格式状态,内容与控制符setiosflags括号中的内容相同
unsetf() resetioflags() 终止已设置的输出格式状态,在括号中应指定内容

流成员函数setf和控制符setiosflags括号中的参数表示格式状态,它是通过格式标志来指定的。格式标志在类ios中被定义位枚举值。因此在引用这些格式标志时要在前面加上类名ios和域运算符"::"。

格式标志 作 用
iso::left 输出数据在本域宽范围内向左对齐
iso::right 输出数据在本域宽范围内向右对齐
iso::internal 数值的符号位在域宽内左对齐,数值右对齐,中间由填充字符填充
iso::dec 设置整数的基数为 10
iso::oct 设置整数的基数为 8
iso::hex 设置整数的基数为 16
iso::showbase 强制输出整数的基数(八进制数以 0 打头,十六进制数以 0x 打头)
iso::showpoint 强制输出浮点数的小点和尾数 0
iso::uppercase 在以科学记数法格式 E 和以十六进制输出字母时以大写表示
iso::showpos 对正数显示 '+' 号
iso::scientific 浮点数以科学记数法格式输出
iso::fixed 浮点数以定点格式(小数形式)输出
ios::unitbuf 每次输出之后刷新所有的流
ios::stdio 每次输出之后清除 stdout, stderr

示例:用流对象的成员函数控制输出数据格式

复制代码
#include<iostream>
using namespace std;
​
int main() {
    int a = 21;  // 定义整数变量a并赋值21
    
    // 设置显示基数符号(八进制以0开头,十六进制以0x开头)
    cout.setf(ios::showbase); 
    // 默认以十进制输出a,由于设置了showbase,十进制不会显示前缀
    cout << "dec:" << a << endl;  
    
    // 终止十进制格式(演示unsetf用法,实际默认就是十进制)
    cout.unsetf(ios::dec); 
​
    // 设置以十六进制输出
    cout.setf(ios::hex); 
    // 输出十六进制数,会显示0x前缀(因showbase已设置)
    cout << "hex:" << a << endl;  
    // 终止十六进制格式
    cout.unsetf(ios::hex); 
​
    // 设置以八进制输出
    cout.setf(ios::oct); 
    // 输出八进制数,会显示0前缀(因showbase已设置)
    cout << "oct:" << a << endl;  
    // 终止八进制格式(好习惯,避免影响后续输出)
    cout.unsetf(ios::oct); 
​
    // 定义字符指针指向字符串"China"
    char *pt = "China"; 
    // 指定输出域宽为10,不足部分默认用空格填充(右对齐)
    cout.width(10); 
    cout << pt << endl;  // 输出结果:     China(5个空格+5个字符)
​
    // 再次指定域宽为10
    cout.width(10); 
    // 设置填充字符为'*'(此设置会持续生效,直到被更改)
    cout.fill('*'); 
    cout << pt << endl;  // 输出结果:*****China(5个*+5个字符)
​
    // 计算pi的近似值(22/7是简单近似)
    double pi = 22.0 / 7.0; 
    // 设置浮点数以科学记数法输出
    cout.setf(ios::scientific); 
    cout << "pi="; 
    // 指定输出域宽为14(包括所有字符,如符号、数字、e、指数等)
    cout.width(14); 
    cout << pi << endl;  // 按科学记数法和指定宽度输出pi
    
    // 终止科学记数法输出状态
    cout.unsetf(ios::scientific); 
​
    // 设置浮点数以定点形式(固定小数位数)输出
    cout.setf(ios::fixed); 
    // 设置域宽为12
    cout.width(12);
    // 设置显示正数的'+'号
    cout.setf(ios::showpos);
    // 设置内部对齐:符号左对齐,数值右对齐,中间用填充字符填充
    cout.setf(ios::internal);
    // 设置精度为6位(在fixed模式下表示6位小数)
    cout.precision(6);
    
    cout << pi << endl;
    
    return 0;
}

5.2.3 用流成员函数put输出字符

成员函数put专用于输出单个字符、

示例:有一个字符串"BASIC",要哦i去将它们按照相反的顺序输出。

复制代码
#include<iostream>
using namespace std;
​
int main()
{
    const char* p = "BASIC";  // 修正:使用const char*指向字符串常量
    
    // 循环从i=4递减到i=0,逆序输出字符串
    for(int i = 4; i >= 0; i--)
        cout.put(*(p + i));  // 依次访问并输出每个字符:C、I、S、A、B
    
    cout.put('\n');  // 输出换行符
    
    return 0;
}

5.3 标准输入流

标准输入流时从标准输入设备(键盘)流向计算机内存的数据。

5.3.1 cin流

cinistream类的派生类的对象,它从标准输入设备(键盘)获取数据,程序中的变量通过流提取符">>"从流中提取数据。流提取符">>"从流中提取数据遇到输入流中的空格、tab键、换行符等空白字符时,会作为一个数据的结束。

注意:只有在键盘输入完数据并按回车键后,该行数据才会被送入键盘缓冲区,形成输入流,提取运算符">>"才能从中提取数据。需要注意保证从流中读取数据能正常进行。

cin 的 "真值" 是对输入流状态的简化判断:输入成功则为真,失败(含类型不匹配、文件尾等)则为假。这一特性是 C++ 中处理流式输入的核心机制,确保程序能安全处理无效输入场景。

示例:先后向变量grade输入若干考试成绩,并对成绩在85分以上者输出信息"GOOD!",小于60分者输出"FALL"。要求在输入时通过测试cin的真值,判断流对象是否处于正常状态。

复制代码
#include<iostream>
using namespace std;
​
int main()
{
    float grade;
    cout << "enter grade: ";  
    
    // 循环读取成绩,直到输入非数字时退出循环
    while(cin >> grade)
    {
        if(grade >= 85)
            // 85分及以上输出"GOOD!"
            cout << grade << " GOOD!" << endl;
        else if(grade < 60)  
            // 60分以下输出"FALL!"
            cout << grade << " FALL!" << endl;
        else
            // 60-84分之间输出"OK!"
            cout << grade << " OK!" << endl;  
    }
    
    cout << "The End" << endl;
    return 0;
}

5.3.2 用于字符输入的流成员函数

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

get函数读入一个字符

流成员函数get有3种形式:无参的、有一个参数的、有三个参数的。

  1. 不带参数的get函数

    调用形式:cin.get()

    用来从指定的输入流中提取一个字符(包括空白字符),函数的返回值就是读入的字符。

    若遇到输入流中的文件结束,则函数返回值EOF(EOF时在iostream头文件中定义的符号常量,代表-1)。

    示例:用get函数读入字符

    复制代码
    #include<iostream>
    using namespace std;
    int main()
    {
        int c;
        cout<<"enter a sentence:"<<endl;
        while((c=cin.get())!=EOF)
            cout.put(c);
        return 0;
    }
  2. 有一个参数的get函数

    调用形式:cin.get(ch)

    其作用是从输入流中读取一个字符,赋给字符变量ch。如果读取成功则函数返回非0值,如失败(遇文件结束符)则函数返回0值

  3. 有三个参数的get函数

    调用形式:cin.get(字符数组,字符个数n,终止字符)

    或**cin。get(字符指针,字符个数n,终止字符)**

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

    示例:

    复制代码
    #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;
    }
  4. 用成员函数getline函数读取一行字符

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

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

示例:

复制代码
#include<iostream>
using namespace std;
​
int main()
{
    char ch[20];  // 定义字符数组,用于存储输入的字符串
    
    cout << "enter a sentence:" << endl;
    
    // 1. 使用cin >> 读取字符串
    // 注意:cin >> 遇到空格、制表符或换行符会停止读取,且不会吸收分隔符
    cin >> ch;
    cout << "The string read with cin is:" << ch << endl;
    
    // 关键修正:清除cin >> 留下的换行符(或其他分隔符)
    // 否则后续的getline会直接读取到空字符
    cin.ignore();  // 忽略输入缓冲区中残留的一个字符(通常是换行符)
    
    // 2. 使用带终止符的getline读取
    // 语法:cin.getline(数组名, 最大长度, 终止符)
    // 读取到'/'或达到19个字符(留一个位置给'\0')时停止,会吸收终止符
    cin.getline(ch, 20, '/');
    cout << "The second part is:" << ch << endl;
    
    // 3. 使用默认的getline读取(终止符默认为换行符)
    // 读取到换行符或达到19个字符时停止,会吸收终止符
    cin.getline(ch, 20);
    cout << "The third part is:" << ch << endl;
    
    return 0;
}

5.3.3 istream类的其他成员函数

eof函数

eof是end of file的缩写,表示"文件结束"。

从输入流读取数据时,如果到达文件末尾(遇到文件结束符),eof函数值为真,否则为假

示例:逐个读入一行字符,将其中的非空格字符输出

复制代码
#include<iostream>
using namespace std; 
int main()
{
    char c;
    // 循环读取直到文件结束(Ctrl+Z或Ctrl+D)
    while(!cin.eof())
    {
        // 先读取字符再判断,避免最后一次无效读取
        c = cin.get();
        // 仅输出非空格字符
        if(c != ' ')
            cout.put(c);
    }
    return 0;
}
peek函数

peek函数的作用时观测下一个字符。

其调用形式:c=cin.peek();

cin.peek函数的返回值是指针指向当前字符,但它只是观测,指针仍然停留在当前位置,并不后移。如果要观测的字符是文件结束符,则函数值是EOF

putback函数

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

示例:peek函数和putback函数的用法

复制代码
#include<iostream>
using namespace std;
​
// 修正:main函数拼写错误(mian → main)
int main()
{
    char c[20];  // 字符数组,用于存储读取的字符串
    int ch;      // 用于存储字符的ASCII码
    
    cout << "please enter a sentence:" << endl;
    
    // 1. 读取字符串,直到遇到'/'或读取14个字符(留一个位置给'\0')
    cin.getline(c, 15, '/');
    cout << "The first part is:" << c << endl;
    
    // 2. 查看输入流中的下一个字符(不提取,仅返回ASCII码)
    ch = cin.peek();
    cout << "The next character(ASCII code) is:" << ch << endl;
    
    // 3. 将字符数组c的第11个元素(索引10)放回输入流
    // 注意:需确保c[10]已被初始化(即输入的第一个部分长度≥11),否则可能访问无效内存
    // 这里增加判断,避免访问越界
    if (cin.gcount() > 10) {  // gcount()返回上次getline读取的字符数(不含终止符)
        cin.putback(c[10]);
    } else {
        cout << "Warning: Not enough characters to put back c[10]" << endl;
    }
    
    // 4. 再次读取字符串,直到遇到'/'或读取14个字符
    cin.getline(c, 15, '/');
    cout << "The second part is:" << c << endl;
    
    return 0;
}
​
ignore函数

其调用形式:cin.ignore(n,终止字符)

函数的作用是跳过输入流中n个字符,或在遇到指定的终止字符是提前结束(此时跳过包括终止字符在内的若干字符)。如 cin.gnore(5,'A')。也可以不带参数或只带一个参数。ignore()相当于ignore(1,EOF)

cin.ignore() 函数用于清除输入缓冲区中的指定字符(或一定数量的字符),常用于处理输入残留问题(如 cin >> 后残留的换行符)。以下是其使用示例:

复制代码
#include<iostream>
#include<limits>  // 用于numeric_limits
using namespace std;
​
int main()
{
    // 示例1:忽略单个字符(默认忽略换行符)
    int num;
    char str[20];
    
    cout << "示例1:输入一个整数: ";
    cin >> num;  // 输入整数后按回车,缓冲区残留换行符'\n'
    
    // 忽略缓冲区中的1个字符(清除残留的'\n')
    cin.ignore();
    
    cout << "输入一个字符串: ";
    cin.getline(str, 20);  // 若不ignore,会直接读取到'\n'导致空输入
    cout << "你输入的是: " << str << endl << endl;
​
​
    // 示例2:忽略指定数量的字符
    cout << "示例2:输入一段文字: ";
    cin.ignore(5);  // 忽略前5个字符
    cin.getline(str, 20);
    cout << "忽略前5个字符后读取到: " << str << endl << endl;
​
​
    // 示例3:忽略到指定终止符为止的字符
    cout << "示例3:输入带'#'的文字: ";
    cin.ignore(numeric_limits<streamsize>::max(), '#');  // 忽略到'#'为止的所有字符
    cin.getline(str, 20);  // 读取'#'之后的内容
    cout << "#之后的内容: " << str << endl;
​
​
    return 0;
}

5.4 对数据文件的操作与文件流

5.4.1 文件的概念

在计算机科学和日常使用的语境中,文件(File) 是存储在计算机存储介质(如硬盘、内存、U 盘等)上的、具有指定名称和格式的数据集合。它是计算机系统中管理和组织数据的基本单位,无论是文本、图片、程序还是视频,最终都以文件的形式存在并被操作。

一、文件的核心特征

  1. 命名标识 每个文件都有唯一的名称(文件名),用于在存储介质中定位和区分。文件名通常由 "主文件名 + 扩展名" 组成(如 document.txtimage.jpg),扩展名用于标识文件的格式或类型,帮助系统和软件识别如何处理文件。

  2. 存储结构 文件的数据以二进制或文本形式存储在存储介质中,系统通过文件系统(如 NTFS、FAT32、EXT4 等)管理文件的物理存储位置、大小、修改时间等元信息,用户无需关心数据的具体存储地址,只需通过文件名访问。

  3. 数据载体 文件可存储任意类型的数据,包括:

    • 文本数据(如 .txt 文档、.cpp 代码文件);

    • 二进制数据(如 .exe 可执行程序、.jpg 图片、.mp4 视频);

    • 结构化数据(如 .xlsx 表格、.json 配置文件)。

二、文件的分类(按常见维度)

分类维度 具体类型
存储内容 文本文件(纯字符编码,如 .txt)、二进制文件(非字符编码,如 .exe
用途 程序文件(可执行文件 .exe、库文件 .dll)、数据文件(文档、图片等)
系统属性 普通文件(用户可直接访问)、系统文件(操作系统运行必需,通常隐藏)
访问权限 只读文件、可读写文件、隐藏文件、加密文件等(由操作系统权限机制控制)

三、文件在编程中的核心概念

在编程语言(如 C/C++、Python、Java)中,文件是程序与外部数据交互的重要接口,核心操作围绕文件流(File Stream) 展开:

  1. 文件操作流程

    • 打开文件(open):建立程序与文件的连接,指定访问模式(读、写、追加等);

    • 操作文件(read/write):读取文件内容到程序,或写入数据到文件;

    • 关闭文件(close):释放文件资源,确保数据正确写入存储介质。

  2. C++ 中的文件操作示例 以 C++ 的标准库 <fstream> 为例,通过文件流对象操作文件:

    复制代码
    #include <fstream>
    #include <iostream>
    using namespace std;
    ​
    int main() {
        // 打开文件(写模式)
        ofstream outFile("example.txt"); 
        if (outFile.is_open()) {
            outFile << "Hello, File!";  // 写入数据
            outFile.close();  // 关闭文件
        }
    ​
        // 打开文件(读模式)
        ifstream inFile("example.txt");
        if (inFile.is_open()) {
            string content;
            getline(inFile, content);  // 读取数据
            cout << "文件内容:" << content << endl;  // 输出:Hello, File!
            inFile.close();
        }
        return 0;
    }

四、文件的实际意义

  • 数据持久化:程序运行时的数据存储在内存中,关闭程序后会丢失,而文件可将数据长期保存到硬盘等非易失性存储介质中。

  • 数据共享与传输:文件是数据交换的基本单位,通过复制、传输文件,可在不同程序、设备或用户之间共享数据。

  • 系统管理基础:操作系统通过文件管理硬件资源、配置信息和用户数据,文件系统的设计直接影响系统的性能和安全性。

总之,文件是计算机中数据存储、管理和交互的核心载体,无论是日常使用的文档、图片,还是程序运行依赖的代码和配置,都离不开文件的概念。理解文件的特性和操作方式,是掌握计算机应用和编程的基础。

C++提供了低级和高级的I/O功能 低级 I/O 特点 :以字节为单位直接进行输入输出,无数据格式转换,二进制形式传输,速度快、效率高,常用于内存与设备间大量字节传输,但不直观,难了解数据内容。 高级I/O特点:把若干字节组合为一个有意义的单位(如整数、单精度数、字符串或用户自定义的数据类型的数据),然后以ASCII字符形式输入和输出。

5.4.2 文件流泪与文件流对象

  1. 文件流相关基础:

    • C++ 输入输出由类对象实现(如cincout是流对象 ),cincout仅处理标准设备(非磁盘文件)输入输出,磁盘文件操作需另外定义文件流对象。

    • 文件流概念:以外存文件为输入输出对象的数据流,输出文件流是内存流向文件,输入文件流是文件流向内存,每个文件流对应内存缓冲区;文件流本身不是文件,是操作文件的流,磁盘文件操作需通过文件流实现。

  2. 文件流类

    C++ I/O 类库中用于磁盘文件操作的三类:

    • ifstream类:从istream派生,支持从磁盘文件输入 。

    • ofstream类:从ostream派生,支持向磁盘文件输出 。

    • fstream类:从iostream派生,支持磁盘文件输入输出 。

  3. 文件流对象使用 :磁盘文件输入输出需定义文件流类对象,通过其实现内存与磁盘文件的数据传输;标准设备(如cincout )流对象已预先定义,磁盘文件流对象需用户自定义,且要指定关联的磁盘文件,如定义ofstream outfile;后,需后续指定输出到哪个磁盘文件 。

5.4.3 文件的打开与关闭

打开磁盘文件

打开文件是指在文件读写之前作必要的准备工作,包括

①为文件流对象和指定的磁盘文件建立关联,以便文件流流向指定的磁盘文件。

②指定文件的工作方式,如:该文件是作为输入文件还是输出文件,是ASCII文件还是二进制文件。

以上工作通过两种不同的方法实现:

调用文件流的成员函数open
复制代码
ofstream outfile;                   //定义ofstream类(输出文件流类)对象outfile
outfile.open("f1.dat",ios::out);    //使文件流与f1.dat文件建立关联

一般调用形式:文件流对象.open(磁盘文件名,输入输出方式);

磁盘文件名可以包括路径,若缺省路径,则默认为当前目录下的文件。

在定义文件流对象时指定参数

在声明文件流类时定义了带参数的构造函数,其中包含了打开磁盘文件的功能。因此,可以在定义文件流对象时指定参数,调用文件流类的构造函数实现打开文件的功能,如:

复制代码
ostream outfile("f1.at",ios::out);

一般多用此形式,比较方便,作用与open函数相同。

输入输出方式

方法 作用
ios::in 以输入方式打开文件
ios::out 以输出方式打开文件(这是默认方式),如果已有此名字的文件,则将其原有的内容全部清除。
ios::app 以输出方式打开文件,写入的数据添加在文件末尾
ios::ate 打开一个已有的文件,文件指针指向文件末尾
ios::trunc 打开一个文件,如果文件已存在,则删除其中的全部数据,如文件不存在,则建立新文件。 如指定了ios::out方式而未指定ios::app,ios::ate,ios::in则同时默认此方式。
Ios::binary 以二进制方式打开一个文件。如不指定此方式则默认为ASCII方式
ios::nocreate 打开一个已有的文件,如文件不存在,则打开失败。nocreate的意思是不建立新文件
ios::noreplace 如果文件不存在则建立新文件,如果文件已存在则操作失败,noreplace的意思是不更新原有文件。
`ios::in ios::out`
`ios::out ios::binary`
`ios::in ios::binary`
关闭磁盘文件

在对已打开的磁盘文件的读写操作完成后,应关闭该文件。关闭文件用成员函数close。如 outfile.close();

所谓关闭,实际上是解除该磁盘文件与文件流的关联,原来设置的工作方式也失效,这样,就不能再通过文件流对该文件进行输入或输出。此时可以将文件流与其他磁盘文件建立关联,通过文件流对新的文件进行输入和输出。如outfile.open("f1.dat",ios::app|ios::nocreate);

此时文件流outfile与f2.dat建立关联,并指定了f2.dat的工作方式。

5.4.4 对ASCII文件的操作

ASCII文件:文件的每一个字节中均以ASCII码形式存放数据,即一个字节存放一个字符。

对ASCII文件的读写操作可以用两种方法:

  1. 用流插入运算符"<<"和流提取运算符">>"输入输出标准类型的数据。

  2. 使用成员函数进行字符的输入输出。

示例1:有一个整数组,含10个元素,从键盘输入10个整数给数组,将此数组送到磁盘文件中存放。

复制代码
#include<iostream>
#include<fstream>  
#include<cstdlib>  
using namespace std;
​
int main()
{
    int a[10];  // 定义一个能存储10个整数的数组
    
    // 修正:修复文件名的引号错误,将""f1.dat改为"f1.dat"
    ofstream outfile("f1.dat", ios::out);  // 定义输出文件流对象,以输出模式打开磁盘文件"f1.dat"
    
    // 修正:判断条件错误,应该是如果打开失败(!outfile)才执行错误处理
    if(!outfile)  // 如果打开文件失败,outfile返回0值,!outfile为真
    {
        cerr << "open error!" << endl;  // 输出错误信息到标准错误流
        exit(1);  // 异常退出程序,返回1表示有错误
    }
    
    cout << "enter 10 integer numbers:" << endl;
    
    for(int i = 0; i < 10; i++)
    {
        cin >> a[i];  // 从键盘输入整数到数组元素
        
        // 修正:数组索引错误,a[10]超出了数组范围,应改为a[i]
        outfile << a[i] << " ";  // 将输入的整数写入文件
    }
    
    outfile.close();  // 关闭文件流
    return 0;  // 程序正常结束
}

示例2:从示例1建立的数据文件f1.dat中读入10个整数放在数组中,找出并输出10个数中最大者和它在数组中的序号

复制代码
#include <fstream>
#include <iostream>  // 补充输入输出流头文件
#include <cstdlib>   // 补充exit函数所需头文件
using namespace std;  
​
int main()
{
    int a[10], max, i, order;
    // 打开文件f1.dat用于读取,若文件不存在则打开失败
    ifstream infile("f1.dat", ios::in | ios::nocreate);
    
    // 检查文件是否成功打开
    if (!infile)
    {
        cerr << "open error!" << endl;  // 输出错误信息
        exit(1);  // 异常退出程序
    }
    
    // 从文件读取10个整数到数组a,并同时输出到屏幕
    for (i = 0; i < 10; i++)
    {
        infile >> a[i];
        cout << a[i] << " ";
    }
    cout << endl;  // 换行
    
    // 初始化最大值为数组第一个元素,记录其位置
    max = a[0];
    order = 0;
    
    // 修正:循环结构错误,应使用for循环遍历数组元素
    for (i = 1; i < 10; i++)  // 从第二个元素开始比较
    {
        // 修正:条件判断错误,将赋值符号=改为比较符号==
        if (a[i] > max)  // 若当前元素大于最大值
        {
            max = a[i];   // 更新最大值
            order = i;    // 记录当前最大值的位置
        }
    }
    
    cout << "max=" << max << endl << "order=" << order << endl;
    
    infile.close();  // 关闭文件
    return 0;
}

示例3:从键盘读取一行字符,把其中的小写字母改为大写字母,再存入磁盘文件f2.dat中。再把它从磁盘文件读入程序,从其中的小写字母改为大写字母,再存入磁盘文件f3.dat

复制代码
#include <fstream>   // 文件操作所需头文件
​
using namespace std;
​
// 函数声明:从键盘读入一行字符,转换为大写后存入f2.dat
void save_to_file();
​
// 函数声明:从f2.dat读取内容,转换为大写后存入f3.dat
void get_from_file();
​
int main()
{
    // 从键盘读取并处理后存入f2.dat
    save_to_file();
    
    // 从f2.dat读取并再次处理后存入f3.dat
    get_from_file();
    
    cout << "操作完成!已生成f2.dat和f3.dat文件" << endl;
    return 0;
}
​
// 从键盘读入一行字符,将小写字母改为大写字母后存入磁盘文件f2.dat
void save_to_file()
{
    // 定义输出文件流对象,以输出方式打开f2.dat
    ofstream outfile("f2.dat");
    
    // 检查文件是否成功打开
    if (!outfile)
    {
        cerr << "打开f2.dat失败!" << endl;
        exit(1);  // 异常退出程序
    }
    
    char c[80];  // 存储输入的字符数组
    cout << "请输入一行字符(最多79个字符):" << endl;
    cin.getline(c, 80);  // 从键盘读取一行字符
    
    cout << "转换为大写后的内容:";
    // 遍历输入的每个字符
    for (int i = 0; c[i] != '\0'; i++)  // 修正:字符串结束符是'\0'而非0
    {
        // 修正:判断逻辑错误,正确区分大小写字母
        if (c[i] >= 'a' && c[i] <= 'z')  // 如果是小写字母
        {
            c[i] = toupper(c[i]);  // 转换为大写字母(使用库函数更可靠)
        }
        // 写入文件(包括所有字符,不仅是字母)
        outfile.put(c[i]);
        cout << c[i];  // 同时输出到屏幕
    }
    cout << endl;
    
    outfile.close();  // 修正:将关闭文件移到循环外,避免提前关闭
}
​
// 从磁盘文件f2.dat读入内容,将小写字母改为大写字母后存入f3.dat
void get_from_file()  // 修正:函数定义位置错误,移到save_to_file()外部
{
    char ch;
    // 定义输入文件流对象,以输入方式打开f2.dat
    // 修正:打开模式错误,应使用ios::in | ios::nocreate而非||
    ifstream infile("f2.dat", ios::in | ios::nocreate);
    
    // 修正:变量名错误,应使用infile而非file
    if (!infile)
    {
        cerr << "打开f2.dat失败!" << endl;
        exit(1);
    }
    
    // 定义输出文件流对象,以输出方式打开f3.dat
    ofstream outfile("f3.dat");
    if (!outfile)
    {
        cerr << "打开f3.dat失败!" << endl;
        exit(1);
    }
    
    cout << "从f2.dat读取并转换后写入f3.dat的内容:";
    // 逐个字符读取文件内容,直到文件结束
    while (infile.get(ch))  // 读取一个字符,成功则继续
    {
        // 如果是小写字母则转换为大写
        if (ch >= 'a' && ch <= 'z')
        {
            ch = toupper(ch);
        }
        outfile.put(ch);  // 写入f3.dat
        cout << ch;       // 同时输出到屏幕
    }
    cout << endl;
    
    // 关闭文件流
    infile.close();
    outfile.close();
}

5.4.5 对二进制文件的操作

二进制文件不是以ASCII代码形式存放数据的,它将内存中数据存储形式不加转换地传送到磁盘文件,因此它又称为内存数据的映像文件 。因为文件中的信息不是字符数据,而是字节中的二进制形式的信息,一它又称为字节文件
对二进制文件的操作也需要先打开文件,用完文件要关闭文件。再打开是要用ios::binary指定为二进制形式传送和存储。二进制文件除了可以作为输入文件或输出文件外,还可以是既能输入又能输出的文件。

用成员函数read和write读写二进制文件

对二进制文件的读写主要用istream类的数据成员函数read和write来实现。

示例1:将一批数据以二进制形式存放在磁盘文件中

复制代码
#include <fstream>
#include <iostream>  // 补充输入输出流头文件,用于错误信息输出
using namespace std;
​
// 定义学生结构体,包含姓名、学号、年龄和性别
struct student
{
    char name[20];  // 姓名,字符数组
    int num;        // 学号,整数
    int age;        // 年龄,整数
    char sex;       // 性别,字符('f'表示女,'m'表示男)
};
​
int main()
{
    // 初始化3个学生信息
    student stud[3] = {
        {"Li", 1001, 18, 'f'},    // 第一个学生
        {"Fan", 1002, 19, 'm'},   // 第二个学生
        {"Wang", 1004, 17, 'f'}   // 第三个学生
    };
    
    // 以二进制写模式打开文件stud.dat
    ofstream outfile("stud.dat", ios::binary);
    
    // 检查文件是否成功打开
    if (!outfile)
    {
        cerr << "open error!" << endl;
        abort();  // 打开失败则终止程序
    }
    
    // 循环将3个学生信息写入文件
    for (int i = 0; i < 3; i++)
    {
        // 以二进制方式写入一个学生的信息
        // (char*)&stud[i]:将学生对象地址转换为字符指针
        // sizeof(stud[i]):获取一个学生对象的大小(字节数)
        outfile.write((char*)&stud[i], sizeof(stud[i]));
    }
    
    outfile.close();  // 关闭文件
    cout << "学生信息已成功写入文件stud.dat" << endl;  // 添加操作成功提示
    return 0;
}

示例2:将执行示例1程序时存放在磁盘文件中的二进制形式的数据读入内存,并在显示器显示

复制代码
#include <fstream>
#include <iostream>
#include <string>  // string类型需要的头文件
using namespace std;
​
struct student
{
    // 注意:使用string类型在二进制读写时可能出现问题
    // 建议使用字符数组更适合二进制文件操作
    char name[20];  // 改为字符数组,避免string的二进制读写问题
    int num;
    int age;
    char sex;      
};
​
int main()
{
    student stud[3];
    int i;
    
    // 打开二进制文件用于读取(二进制模式)
    ifstream infile("stud.dat", ios::binary);
    if (!infile)
    {
        cerr << "open error!" << endl;
        abort();
    }
    
    // 从文件读取3个学生的数据
    for (i = 0; i < 3; i++)
    {
        // 读取一个学生结构体大小的数据
        infile.read((char*)&stud[i], sizeof(stud[i]));
    }
    infile.close();  // 关闭文件
    
    // 输出读取到的学生信息
    for (i = 0; i < 3; i++)
    {
        cout << "NO." << i + 1 << endl;
        cout << "name:" << stud[i].name << endl;
        cout << "num:" << stud[i].num << endl;    
        cout << "age:" << stud[i].age << endl;
        cout << "sex:" << stud[i].sex << endl << endl; 
    }
    
    return 0;
}
与文件指针有关的流成员函数

在磁盘文件中有一个文件读写位置标记 (简称文件位置标记文件标记),来指明当前进行读写的位置。

从文件输入时每读入一个字节,该位置就像后移动一个字节。在输出时每向文件输出一个字节,位置标记也向后移动一个字节,随着输出文件中字节不断增加,位置不断后移。

对于二进制文件,允许对位置标记进行2控制,是它按用户的意图移动到所需位置,以便在该位置上进行读写。

文件流提供了一些有关文件位置标记的成员函数

成员函数 作用
gcount() 得到最后一次输入所读入的字节数
tellg() 得到输入文件位置标记的当前位置
tellp() 得到输出文件位置标记当前的位置
seekg(文件中的位置) 将输入文件位置标记移到指定的位置
seekg(位移量,参照坐标) 以参照位置为基础移动若干字节("参照位置"的用法见说明)
seekp(文件中的位置) 将输出文件位置标记移到指定的位置
seekp(位移量,参照坐标) 以参照位置为基础移动若干字节

说明:

函数参数中的"文件中的位置"和"位移量"已被指定为long型整数,以字节为单位。"参照位置"可以是以下三者之一:

  • ios::beg:文件开头(beg是begin的缩写),这是默认值

  • ios::cur:位置标记当前的位置(cur是current的缩写)

  • ios::end:文件末尾

示例:

复制代码
infile.seekg(100);              //输入文件位置标记向迁移到100字节位置
infile.seekg(-50,ios::cur);     //输入文件中位置标记从当前位置后移50字节
infile.seekp(-75,ios::end);     //输出文件中位置标记从文件尾后移50
随机访问二进制数据文件

对于二进制数据文件来说,可以利用上面的成员函数移动指针,随机地访问文件中任一位置上的数据,还可以修改文件中的内容。

示例:有5个同学的数据,要求:

①把它们存到磁盘文件中

②将磁盘文件中的第1,3,5个学生数据读写程序,并显示出来

③从磁盘文件读入修改后的5个学生的数据并显示出来

复制代码
#include <fstream>   // 文件流操作头文件
#include <iostream>  // 输入输出流头文件
#include <cstring>   // 字符串操作函数(如strcpy)头文件
#include <cstdlib>   // 包含abort()函数
using namespace std;
​
// 定义学生结构体:包含学号、姓名、成绩
struct student
{
    int num;         // 学号
    char name[20];   // 姓名(字符数组,适合二进制存储)
    float score;     // 成绩
};
​
int main()
{
    // 初始化5个学生信息
    student stud[5] = {
        {1001, "Li", 85},
        {1002, "Fan", 95.5},
        {1004, "Wang", 54},
        {1006, "Tan", 76.0},  
        {1010, "Ling", 96}    
    };
​
    // 以二进制模式打开文件(可读可写)
    fstream iofile("stud.dat", ios::in | ios::out | ios::binary);
    if (!iofile)  // 检查文件是否成功打开
    {
        cerr << "open error!" << endl;
        abort();  // 打开失败则终止程序
    }
​
    // 向文件写入5个学生的数据
    for (int i = 0; i < 5; i++) {
        // 二进制写入:将结构体地址转换为char*,写入整个结构体大小的数据
        iofile.write((char*)&stud[i], sizeof(stud[i]));
    }
​
    student stud1[5];  // 用于存放从文件读取的数据
    // 读取第0、2、4个学生的数据(索引为0,2,4的元素)
    for (int i = 0; i < 5; i += 2) {  // i=0,2,4
        // 定位到第i个学生数据的起始位置(从文件开头计算偏移量)
        iofile.seekg(i * sizeof(stud[0]), ios::beg);  // 用stud[0]更清晰,所有元素大小相同
        // 读取数据到stud1[i/2]中(i/2=0,1,2)
        iofile.read((char*)&stud1[i / 2], sizeof(stud1[0]));
        // 输出读取到的数据
        cout << stud1[i / 2].num << " " 
             << stud1[i / 2].name << " " 
             << stud1[i / 2].score << endl;
    }
​
    cout << endl;  // 空行分隔输出
​
    // 修改第3个学生(索引为2)的数据
    stud[2].num = 1012;                // 修改学号
    strcpy(stud[2].name, "Wu");        // 修改姓名(使用strcpy复制字符串)
    stud[2].score = 60;                // 修改成绩
​
    // 定位到第3个学生(索引2)数据的起始位置
    iofile.seekp(2 * sizeof(stud[0]), ios::beg);  // seekp用于定位写指针
    // 将修改后的数据写入文件,覆盖原有内容
    iofile.write((char*)&stud[2], sizeof(stud[2]));
​
    // 将文件指针重新定位到文件开头(准备读取所有数据)
    iofile.seekg(0, ios::beg);
​
    // 读取并输出所有5个学生的数据(验证修改结果)
    for (int i = 0; i < 5; i++) {
        iofile.read((char*)&stud[i], sizeof(stud[i]));
        cout << stud[i].num << " " 
             << stud[i].name << " " 
             << stud[i].score << endl;
    }
    iofile.close();  // 关闭文件
    return 0;
}

5.5 字符串流

在 C++ 编程领域,文件流用于处理外存文件与程序间的数据交换,而字符串流(内存流) 聚焦于内存中字符数组(字符串)的数据读写,为内存数据交互提供了高效解决方案。

5.5.1 字符串流的本质

文件流以外部存储文件为 I/O 对象,字符串流 则以内存中用户定义的字符数组(或字符串)为操作对象,实现数据向字符数组的输出及从字符数组的读取,是内存数据交互的 "中转站" 。

5.5.2 工作流程与数据转换

字符串流依托缓冲区运作:

  • 写操作:向字符数组写入数据时,数据先进入流缓冲区,缓冲区满或遇换行符时,数据以 ASCII 码形式存入字符数组(写入前需将二进制数据转 ASCII 码 )。

  • 读操作:从字符数组读取数据时,字符数组的 ASCII 数据先送至流缓冲区,再从缓冲区提取并转回二进制形式赋给变量 。这与键盘(输入)、显示器(输出)的 I/O 流程类似,理解标准设备交互有助于掌握字符串流逻辑。

5.5.3 类体系与继承关系

文件流类有 ifstream(读)、ofstream(写)等,字符串流类对应 istrstream(读字符串)、ostrstream(写字符串)、strstream(双向) 。类名中 strstring 缩写,它们继承自 ostreamistreamiostream 类,操作方法与文件流基本一致,掌握文件流读写即可快速上手。

5.5.4 与文件流的核心差异

虽操作方法相近,但字符串流有独特之处:

数据流向

文件流是内存 ↔ 外存文件 交互,字符串流是内存 ↔ 内存字符数组交互,后者并非传统 I/O(通常指内存与外部设备 / 文件 ),因使用流机制,仍用 "输入输出" 描述读写。

无需文件操作

文件流需打开、关闭文件,字符串流关联内存字符数组,无需开 / 关文件操作,简化流程。

结束标志

外存文件末尾有文件结束符 ,字符串流关联的字符数组无自带结束标志,需用户自定义特殊字符(如 '\0' )作为结束符 。

5.5.5 应用场景

字符串流适用于多种场景:

  • 字符串格式化拼接:拼接不同类型数据(int、float、字符串等 ),用于日志、协议报文构造。

  • 字符串数据解析:按格式提取字符数组数据,如解析配置项、自定义协议内容,比手动分割便捷。

  • 内存数据临时缓存:临时存储内存数据,便于后续处理,充当数据 "临时仓库"。

5.5.6 代码示例:传统与现代用法

传统字符串流(strstream 系列,已渐被弃用 )

复制代码
#include <iostream>
#include <strstream> 
using namespace std;
​
int main() {
    char buffer[100];  
    ostrstream os(buffer, 100);  
    int num = 1001;
    char name[] = "ZhangSan";
    float score = 95.5;
​
    os << num << " " << name << " " << score << ends;  
    cout << "写入字符数组内容:" << buffer << endl;
​
    istrstream is(buffer);  
    int readNum;
    char readName[20];
    float readScore;
​
    is >> readNum >> readName >> readScore;  
    cout << "读出数据:" << readNum << " " << readName << " " << readScore << endl;
​
    return 0;
}

现代推荐(stringstream,更安全 )

复制代码
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
​
int main() {
    stringstream ss;
    int num = 1001;
    string name = "ZhangSan";
    float score = 95.5;
​
    ss << num << " " << name << " " << score;  
    string result = ss.str();  
    cout << "拼接结果:" << result << endl;
​
    ss.str("");  
    ss << result;  
    int readNum;
    string readName;
    float readScore;
​
    ss >> readNum >> readName >> readScore;  
    cout << "读出数据:" << readNum << " " << readName << " " << readScore << endl;
​
    return 0;
}

5.6 整理与总结

C++ 输入输出流函数 分类整理(结合标准 I/O、文件 I/O、字符串流):

一、标准输入函数(istream 及派生类)

函数名 所属类 功能描述 参数说明 示例代码(文档引用)
>>(流提取运算符) istream 从输入流提取数据(跳过空白符) istream& operator>>(类型& 变量) cin >> num;(提取整数)
get() istream 读取单个字符(包括空白符) 无参:返回字符 ASCII 值; 单参:char& ch(存储字符) char c = cin.get();(读取任意字符)
getline() istream 读取一行字符(默认以 \n 结束,丢弃终止符) char* 数组, 大小, 终止符(默认 \n cin.getline(ch, 20);(读取最多 19 个字符)
peek() istream 查看下一个字符(不移动指针) int ch = cin.peek();(返回下一个字符 ASCII 值)
ignore() istream 跳过输入流中指定数量字符 ignore(数量, 终止符)(默认 ignore(1, EOF) cin.ignore(5, '\n');(跳过 5 个字符或直到换行符)
eof() istream 判断是否到达文件结尾 while (!cin.eof()) { ... }(循环读取直到文件结束)

二、标准输出函数(ostream 及派生类)

函数名 所属类 功能描述 参数说明 示例代码(文档引用)
<<(流插入运算符) ostream 向输出流写入数据(自动格式化) ostream& operator<<(类型 数据) cout << "x1=" << x1 << endl;(输出表达式)
put() ostream 写入单个字符 char c(字符) cout.put('A');(输出字符 'A')
endl ostream 输出换行符并刷新缓冲区 cout << "Hello" << endl;(等价 \n 但刷新缓冲区)
cerr/clog ostream 输出错误信息(cerr 无缓冲,clog 有缓冲) cout cerr << "error!" << endl;(调试时即时输出错误)

三、文件流函数(fstream/ifstream/ofstream)

函数名 所属类 功能描述 参数说明 示例代码(文档引用)
open() fstream 打开文件并指定模式 const char* 文件名, 模式(如 `ios::in ios::binary`)
close() fstream 关闭文件流 outfile.close();
read() ifstream 读取二进制数据 char* 缓冲区, 字节数 infile.read((char*)&stud, sizeof(stud));(读取结构体)
write() ofstream 写入二进制数据 const char* 缓冲区, 字节数 outfile.write((char*)&stud, sizeof(stud));(写入结构体)
seekg() ifstream 设置输入流指针位置 long 偏移量, 参照点(如 ios::beg/ios::cur/ios::end infile.seekg(2*sizeof(stud), ios::beg);(定位到第 3 个学生数据)
seekp() ofstream 设置输出流指针位置 seekg() iofile.seekp(2*sizeof(stud), ios::beg);(修改文件指定位置)

四、字符串流函数(stringstream/strstream)

函数名 所属类 功能描述 参数说明 示例代码(文档引用)
str() stringstream 获取或设置流内容(字符串) 无参:返回当前字符串; 有参:const string& s(设置内容) string result = ss.str();(获取拼接结果)
clear() stringstream 重置流状态(清除错误标志) ss.clear(); ss.str("100");(重置流并赋值)
<</>> stringstream 向字符串流读写数据(支持类型转换) 同标准流 ss << 100 << "abc";(拼接整数和字符串)

五、流控制函数(格式 / 状态控制)

函数名 所属类 功能描述 参数说明 示例代码(文档引用)
setw(n) iomanip 设置输出字段宽度(右对齐) int n(宽度) cout << setw(10) << "China";(输出占 10 列,默认空格填充)
setprecision(n) iomanip 设置浮点数精度(有效数字或小数位) int n(精度) cout << setprecision(6) << 3.1415926;(输出 6 位小数)
setfill(c) iomanip 设置填充字符 char c(字符) cout << setfill('*') << setw(10) << "China";(填充星号)
setiosflags(flag) ios 设置格式标志(如左对齐、科学计数法) ios::flag(如 ios::left/ios::scientific cout.setiosflags(ios::fixed);(固定小数位输出)

六、补充:C 风格兼容函数(文档提及)

函数名 头文件 功能描述 注意事项
gets() <cstdio> 读取整行(不安全,已废弃) 无边界检查,推荐 fgets()getline()
puts() <cstdio> 输出字符串并换行 等价 cout << str << endl;
toupper() <cctype> 转换字符为大写 文档示例中用于字符处理

表格说明

  1. 分类逻辑:按功能(输入 / 输出 / 文件 / 字符串 / 控制)和所属类划分,便于理解函数适用场景。

  2. 文档引用:示例代码均来自用户提供的文档内容,如二进制文件读写、字符串流拼接等。

  3. 现代推荐 :优先使用 stringstream(替代 strstream)、getline(替代 gets())等安全函数。

  4. 头文件 :标准流(<iostream>)、文件流(<fstream>)、字符串流(<sstream>)、格式控制(<iomanip>)需按需包含。

相关推荐
励志不掉头发的内向程序员11 分钟前
从零开始的python学习——常量与变量
开发语言·python·学习
西猫雷婶25 分钟前
神经网络|(十六)概率论基础知识-伽马函数·中
人工智能·深度学习·神经网络·学习·机器学习·概率论
睡不醒的kun2 小时前
leetcode算法刷题的第二十一天
数据结构·c++·算法·leetcode·职场和发展·回溯算法·回归算法
小欣加油2 小时前
leetcode 461 汉明距离
c++·算法·leetcode
淮北4942 小时前
linux系统学习(15.启动管理)
运维·服务器·网络·c++·vscode·学习
mit6.8242 小时前
[游戏中的空间划分] KD树|四叉树|价格系统
c++·游戏·游戏程序
diablobaal3 小时前
云计算学习100天-第31天
学习·云计算
三小尛3 小时前
C++继承
开发语言·c++
solicitous3 小时前
整理python接口自动化相关——10、自动考虑点(待续)
python·学习·自动化