【C语言】文件操作 -- 详解(下)

简介: 【C语言】文件操作 -- 详解(下)

【C语言】文件操作 -- 详解(上)https://developer.aliyun.com/article/1514532?spm=a2c6h.13148508.setting.29.4b904f0ejdbHoA

3、字符输出函数 fputc

  • 将参数 char 指定的字符写入到指定的流 stream 中,并把位置标识符向前移动 (字符必须为一个无符号字符)。适用于所有输出流。

#include <stdio.h>
 
int main()
{
    FILE* pf = fopen("test.txt", "w");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
 
    fputc('a', pf);
    fputc('b', pf);
    fputc('c', pf);
 
    fclose(pf);
    pf = NULL;
 
    return 0;
}

abc 成功被写入。

倘若将代码修改为:

#include <stdio.h>
 
int main()
{
    FILE* pf = fopen("test.txt", "w");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
 
    /*fputc('a', pf);
    fputc('b', pf);
    fputc('c', pf);*/
 
    fclose(pf);
    pf = NULL;
 
    return 0;
}

此时再次运行,我们发现那个文件里的内容不见了(大小也变为0kb):


4、文本行输入函数 fgets

  • 从指定的流 stream 读取一行,并把它存储在 string 所指向的字符串中,当读取(n-1)个字符时,或者读取到换行符、到达文件末尾时,它会停止,具体视情况而定。适用于所有输入流。

注意:假如 n 是 100,读取到的就是 99 个字符(n-1),因为要留一个字符给 '\0'。

#include <stdio.h>
 
int main()
{
    char arr[10] = "xxxxxx"; // 存放处
 
    FILE* pf = fopen("test.txt", "r");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
 
    fgets(arr, 4, pf);
    printf("%s\n", arr);
 
    fgets(arr, 4, pf);
    printf("%s\n", arr);
 
    fclose(pf);
    pf = NULL;
 
    return 0;
}


5、文本行输出函数 fputs

  • 将字符串写入到指定的流 stream 中(不包括空字符)。适用于所有输出流。

#include <stdio.h>
 
int main()
{
    FILE* pf = fopen("test.txt", "w");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
 
    fputs("abcdef", pf);
    fputs("123456", pf);
 
    fclose(pf);
    pf = NULL;
 
    return 0;
}

如果想要在 "abcdef" 后面换行,只需在 fputs("abcdef\n", pf); 加上换行符即可。


6、 格式化输入函数 fscanf

  • fscanf 用于对格式化的数据进行读取,从流 stream 读取格式化输入。适用于所有输入流。

#include <stdio.h>
 
struct Student
{
    char name[10];
    int id;
    float score;
};
 
int main()
{
    struct Student s1 = { 0 };
 
    // 对格式化的数据进行写文件
    FILE* pf = fopen("test.txt", "r");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
 
    fscanf(pf, "%s %d %f", s1.name, &(s1.id), &(s1.score));
 
    printf("%s %d %f\n", s1.name, s1.id, s1.score);
 
    fclose(pf);
    pf = NULL;
 
    return 0;
}

注意: p1.name本身就是地址(不用&)。


7、格式化输出函数 fprintf

#include<stdio.h>
struct S
{
  char arr[10];
  int num;
  float sc;
};
 
int main()
{
  struct S s = { "abcdef", 10, 5.5f };
  FILE* pf1 = fopen("test.txt", "w");
  if(pf1 == NULL)
  {
    perror("fopen");
    return 1;
  }
 
  fprintf(pf1, "%s %d %f", s.arr, s.num, s.sc); // 将结构体格式化的数据写入文件
 
  fclose(pf1);
  pf1 = NULL;
 
  struct S temp = { 0 }; // 用于存储读出的数据 
  FILE* pf2 = fopen("test.txt", "r");
  if(pf2 == NULL)
  {
    perror("fopen");
    return 1;
  }
 
  fscanf(pf2, "%s %d %f", temp.arr, &(temp.num), &(temp.sc)); // 再把数据从文件中读到结构体中
 
  printf("%s %d %f\n", temp.arr, temp.num, temp.sc); // 打印
 
  fclose(pf2);
  pf2 = NULL;
  return 0;
}


8、二进制输入函数 fread

  • 从流中读取,从给定流 stream 读取数据到 buffer 所指向的数组中。

#include <stdio.h>
// 二进制的形式读
 
struct S
{
    char arr[10];
    int num;
    float score;
};
 
int main()
{
    struct S s = { 0 };
 
    FILE* pf = fopen("test.txt", "r");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
 
    fread(&s, sizeof(struct S), 1, pf);
 
    printf("%s %d %f", s.arr, s.num, s.score);
 
    fclose(pf);
    pf = NULL;
 
    return 0;
}

9、二进制输出函数 fwrite

  • 写一个数据到流中去,把 buffer 所指向的数组中的数据写入到给定流 stream 中。

