Opening and Closing Files

Working with a file starts by opening it and ends by closing that file. But before doing anything with a file, you must create a variable as a file pointer. The syntax is

FILE *file_pointer_name;

For the sake of simplicity, you'll often see fp used to represent a file pointer:

FILE *fp;

This variable is assigned a value during the process of opening the file for reading or writing. The file pointer will then point to all the information C needs to work with that file.

The functions used to read from and write to files reside in the stdio.h header file, which you're already including in your applications. The first function, fopen(), is used to initialize the file pointer:

fp = fopen (file_name, mode);

The file_name can be as simple as data.txt or be a relative path to the file, like data.txt, ./files/data.txt, or C:\storage\data.txt (see the sidebar). Which mode you use depends on what you want to do with that file: write to it, read from it, append new data to it, or a combination of those tasks (Table 12.1). When choosing a mode, keep in mind that the w mode will always erase any existing contents in an existing file. You should use this mode only when you want to begin with a clean slate.

Table 12.1. These modes are used for setting the actions that can be done with a file. Note that the b flag can be used with w, r, or a.
fopen() Modes
ModeOpen file for…
wWriting, creating the file if it doesn't exist
rReading
aAppending new data to the end of the file, creating the file if it doesn't exist
w+Reading and writing, creating the file if it doesn't exist
r+Reading and writing
a+Reading and writing, appending new data to the end of the file, creating the file if it doesn't exist
bBinary mode, in conjunction with the other modes (e.g., wb, rb+, etc.)

So, to open a file for reading, you code

fp = fopen("data.txt", "r");

If C cannot open the file in the designated mode, fp will be assigned a value of NULL. Before doing anything else—such as trying to write to this file—you should check the value of your pointer:

if (fp != NULL) {
   // Do whatever.
}

Once you've finished using the file, you should close it, using the appropriately named fclose() function. This function takes one argument: the name of the pointer initialized when opening the file:

fclose (fp);

To make certain that this process works, let's write a simple application that opens and closes a file.

One Slash Forward, Two Slashes Back

On non-Windows operating systems (Unix, Linux, Mac OS X, and others), forward slashes are used to reference directories (or folders). To refer to a file in the directory above the current location, you would use ../filename, where the ../ construct points to the parent folder of the current directory. To refer to a file located in a subdirectory of the current one, you would use subdirectory/filename or, more precisely, ./subdirectory/filename, where ./ points to the current directory.

The Windows operating system uses the backslash () to reference folders. But since the backslash has a special meaning already—it's used to create escape sequences like newline ( )—you must use two backslashes to properly refer to directories. So subdirectory\filename and C:\somefolder\filename are proper Windows references. This construct is actually a type of escape, in which the backslash character is escaped with the escape character (which happens to be a backslash) in order to produce a single backslash.


To open and close a file

1.
Create a new file or project in your text editor or IDE.

2.
Type the standard beginning lines of code (Script 12.1):

/* test.c - Script 12.1 */
#include <stdio.h>

Script 12.1. This application opens and then closes a text file to ensure that the process works.


3.
Set the file path and name as a C preprocessor macro:

#define THEFILE "path/to/filename"

The actual syntax will depend on your operating system and where you want the file to be created. Here are possible examples for Mac OS X and Windows:

#define THEFILE
 "/Users/larry/Desktop/data.txt"
#define THEFILE "C:\Documents and
 Settings\Larry
 Ullman\Desktop\data.txt"

By setting this value as a C preprocessor directive, you won't need to go hunting through your code to find the filename if you want to change it later.

You have to use the double quotation marks as part of the definition, since the fopen() function, which uses this macro later, expects a string.

4.
Begin the main function:

int main (void) {

5.
Create a file pointer:

FILE *fp;

This is the file pointer that will be used to reference the file.

6.
Open the file for writing:

fp = fopen(THEFILE, "w");

This line uses the C preprocessing macro defined earlier (see Step 3). Before compilation of the C code, the compiler will replace THEFILE here with the value established earlier. The w mode is used, indicating that we want to write to the file, that it should be created if it doesn't exist, and that any existing contents will be overwritten.

7.
Create a conditional to report on the file opening:

if (fp != NULL) {
     printf ("The file is now
 open.
");
} else {
     printf ("The file could not be
 opened.
");
}

This conditional compares the value of the file pointer against NULL. As long as fp does not equal NULL, the file was successfully opened for writing.

In this example, if the file could not be opened, a simple message is printed. An alternative to this is to abort the execution of the application (by using a return statement), which you'll see in later examples.

8.
Close the file:

if (fclose(fp) != 0) {
     printf ("The file could not be
 closed.
");
} else {
     printf ("The file is now
 closed.
");
}

The fclose() function returns the number 0 if it worked properly, so this conditional reports on its success. This may be overkill, but it's worthwhile knowing how you would validate that a file was closed.

9.
Complete the main function:

    getchar();
    return 0;
}

10.
Save the file as test.c, compile, and debug as necessary.

11.
Run the application (Figures 12.1 and 12.2).

Figure 12.1. Successfully running the test application on Windows.


Figure 12.2. Successfully running the test application on Mac OS X using Xcode.


✓ Tips

  • On the one hand, absolute pathnames (/Users/larry/Desktop/filename or C:\Documents and Settings\Larry Ullman\Desktop\filename) are obviously less portable (because that path may not exist on another computer). On the other hand, they have an advantage over relative pathnames (../filename or subdirectory /filename) in their precision.

  • When using Dev-C++ on Windows, the starting point for relative pathnames is the location of the Dev-C++ application itself, not the location of the compiled test application.

  • A single file pointer can be used to reference multiple file openings in the same application but only one open file at a time. For example:

    fp = fopen("data1.txt", "r");
    // Do whatever.
    fclose(fp);
    fp = fopen("data2.txt", "r");
    // Do whatever.
    fclose(fp);
    
  • There is a limit as to how many files your C application can open at once. Fortunately, for most applications this limit (as high as several hundred) shouldn't be an issue.

  • Because there are many reasons a call to fopen() could fail, you'll always want to ensure that the open call worked—by using a conditional that compares the returned value to NULL—before doing anything with the file.

  • For that matter, if fp is NULL (because the file could not be opened), the fclose() line in this application will fail (because it will be fed a NULL pointer). Subsequent examples demonstrate how you can avoid this potential problem.


Binary and Plain-Text Files

When you're working with files, it's important to understand the two basic file types: plain text (or ASCII) and binary. A plain-text file is human readable and contains no formatting, like your C source code files. A binary file is in a format that a computer can use more quickly but is unreadable to humans (if you've ever opened a file and seen a bunch of gibberish, that's binary data).

Another distinction between the two types is that plain-text files can contain multiple lines of data, whereas a binary file is one long stream of information without any breaks. On the other hand, because it's a more native format, computers can often deal with binary files faster than plain-text files. Another benefit of binary files is that a computer does not have to access them linearly as it normally does plain-text files (which are often read one line or character at a time).

When programming in C, the distinction between binary and ASCII files is most important for Windows users, because that operating system distinguishes between the two formats. People using Unix (including Linux and Mac OS X) don't need to worry about this since those operating systems see no difference between the two file types. Because the Unix variants (including Mac OS X) do not distinguish between binary and plain text files, the use of b to indicate binary mode when opening a file has no effect on those operating systems.

In these first few examples, you'll be using plain-text files. At the end of the chapter, you'll also see several examples of working with binary files.


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset