Stream In Computer

iostream, fstream, stringstream……所以stream(流)到底是个啥。

在上次的一篇文章中说到,stream的本质就是一个“线性”数据结构:所谓线性(当然不是说满足线性空间的八大条件)是指其有类似于数列的结构,你可以在上面通过读头head来顺序访问

还是举个例子比较好,就拿最简单的stringstream举例子:

首先,我们考察这样的字符串

"123 456 1.3"

将其作为一个stream,我们假设此时读头指向第一个字符:

"123 456 1.3"
 ^(head)

那么,其线性体现在 head 可以在其上进行移动,例如前进4位seek(4)

seek(4 /*前进 4 步*/, ios::cur /* 从当前位置开始数 */)

"123 456 1.3"
     ^(head)

访问是指,可以读取当前读头下的值:

get() // return '4'
"123 456 1.3"
      ^(head)

可以写入当前读头下的值:

put('7')
"123 476 1.3"
       ^(head)

通常情况下,getput函数还会使得head向前前进一步。同时,为了更好的在stream上定位,其还提供获取当前位置(相对于起始位置)的方法:

tell() // return 6
"123 456 1.3"
        ^(head)

总结一下,stream是这样一个线性数据结构:

  1. seek:设置head位置
  2. tell:获取head的相对于
  3. put:将值写入head位置,并前进
  4. get:获取head下的值,并前进

被封装的stream

那iostream帮你做了啥呢,简而言之,就是在这样的数据结构上帮你做了常用类型在流上的读写。

比如,我们的输出流cout

cout << 13;

他在控制台输出了13,但是如何实现的呢?

假设我们之前输出了:

"abca 123 " // this is our `cout` stream.
          ^(head)

这里head指向了流的末尾

那么后一步:

"abca 123 13" // this is our `cout` stream.
            ^(head)

13被我们输出到了流的末尾。

那 cin 做了啥呢,假设我们输入了"123 456 1.3"并执行如下的代码:

cin >> n >> m >> x; // assume n, m is int, x is double 

执行过程如下:

// 啥都没读
"123 456 1.3"
 ^(head)
// read n
"123 456 1.3"
     ^(head)
// read m
"123 456 1.3"
         ^(head)
// read x
"123 456 1.3"
            ^(head)
// done.

文件流

好吧,至此你应该理解文件和你手动输入实际上没啥区别了,都只是在一个流结构上进行读写罢了。使用文件流需要你加一句。

#include <fstream>

常见的文件操作如下:

打开文件

ifstream file("input.txt");  // ifstream stand for input  file stream
ofstream file("output.txt"); // ofstream stand for output file stream

我推荐使用这样的语法,符合C++RAII的原则。

这样写等价于课上提到的:

ifstream file;
file.open("input.txt");

判断是否打开

我们处理文件的第一个事情一定是判断是否成功打开了文件。

file.is_open()

非常重要!

读写

读写实际上和cin/cout是相通的。

只不过读的时候,通常需要判断是否文件已经读完:

file.eof() // eof() stand for End Of File

例如,在cout中输出一个文件的所有内容:

while (! file.eof()) {
  char c;
  c = file.get();
  cout << c;
}

课上类似于(不推荐现在写!)

while (s >> n) {
  std::cout << n << '\n';
}

解释需要类的知识:https://en.cppreference.com/w/cpp/io/basic_ios/operator_bool

关闭文件

实际上没必要,因为遵循raii的原则,c++应该在destructor上自动关该文件。

file.close();

说实话c++的文件还挺复杂的,不需要在上面花太多时间。

A demo

请在你的作业中使用绝对路径

e.g.

  1. D:\\cpp-homework\\data.txt (for windows)
  2. ~/Documents/my_homework/data.txt (for mac)

读入一个文件的所有内容并打印在控制台上

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

int main () {
  // Declare a input file stream.
  ifstream input_file;

  // 1. Open the file: full path is recommended here.
  input_file.open("D:\\data\\input_data.txt");

  // 2. Check the file is opend correctly:
  if (! input_file.is_open()) {
    // error: file is not opened.
    cout << "Cannot open file." << endl;
    // exit the program.
    return -1;
  }
  // 3. Read the file, until End Of File (eof)
  while (! input_file.eof()) {
    char c;

    // Get one char from file.
    input_file.get(c);
    // If the operation failed, break the loop.
    if (! input_file.good()) {
      break;
    }
    
    // else print the char c in console.
    cout << c;
  }

  // 3. (Optional) Close the file.
  input_file.close();
  return 0;
}

输出所有素数到文件:

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

bool is_prime(int n) {
  if (n == 1) {
    return false;
  }

  for (int i = 2; i < n - 1; ++i) {
    if (n % i == 0) {
      return false;
    }
  }
  return true;
}


int main () {
  // Declare an output file stream.
  ofstream output_file;

  // 1. Open the file: full path is recommended here.
  output_file.open("D:\\Data\\output_data.txt");

  // 2. Check the file is opend correctly:
  if (! output_file.is_open()) {
    // error: file is not opened.
    cout << "Cannot open file." << endl;
    // exit the program.
    return -1;
  }
  
  // 3. write the file, the operation is similar to cout.
  for (int i = 1; i < 100; ++i) {
    // Here, write the primes between [1, 100) to output_file.
    if (is_prime(i)) {
      output_file << i << endl;
    }
  }

  // 3. (Optional) Close the file.
  output_file.close();
  return 0;
}