File I/O

A file is like a safe — you must open it before use (fopen), follow the rules for storing and retrieving items (read/write modes), and always lock it when done (fclose), or your data may be lost.

Basic Concepts of Files

C treats all external devices as "streams". There are two types of file streams:

The operating system manages open files through a FILE structure, and we operate on it via a FILE * pointer.

File Pointer

C
FILE *fp;

This pointer is the "key" to all subsequent file operations. Without it, nothing can be done.

Opening and Closing Files

fopen

C
FILE *fopen(const char *filename, const char *mode);
Mode Meaning If file doesn't exist If file exists
"r" Read only Error Read from beginning
"w" Write only Create Truncate content
"a" Append Create Append at end
"r+" Read and write Error Operate from beginning
"w+" Read and write Create Truncate content
"a+" Read and append Create Append at end
"rb" Binary read only Error Read from beginning
"wb" Binary write only Create Truncate content
"ab" Binary append Create Append at end
💡 Tip: "w" mode truncates existing files! This is the most common mistake beginners make — using "w" when they meant to append, resulting in all data being lost.

fclose

C
int fclose(FILE *fp);

Closing a file flushes the buffer and releases resources. Returns 0 on success, EOF on failure.

C
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("data.txt", "w");
    if (fp == NULL) {
        printf("Failed to open file\n");
        return 1;
    }
    fprintf(fp, "Hello, File!\n");
    fclose(fp);
    return 0;
}
⚠️ Note: Every fopen must have a corresponding fclose. Forgetting to close a file causes memory leaks and data loss.

Text File I/O

Character I/O: fgetc / fputc

C
int fgetc(FILE *fp);
int fputc(int ch, FILE *fp);

fgetc reads one character, returning EOF at the end of file. fputc writes one character.

String I/O: fgets / fputs

C
char *fgets(char *str, int n, FILE *fp);
int fputs(const char *str, FILE *fp);

fgets reads at most n-1 characters, stopping at a newline or end of file, and automatically appends '\0'.

C
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("poem.txt", "w");
    if (fp == NULL) {
        return 1;
    }
    fputs("Riverside at sunset\n", fp);
    fputs("River flows to the sea\n", fp);
    fclose(fp);

    fp = fopen("poem.txt", "r");
    if (fp == NULL) {
        return 1;
    }
    char line[256];
    while (fgets(line, sizeof(line), fp) != NULL) {
        printf("%s", line);
    }
    fclose(fp);
    return 0;
}
TEXT
Riverside at sunset
River flows to the sea

Binary File I/O

fread / fwrite

C
size_t fread(void *ptr, size_t size, size_t count, FILE *fp);
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *fp);

Parameters: ptr is the data buffer, size is the byte size of each element, count is the number of elements. The return value is the actual number of elements read or written.

C
#include <stdio.h>

typedef struct {
    int id;
    char name[20];
    float score;
} Student;

int main(void) {
    Student stu = {1, "Alice", 92.5f};

    FILE *fp = fopen("student.dat", "wb");
    if (fp == NULL) {
        return 1;
    }
    fwrite(&stu, sizeof(Student), 1, fp);
    fclose(fp);

    Student read_stu;
    fp = fopen("student.dat", "rb");
    if (fp == NULL) {
        return 1;
    }
    fread(&read_stu, sizeof(Student), 1, fp);
    fclose(fp);

    printf("ID: %d, Name: %s, Score: %.1f\n",
           read_stu.id, read_stu.name, read_stu.score);
    return 0;
}
TEXT
ID: 1, Name: Alice, Score: 92.5
💡 Tip: Binary mode is ideal for storing struct data — it's efficient and preserves content exactly. Text mode may handle newline characters differently across operating systems, while binary mode saves everything as-is.

Formatted I/O

fprintf / fscanf

C
int fprintf(FILE *fp, const char *format, ...);
int fscanf(FILE *fp, const char *format, ...);

Their usage is identical to printf/scanf, just with an additional file pointer parameter.

C
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("scores.txt", "w");
    if (fp == NULL) {
        return 1;
    }
    fprintf(fp, "%s %d %.1f\n", "Bob", 2, 88.0);
    fprintf(fp, "%s %d %.1f\n", "Carol", 3, 95.5);
    fclose(fp);

    fp = fopen("scores.txt", "r");
    if (fp == NULL) {
        return 1;
    }
    char name[20];
    int id;
    float score;
    while (fscanf(fp, "%s %d %f", name, &id, &score) == 3) {
        printf("Name: %s, ID: %d, Score: %.1f\n", name, id, score);
    }
    fclose(fp);
    return 0;
}
TEXT
Name: Bob, ID: 2, Score: 88.0
Name: Carol, ID: 3, Score: 95.5

