#include <stdio.h>
#include <string.h>
int main ()
{

    fprintf(stdout, "Hello\n");

    /*
    stdin  标准输入设备
    stdout 标准输出设备
    stderr 标准错误设备
FILE

    文件操作
    打开创建 fopen

    读取数据 fread
    写入数据 fwrite
    文件指针 ftell fseek feof rewind

    关闭     fclose

    文件包括 : 文本方式(ascii)   二进制方式
    如果12345678,二进制就是 78 56 34 12
                文本就是 31 32 33 34 35 36 37 38(ascii保存)


    如果是文本方式,就把文件当作屏幕就好了
    fprintf
    fscanf
    fputs
    fgets

    如果是二进制方式
    读取数据 fread
    写入数据 fwrite
    */

    char player_name[32] = "kiciot";
    int  player_lv = 99;
    float player_money = 33.0;

    // 写入数据
    FILE *fd = fopen("C:\\Users\\kiciot\\Project\\temp\\1.dat", "wb");
    if (fd == NULL)
    {
        perror("fopen");
        return 0;
    }
    printf("open file ok fd: %p\n", fd);

    // 写入 player_name
    size_t size = fwrite(player_name, 1, sizeof(player_name), fd);
    if (size != sizeof(player_name))
    {
        perror("fwrite player_name");
    }
    else
    {
        printf("write player_name ok size: %zu\n", size);
    }

    // 写入 player_lv
    size = fwrite(&player_lv, 1, sizeof(player_lv), fd);
    if (size != sizeof(player_lv))
    {
        perror("fwrite player_lv");
    }
    else
    {
        printf("write player_lv ok size: %zu\n", size);
    }

    // 移动文件指针
    if (fseek(fd, 16, SEEK_SET) != 0)
    {
        perror("fseek");
        fclose(fd);
        return 0;
    }

    // 写入 player_money
    size = fwrite(&player_money, 1, sizeof(player_money), fd);
    if (size != sizeof(player_money))
    {
        perror("fwrite player_money");
    }
    else
    {
        printf("write player_money ok size: %zu\n", size);
    }

    fclose(fd);

    // 读取数据
    fd = fopen("C:\\Users\\kiciot\\Project\\temp\\1.dat", "rb");
    if (fd == NULL)
    {
        perror("fopen");
        return 0;
    }

    // 读取 player_name
    size = fread(player_name, 1, sizeof(player_name), fd);
    printf("read player_name size: %zu\n", size);
    printf("player_name: %s\n", player_name);

    // 读取 player_lv
    size = fread(&player_lv, 1, sizeof(player_lv), fd);
    printf("read player_lv size: %zu\n", size);
    printf("player_lv: %d\n", player_lv);

    // 移动文件指针
    fseek(fd, 16, SEEK_SET);

    // 读取 player_money
    size = fread(&player_money, 1, sizeof(player_money), fd);
    printf("read player_money size: %zu\n", size);
    printf("player_money: %.2f\n", player_money);

    fclose(fd);

    //文件读取
    FILE* fd = fopen("C:\\Users\\kiciot\\Project\\temp\\1.dat", "rb");
    //读第一个
    size_t size = fread(player_name, 1, 32, fd);
    printf("read size:%d\n", size);

    //读第二个
    size = fread(&player_lv, 1, sizeof(player_lv), fd);
    printf("read size:%d\n", size);

    //移动文件指针
    fseek(fd, 16, SEEK_SET);

    //读第三个
    size = fread(&player_money, 1, sizeof(player_money), fd);
    printf("read size:%d\n", size);

    fclose(fd);


    //文本方式写入
    FILE* fd2 = fopen("C:\\Users\\kiciot\\Project\\temp\\1.txt", "w");
    if (fd2 == NULL) {
        perror("File opening failed");
        return 1;
    }

    fprintf(fd2, "player_name:%s\nplayer_lv:%d\nplayer_money:%f\n", player_name, player_lv, player_money);
    fclose(fd2);

    // 读取文本
    char player_name2[32] = {0};
    int player_lv2 = 0;
    float player_money2 = 0;

    fd2 = fopen("C:\\Users\\kiciot\\Project\\temp\\1.txt", "r");
    if (fd2 == NULL) {
        perror("File opening failed");
        return 1;
    }

    char buf[260];
    while (fgets(buf, sizeof(buf), fd) != NULL) {
        if (sscanf(buf, "player_name:%s", player_name2) == 1) continue;
        if (sscanf(buf, "player_lv:%d", &player_lv2) == 1) continue;
        if (sscanf(buf, "player_money:%f", &player_money2) == 1) continue;
    }

    fclose(fd2);

    printf("Read player_name: %s\n", player_name2);
    printf("Read player_lv: %d\n", player_lv2);
    printf("Read player_money: %f\n", player_money2);

    // 使用strtok拆分字符串
    char str[] = "hello world test";
    char* token = strtok(str, " ");
    while (token != NULL) {
        printf("%s\n", token);
        token = strtok(NULL, " ");
    }

    return 0;

}
  1. 二进制文件适用于需要高效存储和访问大量数据的场景,但其可读性较差。

  2. 文本文件适用于需要易于阅读和编辑的场景,但其存储效率可能较低。

  3. 根据具体的应用需求选择使用二进制还是文本方式存储数据。

