
A high-score table that vanishes when the machine is switched off is not much of a table. Files are how a program keeps something between runs, and on this compiler they work through a handful of functions that look like the printing and reading you already know, with a file where the screen or keyboard was. The wrinkle is what a text file is on this machine, and the section ends with the bytes laid out so that you can see it.
FILE *f;
f = fopen("NOTES.TXT", "w");
fopen takes a name and a mode - "w" to write a new file, "r" to read one that exists - and returns a FILE *, a pointer the other functions take to say which file. FILE is a name from STDIO.H. If the file cannot be opened, fopen returns 0, and the only way to know is to test:
if (f == 0) {
printf("no such file\n");
return;
}
Opening a missing file for reading gives 0; so, presumably, would a full disc for writing. A program that carries on with a 0 is writing through a null pointer, and S16 said where that leads. Test every fopen.
"w" on a file that already exists replaces it: write first version, close, open with "w" again, write second, and the file holds only second. There is no message; if the old contents mattered, they are gone.
When you have finished, fclose(f). Until then what you wrote may not be on the disc at all: open the file again for reading without closing it first and fgets finds nothing. (A file left open when the program ended did have its text on the disc afterwards, but that is not something to lean on.) Close what you open, and close it before you open it again.
Three pairs, from the smallest up:
| Write | Read | Unit |
|---|---|---|
putc(c, f) | c = getc(f) | one character; getc returns EOF at the end |
fputs(s, f) | fgets(buf, n, f) | a line; fgets keeps the \n, returns 0 at the end |
fprintf(f, format, ...) | - | anything printf can print |
getc returns an int, not a char, because it has to be able to return EOF - which is -1, and a char cannot be negative here (S9). Declare the variable that receives it as int.
fgets(line, 40, f) reads one line into line, at most 39 characters and the 0, and leaves the \n at the end of the string. It returns the address of line, or 0 when there is nothing left, which makes the usual loop while (fgets(line, 40, f)). fputs writes a string with no \n added, so put one in the string.
fprintf is printf with a file first: fprintf(f, "%d squared is %d\n", i, i * i). It is how a score table is written, and fgets with atoi (S14) is how it is read back.
Write one and two with fputs, each with a \n, close, and open the file again with the mode "rb" - read binary, which gives the bytes exactly as they are on the disc. Print them as numbers:
111 110 101 13 10 116 119 111 13 10 26 26 26 26 ...
Two things there that you did not write.
Every \n you wrote became two bytes, 13 and 10 - carriage return and line feed, which is how CP/M and XtalDOS mark a line's end. When you read the file in the ordinary "r" mode the pair comes back as one '\n' again, so a program that writes and reads text never sees it. A program reading a file byte by byte, counting characters, or seeking to a position will. DISP and ED80 expect the pair.
After the last line come 26s until the end of the file - character 26, ^Z, which is CP/M's marker for the end of a text file. The disc holds files in 128-byte pieces, so a ten-byte file is padded out, and the padding is 26 so that a reader knows to stop. getc in "r" mode returns EOF at the first 26, which is why the ordinary read gave 72 105 10 and stopped. A byte of 26 in your own data will end the file early, which is one reason a game's saved data is better written as text than as raw bytes.
#include STDIO.H
main()
{
FILE *f;
char line[40];
int c, n;
f = fopen("TWO.TXT", "w");
if (f == 0) {
printf("cannot write\n");
return;
}
fputs("one\n", f);
fputs("two\n", f);
fclose(f);
f = fopen("TWO.TXT", "r");
while (fgets(line, 40, f))
printf("got: %s", line);
fclose(f);
f = fopen("TWO.TXT", "rb");
n = 0;
while ((c = getc(f)) != EOF && n++ < 12)
printf("%d ", c);
printf("\n");
fclose(f);
f = fopen("NOSUCH.TXT", "r");
printf("a missing file gives %d\n", f);
}
#include ?STDIO.LIB?
got: %s has no \n of its own because the line fgets read still has one. The binary read stops after twelve bytes rather than print a screenful of 26s.
Starting from
Typed as FILES.C; compiled and run.
What you should see
got: one
got: two
111 110 101 13 10 116 119 111 13 10 26 26
a missing file gives 0
Afterwards, DIR TWO.* shows TWO.TXT, 2K as every file is, and DISP TWO.TXT shows the two lines.
n++ < 12 out of the binary loop. How many 26s are there, and what does that say about the file's size?fopen from "r" to "rb" and run it. Where does the 13 go?fclose(f) after the two fputs lines. What do the two reading loops print now?20.1 Write a program that asks for three names, writes them to NAMES.TXT one per line, then reads the file back and prints them numbered.
20.2 Write SCORE.TXT containing one number with fprintf, then a second program that reads it with fgets and atoi, adds 10, and writes it back. Run the second program three times.
20.3 Count the lines in STDIO.H by reading it with getc and counting 10s.
Worked solutions are in Appendix II.
| Symptom | Cause |
|---|---|
fopen returned 0 | The file is not there, or the name is misspelt (NOTES.TXT, not notes.txt; XtalDOS names are capitals). Test for 0. |
| A file you wrote earlier is gone | "w" on its name replaced it. |
The file's contents look right in DISP but are one byte longer per line than you counted | \n is two bytes on the disc. |
| Reading stops early | A byte of 26 in the data. Write text, not raw bytes. |
A char variable never equals EOF | It cannot hold -1. Use an int for what getc returns. |
fgets gives lines with \n at the end, and printf prints a blank line after each | Leave the \n out of your format, or take it off the string. |
undefined symbol fputs (or fgets) at the end of the compile | #include ?STDIO.LIB? is missing or is above main. |
fopen(name, "w" / "r" / "rb") gives a FILE *, 0 on failure - test it; "w" replaces. putc/getc (an int, EOF at the end), fputs/fgets (keeps the \n, 0 at the end), fprintf. fclose what you open. On the disc a line ends in 13 10, and the file is padded to 128 bytes with 26, which means stop.
S21, Memory - where everything in a running program lives, how to ask for more with malloc, how much there is, and what runs out first.