Reading and Writing Text Files in C
When your program ends, all its variables disappear. Everything in memory is gone.
If you want to save data - high scores, settings, documents - you need to write it to a file.
Files are collections of data stored on your hard drive or SSD. This lesson teaches you how to create files, write to them, and read them back.
Opening a File
Before you can read or write, you need to open the file:
#include <stdio.h>
int main(void) {
FILE *file = fopen("data.txt", "r"); // Open for reading
if (file == NULL) {
printf("Couldn't open file!\n");
return 1;
}
// Use the file...
fclose(file); // Always close when done
return 0;
}Let’s break this down:
FILE *file- Creates a variable that represents an open file. Think of it like a ticket that lets you access the file.fopen("data.txt", "r")- Opens a file called “data.txt” for readingfclose(file)- Closes the file when you’re done
Important: Always check if fopen returned NULL. This happens if the file doesn’t exist or you don’t have permission to open it.
File Modes
The second argument to fopen tells it what you want to do:
| Mode | What it does |
|---|---|
"r" | Read - Open an existing file to read it |
"w" | Write - Create a new file (or erase an existing one) |
"a" | Append - Add to the end of a file (creates if needed) |
"r+" | Read and write - File must already exist |
"w+" | Read and write - Creates new file (or erases existing) |
"a+" | Read and append - Creates if needed, writes go to end |
Warning: "w" mode erases the file if it exists! Use "a" if you want to add to an existing file.
Writing to a File
Writing Text with fprintf
fprintf works just like printf, but writes to a file:
#include <stdio.h>
int main(void) {
FILE *file = fopen("output.txt", "w"); // Open for writing
if (file == NULL) {
printf("Couldn't create file!\n");
return 1;
}
fprintf(file, "Hello, File!\n");
fprintf(file, "The answer is %d\n", 42);
fprintf(file, "Pi is about %.2f\n", 3.14159);
fclose(file);
printf("Done! Check output.txt\n");
return 0;
}After running this, output.txt contains:
Hello, File!
The answer is 42
Pi is about 3.14Writing Strings with fputs
For simple strings without formatting:
FILE *file = fopen("output.txt", "w");
fputs("Line 1\n", file);
fputs("Line 2\n", file);
fclose(file);Reading from a File
Reading Line by Line with fgets
fgets reads one line at a time:
#include <stdio.h>
int main(void) {
FILE *file = fopen("input.txt", "r");
if (file == NULL) {
printf("Couldn't open file!\n");
return 1;
}
char line[256]; // Buffer to hold each line
while (fgets(line, sizeof(line), file) != NULL) {
printf("Read: %s", line); // line already has \n
}
fclose(file);
return 0;
}fgets(line, sizeof(line), file) means:
- Read into
line - Read at most
sizeof(line) - 1characters (leaves room for the null terminator) - Read from
file - Stop at a newline or end of file
- Return
NULLwhen there’s nothing left to read
Reading Formatted Data with fscanf
fscanf works like scanf, but reads from a file:
#include <stdio.h>
int main(void) {
FILE *file = fopen("numbers.txt", "r");
if (file == NULL) {
return 1;
}
int num;
while (fscanf(file, "%d", &num) == 1) { // Keep reading while successful
printf("Got: %d\n", num);
}
fclose(file);
return 0;
}If numbers.txt contains:
10
20
30The program prints:
Got: 10
Got: 20
Got: 30Error Messages with perror
When something goes wrong, perror prints a helpful message:
FILE *file = fopen("nonexistent.txt", "r");
if (file == NULL) {
perror("Error opening file");
// Prints: Error opening file: No such file or directory
return 1;
}perror adds the system’s error message, so you know exactly what went wrong.
A Complete Example: High Scores
Let’s build something practical - a program that saves and loads high scores:
#include <stdio.h>
#include <string.h>
#define MAX_SCORES 5
typedef struct {
char name[20];
int score;
} HighScore;
void save_scores(HighScore scores[], int count) {
FILE *file = fopen("highscores.txt", "w");
if (file == NULL) {
printf("Couldn't save scores!\n");
return;
}
for (int i = 0; i < count; i++) {
fprintf(file, "%s %d\n", scores[i].name, scores[i].score);
}
fclose(file);
printf("Scores saved!\n");
}
int load_scores(HighScore scores[], int max) {
FILE *file = fopen("highscores.txt", "r");
if (file == NULL) {
return 0; // No saved scores yet
}
int count = 0;
while (count < max &&
fscanf(file, "%19s %d", scores[count].name, &scores[count].score) == 2) {
count++;
}
fclose(file);
return count;
}
void print_scores(HighScore scores[], int count) {
printf("\n=== HIGH SCORES ===\n");
for (int i = 0; i < count; i++) {
printf("%d. %-15s %d\n", i + 1, scores[i].name, scores[i].score);
}
printf("===================\n");
}
int main(void) {
HighScore scores[MAX_SCORES];
int count = load_scores(scores, MAX_SCORES);
if (count > 0) {
printf("Loaded %d saved scores\n", count);
print_scores(scores, count);
} else {
printf("No saved scores found\n");
}
// Add a new score
if (count < MAX_SCORES) {
printf("\nEnter your name: ");
scanf("%19s", scores[count].name);
printf("Enter your score: ");
scanf("%d", &scores[count].score);
count++;
save_scores(scores, count);
print_scores(scores, count);
}
return 0;
}Run it multiple times - your scores persist between runs!
Appending to Files
Use "a" mode to add to the end of a file without erasing it:
FILE *log = fopen("log.txt", "a");
fprintf(log, "Program started\n");
fclose(log);Every time you run this, it adds another line to the log file.
Checking for End of File
The feof() function tells you if you’ve reached the end:
while (!feof(file)) {
// Read stuff...
}But it’s usually better to check the return value of your read function (like we did with fgets returning NULL).
Try It Yourself
- Write a program that counts how many lines are in a text file
- Write a program that copies one file to another
- Create a simple note-taking app that saves notes to a file and shows all saved notes
- Write a program that reads names from a file and prints them in reverse order
Common Mistakes
- Forgetting to check if
fopenreturnedNULL - Forgetting to call
fclosewhen done (your data might not get saved!) - Using
"r"mode when the file doesn’t exist - Using
"w"mode when you wanted"a"(write erases, append adds) - Forgetting the
&infscanffor numbers (but not for strings!)
Next Up
In Part 22, we’ll learn about binary files - saving data exactly as it exists in memory, which is faster and more compact.
Enjoyed This?
If this helped something click, subscribe to my YouTube channel. More content like this, same approach - making things stick without insulting your intelligence. It’s free, it helps more people find this stuff, and it tells me what’s worth making more of.