【C++grammar】文件I/O流的基本用法

175 阅读5分钟

目录

1、输入输出类介绍

1.C/C++文件操作对比

在这里插入图片描述

2.什么是流?

流是一个数据序列。
一个流是有两个端点的,一边是data source(数据源),一边是程序。
一个I / O流表示输入源或输出目的地。流可以表示许多不同种类的源和目标,包括磁盘文件,设备,其他程序和内存阵列。
流支持许多不同类型的数据,包括简单字节,原始数据类型,本地化字符和对象。一些流只是传递数据;其他则以有用的方式操纵和转换数据。
无论它们在内部如何工作,所有流都向使用它们的程序提供相同的简单模型:流是数据序列。
程序使用输入流从源中一次读取一项数据:
在这里插入图片描述

程序使用输出流将数据写入目的地,一次写入一项:
在这里插入图片描述
摘自:I / O流

3.C++ I/O流类层次

在这里插入图片描述

C++的流类主要有五类:

  1.  流基类(ios_base和ios)
    
  2.  标准输入输出流类(istream/ostream/iostream)
    
  3.  字符串流类(istringstream/ostringstream)
    
  4.  文件流类(ifstream/ofstream/fstream)
    
  5.  缓冲区类(streambuf/stringbuf/filebuf)
    

标准输入输出流对象 cin 和 cout 分别是类 istream 和 ostream 的实例

字符串流:将各种不同的数据格式化输出到一个字符串中,可以使用I/O操纵器控制格式;反之也可以从字符串中读入各种不同的数据。

4.带缓冲的输入输出

C++的I/O流是有内部缓冲区的。
c = cin.get(void)每次读取一个字符并把由Enter键生成的换行符留在输入队列中

#include<iostream>

using namespace std;

int main() {
    char c;
    int i = 0;
    do {
        c = cin.get();
        cout << ++i << " : " <<
            static_cast<int>(c) << endl;
    } while (c != 'q');
    return 0;

}

在这里插入图片描述

本部分要展示的内容如下:
1、键盘输入一个字母后回车,实际进入缓冲区的是两个字符
2、使用cin输入信息后,有换行字符残留在缓冲区,从而导致return语句前的cin.get()不起作用

#include <iostream>

int main()
{
	//拿到cin对象的缓冲区指针

	auto p = std::cin.rdbuf();
	//从键盘读入字符到缓冲区,保留所有字符在缓冲区

	auto x = std::cin.peek();
	std::cout << "x= " << x << std::endl;
	//显示缓冲区中的字符数量
	//由于每次读取返回值都不一样,所以先将初始值保存
	auto count = p->in_avail();
	std::cout << "There are " << count << "characters in the buffer." << std::endl;
	//把缓冲区的字符都取出来并显示
	for (int i = 0; i < count; i++)
	{
		std::cout << i + 1 << ":" << std::cin.get() << std::endl;
	}

	std::cin.get();
	return 0;
}

5.gcc编译器cin.in_avail()

在使用gcc编译器的时候,存在这样一种意外的情况:

无论输入多少个字符, cin.in_avail()函数返回值永远是0。

这是由于GCC编译器中配套的libstdc++实现中的问题(可以看作是标准库gcc实现的一个bug,或者一个特性)。

解决办法是,在使用cin之前,插入下面一行代码

cin.sync_with_stdio(false);

简单解释原因:GCC的libstdc++默认是保证C++的cin与C的stdin同步的。

2、向文件写入数据

ofstrem可向文本文件中写数据.
输出数据的流程:
在这里插入图片描述
文件已存在,则直接清除内容。
Writing Data to a File – Auto type recognition (自动类型识别)

1.写文件小练习

本部分要展示的内容如下:
1、创建文件输出流
2、向文件写数据
output << “Lilei” << " " << 90.5 << endl;
output << “HanMeimei” << " " << 85 << endl;
3、关闭文件
4、用文本编辑器打开文件,对比代码语句检查结果

//std::c++latest
#include <fstream>
#include <iostream>
#include <filesystem>

using std::ifstream;
using std::ofstream;
using std::cout;
using std::endl;
namespace fs = std::filesystem;

int main()
{
	//第一步,关联文件

	fs::path p{ "scores.txt" };

	//第二步,创建一个流输出对象
	ofstream output{ p };

	double lileiScore{ 90.5 };
	int hanmeimeiScore{ 84 };

	output << "Lilei " << lileiScore << endl;
	output << "HanMeimei " << hanmeimeiScore << endl;

	output.close();

	cout << "size of " << p << " is " << fs::file_size(p) << endl;
	std::cin.get();
	return 0;
}

打开一个输出文件流的方法包括:
在这里插入图片描述

2.如何将信息同时输出到文件和屏幕?

在软件的调试技术中,很重要的一个技术是将软件运行过程中的一些信息写入到“日志文件”中。但是同时还要将信息显示到屏幕上,以方便程序员实时查看这些信息。
最简单的一种办法是这样的:

