编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 01.基础语法
  • 02.运算符和表达式
  • 03.判断和循环
  • 04.函数实践
  • 05.指针和引用
  • 06.数组和容器
  • 07.类和对象
  • 08.继承和派生
  • 09.多态与虚函数
  • 10.多线程和并发
  • 11.线程安全锁
  • 12.内存分配堆和栈
  • 13.IO流与文件
  • 14.异常处理
  • 15.STL标准模板库

13.IO流与文件

目录介绍

  • 13.1 输入输出介绍
    • 13.1.1 输入输出流
    • 13.1.2 I/O库头文件
    • 13.1.3 标准输出流cout
    • 13.1.4 标准输入流cin
    • 13.1.5 标准错误流cerr
    • 13.1.6 标准日志流clog
  • 13.2 文件操作实践
    • 13.2.1 文件是什么
    • 13.2.2 文件类型分类
    • 13.2.3 操作文件类
    • 13.2.4 文本文件写
    • 13.2.5 文本文件读
    • 13.2.6 二进制文件写
    • 13.2.7 二进制文件读
  • 13.3 文件读写指针
    • 13.3.1 文件错误与状态
    • 13.3.2 文件的追加
    • 13.3.3 文件结尾的判断
    • 13.3.4 在指定位置读/写文件
  • 13.4 IO综合案例
    • 13.4.1 键盘输入写文本文件
    • 13.4.2 实现文件复制
    • 13.4.3 合并两个文件信息
    • 13.4.4 文件加密
    • 13.4.5 在文件中查找关键词

13.1 输入输出介绍

13.1.1 输入输出流

流是字节序列。如果字节流是从设备流向内存,这叫做输入操作。如果字节流是从内存流向设备,这叫做输出操作。

学过 C 语言的读者应该知道,它有一整套完成数据读写(I/O)的解决方案:

  1. 使用 scanf()、gets() 等函数从键盘读取数据,使用 printf()、puts() 等函数向屏幕上输出数据;
  2. 使用 fscanf()、fgets() 等函数读取文件中的数据,使用 fprintf()、fputs() 等函数向文件中写入数据。

C 语言的这套 I/O 解决方案也适用于 C++ 程序,但 C++ 并没有“偷懒”,它自己独立开发了一套全新的 I/O 解决方案,其中就包含大家一直使用的 cin 和 cout。

  1. 用 cin 接收从键盘输入的数据,用 cout 向屏幕上输出数据(这 2 个过程又统称为“标准 I/O”)。
  2. 除此之外,C++ 也对从文件中读取数据和向文件中写入数据做了支持(统称为“文件 I/O”)。

13.1.2 I/O库头文件

ios 是所有流类的基类,它派生出 istream 和 ostream。

特别需要指出的是,为了避免多继承的二义性,从 ios 派生出 istream 和 ostream 时,均使用了 virtual 关键字(虚继承)。

这些流类各自的功能分别为:

  1. istream:常用于接收从键盘输入的数据;
  2. ostream:常用于将数据输出到屏幕上;
  3. ifstream:用于读取文件中的数据;
  4. ofstream:用于向文件中写入数据;
  5. iostream:继承自 istream 和 ostream 类,因为该类的功能兼两者于一身,既能用于输入,也能用于输出;
  6. fstream:兼 ifstream 和 ofstream 类功能于一身,既能读取文件中的数据,又能向文件中写入数据。

<iostream>分别对应于标准输入流、标准输出流。<iomanip>声明对执行标准化 I/O 有用的服务。<fstream>为用户控制的文件处理声明服务。

13.1.3 标准输出流cout

在C++中,cout是标准输出流对象,用于向控制台或终端输出数据。它是iostream库中的一部分,可以通过包含头文件<iostream>来使用。

使用cout进行输出非常简单,只需使用<<运算符将要输出的数据插入到cout对象中即可。以下是一些示例用法:

#include <iostream>

int main() {
    int number = 42;
    double pi = 3.14159;
    std::string message = "Hello, world!";

    std::cout << "This is a number: " << number << std::endl;
    std::cout << "This is pi: " << pi << std::endl;
    std::cout << "This is a message: " << message << std::endl;
    return 0;
}

cout对象可以与各种数据类型一起使用,包括基本数据类型(如整数、浮点数、字符等)和自定义类型(如字符串、数组、对象等)。

需要注意的是,cout是一个全局对象,属于std命名空间。因此,在使用cout之前,我们需要使用std::前缀来指定命名空间,或者使用using namespace std;语句来引入整个std命名空间。

13.1.4 标准输入流cin

在C++中,cin是标准输入流对象,用于从控制台或终端读取用户输入的数据。它也是iostream库的一部分,可以通过包含头文件<iostream>来使用。

使用cin进行输入非常简单,只需使用>>运算符将输入的数据存储到相应的变量中。以下是一些示例用法:

#include <iostream>

int main() {
    int number;
    double pi;
    std::string message;

    std::cout << "Enter a number: ";
    std::cin >> number;

    std::cout << "Enter the value of pi: ";
    std::cin >> pi;

    std::cout << "Enter a message: ";
    std::cin.ignore(); // 忽略之前的换行符
    std::getline(std::cin, message);

    std::cout << "You entered: " << number << ", " << pi << ", " << message << std::endl;

    return 0;
}

使用cin对象从用户那里读取了一个整数、一个浮点数和一行字符串。每个输入都使用>>运算符将输入的数据存储到相应的变量中。std::getline函数用于读取一行字符串,而std::cin.ignore()函数用于忽略之前的换行符。

13.1.5 标准错误流cerr

cerr是标准错误流对象,用于向控制台或终端输出错误信息。它也是iostream库的一部分,可以通过包含头文件<iostream>来使用。

使用cerr进行输出也非常简单,只需使用<<运算符将要输出的错误信息插入到cerr对象中即可。以下是一个示例用法:

#include <iostream>

int main() {
    int dividend = 10;
    int divisor = 0;

    if (divisor == 0) {
        std::cerr << "Error: Division by zero!" << std::endl;
    } else {
        int result = dividend / divisor;
        std::cout << "Result: " << result << std::endl;
    }
    return 0;
}

进行了一个除法运算,但是当除数为零时,会输出一个错误信息到cerr对象中。这样可以将错误信息与正常输出分开,使其更易于识别和处理。

与cout不同的是,cerr对象的输出通常会直接显示在控制台或终端的错误输出流中,而不会被重定向或缓冲。这使得cerr适用于输出紧急或重要的错误信息,以便及时通知用户。

13.1.6 标准日志流clog

clog是标准日志流对象,用于向控制台或终端输出日志信息。它也是iostream库的一部分,可以通过包含头文件<iostream>来使用。

#include <iostream>

int main() {
    int value = 42;
    std::clog << "This is a log message: " << value << std::endl;
    return 0;
}

使用clog对象输出了一个日志信息,其中包含一个整数值。与cout和cerr不同,clog对象的输出通常会被缓冲,这意味着日志信息可能不会立即显示在控制台或终端上,而是在适当的时机进行刷新。

clog对象通常用于输出一般的日志信息,既不像cout那样用于正常输出,也不像cerr那样用于错误信息。它可以用于记录程序的运行状态、调试信息或其他重要的日志记录。

13.2 文件操作实践

13.2.1 文件是什么

内存中存放的数据在计算机关机后就会消失。要长久保存数据,就要使用硬盘、光盘、U 盘等设备。为了便于数据的管理和检索,引入了“文件”的概念。

成千上万个文件如果不加分类放在一起,用户使用起来显然非常不便,因此又引入了树形目录(目录也叫文件夹)的机制,可以把文件放在不同的文件夹中,文件夹中还可以嵌套文件夹,这就便于用户对文件进行管理和使用。

所谓“格式”,就是关于文件中每一部分的内容代表什么含义的一种约定。 例如,常见的纯文本文件(也叫文本文件,扩展名通常是“.txt”),指的是能够在 Windows 的“记事本”程序中打开,并且能看出是一段有意义的文字的文件。文本文件的格式可以用一句话来描述:文件中的每个字节都是一个可见字符的 ASCII 码。

所谓“文本文件”和“二进制文件”,只是约定俗成的、从计算机用户角度出发进行的分类,并不是计算机科学的分类。因为从计算机科学的角度来看,所有的文件都是由二进制位组成的,都是二进制文件。文本文件和其他二进制文件只是格式不同而已。

13.2.2 文件类型分类

程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放。

