Overview of File Output
Assumptions: (from CS120)Basics:
The fstream header actually contains definitions for two types: ifstream and ofstream.#include <fstream> // No .h extension
| Code | Output in foo.txt (showing the new lines) |
|---|---|
|
This is a line of text<NL> Another line of text<NL> An integer: 42<NL> A double: 3.1415<NL> |
We can open the file in the constructor:
void f2()
{
// instantiate and open output file
std::ofstream outfile("foo.txt");
// Write some data to the file
outfile << "This is a line of text" << std::endl;
outfile << "Another line of text" << std::endl;
outfile << "An integer: " << 42 << std::endl;
outfile << "A double: " << 3.1415 << std::endl;
} // stream is closed in ~ofstream()
Streaming a user-defined type:
| Code | Output (in file1.txt) |
|---|---|
|
login: jdoe<NL> age: 22<NL> year: 4<NL> GPA: 3.76<NL> 00:01:30<NL> |
| Student class |
|---|
|
| StopWatch class |
|---|
|
void f4()
{
// Open and check file status
std::ofstream outfile("foo.txt");
if (outfile.is_open())
{
// Write some data to the file
outfile << "This is a line of text" << std::endl;
outfile << "Another line of text" << std::endl;
outfile << "An integer: " << 42 << std::endl;
outfile << "A double: " << 3.1415 << std::endl;
}
else
std::cout << "Can't open file for output.\n";
}
Rebinding files:
| Code | Output (in files) |
|---|---|
|
file1.txt: Blah, blah, blah<NL> Blah, blah, blah<NL> Blah, blah, blah<NL> file2.txt: Blah, blah, blah<NL> Blah, blah, blah<NL> Blah, blah, blah<NL> file3.txt: Blah, blah, blah<NL> Blah, blah, blah<NL> Blah, blah, blah<NL> |
Aside:
Comparing the use of vector and string instead of an array:
| vector<const char *> | vector<string> |
|---|---|
|
|
This code will work with any container:
std::vector<std::string>::iterator it;
for (it = fnames.begin(); it != fnames.end(); ++it)
{
outfile.open((*it).c_str()); // could use it->c_str()
if (!outfile.is_open())
continue;
// Other code...
}
This demonstrates again how iterators are very powerful and flexible and why using
iterators instead of subscript operators can lead to more useful (reusable) code.
An even more C++-like implementation might look like this:
Or with C++11 uniform initializer syntax:
void f8c() { std::vector<std::string> fnames; fnames.push_back("file1.txt"); fnames.push_back("file2.txt"); fnames.push_back("file3.txt"); for_each(fnames.begin(), fnames.end(), PrintToFile); }void PrintToFile(const std::string& fname) { std::ofstream outfile(fname.c_str()); if (!outfile.is_open()) return; outfile << "Blah, blah, blah" << std::endl; outfile << "Blah, blah, blah" << std::endl; outfile << "Blah, blah, blah" << std::endl; }
void f8d()
{
std::vector<std::string> fnames {"file1.txt", "file2.txt", "file3.txt"};
for_each(fnames.begin(), fnames.end(), PrintToFile);
}
And while we're at it, using a lambda expression (or anonymous function):
void f8e(void)
{
std::vector<std::string> fnames {"file1.txt", "file2.txt", "file3.txt"};
for_each(fnames.begin(), fnames.end(),
[](const std::string& fname)
{
std::ofstream outfile(fname.c_str());
if (!outfile.is_open())
return;
outfile << "Blah, blah, blah" << std::endl;
outfile << "Blah, blah, blah" << std::endl;
outfile << "Blah, blah, blah" << std::endl;
}
);
}
Overview of File Input
File input is very similar to the use of cin.
Given the text in the file foo.txt from above and showing the spaces and newlines:
We can read the words back into the program one at at time:This·is·a·line·of·text¶ Another·line·of·text¶ An·integer:·42¶ A·double:·3.1415¶
| Code | Output |
|---|---|
|
This is a line of text Another line of text An integer: 42 A double: 3.1415 3.1415 |
| Using FILE pointers | Using C++ streams |
|---|---|
|
|
Reading in an entire line at a time:
| Using FILE pointers w/C-style strings | Using streams w/std::string |
|---|---|
|
|
| Using FILE pointers w/C-style strings | Using streams w/std::string |
|---|---|
|
|
File Modes
In C, we opened files with the fopen function. It had a signature like this:The mode parameter specified different attributes of the file:FILE *fopen( const char *filename, const char *mode );
| Read (input) Write (output) Append (output)
--------+----------------------------------------------
Text | "r" "w" "a"
Binary | "rb" "wb" "ab"
C++ streams use a slightly different approach to modes using flags. These flags are very similar to the ones
used by the cin and cout objects and modify their behavior.
Intro to I/O.
Mode Meaning ios_base::in Open file for input (default for ifstream) ios_base::out Open file for output (default for ofstream) ios_base::app Seek to the end before each write (append) ios_base::trunc Truncate file (delete contents) after opening (default for ofstream) ios_base::binary Open file in binary mode
Sample usage:
ofstream os;
os.open("somefile.bin", ios::app | ios::binary | ios::out);
More information on C++ streams
In-Depth Example
The assignment is to create a program similar to the Unix (Cygwin) program wc. (Reference)
Directory of E:\Data\Courses\Notes\CS170\Code\mywc
03/19/2020 03:10p 819 Circle.cpp
03/19/2020 03:10p 2,191 fibonacci.cpp
03/19/2020 03:10p 4,246 Ocean.cpp
03/19/2020 03:10p 11,959 Polygon.cpp
03/19/2020 03:10p 164 WarBoats.cpp
5 File(s) 19,379 bytes
0 Dir(s) 5,421,133,824 bytes free
Running the command:
produces this output:wc Circle.cpp fibonacci.cpp Ocean.cpp Polygon.cpp WarBoats.cpp
50 110 819 Circle.cpp 87 265 2191 fibonacci.cpp 158 450 4246 Ocean.cpp 402 1353 11959 Polygon.cpp 14 20 164 WarBoats.cpp 711 2198 19379 total
| High-Level Plan | Detailed Plan | |
|---|---|---|
|
|
// Print the counts and filename formatted
void print_results(size_t char_count, size_t word_count,
size_t line_count, const std::string& filename)
{
std::cout << std::setw(10) << line_count;
std::cout << std::setw(10) << word_count;
std::cout << std::setw(10) << char_count;
std::cout << " " << filename << std::endl;
}
The function where the "real" work is done: (Process the file)
void CountLWC(const std::string& filename)
{
// 1. Open the text file for reading
std::ifstream infile(filename.c_str());
if (!infile.is_open())
std::cout << "Can't open file: " << filename << std::endl;
else
{
// 2. Initialize the counters
size_t char_count = 0; // characters
size_t word_count = 0; // words
size_t line_count = 0; // lines
// 3. For each line in the file
while (!infile.eof())
{
// 1. Read an entire line from the file
std::string line;
if (std::getline(infile, line).eof())
break;
line_count++; // 2. Increment line count
char_count += line.size() + 1; // 3. Increment char count (Account for newline)
// 4. Count words in the line
std::string word;
std::stringstream words(line);
while (!words.eof())
{
words >> word; // Try to read next word
if (!words.fail()) // If there was a next word
word_count++; // count it
}
}
// 4. Print out the counts and filename.
print_results(char_count, word_count, line_count, filename);
}
} // 5. Close the file (The file is closed automatically in the destructor.)
The main function:
int main(int argc, char *argv[])
{
// Need at least one filename
if (argc < 2)
{
std::cout << "Usage: mywc <textfile1> [textfile2] ...\n";
return 1;
}
// files to process
std::vector<std::string> filenames;
// Put the filenames into the vector
for (int i = 1; i < argc; i++)
filenames.push_back(argv[i]);
// Count the chars, words, and lines and print them
for (size_t i = 0; i < filenames.size(); i++)
CountLWC(filenames[i]);
return 0;
}
int main(int argc, char *argv[])
{
// Need at least one filename
if (argc < 2)
{
std::cout << "Usage: mywc <textfile1> [textfile2] ...\n";
return 1;
}
// files to process
std::vector<std::string> filenames;
// Put the filenames into the vector
for (int i = 1; i < argc; i++)
filenames.push_back(argv[i]);
// Count the chars, words, and lines and print them
std::for_each(filenames.begin(), filenames.end(), CountLWC);
return 0;
}
int main(int argc, char *argv[])
{
// Need at least one filename
if (argc < 2)
{
std::cout << "Usage: mywc <textfile1> <textfile2> ...\n";
return 1;
}
// Count the chars, words, and lines and print them
std::for_each(argv + 1, argv + argc, CountLWC);
return 0;
}
Video review - Why you should be using the STL.
Accessing command line arguments from CS120.
A closer look at for_each:
Implementation:template<typename InputIt, typename Op> Op for_each(InputIt first, InputIt last, Op op);
template<typename InputIt, typename Op>
Op for_each(InputIt first, InputIt last, Op op)
{
while (first != last)
{
op(*first);
++first;
}
return op;
}
These are the files that need to be included:
#include <iostream> // cout, endl #include <iomanip> // setw #include <vector> // vector #include <string> // string #include <fstream> // ifstream #include <sstream> // stringstream #include <algorithm> // for_each
Running the program:
Output:mywc Circle.cpp fibonacci.cpp Ocean.cpp Polygon.cpp WarBoats.cpp
50 110 819 Circle.cpp
87 265 2191 fibonacci.cpp
158 450 4246 Ocean.cpp
402 1353 11959 Polygon.cpp
14 20 164 WarBoats.cpp
| Cygwin/macOS/Linux (wc) | Our program (mywc) |
|---|---|
50 110 819 Circle.cpp 87 265 2191 fibonacci.cpp 158 450 4246 Ocean.cpp 402 1353 11959 Polygon.cpp 14 20 164 WarBoats.cpp 711 2198 19379 total |
|
The complete program.
Exercise for students: Add the totals to the output.
Also, there is a caveat with this program:
It reads files in one line-at-a-time and assumes that the files are text files. It also assumes
that newlines are a single-character (LF)
like Linux/Mac OS X, not two characters (CR/LF) like Windows. You would need to modify the
file reading logic to handle both systems. Or, read in characters instead of lines
and then account for whitespace to delimit words and newlines to delimit lines.
This example chose to keep it simple to illustrate the file I/O.