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) |
---|---|
void f1() { std::ofstream outfile; // instantiate an output stream object outfile.open("foo.txt"); // associate a disk file // 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; outfile.close(); // close the file [optional] } |
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) |
---|---|
void f3() { Student st1("jdoe", 22, 4, 3.76F); StopWatch sw1(90); // Send to a file std::ofstream outfile("file1.txt"); outfile << st1; outfile << sw1; } |
login: jdoe<NL> age: 22<NL> year: 4<NL> GPA: 3.76<NL> 00:01:30<NL> |
Student class |
---|
std::ostream& operator<<(std::ostream& os, const Student& s) { os << "login: " << s.get_login() << std::endl; os << " age: " << s.get_age() << std::endl; os << " year: " << s.get_year() << std::endl; os << " GPA: " << s.get_GPA() << std::endl; return os; } |
StopWatch class |
---|
std::ostream& operator<<(std::ostream& os, const StopWatch& rhs) { int hours, minutes, seconds; hours = rhs.seconds_ / 3600; minutes = (rhs.seconds_ - (hours * 3600)) / 60; seconds = rhs.seconds_ % 60; os.fill('0'); os << std::setw(2) << hours << ':'; os << std::setw(2) << minutes << ':'; os << std::setw(2) << seconds << std::endl; return os; } |
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) |
---|---|
void f7() { std::ofstream outfile; const char* fnames[] = {"file1.txt", "file2.txt", "file3.txt" }; int size = sizeof(fnames) / sizeof(*fnames); for (int i = 0; i < size; i++) { outfile.open(fnames[i]); if (!outfile.is_open()) continue; outfile << "Blah, blah, blah" << std::endl; outfile << "Blah, blah, blah" << std::endl; outfile << "Blah, blah, blah" << std::endl; outfile.close(); } } |
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> |
---|---|
void f8() { std::ofstream outfile; std::vector<const char *> fnames; fnames.push_back("file1.txt"); fnames.push_back("file2.txt"); fnames.push_back("file3.txt"); for (unsigned i = 0; i < fnames.size(); i++) { outfile.open(fnames[i]); if (!outfile.is_open()) continue; outfile << "Blah, blah, blah" << std::endl; outfile << "Blah, blah, blah" << std::endl; outfile << "Blah, blah, blah" << std::endl; outfile.close(); } } |
void f8() { std::ofstream outfile; std::vector<std::string> fnames; fnames.push_back("file1.txt"); fnames.push_back("file2.txt"); fnames.push_back("file3.txt"); for (unsigned i = 0; i < fnames.size(); i++) { outfile.open(fnames[i].c_str()); if (!outfile.is_open()) continue; outfile << "Blah, blah, blah" << std::endl; outfile << "Blah, blah, blah" << std::endl; outfile << "Blah, blah, blah" << std::endl; outfile.close(); } } |
This code will work with any container:
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.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... }
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; }
And while we're at it, using a lambda expression (or anonymous function):void f8d() { std::vector<std::string> fnames {"file1.txt", "file2.txt", "file3.txt"}; for_each(fnames.begin(), fnames.end(), PrintToFile); }
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 |
---|---|
void f5() { std::ifstream infile("foo.txt"); if (!infile.is_open()) std::cout << "Can't open file.\n"; else { std::string str; while (!infile.eof()) { infile >> str; std::cout << str << std::endl; } } } |
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 |
---|---|
void get_numbers2() { // Holds unlimited integers std::vector<int> numbers; // Open the file for reading FILE *fp = fopen("numbers.txt", "r"); if (!fp) std::cout << "Can't open file.\n"; // Do something else or exit function? // Process the entire file while (!feof(fp)) { int number; // Read next integer if (fscanf(fp, "%i", &number) == 0) break; // Add number to the end numbers.push_back(number); } // Close the file fclose(fp); // Print the vector print_vector(numbers); } |
void get_numbers3() { // Holds unlimited integers std::vector<int> numbers; // Open the file for reading std::ifstream infile("numbers.txt"); if (!infile.is_open()) std::cout << "Can't open file.\n"; // Do something else or exit function? // Process the entire file while (!infile.eof()) { int number; // Read next integer infile >> number; if (infile.eof()) break; // Add number to the end numbers.push_back(number); } // Print the vector print_vector(numbers); } // File is closed here |
Reading in an entire line at a time:
Using FILE pointers w/C-style strings | Using streams w/std::string |
---|---|
void f10() { FILE *infile = fopen("foo.txt", "rt"); if (!infile) std::cout << "Can't open file.\n"; else { while (!feof(infile)) { char str[100]; fgets(str, 100, infile); std::cout << str; } fclose(infile); } } Output: This is a line of text Another line of text An integer: 42 A double: 3.1415 A double: 3.1415 |
void f11() { std::ifstream infile("foo.txt"); if (!infile.is_open()) std::cout << "Can't open file.\n"; else { std::string str; while (!infile.eof()) { std::getline(infile, str); std::cout << str << std::endl; } } } Output: This is a line of text Another line of text An integer: 42 A double: 3.1415 [empty line here] |
Using FILE pointers w/C-style strings | Using streams w/std::string |
---|---|
void f10() { FILE *infile = fopen("foo.txt", "rt"); if (!infile) std::cout << "Can't open file.\n"; else { while (!feof(infile)) { char str[100]; if (!fgets(str, 100, infile)) break; std::cout << str; } fclose(infile); } } Output: This is a line of text Another line of text An integer: 42 A double: 3.1415 |
void f11() { std::ifstream infile("foo.txt"); if (!infile.is_open()) std::cout << "Can't open file.\n"; else { std::string str; while (!infile.eof()) { if (std::getline(infile, str).eof()) break; std::cout << str << std::endl; } } } Output: This is a line of text Another line of text An integer: 42 A double: 3.1415 |
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 );
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.| Read (input) Write (output) Append (output) --------+---------------------------------------------- Text | "r" "w" "a" Binary | "rb" "wb" "ab"
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)
Running the command: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
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;
}
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 |
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 |
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.