File Position Pointer

Every open file has a "position pointer" indicating the current read/write location. It's like a bookmark while reading — wherever you've read to, the bookmark marks that spot.

fseek

C
int fseek(FILE *fp, long offset, int origin);
origin Meaning
SEEK_SET Beginning of file
SEEK_CUR Current position
SEEK_END End of file

ftell / rewind

C
long ftell(FILE *fp);
void rewind(FILE *fp);

ftell returns the current position (byte offset from the beginning of the file). rewind is equivalent to fseek(fp, 0, SEEK_SET).

Example

Using fseek and ftell to calculate file size:

C
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("data.txt", "rb");
    if (fp == NULL) {
        printf("Failed to open file\n");
        return 1;
    }
    fseek(fp, 0, SEEK_END);
    long size = ftell(fp);
    rewind(fp);
    printf("File size: %ld bytes\n", size);
    fclose(fp);
    return 0;
}
▶ Try it Yourself

Example

Random access to a specific record in a binary file:

C
#include <stdio.h>

typedef struct {
    int id;
    char name[20];
    float score;
} Student;

int main(void) {
    Student students[3] = {
        {1, "Alice", 92.5f},
        {2, "Bob", 88.0f},
        {3, "Carol", 95.5f}
    };

    FILE *fp = fopen("students.dat", "wb");
    if (fp == NULL) {
        return 1;
    }
    fwrite(students, sizeof(Student), 3, fp);
    fclose(fp);

    fp = fopen("students.dat", "rb");
    if (fp == NULL) {
        return 1;
    }
    int target = 2;
    fseek(fp, (target - 1) * sizeof(Student), SEEK_SET);
    Student s;
    fread(&s, sizeof(Student), 1, fp);
    fclose(fp);

    printf("Record %d: ID=%d, Name=%s, Score=%.1f\n",
           target, s.id, s.name, s.score);
    return 0;
}
▶ Try it Yourself
TEXT
Record 2: ID=2, Name=Bob, Score=88.0
💡 Tip: This is the principle behind "random access" in databases! By calculating the offset, you can jump directly to a specific position without traversing from the beginning.

Other File Functions

feof

C
int feof(FILE *fp);

Checks whether the end of file has been reached. Returns non-zero at EOF. Note: feof only becomes true after a read operation has failed, not as a pre-check.

fflush

C
int fflush(FILE *fp);

Forces a flush of the buffer, writing buffered data to the file. Calling fflush on an input stream results in undefined behavior.

remove / rename

C
int remove(const char *filename);
int rename(const char *old, const char *new);

Delete and rename files. Return 0 on success.

General Pattern for File Operations

Most file operations follow this pattern:

  1. Open the file with fopen and check the return value
  2. Read or write data in a loop
  3. Close the file with fclose
C
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("input.txt", "r");
    if (fp == NULL) {
        perror("fopen");
        return 1;
    }

    int ch;
    while ((ch = fgetc(fp)) != EOF) {
        putchar(ch);
    }

    fclose(fp);
    return 0;
}
⚠️ Note: Checking the fopen return value is an absolute rule! The file might not exist, you might lack permissions, or the disk could be full — using a null pointer without checking will crash immediately.

❓ FAQ

Q fopen returns NULL but the file clearly exists. What's wrong?
A It's likely a path issue. Relative paths are based on the program's working directory, not the source file directory. When running in an IDE, the working directory may differ from what you expect.
Q What's the difference between text mode and binary mode?
A On Windows, text mode translates between \n and \r\n during read/write, while binary mode performs no translation. On Linux, there is no difference between the two.
Q Are files written with fwrite compatible across different compilers?
A Not necessarily. Memory alignment, byte order (endianness), and basic type sizes may all differ. Cross-platform code requires manual serialization.
Q Why can't feof be used as a loop condition?
A feof only becomes true after a read operation has passed the end of file. Using it as a loop condition causes the last iteration to process invalid data. Use the return value of fread/fgets instead.

📖 Summary

📝 Exercises

  1. Write a program that counts the number of lines, words, and characters in a text file
  2. Implement a simple file copy program that reads a source file in binary mode and writes to a destination file
  3. Write a program that saves an array of structs to a binary file, then reads them back and prints them out
100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