std::ofstream output("debug.log", ios::out);
output << __FILE__ << ":" << __LINE__ << "\t" << "Variable x = " << x;
cout << __FILE__ << ":" << __LINE__ << "\t" << "Variable x = " << x;

下面使用streambuf构造一个自己的类,来实现这个功能

#include <streambuf>
#include <iostream>
#include <fstream>

//Linux tee命令用于读取标准输入的数据,并将其内容输出成文件。
//tee指令会从标准输入设备读取数据,将其内容输出到标准输出设备,同时保存成文件。
class teebuf : public std::streambuf
{
public:
    // Construct a streambuf which tees output to both input
    // streambufs.
    teebuf(std::streambuf* sb1, std::streambuf* sb2)
        : sb1(sb1)
        , sb2(sb2)
    {
    }
private:
    // This tee buffer has no buffer. So every character "overflows"
    // and can be put directly into the teed buffers.
    virtual int overflow(int c)
    {
        if (c == EOF)
        {
            return !EOF;
        }
        else
        {
            int const r1 = sb1->sputc(c);
            int const r2 = sb2->sputc(c);
            return r1 == EOF || r2 == EOF ? EOF : c;
        }
    }

    // Sync both teed buffers.
    virtual int sync()
    {
        int const r1 = sb1->pubsync();
        int const r2 = sb2->pubsync();
        return r1 == 0 && r2 == 0 ? 0 : -1;
    }
private:
    std::streambuf* sb1;
    std::streambuf* sb2;
};

class teestream : public std::ostream
{
public:
    // Construct an ostream which tees output to the supplied
    // ostreams.
    teestream(std::ostream& o1, std::ostream& o2);
private:
    teebuf tbuf;
};

teestream::teestream(std::ostream& o1, std::ostream& o2)
    : std::ostream(&tbuf)
    , tbuf(o1.rdbuf(), o2.rdbuf())
{
}

int main()
{
    std::ofstream output("debug.log");
    //1、创建文件/屏幕输出流对象tee
    teestream tee(std::cout, output);

    auto x = 1.1;
    tee << __FILE__ << ":" << __LINE__ << "\t" << "Variable x = " << x;

    return 0;
}

效果:
在这里插入图片描述

3、从文件读数据

ifstrem可从文本文件中读数据,并检测文件是否成功打开。
在这里插入图片描述
了解数据格式
若想正确读出数据,必须确切了解数据的存储格式。
用流提取运算符从文件流中读数据,所读入的信息的长度与流提取运算符右侧的变量的类型有关。

1.检测文件是否成功打开

可能出现错误:

1、读文件时文件不存在
2、写文件时介质只读

检测文件是否正确打开的方法:

1、open()之后马上调用fail()函数
2、fail()返回true, 文件未打开

ofstream output("scores.txt");
if (output.fail())  {
    cout << R"(Can't open file "scores.txt"!)";
  }

2.检测是否已到文件末尾

若你不知道文件有多少行,还想把他们全读出来,用eof()函数检查是否是文件末尾
由于get函数返回值是int,所以需要强制类型转换再进行屏幕输出。

ifstream in("scores.txt");
while (in.eof() == false) {
  cout << static_cast<char>(in.get());
}

3.读取文件小练习

本部分要展示的内容如下:
1、创建文件输入流,打开文件score.txt
2、用>>从文件读取数据
3、使用fail()函数检测文件是否打开
4、将读文件语句放入循环中,使用eof()作为循环条件
5、关闭文件

//std::c++latest
#include <fstream>
#include <iostream>
#include <filesystem>
#include <string>
using std::ifstream;
using std::ofstream;
using std::cout;
using std::endl;
using std::string;
namespace fs = std::filesystem;

int main()
{
	//第一步,关联文件

	fs::path p{ "scores.txt" };

	//第二步,创建一个流输入对象
	ifstream input{ p };

	//第三步使用fail判断流是否正常打开
	if (input.fail())
	{
		cout << "Can't open file " << p << endl;
		std::cin.get();
		return 0;
	}
	//第四步,定义一些变量去存这些数据
	string name{ "" };
	double score{ 0.0 };

	//读取文件中的姓名和分数
	//input >> name >> score;
	//cout << name << " " << score << endl;
	//input >> name >> score;
	//cout << name << " " << score << endl;
	
	while (input.eof() == false)
	{
		cout << static_cast<char>(input.get());
	}

	std::cin.get();
	return 0;
}

在这里插入图片描述

4.bad()函数能否用来判断文件流是否成功打开?

在ifstream和ofstream类中,除了fail()函数之外,还有bad()函数。

那么我们能否用bad()函数取代fail()函数判断流是否成功打开?

不能,bad() 如果出现意外的问题,如文件受损或硬件故障,最后一次读取数据的时候发生了这样的问题,方法bad()将返回true.