#include <stdio.h>
// 二进制的形式写
 
struct S
{
    char arr[10];
    int num;
    float score;
};
 
int main()
{
    struct S s = { "abcde", 10, 5.5f };
 
    FILE* pf = fopen("test.txt", "w");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
    fwrite(&s, sizeof(struct S), 1, pf);
 
    fclose(pf);
    pf = NULL;
 
    return 0;
}

为什么是乱码?为什么 abcde 不是乱码?
  1. 我们刚才用的都是文本编译器,文本编译器打开二进制形式的文件完全是两种状态。
  2. 因为字符串以文本形式写进去和以二进制形式写进去是一样的,但是对于整数、浮点数等来说就不一样了,文本形式写入和二进制形式写入完全是两个概念。

结论:fwritefread 是一对,fwrire 写进去用 fread 读。


八、文件的随机读写

1、文件指针定位函数 fseek

  • 根据文件指针的位置和偏移量来定位文件指针。

int fseek (FILE* stream, long int offset, int origin);
 
// offset是偏移量,origin是起始位置,有三种选项:
 
// 1、SEEK_CUR - 当前文件指针的位置开始偏移。
// 2、SEEK_END - 文件的末尾位置开始偏移。
// 3、SEEK_SET - 文件的起始位置开始偏移。

#include <stdio.h>
 
int main()
{
    FILE* pf = fopen("test.txt", "r");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
 
    int ch = fgetc(pf);
    printf("%c\n", ch);
    ch = fgetc(pf);
    printf("%c\n", ch);
    ch = fgetc(pf);
    printf("%c\n", ch);
 
    fclose(pf);
    pf = NULL;
 
    return 0;
}

如果我想得到 a a b,该怎么处理呢?
#include <stdio.h>
 
int main()
{
    FILE* pf = fopen("test.txt", "r");
    if (pf == NULL) 
    {
        perror("fopen");
        return 1;
    }
 
    int ch = fgetc(pf);
    printf("%c\n", ch);
 
    // 调整文件指针
    fseek(pf, -1, SEEK_CUR); // SEEK_CUR为当前文件指针位置,偏移量为-1,向前移动1个单位
    ch = fgetc(pf);
    printf("%c\n", ch);
    ch = fgetc(pf);
    printf("%c\n", ch);
 
    fclose(pf);
    pf = NULL;
 
    return 0;
}


⚪用 SEEK_SET ,打印 a d e

#include <stdio.h>
 
int main()
{
    FILE* pf = fopen("test.txt", "r");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
 
    int ch = fgetc(pf);
    printf("%c\n", ch);
 
    // 调整文件指针
    fseek(pf, 2, SEEK_CUR); // SEEK_SET为文件的起始位置,偏移量为2,向后移动2个单位
    ch = fgetc(pf);
    printf("%c\n", ch);
    ch = fgetc(pf);
    printf("%c\n", ch);
 
    fclose(pf);
    pf = NULL;
 
    return 0;
}


⚪用 SEEK_END ,打印 a e f

#include <stdio.h>
 
int main()
{
    FILE* pf = fopen("test.txt", "r");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
 
    int ch = fgetc(pf);
    printf("%c\n", ch);
 
    // 调整文件指针
    fseek(pf, -2, SEEK_END); // SEEK_END为当前文件末尾位置,偏移量为-2,向前移动2个单位
    ch = fgetc(pf);
    printf("%c\n", ch);
    ch = fgetc(pf);
    printf("%c\n", ch);
 
    fclose(pf);
    pf = NULL;
 
    return 0;
}


2、返回偏移量函数 ftell

  • 返回文件指针相对于起始位置的偏移。

long int ftell ( FILE * stream );
#include <stdio.h>
 
int main()
{
    FILE* pf = fopen("test.txt", "r");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
 
    // 调整文件指针
    fseek(pf, 5, SEEK_CUR); // SEEK_CUR为当前文件指针位置,偏移量为5,向后移动5个单位
 
    int ch = fgetc(pf);
    printf("%c\n", ch); // f
 
    // 返回偏移量
    int ret = ftell(pf);
    printf("%d\n", ret); // 6
 
    fclose(pf);
    pf = NULL;
 
    return 0;
}


3、文件指针回到起始位置函数 rewind

  • rewind(意为倒带,磁带倒带),设置文件位置为给定流 stream 的文件的开头,让文件指针回到起始位置

#include <stdio.h>
 
int main()
{
    FILE* pf = fopen("test.txt", "r");
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
 
    // 调整文件指针
    fseek(pf, 5, SEEK_CUR); // SEEK_CUR为当前文件指针位置,偏移量为5,向后移动5个单位
 
    // 返回偏移量
    int loc = ftell(pf);
    printf("fseek调整文件指针后:%d\n", loc); // 6
 
    // 让文件指针回到起始位置
    rewind(pf);
 
    // 再次返回偏移量,看看是不是回到起始位置了
    loc = ftell(pf);
    printf("使用rewind后:%d\n", loc); // 6
 
    fclose(pf);
    pf = NULL;
 
    return 0;
}