1. 文件打开和关闭

  • fopen:用于打开文件,返回一个指向 FILE 结构的指针。

FILE *fopen(const char *filename, const char *mode);
  • filename 是文件名。

  • mode 是文件的打开模式,比如 "r" (只读),"w" (只写),"a" (追加)。

  • fclose:关闭文件,释放相关资源。

int fclose(FILE *stream);
  • stream 是文件指针,指向被关闭的文件。

2. 文件读写

  • fgetc:从文件中读取一个字符。

int fgetc(FILE *stream);
  • 返回读取的字符,如果到达文件末尾则返回 EOF

  • fputc:向文件中写入一个字符。

int fputc(int character, FILE *stream);
  • character 是要写入的字符。

  • fgets:从文件中读取一行字符串,直到换行符、文件结束符或读取到指定大小为止。

char *fgets(char *str, int num, FILE *stream);
  • fputs:将一个字符串写入文件。

int fputs(const char *str, FILE *stream);
  • fread:以二进制方式从文件中读取数据块。

size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
  • ptr 是数据存储的目标地址。

  • size 是每个数据块的大小。

  • count 是读取的数据块数量。

  • fwrite:以二进制方式将数据块写入文件。

size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
  • fprintffscanf:格式化地将数据写入和从文件中读取。

int fprintf(FILE *stream, const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
  • 用于格式化地写入和读取数据,类似 printfscanf,但操作对象是文件。

  • sscanfsprintf:用于将格式化数据从字符串读入和写出到字符串。

int sscanf(const char *str, const char *format, ...);
int sprintf(char *str, const char *format, ...);

3. 文件定位

  • fseek:用于移动文件指针到指定位置。

int fseek(FILE *stream, long int offset, int origin);
  • offset 表示偏移量,origin 可以是 SEEK_SET (文件开头),SEEK_CUR (当前位置) 或 SEEK_END (文件末尾)。

  • ftell:返回文件指针的当前位置。

long int ftell(FILE *stream);
  • rewind:将文件指针重置到文件的开头。

void rewind(FILE *stream);

4. 文件状态和错误检查

  • feof:检查文件指针是否到达文件末尾。

int feof(FILE *stream);
  • 当到达文件末尾时返回非零值。

  • ferror:检查文件操作是否出错。

int ferror(FILE *stream);
  • clearerr:清除文件的错误和文件结束标志。

void clearerr(FILE *stream);

5. 临时文件操作

  • tmpfile:创建一个临时文件,文件自动删除。

FILE *tmpfile(void);
  • 生成一个临时文件供读写,文件在关闭后自动删除。

  • tmpnam:生成一个唯一的临时文件名。

char *tmpnam(char *str);

6. 文件删除和重命名

  • remove:删除文件。

int remove(const char *filename);
  • rename:重命名文件。

int rename(const char *oldname, const char *newname);

示例代码

以下代码展示了如何使用上述函数完成文件的打开、写入、读取和关闭操作:

#include <stdio.h>

int main() {
    // 打开文件用于写入
    FILE *file = fopen("example.txt", "w");
    if (file == NULL) {
        perror("Failed to open file");
        return 1;
    }

    // 写入数据
    fprintf(file, "Name: %s\nAge: %d\n", "Alice", 23);
    fclose(file);

    // 打开文件用于读取
    file = fopen("example.txt", "r");
    if (file == NULL) {
        perror("Failed to open file");
        return 1;
    }

    // 读取并打印数据
    char name[20];
    int age;
    fscanf(file, "Name: %s\nAge: %d\n", name, &age);
    printf("Read from file - Name: %s, Age: %d\n", name, age);

    fclose(file);

    return 0;
}

这些是 C 语言中最常用的文件操作函数,可以完成大部分文件处理需求。

sscanffscanf 都是用于从字符串或文件中读取格式化数据的函数,但它们在使用场景和参数上有一些区别。

sscanf

  • 用于从字符串中读取数据。

  • 语法:int sscanf(const char *str, const char *format, ...);

  • 读取的数据源是字符串 str,而不是文件。

  • 常用于从内存中的字符串进行解析,而不涉及文件操作。

示例:

char str[] = "player_name:Alice";
char player_name[32];
sscanf(str, "player_name:%s", player_name);
printf("Player name: %s\n", player_name);

fscanf

  • 用于从文件中读取数据。

  • 语法:int fscanf(FILE *stream, const char *format, ...);

  • 读取的数据源是文件指针 stream,如通过 fopen 打开的文件。

  • 适合直接从文件中解析数据,而无需逐行读取和缓冲处理。

示例:

FILE *file = fopen("data.txt", "r");
char player_name[32];
fscanf(file, "player_name:%s", player_name);
fclose(file);
printf("Player name: %s\n", player_name);

总结区别

  • 数据源sscanf 从字符串中读取数据,fscanf 从文件中读取数据。

  • 应用场景sscanf 常用于内存数据处理,fscanf 更适用于文件数据处理。

FILE 结构体是一个表示文件流的结构体,用于管理文件的打开、关闭、读写等操作。 struct _iobuf 结构体定义了多个成员,描述了文件流的内部状态:

struct _iobuf {
    char *_ptr;        // 指向当前文件位置的指针
    int _cnt;          // 剩余的未读取/写入的字节数
    char *_base;       // 缓冲区的起始地址
    int _flag;         // 文件状态标志,描述文件流的状态(例如是否为读、写等)
    int _file;         // 文件描述符,用于标识文件
    int _charbuf;      // 单字符缓冲区标志
    int _bufsiz;       // 缓冲区的大小
    char *_tmpfname;   // 临时文件名(如果是临时文件)
};

各成员的具体说明

  1. _ptr:指向文件缓冲区中的当前位置,即当前读写操作的位置。

  2. _cnt:表示缓冲区中剩余可读取或可写入的字节数。文件流在读取或写入时会使用此值判断是否需要刷新缓冲区或重新填充数据。

  3. _base:指向文件流的缓冲区的起始位置。对于已打开的文件流,缓冲区用于存储文件中即将读取或写入的数据。

  4. _flag:文件状态标志,包含文件打开模式、是否到达文件末尾、是否出现错误等状态信息。通常,文件标志位通过位运算管理,例如 _IOREAD 表示读取模式,_IOWRT 表示写入模式等。

  5. _file:文件描述符,用于唯一标识一个打开的文件流。此值通常由操作系统提供,表示文件系统中的文件句柄。

  6. _charbuf:用于指示是否启用了单字符缓冲。在某些场景下,可能只需要单字符的缓冲模式。

  7. _bufsiz:缓冲区的大小,决定了文件流可以使用的缓冲区内存量。

  8. _tmpfname:指向文件的临时文件名,通常在创建临时文件时使用。这一字段可以为空或未使用。

用途

通过 typedef struct _iobuf FILE;,这个结构体被定义为 FILE,便于在标准输入输出库中使用 FILE * 作为文件指针。这样用户在操作文件时,只需使用 FILE * 指针调用标准 I/O 函数(如 fopenfreadfwritefclose 等),不需要直接操作 struct _iobuf 的成员。这种设计抽象了文件的细节,使得文件流管理更加方便和安全。

fflush

fflush 是 C 标准库中的一个函数,主要用于刷新文件流的缓冲区。函数原型如下:

int fflush(FILE *stream);

功能

fflush 函数的主要功能是将文件流的输出缓冲区中的数据立即写入到文件中。如果 stream 参数为 NULLfflush 会刷新所有已打开的输出流。

使用场景

在标准 I/O 库中,大多数文件操作都是缓冲的,意味着写入的数据通常会先存储在内存中的缓冲区,直到缓冲区填满或遇到文件关闭操作时才会写入到文件中。fflush 用于立即将这些缓冲的数据写入文件,避免因程序崩溃或其他原因导致数据丢失。

常见应用

  1. 文件写入场景:当程序向文件中写入数据,但希望确保数据立即生效时,调用 fflush 能强制刷新缓冲区,把数据真正写入文件。

  2. 标准输出刷新:在 stdout 上使用 fflush(stdout); 可以确保在标准输出上立即看到打印的内容,避免缓冲延迟。这在需要即时显示日志或调试信息时非常有用。

  3. 交互式输入/输出:在输入之前刷新标准输出,有助于确保提示信息立即显示给用户。例如:

printf("Enter your name: ");
fflush(stdout); // 确保提示立即显示
scanf("%s", name);

返回值

  • 如果成功刷新,fflush 返回 0

  • 如果出现错误,fflush 返回 EOF,可以通过 errno 来获取具体的错误信息。

注意事项

  • fflush 对于输入流(如 stdin)的行为是未定义的,因此不应在 stdin 等输入流上使用它。

  • 如果缓冲区已满或遇到文件关闭等情况,缓冲区内容会自动刷新,因此不必每次写入都调用 fflush

其他

因为文件读写的时候,都是一个扇区或者一个块去读写的,至少是512,但是一般是一个页,4096

//得知道数据的格式

//批量读取
    char buffer[1024];
    FILE* fd3 = fopen("2.txt", "rt");

    while(!feof(fd3))
    {
        fgets(buffer, sizeof(buffer), fd3);
        printf("%s", buffer);
    }

    fclose(fd3);

//防止EOF
    FILE* fd4 = fopen("3.txt", "rt");
    while (true)
    {
        char ch = fgetc(fd4);
        if(ch == EOF)
            break;
        printf("%c", ch);
    }

    fclose(fd4);

bug

#include <stdio.h>

int main() {
    char buf[7];
    FILE* fd5 = fopen("4.dat", "r+");  // 以读写模式打开文件
    if (fd5 == NULL) {
        perror("Failed to open file");
        return 1;
    }

    fread(buf, 1, 7, fd5);  // 从文件中读取 7 个字节到 buf 中

    // 打印读取的字节数据(可选)
    for (int i = 0; i < 7; i++) {
        printf("%02x ", (unsigned char)buf[i]);
    }
    printf("\n");

    buf[0] = 1;   // 修改 buf 的第一个字节为 1
    buf[1] = 2;   // 修改 buf 的第二个字节为 2
    buf[2] = 3;   // 修改 buf 的第三个字节为 3

    fseek(fd5, 0, SEEK_SET);  // 将文件指针移到文件开头
    fwrite(buf, 1, 7, fd5);   // 将修改后的 buf 写入文件前 7 个字节

    fflush(fd5);  // 刷新缓冲区,确保写入文件
    fclose(fd5);  // 关闭文件

    return 0;
}

调一调bug

读入的时候会多一个换行

在 C 语言中,"r+""rb+" 都是用于以读写模式打开文件的,但它们的区别在于文件的文本模式二进制模式

  1. "r+":以读写模式打开文件,文件必须存在。使用这种方式打开的文件是文本模式,这意味着在某些操作系统(如 Windows)中,文件中的换行符 \n 会被处理成 \r\n(回车换行符)。这对读取和写入文件内容时会有一些影响,特别是在处理换行符时。

  2. "rb+":以读写模式打开文件,但以二进制模式进行操作。文件同样必须存在。在二进制模式下,文件的内容会按照原始的字节流读写,不会对换行符 \n\r\n 进行转换。这对精确控制文件内容的读写非常有用,尤其是当文件包含非文本数据或需要严格控制文件字节时(如图像、音频、二进制数据等)。

主要区别

  • 换行符转换:在文本模式("r+")下,'\n''\r\n' 之间可能会有转换,这在 Windows 系统上尤其明显。而在二进制模式("rb+")下,文件内容不作任何转换,完全按照原始字节流操作。

  • 平台依赖性:在不同平台(Windows、Linux 等)下,文本模式下的文件读写行为可能略有不同(特别是在换行符处理上),而二进制模式则是平台无关的。

何时使用

  • 如果处理文本文件(如 .txt 文件),并希望处理平台相关的换行符,则可以使用 "r+"

  • 如果处理二进制文件(如图片、音频、视频文件,或任何需要精确字节控制的文件),建议使用 "rb+"

示例对比

假设文件 data.txt 内容如下(在 Windows 系统上):

Hello
World

使用 "r+" 打开并读取

FILE *file = fopen("data.txt", "r+");
fread(buffer, sizeof(char), 10, file);

在 Windows 上,文件中每行的 \n 会被转换成 \r\n,因此实际读取的字节数可能比预期的少或多。

使用 "rb+" 打开并读取

FILE *file = fopen("data.txt", "rb+");
fread(buffer, sizeof(char), 10, file);

在二进制模式下,文件内容会精确读取,'\n' 不会被转换,文件中的每个字节都以原始形式读取。

总结

  • "r+":文本模式,可能会对换行符进行转换,适合处理纯文本文件。

  • "rb+":二进制模式,不会进行任何转换,适合处理二进制文件或需要精确控制字节的操作。

文件打开模式

在C语言中,fopen()函数的文件打开模式有很多种,每种模式在文件读写上有不同的行为。以下是常用的文件打开模式及其说明:

读取模式

  • "r":以只读模式打开文件,文件必须存在。

  • "rb":以二进制的只读模式打开文件,文件必须存在。

写入模式

  • "w":以只写模式打开文件,若文件已存在会清空内容,文件不存在则创建新文件。

  • "wb":以二进制的只写模式打开文件,若文件已存在会清空内容,文件不存在则创建新文件。

追加模式

  • "a":以追加模式打开文件,文件指针指向文件末尾,文件不存在则创建新文件。

  • "ab":以二进制追加模式打开文件,文件指针指向文件末尾,文件不存在则创建新文件。

读写模式

  • "r+":以读写模式打开文件,文件必须存在。

  • "rb+""r+b":以二进制读写模式打开文件,文件必须存在。

  • "w+":以读写模式打开文件,若文件已存在会清空内容,文件不存在则创建新文件。

  • "wb+""w+b":以二进制读写模式打开文件,若文件已存在会清空内容,文件不存在则创建新文件。

  • "a+":以读写追加模式打开文件,文件指针指向文件末尾,文件不存在则创建新文件。

  • "ab+""a+b":以二进制读写追加模式打开文件,文件指针指向文件末尾,文件不存在则创建新文件。

模式总结表