Files
Everything a program keeps in variables is gone when it ends. To keep something for next time, write it to a file. This program writes one line to hello.txt, closes it, then opens it again and reads the line back:
1#include <stdio.h>2 3int main(void) {4 FILE *f = fopen("hello.txt", "w");5 fprintf(f, "Hello, file!\n");6 fclose(f);7 8 char line[100];9 f = fopen("hello.txt", "r");10 fgets(line, sizeof line, f);11 fclose(f);12 printf("Read back: %s", line);13 return 0;14}
Read back: Hello, file!
fopen opens a file and gives back a FILE *, a pointer to what C knows about the open file. Every other file function takes that pointer. fprintf works just like printf, except that its first argument says which file to write to.
On this page, each example runs in a small folder of its own in your browser. A file it writes is there until the program ends, then it's gone. That's why every example here writes its file first, then reads it, all in the same program.
Opening and closing
The second argument of fopen is the mode: what you want to do with the file.
| Mode | What it does | If the file doesn't exist | If it already exists |
|---|---|---|---|
"r" | read | fopen gives back NULL | reads from the start |
"w" | write | creates it | empties it first |
"a" | append (add to the end) | creates it | writes after what's there |
When you're done, call fclose. What you write with fprintf first waits in memory and goes into the file in bigger pieces; fclose makes sure all of it gets there, and lets go of the file. Close every file you open.
What does this print?
1#include <stdio.h>2 3int main(void) {4 FILE *f = fopen("day.txt", "w");5 fprintf(f, "Monday\n");6 fclose(f);7 f = fopen("day.txt", "w");8 fprintf(f, "Tuesday\n");9 fclose(f);10 11 char line[100];12 f = fopen("day.txt", "r");13 while (fgets(line, sizeof line, f) != NULL) {14 printf("%s", line);15 }16 fclose(f);17 return 0;18}
"w" empties the file before writing. The second fopen throws away Monday, so the file holds only Tuesday. To add to a file instead, open it with "a".
When fopen fails
fopen doesn't always work. Open a file that doesn't exist for reading, or get its name wrong, and it gives back NULL. Always check before you use the pointer:
1#include <stdio.h>2 3int main(void) {4 FILE *f = fopen("plans.txt", "r");5 if (f == NULL) {6 printf("Can't open plans.txt\n");7 return 1;8 }9 printf("Opened it\n");10 fclose(f);11 return 0;12}
Can't open plans.txt
return 1; ends the program early. Any number other than 0 tells the system that something went wrong.
Without the check, the NULL goes on into the next function. In real C, the program usually crashes there. Our engine stops on that line and tells you why:
1#include <stdio.h>2 3int main(void) {4 char line[100];5 FILE *f = fopen("plans.txt", "r");6 fgets(line, sizeof line, f);7 printf("%s", line);8 return 0;9}
fgets on a NULL FILE *What happened: Stopped at line 6. This pointer is NULL: nothing can be read or written through it.
Reading line by line
fgets(line, sizeof line, f) reads one line into the char array line, at most sizeof line - 1 characters, and adds the '\0'. When there's nothing left, it gives back NULL. So the usual loop is "keep reading until fgets gives NULL":
1#include <stdio.h>2 3int main(void) {4 FILE *f = fopen("shopping.txt", "w");5 fprintf(f, "milk\n");6 fprintf(f, "eggs\n");7 fprintf(f, "%d apples\n", 6);8 fclose(f);9 10 char line[100];11 int count = 0;12 f = fopen("shopping.txt", "r");13 while (fgets(line, sizeof line, f) != NULL) {14 count++;15 printf("%d: %s", count, line);16 }17 fclose(f);18 printf("%d lines\n", count);19 return 0;20}
1: milk 2: eggs 3: 6 apples 3 lines
Notice printf("%d: %s", count, line) has no \n. fgets keeps the '\n' at the end of the line, so line already ends with one. Press Step and look at the cells of line after each fgets: the '\n' is there, right before the '\0'.
Reading numbers
fgets gives you text. For numbers, fscanf is easier: it's scanf (lesson 3) for a file. It gives back how many values it read, so == 1 means "got one number". At the end of the file it can't read any more, and the loop stops.
1#include <stdio.h>2 3int main(void) {4 FILE *f = fopen("steps.txt", "w");5 int week[5] = {4200, 6100, 5300, 8000, 7200};6 for (int i = 0; i < 5; i++) {7 fprintf(f, "%d\n", week[i]);8 }9 fclose(f);10 11 int steps;12 int total = 0;13 int days = 0;14 f = fopen("steps.txt", "r");15 while (fscanf(f, "%d", &steps) == 1) {16 total += steps;17 days++;18 }19 fclose(f);20 printf("%d days, %d steps, %.1f a day\n", days, total, (double)total / days);21 return 0;22}
5 days, 30800 steps, 6160.0 a day
fscanf skips spaces and new lines before a number, so it doesn't matter whether the numbers are on one line or many.
A name and a number on each line
fscanf can read several values at once. With "%s %d", %s reads one word into a char array (up to the next space), then %d reads a number. It gives back 2 when it got both:
1#include <stdio.h>2#include <string.h>3 4int main(void) {5 FILE *f = fopen("scores.txt", "w");6 fprintf(f, "Ana 87\n");7 fprintf(f, "Ben 92\n");8 fprintf(f, "Chloe 78\n");9 fclose(f);10 11 char name[20];12 int score;13 char best_name[20] = "";14 int best = -1;15 f = fopen("scores.txt", "r");16 while (fscanf(f, "%19s %d", name, &score) == 2) {17 if (score > best) {18 best = score;19 strcpy(best_name, name);20 }21 }22 fclose(f);23 printf("Top score: %s, %d\n", best_name, best);24 return 0;25}
Top score: Ben, 92
Two things to see here. name has no &: an array's name is already an address (lesson 17). And %19s reads at most 19 characters, so a long name can't go past the end of name[20].
Adding to a file
Mode "a" keeps what's in the file and writes after it. A log, for example, should grow, not start over every time:
1#include <stdio.h>2 3int main(void) {4 FILE *f = fopen("log.txt", "w");5 fprintf(f, "09:00 opened\n");6 fclose(f);7 8 f = fopen("log.txt", "a");9 fprintf(f, "12:30 lunch break\n");10 fprintf(f, "18:00 closed\n");11 fclose(f);12 13 char line[100];14 f = fopen("log.txt", "r");15 while (fgets(line, sizeof line, f) != NULL) {16 printf("%s", line);17 }18 fclose(f);19 return 0;20}
09:00 opened 12:30 lunch break 18:00 closed
The first fopen uses "w", so the log starts empty. The second uses "a", so the two new lines go after 09:00 opened.
Tip: In "w" and "a" you can only write, and in "r" you can only read. To read a file you just wrote, close it and open it again with "r", as every example here does.
Practice
Write the code, then press Check. A hint costs nothing; the answer is there when you need it.
Number the lines
✓The program writes three lines to todo.txt. Read them back and print each one with its number in front, like 1. buy milk.
1. buy milk 2. call mom 3. pay rent
1#include <stdio.h>2 3int main(void) {4 FILE *f = fopen("todo.txt", "w");5 fprintf(f, "buy milk\n");6 fprintf(f, "call mom\n");7 fprintf(f, "pay rent\n");8 fclose(f);9 10 return 0;11}
Add up the prices
✓The program writes some prices to prices.txt. Read them back with fscanf, then print the count and the total, like 4 items, 23.75 in all.
4 items, 23.75 in all
1#include <stdio.h>2 3int main(void) {4 FILE *f = fopen("prices.txt", "w");5 fprintf(f, "3.50\n8.25\n2.00\n10.00\n");6 fclose(f);7 8 return 0;9}
The lost line
✓The program should save two notes, but only the second one is left in the file. Fix it so it prints both lines.
Rained all day Sunny at last
1#include <stdio.h>2 3int main(void) {4 FILE *f = fopen("diary.txt", "w");5 fprintf(f, "Rained all day\n");6 fclose(f);7 f = fopen("diary.txt", "w");8 fprintf(f, "Sunny at last\n");9 fclose(f);10 11 char line[100];12 f = fopen("diary.txt", "r");13 while (fgets(line, sizeof line, f) != NULL) {14 printf("%s", line);15 }16 fclose(f);17 return 0;18}
What you learned
fopen(name, mode)opens a file and gives back aFILE *;"r"reads,"w"writes (emptying the file first),"a"adds to the end.fopengives backNULLwhen it can't open the file: check withif (f == NULL)before using it.fprintf(f, ...)writes likeprintf;fclose(f)makes sure everything is written and lets go of the file.fgets(line, sizeof line, f)reads one line, keeping its'\n', and givesNULLat the end of the file.fscanf(f, "%d", &x)reads values likescanfand gives back how many it read: loop while it's== 1(or== 2for two values).