九、文件读取结束的判定

1、被错误使用的 feof

  • 在文件结束时判断文件因为何种原因导致文件结束的函数判断是因为读取失败而结束,还是因为遇到文件尾而结束。如果文件结束,则返回非 0 值,否则返回 0。

注意feof 函数是个经常被错误使用的一个函数。在文件读取过程中,不能用 feof 函数的返回值直接判断文件是否结束!feof 函数绝对不是用来判断文件是否结束的函数!feof 不是用来判定文件是否结束了的,还是在文件已经结束时,判断是什么原因导致文件结束的

文本文件读取是否结束,判断返回值是否为EOF fgetc),或者NULLfgets

  • fgetc 判断是否为 EOF。
  • fgets 判断返回值是否为 NULL。
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int ch = 0; // 注意:int,非char,要求处理EOF
    FILE* pf = fopen("test.txt", "r");
    if (!pf)
    {
        perror("fopen");
        return EXIT_FAILURE; // 符号常量EXIT_FAILURE,表示没有成功地执行一个程序
    }
 
    // fgetc - 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
    while ( (ch = fgetc(pf)) != EOF )
    {
        putchar(ch);
    }
    printf("\n");
 
    // 判断文件结束的原因
    if (ferror(pf)) // ferror - 检查是否出现错误
    {  
        puts("读取失败错误(I/O error when reading)");
    }
    else if (feof(pf))
    {
        puts("遇到文件尾而结束(End of file reached successfully)");
    }
 
    fclose(pf);
    pf = NULL;
}


二进制文件的读取结束判断,判断返回值是否小于实际要读的个数

  • fread 判断返回值是否小于实际要读的个数。
#include <stdio.h>
 
enum { SIZE = 5 };
 
int main(void)
{
    double a[SIZE] = {1.0,2.0,3.0,4.0,5.0};
    double b = 0.0;
    size_t ret_code = 0;
 
    FILE *fp = fopen("test.bin", "wb"); // 必须用二进制模式
    fwrite(a, sizeof(*a), SIZE, fp); // 写double的数组
    fclose(fp);
    fp = fopen("test.bin","rb");
 
    // 读 double 的数组
    while((ret_code = fread(&b, sizeof(double), 1, fp))>=1)
    {
        printf("%lf\n",b);
    }
 
    if (feof(fp))
    {
        printf("Error reading test.bin: unexpected end of file\n");
    }
    else if (ferror(fp))
    {
        perror("Error reading test.bin");
    }
 
    fclose(fp);
    fp = NULL;
}


2、正确判定文件是否读取结束的方法

  • 文本文件读取是否结束,判断返回值是否为 EOFfgetc),或者 NULLfgets)。
  1. fgetc 函数在读取结束时会返回 EOF,正常读取时,返回读取到的字符的 ASCII 码值
  2. fgets 函数在读取结束时会返回 NULL,正常读取时,返回存放字符串的空间的起始地址
  3. fread 函数在读取结束时会返回实际读取到的完整元素的个数,如果发现读取到的完整的元素个数小于指定的元素个数,那么就是最后一次读取了。


相关文章
|
5天前
|
C语言
C语言——文件操作
C语言——文件操作
20 2
C语言——文件操作
|
3天前
|
存储 程序员 编译器
文件操作(C语言)
文件操作(C语言)
|
14天前
|
存储 C语言 C++
【C语言基础】:文件操作详解(前篇:准备知识)
【C语言基础】:文件操作详解(前篇:准备知识)
|
14天前
|
C语言
【C语言基础】:文件操作详解(后篇)-2
【C语言基础】:文件操作详解(后篇)
|
14天前
|
存储 C语言
【C语言基础】:文件操作详解(后篇)-1
【C语言基础】:文件操作详解(后篇)
|
18天前
|
数据库 C语言
C语言进阶 文件操作知识(上)
C语言进阶 文件操作知识(上)
14 3
|
18天前
|
存储 C语言
C语言进阶 文件操作知识(下)
C语言进阶 文件操作知识(下)
17 2
|
25天前
|
存储 编译器 数据库
【再识C进阶5(上)】详细介绍C语言文件操作——文件是用于存储数据
【再识C进阶5(上)】详细介绍C语言文件操作——文件是用于存储数据
|
4天前
|
存储 C语言
【c语言】详解文件操作(二)
【c语言】详解文件操作(二)
13 0
|
4天前
|
存储 程序员 编译器
【c语言】详解文件操作(一)
【c语言】详解文件操作(一)
5 0