C in C is best learned by connecting the rule to a small command-line program. Start with the smallest function, observe the output, and then add one realistic constraint so the concept becomes practical.
The key habit for this lesson is to watch pointer, array, or file buffer as it changes. That makes the topic easier to debug, easier to explain in interviews, and easier to use in real code without memorizing isolated syntax.
C provides file I/O through the <stdio.h> library. Files are accessed through a FILE pointer. Always close files after use with fclose().
| Mode | Description | File Exists | File Missing |
|---|---|---|---|
| "r" | Read text | Opens for reading | Returns NULL |
| "w" | Write text | Truncates to empty | Creates new file |
| "a" | Append text | Appends to end | Creates new file |
| "r+" | Read + Write | Opens for both | Returns NULL |
| "w+" | Write + Read | Truncates to empty | Creates new file |
| "a+" | Append + Read | Appends, reads from start | Creates new file |
| "rb" | Read binary | Opens for binary read | Returns NULL |
| "wb" | Write binary | Truncates | Creates new file |
| Function | Description |
|---|---|
| fopen(path, mode) | Opens a file; returns FILE* or NULL on failure |
| fclose(fp) | Closes the file and flushes buffer |
| fprintf(fp, fmt, ...) | Writes formatted text to file |
| fscanf(fp, fmt, ...) | Reads formatted data from file |
| fputc(c, fp) | Writes a single character |
| fgetc(fp) | Reads a single character |
| fputs(str, fp) | Writes a string (no newline added) |
| fgets(buf, n, fp) | Reads a line (up to n-1 chars) |
| fwrite(ptr, size, n, fp) | Writes n binary blocks of given size |
| fread(ptr, size, n, fp) | Reads n binary blocks of given size |
| fseek(fp, offset, origin) | Moves file position pointer |
| ftell(fp) | Returns current file position |
| rewind(fp) | Resets position to beginning |
| feof(fp) | Returns non-zero if end of file reached |
| ferror(fp) | Returns non-zero if an error occurred |
#include <stdio.h>
int main() {
FILE *fp;
// --- Write to file ---
fp = fopen("students.txt", "w");
if (fp == NULL) {
printf("Error opening file!\n");
return 1;
}
fprintf(fp, "Alice 20 3.85\n");
fprintf(fp, "Bob 22 3.20\n");
fprintf(fp, "Carol 21 3.65\n");
fclose(fp);
printf("Data written to students.txt\n");
// --- Read from file ---
fp = fopen("students.txt", "r");
if (fp == NULL) {
printf("Error opening file!\n");
return 1;
}
char name[50];
int age;
float gpa;
printf("\nReading from file:\n");
while (fscanf(fp, "%s %d %f", name, &age, &gpa) == 3) {
printf("Name: %-10s Age: %d GPA: %.2f\n", name, age, gpa);
}
fclose(fp);
return 0;
}
#include <stdio.h>
int main() {
FILE *src, *dest;
int ch;
src = fopen("students.txt", "r");
if (src == NULL) { printf("Cannot open source file\n"); return 1; }
dest = fopen("students_copy.txt", "w");
if (dest == NULL) { fclose(src); printf("Cannot create dest file\n"); return 1; }
// Copy character by character
while ((ch = fgetc(src)) != EOF) {
fputc(ch, dest);
}
fclose(src);
fclose(dest);
printf("File copied successfully!\n");
// Read back using fgets
dest = fopen("students_copy.txt", "r");
char line[100];
printf("\nContents of copy:\n");
while (fgets(line, sizeof(line), dest) != NULL) {
printf("%s", line);
}
fclose(dest);
return 0;
}
#include <stdio.h>
#include <string.h>
typedef struct {
char name[30];
int age;
float salary;
} Employee;
int main() {
Employee emp[3] = {
{"Alice", 30, 75000.0f},
{"Bob", 25, 60000.0f},
{"Carol", 35, 90000.0f}
};
// Write binary data
FILE *fp = fopen("employees.bin", "wb");
if (!fp) { printf("Cannot open file\n"); return 1; }
fwrite(emp, sizeof(Employee), 3, fp);
fclose(fp);
printf("Binary data written.\n");
// Read binary data
Employee read_emp[3];
fp = fopen("employees.bin", "rb");
if (!fp) { printf("Cannot open file\n"); return 1; }
int count = fread(read_emp, sizeof(Employee), 3, fp);
fclose(fp);
printf("\nRead %d records:\n", count);
for (int i = 0; i < count; i++) {
printf("%-10s Age: %d Salary: %.2f\n",
read_emp[i].name, read_emp[i].age, read_emp[i].salary);
}
// fseek and ftell
fp = fopen("employees.bin", "rb");
fseek(fp, sizeof(Employee), SEEK_SET); // skip first record
Employee second;
fread(&second, sizeof(Employee), 1, fp);
printf("\nSecond record: %s\n", second.name);
printf("File position: %ld bytes\n", ftell(fp));
fclose(fp);
return 0;
}
Use C when the program needs a clear answer to a specific problem, not because the keyword looks familiar. In a real C task, first name the input, then name the transformation, then name the output. This small discipline shows whether the topic is being used correctly or only copied from an example.
A reliable practice flow is: create the smallest working function, add one normal case, add one edge case such as missing files and partial writes, and then confirm the result with compiler warnings and printed output. If the result surprises you, reduce the code until the behavior is visible again.
The most common trap here is ignoring failed opens, paths, and resource cleanup. Avoid it by writing one sentence before the code that explains why C is the right choice. After the code runs, verify the lesson by doing this: check the return value before reading or writing.
Ignoring failed opens, paths, and resource cleanup.
Write the expected behavior first, then make the example prove it.
Practicing only the perfect input.
Also test missing files and partial writes before considering the lesson complete.
Looking only at the final output.
Trace pointer, array, or file buffer through each important step.
Use it when the problem matches the behavior shown in the example and when the result can be verified through compiler warnings and printed output.
Start with a tiny case, then test missing files and partial writes. The main warning sign is ignoring failed opens, paths, and resource cleanup.
Trace pointer, array, or file buffer, predict the result, run the example, and compare your prediction with the actual output.
Explore 500+ free tutorials across 20+ languages and frameworks.