File Input/Output

What is IO?

Input/Output is usually stylized as I/O or IO

The concept of:

  • Sending information from inside our program to an outside system
  • Receiving information from an outside system and using it inside our program

We can send and receive information from many sources:

  • Operating system
  • Network
  • Files
  • Databases
  • etc.

Files

Files are units of data stored in the computer’s file system. Files exist in most operating systems.

The concept of a file is based on the metaphor of a file cabinet. A file cabinet has folders which contain pieces of paper (files). The pieces of paper have writing on them (data).

Files have data and attributes.

Data

Files contain a sequence of bytes, like 01000001 01110000 01110000 .... This is their data.

  flowchart LR
    A@{ shape: card, label: "**File**
        01000001 01110000 01110000 01101100
        01100101 01110011 00100000 01100001
        01110010 01100101 00100000 01110100
        01100001 01110011 01110100 01111001
        00100001
    " }

Attributes

AttributeDescriptionExample
FilenameThe unique identifier of the file. Usually has an extension indicating the type of data the file contains.pokemon.txt
SizeThe number of bytes in the file1,024 bytes
Last ModifiedThe operating system keeps track of the date when the file was most recently changed28 Feb 2025 10:09 AM
PermissionsThe operating system can allow only certain users or processes from accessing a file. Certain operations can be restricted too.Allowed Users: admins; Allowed Operations: read-only

Binary vs. Text Files

There are two main types of files: binary and text files.

Binary

The contents of binary files are just their bytes, as-is. These files are not human-readable. Opening them in a text editor will result in meaningless characters.

  flowchart LR
    A@{ shape: card, label: "**Binary File**
        01000001 01110000 01110000 01101100
        01100101 01110011 00100000 01100001
        01110010 01100101 00100000 01110100
        01100001 01110011 01110100 01111001
        00100001
    " }
Binary File Examples
CategoryFile types
Images.jpg, .png
Audio.mp3, wav
Video.mp4 .mov
Executable Machine Code.exe, .o, or no extension

Text File

The contents of a text file represent characters, according to an encoding, like ASCII or UTF-8. These files are human-readable. Opening a text file in a text editor will result in human-readable characters, because the text editor applies the encoding, transforming the bytes into characters.

000000110111011111000001010001010100000000T100111ex000000t111010111111F100010i000000l010000e00001001101100000111111211111111110000000011A01100p01000TpelxetsEadrietotsrapsalaeay-tcyex!Decsopdpeaas!ecdepsrtt
Text File Examples
CategoryFile types
XML.xml
JSON.json
Tab-Delimited.tsv
Comma-Delimited.csv
Unstructured.txt

File IO

File IO is implemented with streams. Why might file IO be implemented with streams?

  • Reduced Memory Use — Reading an entire file into memory is often unnecessary. Streams let us examine just one “chunk” at a time, saving memory.
  • Speed — Reading an entire file at once could be very slow, especially if the file is massive. Streams let us control the amount we read, saving time.

Implementation

Provided by fstream in the C++ Standard Library. The functionality is separated into two classes:

  1. ifstream — Input File Stream
  2. ofstream — Output File Stream

ifstream

ifstream is a stream for reading input from a file.

input file stream
i     f    stream   →  ifstream
  flowchart LR
  A[File]-- ifstream-->B[C++ Program]

ofstream

ofstream is a stream for writing output to a file.

output file stream
o      f    stream  →  ofstream
  flowchart LR
  A[C++ Program]-- ofstream-->B[File]

Example

1
2
3
4
5
6
7
8
#include <fstream> // include fstream
using namespace std;

int main() {
    // ifstream & ofstream are now available for use
    ifstream variable_name;
    ofstream another_variable_name;
}

Quiz

  1. Which stream can work with the operating system IO?

    Answer: cin & cout

  2. Which streams can work with file IO?

    Answer: ifstream & ofstream

  3. Which manipulators can affect the cin & cout streams?

    Answer:

    • Make tables with setw(n),left,right,setfill(c)
    • Format Decimals with setprecision(n),fixed,scientific,showpoint
    • Affect the Buffer with endl,flush
  4. Which manipulators can affect the ifstream & ofstream streams?

    Answer: The same ones!

    This is a benefit of the stream concept. The same techniques can be shared among different streams, reusing your existing knowledge.

Reading & Writing Files

How to Read a Text File

  1. Allocate an ifstream
  2. Open the file with its name
  3. Read the file with stream functions
  4. Close the file

How to Write a Text File

  1. Allocate an ofstream
  2. Open the file with its name
  3. Write to the the file with stream functions
  4. Close the file

ifstream Interface

MethodUse
.open(s)Open a file by file name (string)
.is_open()Whether the .open(s) action was successful
.fail()Whether the most recent “read” action was successful
.close()Close the connection to the file
ifstream >> variableExtract file contents into the variable, stopping at the next whitespace
getline(ifstream, variable)Extract file contents into the variable, stopping at the next newline

zyBook Table 10.3.1: Stream error state flags and functions to check error state

Reading a Text File

Read pokemon.txt into a vector<string>

pokemon.txt

1
2
3
Bulbasaur
Charmander
Squirtle

main.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <string>
#include <fstream>

int main() {
    // Input Stream entity. It "streams" the contents of the file.
    ifstream pokemonFS;

    // Open the file by filename
    pokemonFS.open("pokemon.txt");

    // To hold each line, one at a time
    string line;

    // Read each line of the file
    while (pokemonFS >> line /* getline(pokemonFS, line) */) {
        // ...
    }

    // Close the file
    pokemonFS.close();
}

Choosing the “Chunk”

1
fileFS >> line        // reads until next whitespace
1
getline(fileFS, line) // reads until next newline

Analysis

How does this work?

  • ifstream provides an interface to access the file system, and process the text file.

  • line provides a “container” to store each piece of the file as a string, as it is “streamed”.

  • while loops until (implicit) call to .fail() returns true, either because there is nothing left to read or an error occurred.

Why does it work?

What type of expression does while expect?

1
while(pokemonFS >> line) { /* ... */ }

while expects a boolean.

  • >> and getline() both return a reference to the stream.
  • ifstream can be converted into a bool implicitly by the compiler.
  • The result of the .fail() method is automatically.
  • Trying to read after end-of-file is reached causes .fail() to return true.
  • Therefore, the while loop ends when a read is attempted after end-of-file is reached.

Other Resources:

Another Option

We can also call .fail() directly to stop reading at end-of-file, but we must do the checks in a certain order.

This approach is not recommended because it is easy to get the operations out of order by mistake.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// We must read the first line before starting the loop
pokemonFS >> line;

// Check if read failed in the previous loop
while(!pokemonFS.fail()) {

    cout << line;

    // We must read another chunk only after using line
    pokemonFS >> line;
}

Writing a Text File

Let’s extend the program above to copy the text input file, adding line numbers. Use the insertion operator << to insert into an ofstream.

pokemon.txt

1
2
3
Bulbasaur
Charmander
Squirtle

main.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <fstream>
using namespace std;

int main() {
    ifstream fin;
    ofstream fout; // Open the output file, too

    // Open both files by filename
    fin.open("pokemon.txt");              // input file
    fout.open("pokemon-with-numbers.txt"); // output file

    string line;
    int number = 0;
    // Read each line of the input file
    while (getline(fin, line)) {
        number += 1;
        fout << number << ". " << line << endl;
    }

    // Close both files
    fin.close();  // input file
    fout.close(); // output file
}

Write Modes

By default, writing to an ofstream will overwrite the contents of the file. We can change that behavior by providing a second argument to the open method.

1
2
// Overwrite contents
fout.open("filename.txt");
1
2
// Append each write
fout.open("filename.txt", ios::app);

Append to File Exercise

Extend the Program above to append the contents of pokemon-chunk.txt to the output file. Note: Each time we run the code, the contents of pokemon-chunk.txt will be appended again.

What will be the expected output, given the following files? pokemon.txt

1
2
3
Bulbasaur
Charmander
Squirtle

pokemon-chunk.txt

1
2
Caterpie
Metapod

What will be the expected output if we run the code again?

ifstream & ofstream in Functions

File streams can be passed into functions as arguments. This is useful for abstracting logic into a function for reuse.

File streams must be passed by reference.

Why? ifstream & ofstream are not copyable because streams are intended to exist once and be shared throughout your code. Passing by value would require making a copy.

Syntax

1
2
3
4
5
6
7
8
9
// Adds all numbers in the file stream
int sum(ifstream &fin) {
    int num;
    int sum = 0;
    while (fin >> num) {
        sum += num;
    }
    return sum;
}

Buffers

Writing and reading files in the underlying operating system is slow. File streams increase utilizing a buffer to batch the reads and writes made by our code. This has two benefits:

  1. Increased read & write speed
  2. Increased disk life. Each write to the file system is one step closer to the disk wearing out.

Reading

  • Opening the file creates a buffer & fills it up with the first chunk of the file.
  • Reading moves the data from the buffer to the destination in your code.
  • The buffer “automatically” refills itself when it needs to supply more data but it is already empty.
  flowchart LR

F@{shape: docs, label: File System}
A(Code)
B(Buffer)
O((Open))
O-- Creates -->B
F-- Refills When Empty -->B
B-- Read -->A

Writing

  • Opening the file for writing creates an empty buffer.
  • Writing streams data from your code into the buffer.
  • The Buffer is written to disk each time it becomes full. Closing the file writes the remaining contents of the buffer to disk, if any.
  flowchart LR
F@{shape: docs, label: File System}
B(Buffer)
O((Open))
A(Code)

O -- Creates -->B
B-- Writes When Full -->F
A-- Write -->B

Errors

There are several errors which can happen when working with file streams.

Summary

ProblemSolution
Nonexistent File.is_open()
Improper Variable TypeDon’t make mistakes ☹️
Reading Past EOFStop reading when .fail() == true
Overwriting Existing FileDetect whether the file exists before writing

Problem: Opening a file which does not exist

A file with the provided name might not exist, or the code may be denied access. Check that the file is open with .is_open() before reading.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
ifstream pokemonFS;
string line;

// Mistake in filename
string filename = "pokem0n.txt";
pokemonFS.open(filename);

// Evaluates to `false`!
while (pokemonFS >> line) {
    // ...
}

// Proceeds without reading any file contents...

Fixed by terminating the program if the file could not be opened.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
ifstream pokemonFS;
string line;

// Mistake in filename
string filename = "pokem0n.txt";
pokemonFS.open(filename);

// Terminate application, since we can't do anything useful without the file
if (!pokemonFS.is_open()) {
    cout << "Could not open file " + filename ". Does it exist?";
    return 1;
}

while (pokemonFS >> line) {
    // ...
}

Problem: Assigning data to a variable which cannot hold the data

If we assign content to a variable which cannot hold it, there will be an error.

1
2
3
4
5
6
7
ifstream fin;
int num;

// ❌ getline() returns string, which cannot be assigned to num
while (getline(fin, num)) {
    //...
}

Fixed by storing into a variable with the right type.

1
2
3
4
5
6
7
ifstream fin;
int num;

// ✅ The Extraction operator can convert the characters into an `int`
while (fin >> num) {
    //...
}

Problem: Trying to read when there is nothing left

If we attempt to read when there is nothing to read, logic errors will result. We must check if the read was successful each time, before using the data. Stop reading at the end-of-file. Use an idiom which handles EOF and errors. See discussion above.

Example

pokemon.txt

1
2
3
Bulbasaur
Charmander
Squirtle

main.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
ifstream pokemonFS;
string bulbasaur;
string charmander;
string squirtle;
string caterpie;

pokemonFS >> bulbasaur;
pokemonFS >> charmander;
pokemonFS >> squirtle;
pokemonFS >> caterpie; // ❌ No 4th chunk!

Fixed by looping until end-of-file

1
2
string line;
while(pokemonFS >> line) { /* ... */ }

Problem: Accidentally Overwriting an existing File

Writing to a file will overwrite any existing file with the same name. A programmer can detect whether a given already file exists by attempting to read the file, then checking .fail().

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// I hope this does not already exist
const string filename = "receipt.txt";

ifstream does_it_exist;
string trash_can;

// Try to open the file and read a line
does_it_exist.open(filename);
does_it_exist >> trash_can;

if (does_it_exist.fail())
{
    // File is empty or does not exist. It is safe to write
    ofstream fout;
    fout.open(filename);
    fout << "very important data";
    fout.close();
}
else
{
    cout << "File " << filename << " already exists!" << endl;
}

Appendix: Example Programs

Read a TSV File

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Number	Name	Type
1	Bulbasaur	Grass/Poison
2	Ivysaur	Grass/Poison
3	Venusaur	Grass/Poison
4	Charmander	Fire
5	Charmeleon	Fire
6	Charizard	Fire/Flying
7	Squirtle	Water
8	Wartortle	Water
9	Blastoise	Water
10	Caterpie	Bug
11	Metapod	Bug
12	Butterfree	Bug/Flying
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <string>
#include <vector>
#include <fstream>
#include <iostream>
using namespace std;

vector<string> parse_tsv_row(string &row, char separator = '\t')
{
    vector<string> out;
    int start = 0;
    int found = 0;
    while (found != -1)
    {
        found = row.find(separator, start);
        string column = row.substr(start, found - start);
        start = found + 1;

        out.push_back(column);
    }
    return out;
}

const string TSV_FILENAME = "pokemon.tsv";
const string SEARCH_TYPE = "Fire";
const int TSV_INDEX_FOR_TYPE = 2;

int main()
{
    ifstream fin;
    string line;

    // Output
    int count = 0;

    // Open file
    fin.open(TSV_FILENAME);

    // Discard Titles row
    getline(fin, line);

    // Count each row where type is "grass"
    while (getline(fin, line))
    {
        // Parse Each TSV Row
        vector<string> data = parse_tsv_row(line);

        // Access "Type" column
        string type = data.at(TSV_INDEX_FOR_TYPE);
        // Determine if the Pokémon has the type we are searching for
        if (type.find(SEARCH_TYPE) != -1)
        {
            count += 1;
        }
    }

    cout << "Found " << count << ' ' << SEARCH_TYPE << "-type Pokémon" << endl;
}