.. highlight:: c++ Chapter 6 Notes =============== Please read chapter 6 of the textbook. Files ----- input and output, I/O for short, is a very important and practical topic in this course, we'll look at how you can use C++ **streams** to read and write files basically, a file is a named sequence of bytes that is stored on a disk, or some other storage device the operating system (OS) of your computer is responsible for managing files (and directories) while every file can be viewed as a sequence of bytes, they often have some other structure, e.g. a file of doubles, a text file, an image file, etc. C++ Streams ----------- we'll be using C++'s stream functions to read and write files conceptually, a stream is a "flow" of data you could have a stream of chars, or doubles, or strings, etc. there are **input streams** for getting data from a file and **output streams** for putting data into a file for instance, ``cout`` is an output stream and ``cin`` is an input stream C++ Streams ----------- ``cin`` and ``cout`` are examples of streams more generally, they are examples of **objects** i.e. streams are objects in C++, objects are a related collection of data and functions in later courses we'll learn much more about objects but for now, it's useful to know that ``cin`` and ``cout`` are pre-defined objects C++ Streams ----------- reading and writing files with C++ streams is very similar to using ``cin`` and ``cout`` we will create special streams attached to files, and then use the ``>>`` and ``<<`` operators to read and write data with files Reading and Writing Text Files with Streams ------------------------------------------- the following program sums the numbers in a text file :: // sum_file.cpp #include "cmpt_error.h" #include #include using namespace std; int main() { // open test_doubles.dat for reading // it is assumed to be a text file containing doubles ifstream fin("test_doubles.dat"); if (fin.fail()) { cmpt::error("couldn't open file"); } double sum = 0.0; double x = 0.0; while (fin >> x) { sum += x; } // create an output file to write the output to ofstream fout("sum_out.txt"); if (fout.fail()) { cmpt::error("couldn't open file for fout"); } fout << "sum = " << sum << "\n"; } ``fstream`` is where the file I/O streams are contained ``ifstream`` is the data type for a file *input* stream ``ofstream`` is the data type for a file *output* stream notice how ``fin`` and ``fout`` are used in pretty much the same was as ``cin`` and ``cout`` that's by design --- the C++ stream library tries to make all input and output work this way Reading and Writing Text Files with Streams ------------------------------------------- note how we check ``fin.fail()`` and ``fout.fail()`` to see if the files have been successfully opened ``fin`` and ``fout`` are examples of objects ``fail()`` is an example of a **member function** member functions are functions that belong to objects member functions are called using dot-notation, e.g. ``fin.fail()`` means "call the member function in the object ``fin`` named ``fail()``" Some Notes on the Textbook -------------------------- the textbook suggests defining string variables like this :: char file_name[16]; // a C-style string that can hold 16 characters this is how strings are declared in C C-style strings have a fixed size, in this case 16 characters ``file_name`` simply cannot hold more than 16 characters but in C++, for application-level programs it is almost always better to just use the ``string`` class, e.g. :: string file_name; ``file_name`` can now be of any length C-style strings are perhaps most useful for low-level applications, such as making your own ``string`` class (this is the sort of thing we'll do in later courses) Some Notes on the Textbook -------------------------- the textbook suggests using the ``exit(code)`` function to end your program when you've encountered an error however, it is better to use ``cmpt::error`` for two reasons with ``cmpt::error``, we provide an English message instead of a code, e.g. ``cmpt::error("file not found")`` is easier for people to read than ``exit(2)`` (what does 2 mean??) the ``exit`` function does not throw an exception, which means it cannot be caught in other words, when you call ``exit``, your program is guaranteed to stop if this is really what you want, then that's good but in most real-world programs, we don't want a single error to crash the entire program ``cmpt::error`` is different because it throws an exception an exception is a special error object that other code in your program can, if it wants to, catch we have not been catching our errors, but when we learn about ``try``/``catch`` structures we will see how we could do this Formatted Streams ----------------- C++ streams come with a number of functions that can be used for formatting see page 327 for a table showing the most commonly used functions also, on page 329 the textbook discusses stream manipulators you won't be expected to memorize any of this in this course just be aware of it and look it up when you need it Character I/O ------------- it's often useful to read a file a character at a time unfortunately, this approach does not work :: char c; while (fin >> c) { // skips whitespace // process char c } the problem is that ``>>`` skips whitespace, so characters such as space, newline, and tab will **not** be processed by this code Character I/O ------------- you can use the ``get()`` function to process one character at a time :: #include "cmpt_error.h" #include #include using namespace std; void main() { ifstream fin("test.txt"); if (fin.fail()) { cmpt::error("couldn't open file"); } int char_count = 0; char c = fin.get(); // must read at least one character // for fin.eof() to work while (!fin.eof()) { // loop until end-of-file reached ++char_count; if (c == '\n') { cout << char_count << " '\\n'\n"; // note the double back-slashes } else { cout << char_count << " '" << c << "'\n"; } c = fin.get(); } cout << char_count << " characters\n"; } ``fin.get()`` tries to read the next character of the ``fin`` stream if there is a next character, then it is returned if there is no next character, that means that the special end-of-file character has been read it's value is returned and ``fin.eof()`` is true Character I/O ------------- it's worth noting that that ``fin.get()`` does **not** return a ``char`` (!?) instead, ``fin.get()`` returns an ``int`` this way it can return a special non-character value to indicate EOF if it did return a ``char``, what ``char`` should it return when it reads the EOF? Character I/O ------------- here's an example of how you might use character I/O this code reads input from ``cin`` one character at a time this includes spaces, so it is different than ``>>`` :: cout << "Please enter some words: "; string line; char c = cin.get(); while (c != '\n') { // stop after user presses enter line += c; c = cin.get(); } the statement ``line += c`` appends to the character ``c`` to the string ``line`` this is a quick and dirty way to create a new string a character at a time we could also have written this as a for-loop :: for(char c = cin.get(); c != '\n'; c = cin.get()) { line += c; } cout << "line = \"" << line << "\"\n"; while the for-loop header is complex, the benefit is that all the loop-control information is gathered in one place Sample program: Average of Numbers Using Files ---------------------------------------------- :: // temps.cpp #include #include #include "cmpt_error.h" using namespace std; int main() { ifstream fin("meanTemps.txt"); if (fin.fail()) { cmpt::error("couldn't open file"); } // cout << "Please enter some temperatures: "; double sum = 0.0; int count = 0; double temp; while (fin >> temp) { sum += temp; count++; } fin.close(); // optional ofstream fout("log.txt"); fout << count << " temperatures read\n"; double mean = sum / count; fout << "Average = " << mean << "\n"; cout << "Results printed to log.txt\n"; } Sample program: Reading Character-by-Character ---------------------------------------------- :: // readchar.cpp #include using namespace std; int main() { cout << "Please enter some text: "; string line; char c = cin.get(); while (c != '!') { // keep reading until first '!' line += c; c = cin.get(); } // replace each newline character with the '\' and 'n' cout << "line=\""; for(char c : line) { // note the for-each loop used here if (c == '\n') { cout << "\\n"; } else { cout << c; } } cout << "\"\n"; }