通过文件可以将数据持久化

C++中对文件操作需要包含头文件 ==< fstream >==

文件类型分为两种:

  1. 文本文件 —— 文件以文本的ASCII码形式存储在计算机中
  2. 二进制文件 —— 文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们

13.2.3 操作文件类

C++ 标准库中还专门提供了 3 个类用于实现文件操作,它们统称为文件流类,这 3 个类分别为:

  1. ifstream:专用于从文件中读取数据;
  2. ofstream:专用于向文件中写入数据;
  3. fstream:既可用于从文件中读取数据,又可用于向文件中写入数据。

13.2.4 文本文件写

写文件步骤如下:

  1. 包含头文件 :#include <fstream>
  2. 创建流对象 :ofstream ofs;
  3. 打开文件 :ofs.open("文件路径",打开方式);
  4. 写数据 :ofs << "写入的数据";
  5. 关闭文件 :ofs.close();

文件打开方式:

打开方式解释
ios::in为读文件而打开文件
ios::out为写文件而打开文件
ios::ate初始位置:文件尾
ios::app追加方式写文件
ios::trunc如果文件存在先删除,再创建
ios::binary二进制方式

注意: 文件打开方式可以配合使用,利用|操作符

**例如:**用二进制方式写文件 ios::binary | ios:: out

示例:

#include <fstream>

void test1() {
    ofstream ofs;
    ofs.open("2_4.txt" ,ios::out);
    ofs << "姓名:张三" << endl;
    ofs << "性别:男" << endl;
    ofs << "年龄:18" << endl;
    ofs.close();
}

int main() {
    test1();
    return 0;
}

总结:

  • 文件操作必须包含头文件 fstream
  • 读文件可以利用 ofstream ,或者fstream类
  • 打开文件时候需要指定操作文件的路径,以及打开方式
  • 利用<<可以向文件中写数据
  • 操作完毕,要关闭文件

13.2.5 文本文件读

读文件与写文件步骤相似,但是读取方式相对于比较多。读文件步骤如下:

  1. 包含头文件 :#include <fstream>
  2. 创建流对象 :ifstream ifs;
  3. 打开文件并判断文件是否打开成功 :ifs.open("文件路径",打开方式);
  4. 读数据 :四种方式读取
  5. 关闭文件 :ifs.close();

示例:

#include "fstream"
#include "string"
#include "iostream"

using namespace std;

void test() {
    ifstream ifs;
    ifs.open("text.txt", ios::in);
    if (!ifs.is_open()) {
        cout << "文件打开失败" << endl;
        return;
    }

    //第一种方式
    char buf[1024] = {0};
    while (ifs >> buf) {
        cout << buf << endl;
    }

    //第二种
//    char buf[1024] = {0};
//    while (ifs.getline(buf, sizeof(buf))) {
//        cout << buf << endl;
//    }

    //第三种
//    string buf;
//    while (getline(ifs, buf)) {
//        cout << buf << endl;
//    }

//    char c;
//    while ((c = ifs.get()) != EOF) {
//        cout << c;
//    }
    ifs.close();
}

int main() {
    test();
    return 0;
}

总结:

  • 读文件可以利用 ifstream ,或者fstream类
  • 利用is_open函数可以判断文件是否打开成功
  • close 关闭文件

13.2.6 二进制文件写

以二进制的方式对文件进行读写操作。打开方式要指定为 ==ios::binary==

二进制方式写文件主要利用流对象调用成员函数write

函数原型 :ostream& write(const char * buffer,int len);

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

示例:

#include "fstream"
#include "string"
using namespace std;

class Person {
public:
    char name[64];
    int age;
};

void test() {
    //1、包含头文件
    //2、创建输出流对象
    ofstream ofs("person.txt", ios::out | ios::binary);
    //3、打开文件
    //ofs.open("person.txt", ios::out | ios::binary);
    Person p = {"张三"  , 18};
    //4、写文件
    ofs.write((const char *)&p, sizeof(p));
    //5、关闭文件
    ofs.close();
}

int main() {
    test();
    return 0;
}

总结:

  • 文件输出流对象 可以通过write函数,以二进制方式写数据

13.2.7 二进制文件读

二进制方式读文件主要利用流对象调用成员函数read

函数原型:istream& read(char *buffer,int len);

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

示例:

#include "fstream"
#include "string"
#include "iostream"
using namespace std;

class Person {
public:
    char name[64];
    int age;
};

void test() {
    //2、创建输入流对象
    ifstream ifs("person.txt", ios::in | ios::binary);
    if (!ifs.is_open()) {
        cout << "文件打开失败" << endl;
        return;
    }
    //3、打开文件
    //ofs.open("person.txt", ios::in | ios::binary);
    Person p;
    //4、读文件
    ifs.read((char *)&p, sizeof(p));
    //5、关闭文件
    ifs.close();
    cout << "姓名: " << p.name << " 年龄: " << p.age << endl;
}

int main() {
    test();
    return 0;
}

文件输入流对象 可以通过read函数,以二进制方式读数据

13.3 文件读写指针

13.3.1 文件错误与状态

std::fstream类中定义了一些成员函数和状态标志,用于检测和处理文件错误和状态。以下是一些常用的状态标志和相关的成员函数:

  1. fail():检测文件操作是否失败。返回true表示失败,返回false表示成功。
  2. bad():检测文件流是否处于错误状态。返回true表示错误,返回false表示正常。
  3. eof():检测文件流是否到达文件末尾。返回true表示到达末尾,返回false表示未到达末尾。
  4. good():检测文件流是否处于正常状态。返回true表示正常,返回false表示出现错误。
  5. clear():清除文件流的错误状态标志。
  6. rdstate():返回当前文件流的状态标志。
  7. setstate():设置文件流的状态标志。

通过使用这些成员函数和状态标志,可以检测和处理文件操作中的错误和状态。例如,可以使用fail()函数来检测文件读取操作是否失败,然后使用clear()函数来清除错误状态标志。

void test() {
    cout << "文件错误与状态" << endl;
    std::ifstream file("example.txt");
    //std::ifstream file("yc.txt");
    if (!file) {
        //我们检测文件是否成功打开,如果打开失败,则输出错误信息并返回。
        std::cerr << "Failed to open the file." << std::endl;
        return;
    }
    int num;
    //然后,我们使用while循环从文件中读取整数,并检测读取操作是否失败。如果失败,则输出错误信息并清除错误状态标志。
    while (file >> num) {
        if (file.fail()) {
            std::cerr << "Error reading the file." << std::endl;
            file.clear();  // 清除错误状态标志
            break;
        }
        std::cout << num << " ";
    }
    if (file.eof()) {
        //最后,我们检测是否到达文件末尾,并关闭文件。
        std::cout << std::endl << "End of file reached." << std::endl;
    }
    file.close();
    std::cerr << "file to close" << std::endl;
    //在上述示例中,我们打开了一个名为example.txt的文件,并使用std::ifstream对象file进行读取操作。
}

int main() {
    test();
    return 0;
}

13.3.2 文件的追加

在C++中,可以使用文件流对象的std::ofstream类来实现文件的追加操作。可以创建新文件或打开已存在的文件进行写入。

要实现文件的追加,需要在打开文件时指定追加模式。可以使用std::ios::app标志来指定追加模式,它会将数据追加到文件的末尾而不是覆盖原有内容。

void test() {
    cout << "文件的追加" << endl;
    std::ofstream file("yc.txt" , std::ios::app);
    if (!file) {
        std::cerr << "Failed to open the file." << std::endl;
        return;
    }
    file << "This is a new line." << std::endl;
    file << "This is another line." << std::endl;
    file.close();
    

    std::ifstream inFile("yc.txt" , std::ios::binary | std::ios::in);
    if (inFile.is_open()) {
        int data[5];
        inFile.read(reinterpret_cast<char*>(data), sizeof(data));
        for (int i = 0; i < 5; i++) {
            std::cout << data[i] << " " <<endl;
        }
        inFile.close();
        std::cout << std::endl << "Binary file read successfully." << std::endl;
    } else {
        std::cout << "Failed to open the file for reading." << std::endl;
    }
}

int main() {
    test();
    return 0;
}

13.3.3 文件结尾的判断

在C++中,可以使用文件流对象的eof()函数来判断文件是否已经到达结尾。eof()函数是std::istream和std::ostream类的成员函数,用于检测文件流是否已经到达文件末尾。

eof()函数返回一个bool值,如果文件流已经到达文件末尾,则返回true,否则返回false。

