File IO
So far, any information coming and going between our programs and the "outside world" have been related to the idea of a person using the program. While that's a very important source/destination to consider, there are plenty more! The first one we'll learn about is files
Since File IO is very similar to Formatted IO, it may help to review that topic before moving on.
One of the important technical details we skipped over when covering Formatted IO is streams. The term streams reflects an abstraction used to describe or imagine how information flows to and from the program. Since streams only flow in one direction, we tend to consider them in terms of input or output streams.
Syntax
In C, streams are implemented using a file pointer data type: FILE *
. The input and output streams used for Formatted IO are called stdin
and stdout
. Since these always connect to the keyboard or screen as the source or destination respectively, they can be maintained by the stdio
library and we don't have to add any special code to use them.
Files, on the other hand, are stored all over the hard drive so the information needed to connect to a file changes over time. This means we need to write code to create and use our own file pointers when we want to use File IO. Luckily, much of the code to create these file streams in our programs is pretty standard!
File IO starts with a file pointer declaration:
FILE* filePointer;
The fopen
function is used to try to connect our file pointer to the file, thereby establishing the stream:
filePointer = fopen("filename.txt", "r");
The first argument is a string containing the filename (this is the only time the filename is used). The second argument is the mode, which determines which "direction" the stream flows (in or out). Although there are more modes, we will stick primarily to the following modes:
Mode | Code | Road |
---|---|---|
Read | "r" |
get input info into the program from a file |
Write | "w" |
output info from the program to a file, starting at the file beginning (any previous contents are erased) |
Append | "a" |
output info from the program to a file, starting at the file end (any previous contents remain) |
If it fails to connect to the file, the fopen
function will return a NULL
[1] pointer. Before we do anything with the stream or file pointer, we first have to check to see if the connection failed by checking the file pointer for NULL
.
if(filePointer == NULL){ // unsuccessful connection
printf("Could not open file.\n");
// you must skip any further File IO code or...
// you could even end the program
return 0;
}
If the file pointer is not NULL
, then you can continue on to do the actual reading from or writing to a file (covered in the Behavior section). When you are done with the file, you must close it by sending the file pointer as an argument into the fclose
function.
fclose(filePointer);
Best practices often orgranize programs by putting the opening and closing of files in the main
function, and passing the connected file pointer into a different function to do the actual File IO work. In that case our program looks a little something like this:
Behavior
Output
Once a stream or file pointer is successfully connected to a file in "w"
mode, we can "output" or write to it. We primarily use the fprintf
function for that. It is almost identical to printf
with one exception... Can you find it?
fprintf(filePointer, "Hello World!\n");
Yep! A new argument goes before the format string: the file pointer!
Input
Once a stream or file pointer is successfully connected to a file in "r"
mode, we can get "input" or read from it. We primarily use the fscanf
function for that. It is almost identical to fscanf
with the same exception as above: the file pointer is added!
fscanf(filePointer, "%s", firstName);
The same constraints apply to fscanf
as scanf
for strings: it stops at the first blank space. If we need more than that, we can always use the fgets
function!
fgets(fullName, SIZE, filePointer);
We'll spend a lot of time in class going over examples, especially situations where we need to "read" multiple lines of data from a file and store that data into an array.
Metaphor
I've often used the process of making a phone call as a metaphor for some parts of File IO.
The file pointer is the phone and we have to get one first! Then, we have to use the fopen
function which is a lot like dialing the phone number, especially because we only use the phone number once then we talk on the phone. It's the same with File IO: we only use the filename once, then we use the file pointer for the rest of the program.
It's also important we wait until the phone is connected before the conversation begins. If we don't connect then we can put the phone away. Same with File IO!
Finally, once we're done with the conversation we hang open the phone (or call the fclose
function in File IO). We don't need to hange up if the call doesn't go through and sending a NULL
pointer to the fclose
function will cause a segmentation fault. Also, if we don't hang up then we can't make another call!
1. NULL
is a commonly used label for the null pointer. It’s a lot like a default value for pointers when they’re not pointing at something.