|
This section describes the use of C's input / output facilities for reading and writing Files
How will we specify that we want to access a particular data File? It would theoretically be possible to mention the name of a File each time it was desired to read from or write to it. But such an approach would have a number of drawbacks. Instead, the usual approach (and the one taken in C's stdio library) is that you mention the name of the File once, at the time you open it. Thereafter, you use some little token--in this case, the File pointer--which keeps track (both for your sake and the library's) of which File you're talking about. Whenever you want to read from or write to one of the Files you're working with, you identify that File by using its File pointer (that is, the File pointer you obtained when you opened the File. As we'll see, you store File pointers in variables just as you store any other data you manipulate, so it is possible to have several Files open, as long as you use distinct variables to store the File pointers.
You declare a variable to store a File pointer like this:
The type File is predefined for you by <stdio.h>. It is a data Structure which holds the information the standard I/O library needs to keep track of the File for you. For historical reasons, you declare a variable which is a pointer to this File type. The name of the variable can (as for any variable) be anything you choose; it is traditional to use the letters fp in the variable name (since we're talking about a File pointer). If you were reading from two Files at once you'd probably use two File pointers:
If you were reading from one file and writing to another you might declare and input file pointer and an output file
pointer.
Like any pointer variable, a file pointer isn't any good until it's initialized to point to something. (Actually, no variable of any type is much good until you've initialized it.) To actually open a file, and receive the ``token'' which you'll store in your file pointer variable, you call fopen. fopen accepts a file name (as a string) and a mode value indicating among other things whether you intend to read or write this file. (The mode variable is also a string.) To open the file input.dat for reading you might call
ifp = fopen("input.dat", "r");
|
The mode string "r" indicates reading. Mode "w" indicates writing, so we could open output.dat for output like
this.
ofp = fopen("output.dat", "w");
|
The other values for the mode string are less frequently used. The third major mode is "a" for append. (If you use "w" to write to a File which already exists, its old contents will be discarded.) You may also add a + character to the mode string to indicate that you want to both read and write, or a b character to indicate that you want to do ``binary'' (as opposed to text) I/O.
One thing to beware of when opening Files is that it's an operation which may fail. The requested File might not exist, or it might be protected against reading or writing. (These possibilities ought to be obvious, but it's easy to forget them.) fopen returns a null pointer if it can't open the requested File, and it's important to check for this case before going off and using fopen's return value as a File pointer. Every call to fopen will typically be followed with a test, like
this.
|
ifp = fopen("input.dat", "r");
if(ifp == NULL)
{
printf("can't open file\n");
exit or return
}
|
If fopen returns a null pointer, and you store it in your file pointer variable and go off and try to do I/O with it, your program will typically crash.
It's common to collapse the call to fopen and the assignment in with the test.
if((ifp = fopen("input.dat", "r")) == NULL)
{
printf("can't open File\n");
exit or return
}
|
You don't have to write these ``collapsed'' tests if you're not comfortable with them, but you'll see them in other people's code, so you should be able to read them.
|
|
For each of the I/O library functions we've been using so far, there's a companion function which accepts an additional File pointer argument telling it where to read from or write to. The companion function to printf is fprintf, and the File pointer argument comes first. To print a string to the output.dat File> we opened in the previous section, we might call
.
fprintf(ofp, "Hello, world!\n");
|
The companion function to getchar is getc, and the File pointer is its only argument. To read a character from the input.dat File we opened in the previous section, we might call.
The companion function to putchar is putc, and the File pointer argument comes last. To write a character to output.dat, we could call
.
Our own getline function calls getchar and so always reads the standard input. We could write a companion fgetline function which reads from an arbitrary File
pointer.
|
#include <stdio.h>
/* Read one line from fp, */
/* copying it to line array (but no more than max chars). */
/* Does not place terminating \n in line array. */
/* Returns line length, or 0 for empty line, or EOF for end-of-file. */
int fgetline(file *fp, char line[], int max)
{
int nch = 0;
int c;
max = max - 1; /* leave room for '\0' */
while((c = getc(fp)) != EOF)
{
if(c == '\n')
break;
if(nch < max)
{
line[nch] = c;
nch = nch + 1;
}
}
if(c == EOF && nch == 0)
return EOF;
line[nch] = '\0';
return nch;
}
|
Now we could read one line from ifp by calling
|
char line[MAXLINE];
...
fgetline(ifp, line, MAXLINE);
|
|