以下是一个示例,演示了如何使用eof()函数来判断文件是否已经到达结尾:

void test() {
    cout << "文件结尾的判断" << endl;
    std::ifstream file("yc.txt");
    if (!file) {
        //首先,我们检测文件是否成功打开,如果打开失败,则输出错误信息并返回。
        std::cerr << "Failed to open the file." << std::endl;
        return;
    }
    std::string line;
    //使用std::getline()函数从文件中逐行读取数据,并将每行数据输出到控制台。
    while (std::getline(file,line)) {
        std::cout << line << std::endl;
    }
    if (file.eof()) {
        //使用eof()函数检测文件是否已经到达结尾。如果到达结尾,则输出相应的提示信息。
        std::cout << "End of file reached." << std::endl;
    }
    file.close();
}

int main() {
    test();
    return 0;
}

13.3.4 在指定位置读/写文件

在C++中,可以使用文件流对象的seekg()和seekp()函数来在指定位置进行文件的读取和写入操作。这两个函数用于设置文件流的读取和写入位置。

seekg()函数用于设置输入文件流的读取位置,而seekp()函数用于设置输出文件流的写入位置。这两个函数都接受一个参数,表示要设置的位置。

以下是一个示例,演示了如何在指定位置读取和写入文件:

void test() {
    cout << "在指定位置读/写文件" << endl;
    std::fstream file("yc.txt",std::ios::in | std::ios::out);
    if (!file) {
        std::cerr << "Failed to open the file." << std::endl;
    }
    cout << "修改前的文件内容" << endl;
    string buf;
    while (getline(file, buf)) {
        cout << buf << endl;
    }

    // 在指定位置读取文件
    file.seekg(5, std::ios::beg);  // 从文件开头偏移5个字节
    char ch;
    file >> ch;
    std::cout << "Character at position 5: " << ch << std::endl;

    // 在指定位置写入文件
    file.seekp(10, std::ios::beg);  // 从文件开头偏移10个字节
    file << "XYZ";

    file.close();
}

int main() {
    test();
    return 0;
}

13.4 IO综合案例

13.4.1 键盘输入写文本文件

从控制面板输入信息,把这些内容写到指定的文本文件。第一步:获取键盘输入内容;第二步:将内容写入到对应文本文件。

int main() {
    ofstream ofs;
    ofs.open("yc.txt",ios::out);

    //第一步,从键盘获取数据
    //string data;
    char data[64];
    cout << "Writing to the file" << endl;
    cout << "Enter your name: ";
    //键盘输入数据
    cin.getline(data, 100);
    ofs << data << endl;
    cout << "Enter your age: ";
    cin >> data;
    cin.ignore();
    // 再次向文件写入用户输入的数据
    ofs << data << endl;
    cout << "Enter your book: ";
    cin >> data;
    cin.ignore();
    ofs << data << endl;
    ofs.close();
    return 0;
}

13.4.2 实现文件复制

需求:读取一个文件的内容,然后复制到另一个文件中。

void test() {
    //使用std::ifstream类打开源文件,并使用std::ofstream类创建目标文件。
    //我们使用std::ios::binary标志来以二进制模式打开文件,以确保正确复制文件的内容。
    std::ifstream source_file("yc.txt",std::ios::binary);
    std::ofstream destination_file("ycdoubi.txt",std::ios::binary);
    if (!source_file) {
        std::cout << "无法打开源文件" << std::endl;
        return;
    }
    if (!destination_file) {
        std::cout << "无法创建目标文件" << std::endl;
        return;
    }
    char ch;
    //使用get函数从源文件逐个字节读取内容,并使用put函数将字节写入目标文件,从而实现文件的复制。这个过程会一直进行,直到源文件的末尾。
    while (source_file.get(ch)) {
        destination_file.put(ch);
    }
    //请注意,这个示例是逐个字节复制文件的简单实现。对于大型文件,逐个字节的复制可能效率较低。
    //在实际应用中,您可以使用更高效的方法,如缓冲区复制或使用std::copy函数。
    source_file.close();
    destination_file.close();
    std::cout << "文件复制完成" << std::endl;
}

int main() {
    test();
    return 0;
}
贡献者: yangchong211
上一篇
12.内存分配堆和栈
下一篇
14.异常处理