By the end of this lesson, cadets will be able to:
fscanf and from strings using sscanf. (Outcome 2)Complete before class. These give you the vocabulary and the library calls. The lecture will assume you have read them.
| Source | What it covers | Why read it |
|---|---|---|
| Beej's Guide to C, Chapter 9 (File Input/Output) | fopen, fclose, modes; fgetc, fputc, fgets, fputs; fread, fwrite; fseek, ftell; feof and ferror | The whole conceptual map for today. Read this through once and then again with code open beside it. |
Beej's Library Reference, <stdio.h> |
The reference entries for fscanf and sscanf in particular. Format specifiers, conversion specifications, return values. | fscanf and sscanf are how you parse structured records. The lab leans on sscanf heavily. |
If a reading feels dry, skim once for landmarks, then read again for detail when you sit down for the lab. Reading docs is a course skill; we will keep practicing it.
Before class, work through these three things. Total time: about 30 minutes.
<stdio.h> reference (the specifics). Stop and write down the answers to:
fopen return when the file does not exist?fgetc returning a value and fgets returning a value?fgetc int and not char?sscanf return on success, and what does it return when only some of the fields matched?A file on disk is just a sequence of bytes. C's standard library gives you a FILE pointer, which is an opaque handle that points to a kernel object tracking three things about your access to that file:
Every operation either reads from the cursor and advances it, writes at the cursor and advances it, or moves the cursor without reading or writing (fseek). The walkthrough in Section 5 makes the cursor visible so you can watch it move.
FILE *f = fopen("flights.log", "r"); if (f == NULL) { fprintf(stderr, "cannot open flights.log\n"); return 1; } /* ... read or write through f ... */ fclose(f);
Common modes:
| Mode | What it does |
|---|---|
| "r" | Read. Fails if the file does not exist. |
| "w" | Write. Creates or truncates the file. Anything that was there is gone. |
| "a" | Append. Writes go to the end of the file. Creates if missing. |
| "r+" | Read and write. File must exist. |
| "w+" | Read and write, but starts by truncating. Almost always not what you want. |
fclose flushes any pending writes and releases the file handle. Always close what you open. Forgetting fclose leaks resources and can lose unflushed writes if the process ends abnormally.
Three reading patterns, from finest grained to most structured:
fgetc(f) reads one byte and returns it as an int. On EOF, returns the special value EOF (which is -1). That is why the return type is int, not char: it needs to represent every possible byte plus EOF.fgets(buf, n, f) reads up to n - 1 characters into buf, stopping at a newline or EOF. The newline is included in the buffer if it was read. Returns NULL at EOF. This is your line-by-line workhorse.fscanf(f, fmt, ...) parses formatted fields directly from the file. Powerful but brittle: any deviation in the input throws off the parse and leaves the cursor at the failure point. Most C programmers prefer the two-step pattern of fgets + sscanf for that reason: read a whole line first, then parse it.The canonical line-reading loop:
char line[256];
while (fgets(line, sizeof line, f) != NULL) {
/* process one line */
}
sscanf reads from a string instead of a file. Combined with fgets, it gives you robust line parsing. Format specifiers you need today:
| Specifier | Reads |
|---|---|
| %d | Decimal integer. |
| %f | Floating-point number into a float *. (Use %lf for double.) |
| %s | Whitespace-delimited word. Dangerous without a width limit. |
| %7s | Same, but at most 7 characters. The width is a buffer-overflow defense. |
| %[^,] | All characters up to a comma. Useful for comma-separated records. |
| %4[^,] | Up to 4 characters, no comma. Width-limited string of a non-default character set. |
sscanf returns the number of fields it matched. Always check the return value to detect malformed input. Worked example: parsing a flight record
char tail[8], depart[5], arrive[5]; int duration, fuel; int matched = sscanf(line, "%7[^,],%4[^,],%4[^,],%d,%d", tail, depart, arrive, &duration, &fuel); if (matched != 5) { /* malformed line; skip and warn */ }
fputc(c, f) writes one byte.fputs(str, f) writes a string. Note: unlike puts, fputs does not add a newline. You include the \n yourself if you want one.fprintf(f, fmt, ...) is printf but to a file. Same format specifiers as you already know.An especially useful pattern: fprintf(stderr, ...) writes error messages to the standard error stream so they do not get mixed in with your program's normal output. The lab autograder checks stdout and stderr separately for exactly this reason.
Every file operation can fail. Things that go wrong in real programs:
The standard library tells you what went wrong through return values. The discipline is to check, every time:
| Function | Failure signal |
|---|---|
| fopen | Returns NULL. |
| fgetc | Returns EOF (also returned at end of file; use feof/ferror to distinguish). |
| fgets | Returns NULL at EOF or on error. |
| fscanf, sscanf | Returns count of matched fields. Compare to the count you expected. |
| fputc, fputs, fprintf | Returns EOF or a negative number on write failure. |
| fclose | Returns EOF on failure (rare but possible if a final write was buffered and now fails to flush). |
Add a "b" to the mode string to open in binary mode: "rb", "wb", "r+b".
On Linux and macOS, text and binary modes are identical; the distinction is a no-op. On Windows, text mode silently converts "\r\n" sequences in the file to "\n" in your buffer (and vice versa on write). Binary mode passes bytes through unchanged.
Rule of thumb: read CSV, logs, source code, and other text content in text mode. Read images, executables, and serialized data structures in binary mode. When in doubt, binary is safer because it never lies to you about what is in the file.
Three things to do in class. Use the checklist to track your progress.
make to confirm the starter compiles (it does; the TODO stubs are no-ops), and read flight.h carefully.
parse_flight_line first; it is the function you can write entirely from what we covered today. Get the basic case passing, then tackle malformed input. Submit to Gradescope when you have at least the basic functional test passing.
Fourteen tasks across four sections. Type each statement at the prompt. The terminal pane shows what your statement did; the right pane shows each open file as a row of bytes with the cursor marked underneath.
Enter your name. It will appear on the completion screen so you can include it in your screenshot for the Lesson 21 reflection on Blackboard.
fopen(..., "w"), write some bytes with fputc, then close and reopen for reading. The cursor behavior on freshly-written files is a common point of confusion.