Overview
File Pointers
Functions to handle files come from the stdio library. We need to include the stdio.h header file if we wish to use those functions:
To use files we need to have variables that act as handles to files we are interested in. These are file pointer variables. fp is a file pointer variable.
FILE* fp;
Opening Files with fopen()
To work with a file, you must first open it using the fopen() function. The fopen() function takes two arguments:
- The filename (a string representing the path to the file)
- The mode (a string indicating how the file should be opened)
The function returns a FILE pointer if successful, or NULL if the file cannot be opened.
FILE *fp = fopen("filename.txt", "r");
File Access Modes
The mode parameter determines how the file will be accessed. Here are the most common modes:
| Mode | Meaning | Description |
|---|---|---|
"r" | Read | Opens an existing file for reading. The file must exist. |
"w" | Write | Opens a file for writing. If the file exists, it is truncated (emptied). If it does not exist, a new file is created. |
"a" | Append | Opens a file for writing at the end. If the file exists, new data is added to the end. If it does not exist, a new file is created. |
Error Checking
It is really important to check whether fopen() was successful before attempting to use the file pointer. If the file cannot be opened, fopen() returns NULL. Attempting to use a NULL file pointer will cause your program to crash.
FILE *fp = fopen("data.txt", "r");
int rc = 0; //create a return code
if (fp == NULL) {
printf("Error: Could not open file.\n");
rc = 1;
}
...
//note that if we couldn't open the file, we will often want to return something
//other than 0. This indicates an error.
return rc;
This pattern of checking for NULL should become automatic whenever you open a file.
Reading from Files
Once a file is open for reading, you can read data from it using several functions. The choice of function depends on what you want to read.
Reading Lines with fgets()
The fgets() function reads an entire line from a file (up to a specified number of characters) and stores it in a string.
FILE* fp = fopen("somefile.txt", "r");
char line[100];
if (fgets(line, 100, fp) != NULL) {
printf("Read line: %s", line);
}
The fgets() function takes three arguments:
- A character array (string) where the line will be stored
- The capacity of the character array you are reading into
- The file pointer associated with the file being read
Note that fgets() includes the newline character (\n) in the string if one is present. If we want to get rid of the terminal \n we will need to add a null character where the terminal \n is located
Reading Formatted Data with fscanf()
The fscanf() function works similarly to scanf(), but reads from a file instead of standard input. It is useful for reading structured data.
FILE* fp = fopen("somefile.txt", "r");
int age;
char name[50];
fscanf(fp, "%s %d", name, &age);
The above format string will read a string (without spaces), a space and a number from the file somefile.txt
Writing to Files
Once a file is open for writing or appending, it can be written to. While there are a number of functions that will allow you to write to a file, one of the most familiar function to work with is fprintf().
Writing Formatted Data with fprintf()
The fprintf() function works like printf(), but writes to a file instead of the screen.
FILE* fp = fopen("somefile.txt", "w");
int age = 25;
char name[15] = "Allison";
fprintf(fp, "Name: %s, Age: %d\n", name, age);
Closing Files with fclose()
When you are finished working with a file, you must close it using the fclose() function. Closing a file:
- Flushes any buffered data to the file
- Frees up system resources
- Prevents accidental further access to the file
fclose(fp);
It is good practice to check the return value of fclose(), though this is less critical than checking fopen().
if (fclose(fp) != 0) {
printf("Warning: Error closing file.\n");
}
Failing to close files can lead to data loss (buffered data may not be written to disk) and resource leaks (the operating system may limit the number of open files).
This program:
- Opens
output.txtfor writing - Prompts the user for their name and age
- Writes the data to the file using
fprintf() - Closes the file
- Confirms to the user that the data was written
Example: Appending to a File
The following program appends data to an existing file rather than overwriting it.
#include <stdio.h>
int main(void)
{
FILE *fp;
char entry[100];
int rc = 0;
// Open the file for appending
fp = fopen("log.txt", "a");
if (fp == NULL) {
printf("Error: Could not open log.txt\n");
rc = 1;
}
// Prompt user for entry
printf("Enter a log entry: ");
fgets(entry, sizeof(entry), stdin);
// Append to file
fprintf(fp, "%s", entry);
// Close the file
fclose(fp);
printf("Entry added to log.txt\n");
return rc;
}
Notice that we use mode "a" to append rather than "w" to write. This preserves existing content in the file.
Common File I/O Patterns
Pattern 1: Read Entire File Line by Line
FILE *fp = fopen("file.txt", "r");
int rc = 0; //sets up a return code for the program
//default this to 0 to indicate things went well
if (fp == NULL) {
printf("Error opening file\n");
rc = 1; //modify the return code to indicate an error
}
char line[256];
while (fgets(line, sizeof(line), fp) != NULL) {
// Process line
printf("%s", line);
}
fclose(fp);
Pattern 2: Read Entire File Character by Character
FILE *fp = fopen("file.txt", "r");
int rc = 0;
if (fp == NULL) {
printf("Error opening file\n");
rc = 1;
}
int ch;
while ((ch = fgetc(fp)) != EOF) {
// Process character
printf("%c", (char)ch);
}
fclose(fp);
Pattern 3: Read Structured Data
FILE *fp = fopen("data.txt", "r");
int rc = 0;
int id;
char name[50];
if (fp == NULL) {
printf("Error opening file\n");
rc = 1;
}
//format is:
//id:name
//example:
//12345:Sally Brown
//54321:Calvin Hobbes
while (fscanf(fp, "%d:%[\n]\n", &id, name) == 2) {
// Process record
printf("ID: %d, Name: %s\n", id, name);
}
fclose(fp);
Detecting End-of-File
There are several ways to detect when you have reached the end of a file:
fgets()returnsNULLfscanf()returns fewer items than expected
It is important to use the appropriate method for the function you are using. Alternatively, you can also use the feof() function. This function indicates whether or not a preceding input operation encountered an end of file character.
if (feof(fp)){
printf("this will print if the previous file input operation encountered the end of file marker");
}
Matching format specifiers
When it comes to reading structured input, we have format specifiers that are based on regular expressions:
(AAA) BBB-CCCC
AAA is the area code while BBB-CCCC is the 7 digit phone number. Suppose we wanted to store the area code separately as an integer, BBB as a string and CCCC as another string... we can express a match to that formatting using this format specifiers
"(%d) %[0-9]-%[0-9]"
The above format string says that the input expects an open bracket, then a number, followed by a closed bracket, a space, some numeric chracters followed by a hyphen, then more numeric characters.
Some examples of useful format specifiers. All of these store the data as a nullterminated string:
| format specifier | notes |
| ----------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ---------------------------- | --------------------------------------------- |
| %[0-9] | every numeric only character until a non numeric character is met |
| %[^\n] | every character except the newline |
| %10[^\n] | every character except the newline or 10 characters whichever is met first |
| %[^ | ] | every character except the |cahracter. The^ essentially means 'except' |
| %[a-bA-B] every alphabetic character up to the first non-alphabetic character |