This episode covers the basic std::istream and std::ostream streams, file I/O with std::ifstream, std::ofstream, and std::fstream, formatted input output with manipulators, binary I/O, as well as error handling and checking whether a file exists.

Useful programs almost always read or write data — from keyboards, screens, text files, to binary files. C++ packages all of this into a single abstraction: the stream, a sequential flow of data that doesn't care whether the source or destination is a console or a disk.
Episode 9 covers streams thoroughly: the basics of std::istream and std::ostream, file I/O with std::ifstream and std::ofstream, output formatting with manipulators, efficient binary storage, and how to detect errors when an I/O operation fails.
All C++ I/O operations go through streams. std::cout is the ostream to the screen, std::cin is the istream from the keyboard, std::cerr is the error ostream to stderr. The << operator writes and >> reads:
cat > stream.cpp <<'EOF'
#include <iostream>
int main() {
int a = 5;
double b = 2.25;
std::cout << "Angka: " << a << " dan " << b << "\n";
std::cerr << "Ini pesan error\n";
int input;
std::cout << "Ketik angka: ";
std::cin >> input;
std::cout << "Kalian mengetik " << input << "\n";
}
EOF
g++ -std=c++20 stream.cpp -o stream
./streamstd::cout << a streams the value of a to the output stream. Streams can be chained because << returns the same stream. std::cerr is used for errors because it isn't buffered and appears immediately.
The >> operator stops at whitespace. To read a full line, use std::getline — the pattern while (std::getline(std::cin, baris)) reads lines and stops when input runs out, because getline returns a stream that evaluates to false at the end of input.
std::ofstream writes to a file, std::ifstream reads from a file, and std::fstream does both. Files are opened via the constructor or open, and closed automatically by RAII:
cat > file.cpp <<'EOF'
#include <iostream>
#include <fstream>
#include <string>
int main() {
std::ofstream out("data.txt");
out << "Baris pertama\n";
out << "Baris kedua\n";
out.close();
std::ifstream in("data.txt");
std::string baris;
while (std::getline(in, baris)) {
std::cout << baris << "\n";
}
}
EOF
g++ -std=c++20 file.cpp -o file
./file
cat data.txtstd::ofstream out("data.txt") opens a file for writing (creating it if it doesn't exist). out.close() closes it explicitly; if you don't, the destructor closes it. Reading uses the same pattern with getline.
Files can be opened with specific modes via a second parameter: std::ios::app to append, std::ios::trunc to overwrite, std::ios::binary for binary, and std::ios::in or std::ios::out for direction. The mode std::ios::app ensures new data is added at the end of the file without deleting previous contents — important for log files and history.
Manipulators change how a stream displays data without changing the underlying values. std::setw sets the column width, std::setprecision sets the number of decimal places, and std::fixed forces fixed decimal notation. The combination std::fixed << std::setprecision(2) displays two digits after the decimal point, and std::setw(10) << std::right right-aligns numbers in a column 10 wide. In C++20, std::format offers a cleaner way.
Binary files store data in its original form without text conversion — more compact and faster for large numerical data. Data is written byte by byte with write and read with read:
cat > biner.cpp <<'EOF'
#include <iostream>
#include <fstream>
int main() {
int data[3] = {10, 20, 30};
std::ofstream out("angka.bin", std::ios::binary);
out.write(reinterpret_cast<char*>(data), sizeof(data));
out.close();
int baca[3] = {0, 0, 0};
std::ifstream in("angka.bin", std::ios::binary);
in.read(reinterpret_cast<char*>(baca), sizeof(baca));
in.close();
std::cout << baca[0] << " " << baca[1] << " " << baca[2] << "\n";
}
EOF
g++ -std=c++20 biner.cpp -o biner
./binerout.write(reinterpret_cast<char*>(data), sizeof(data)) writes the entire array as a raw byte block. reinterpret_cast is needed because write and read work on char*. Binary files aren't portable across platforms with different byte orders — a topic for episode 13.
Every stream stores a status: good(), fail(), eof(), and bad(). Before processing a file, check whether the open succeeded:
cat > cekfile.cpp <<'EOF'
#include <iostream>
#include <fstream>
int main() {
std::ifstream in("tidak-ada.txt");
if (!in) {
std::cerr << "Gagal membuka file\n";
return 1;
}
std::cout << "File terbuka\n";
}
EOF
g++ -std=c++20 cekfile.cpp -o cekfile
./cekfileThe pattern if (!in) is the standard idiom: it's true if the open failed or the stream has problems. Always check this status before reading.
Warning
Don't use eof() as a loop condition. The while (!in.eof()) condition processes data one extra time because the eof flag only becomes active after reading past the end of the file.
Here's what to take away:
istream reads, ostream writes; connected by the >> and << operators.ifstream, ofstream, and fstream handle text and binary files.close is optional.setw and setprecision control format without changing values.write and read for compact raw data.if (!stream) before processing a file.In the next episode, episode 10, we'll discuss templates and generic programming — function templates and class templates, template specialization and variadic templates, concepts, type traits, and SFINAE, as well as std::optional and std::variant for values that may be empty or of many types.