C语言入门系列之11.文件和文件操作

CuterCorley
• 阅读 1429

一、C文件概述

1.基本概念

文件是指一组相关数据的有序集合,这个数据集有一个名称,叫做文件名。 我们在前面的已经使用到了很多文件,例如源程序文件、目标文件、可执行文件、库文件 (头文件)等。

文件通常是驻留在外部介质(如磁盘等)上的,在使用时才调入内存中来。

操作系统是以文件为单位对数据进行管理的,示意如下: C语言入门系列之11.文件和文件操作

2.文件分类

从不同的角度可对文件作不同的分类: (1)从用户的角度看,文件可分为:

  • 特殊文件(标准输入输出文件或标准设备文件)
  • 普通文件(磁盘文件)

(2)从操作系统的角度看,每一个与主机相连的输入、输出设备都看作是一个文件。 例如:

  • 输入文件,终端键盘等
  • 输出文件,显示屏和打印机等

(3)按数据的组织形式:

  • ASCII文件(文本文件) 每一个字节放一个ASCII代码。
  • 二进制文件 把内存中的数据按其在内存中的存储形式原样输出到磁盘上存放。

例如整数10000D在内存中的存储形式以及分别按ASCII码形式和二进制形式输出如下图所示: C语言入门系列之11.文件和文件操作

ASCII文件和二进制文件的比较: ASCII文件便于对字符进行逐个处理,也便于输出字符; 但一般占存储空间较多,而且要花费转换时间。

二进制文件可以节省外存空间和转换时间; 但一个字节并不对应一个字符,不能直接输出字符形式。

一般中间结果数据需要暂时保存在外存上,以后又需要输入内存的,常用二进制文件保存。

3.C语言对文件的处理方法

缓冲文件系统: 系统自动地在内存区为每一个正在使用的文件开辟一个缓冲区。 用缓冲文件系统进行的输入输出又称为高级磁盘输入输出

非缓冲文件系统: 系统不自动开辟确定大小的缓冲区,而由程序为每个文件设定缓冲区。 用非缓冲文件系统进行的输入输出又称为低级输入输出系统

在UNIX系统下,用缓冲文件系统来处理文本文件,用非缓冲文件系统来处理二进制文件。

ANSI C标准只采用缓冲文件系统来处理文本文件和二进制文件。

C语言中对文件的读写都是用库函数来实现。

二、文件的打开与关闭

1.文件类型指针

定义文件型指针变量:

FILE  *fp;

fp是一个指向FILE类型结构体的指针变量。

我们使fp指向某一个文件的结构体变量,从而通过该结构体变量中的文件信息能够访问该文件。 如果有n个文件,一般应设n个指针变量,使它们分别指向n个文件,以实现对文件的访问。

定义FILE类型的数组:

FILE f[5];

定义了一个结构体数组f,它有5个元素,可以用来存放5个文件的信息。

2.文件的打开(fopen函数)

函数调用:

FILE  *fp;
fp = fopen(文件名, 使用文件方式);

参数说明: 文件名是准备访问的文件的名字; 使用文件方式是读还是写等; fp指定指向被打开的文件的指针变量。

文件使用方式如下: 方式|含义 ---|--- r| (只读)为输入打开一个文本文件 w| (只写)为输出打开一个文本文件 a | (追加)向文本文件尾增加数据 rb | (只读)为输入打开一个二进制文件 wb | (只写)为输出打开一个二进制文件 ab | (追加)向二进制文件尾增加数据 r+ | (读写)为读/写打开一个文本文件 w+ | (读写)为读/写建立一个新的文本文件 a+ | (读写)为读/写打开一个文本文件 rb+ | | (读写)为读/写打开一个二进制文件 wb+ | (读写)为读/写建立一个新的二进制文件 ab+ | (读写)为读/写打开一个二进制文件

说明: (1)凡用r方式打开一个文件时,该文件必须已经存在,且只能从该文件读出。

(2)用w打开的文件只能向该文件写入。 若打开的文件不存在,则以指定的文件名建立该文件,若打开的文件已经存在,则将该文件删去,重建一个新文件。

(3)若要向一个已存在的文件追加新的信息,要用a方式打开文件,但此时该文件必须是存在的,否则将会出错。

(4)在打开一个文件时,如果出错,fopen将返回一个空指针值NULL。 在程序中可以用这一信息来判别是否完成打开文件的工作,并作相应的处理。

(5)把一个文本文件读入内存时,要将ASCII码转换成二进制码,而把文件以文本方式写入磁盘时,也要把二进制码转换成ASCII码,因此文本文件的读写要花费较多的转换时间。 对二进制文件的读写不存在这种转换。

文件打开练习如下:

#include <stdio.h>
#include <stdlib.h>

int main(){
    FILE *fp;
    if(!(fp = fopen("E:\\Test\\test.txt", "r"))){
        printf("Can not open the file.\n");    
    }
    else{
        printf("Open success.\n");
    }

    return 0;
}

打印:

Open success.

3.文件的关闭(fclose函数)

函数调用:

fclose(文件指针);

函数功能: 使文件指针变量不指向该文件,也就是文件指针变量与文件脱钩,此后不能再通过该指针对原来与其相联系的文件进行读写操作。

返回值: 关闭成功返回值为0,否则返回EOF(-1)。

三、文件的读写

对文件的读和写是最常用的文件操作,在C语言中提供了多种文件读写的函数,使用这些函数时都要包含头文件stdio.h

1.字符读写函数fgetc和fputc

fputc()函数调用:

fputc( ch, fp);

函数功能: 将字符(ch的值)输出到fp所指向的文件中去。

用写或读写方式打开一个已存在的文件时将清除原有的文件内容,写入字符从文件首开始; 如需保留原有文件内容,希望写入的字符以文件末开始存放,必须以追加方式打开文件; 被写入的文件若不存在,则创建该文件。

文件写入练习:

#include <stdio.h>
#include <stdlib.h>

int main(){
    FILE *fp;
    char ch, filename[20];
    printf("Please input the filename ypu want to write: ");
    scanf("%s", filename);
    if(!(fp = fopen(filename, "wt+"))){
        printf("Cannot open the file!\n");
        exit(0);
    }
    printf("Please input the sentences you want to write:\n");
    ch = getchar();
    ch = getchar();
    while(ch != EOF){
        fputc(ch, fp);
        ch = getchar();
    }
    fclose(fp);

    return 0;
}

打印:

Please input the filename ypu want to write: test.txt
Please input the sentences you want to write:
I love c!
^Z

此时查看源程序同级目录,可以看到新增加了一个文件test.txt,里面的内容为:

I love c!

每写入一个字符,文件内部位置指针向后移动一个字节。

fputc函数有一个返回值,如写入成功则返回写入的字符,否则返回一个EOF,可用此来判断写入是否成功。

fgetc()函数调用:

ch = fgetc(fp);

函数功能: 其意义是从打开的文件fp中读取一个字符并传入ch中。

在fgetc函数调用中,读取的文件必须是以读或读写方式打开的。

在文件内部有一个位置指针,用来指向文件的当前读写字节。

在文件打开时,该指针总是指向文件的第一个字节; 使用fgetc函数后,该位置指针将向后移动一个字节。 因此可连续多次使用fgetc函数,读取多个字符。

文件指针和文件内部的位置指针不是一回事: 文件指针是指向整个文件的,须在程序中定义说明,只要不重新赋值,文件指针的值是不变的; 文件内部的位置指针用以指示文件内部的当前读写位置,每读写一次,该指针均向后移动,它不需在程序中定义说明,而是由系统自动设置的。

文件读出练习:

#include <stdio.h>
#include <stdlib.h>

int main(){
    FILE *fp;
    char ch, filename[20];
    printf("Please input the filename ypu want to read: ");
    scanf("%s", filename);
    if(!(fp = fopen(filename, "r"))){
        printf("Cannot open the file!\n");
        exit(0);
    }
    while(ch != EOF){
        ch = fgetc(fp);
        putchar(ch);
    }
    fclose(fp);

    return 0;
}

打印:

Please input the filename ypu want to read: test.txt
I love c!


从一个文本文件顺序读入字符并在屏幕上显示出来:

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

EOF不是可输出字符,因此不能在屏幕上显示; 由于字符的ASCII码不可能出现-1,因此EOF定义为-1是合适的。 当读入的字符值等于-1时,表示读入的已不是正常的字符而是文件结束符。

从一个二进制文件顺序读入字符:

while(!feof(fp){
    ch = fgetc(fp);
})

ANSI C提供一个feof()函数来判断文件是否真的结束: 如果是文件结束,函数feof(fp)的值为1(真); 否则为0(假)。 这也适用于文本文件的读取。

二进制文件读写练习: 实现图片文件合成器。 代码如下:

#include <stdio.h>
#include <stdlib.h>

int main(){
    FILE *f_pic, *f_file, *f_merged;
    char pic_name[50], file_name[50], merged_name[50], ch;
    printf("请输入要合成的图片和文件的名称:\n");
    printf("Picture: ");
    scanf("%s", pic_name);
    printf("File: ");
    scanf("%s", file_name);
    printf("Result: ");
    scanf("%s", merged_name);
    if(!(f_pic = fopen(pic_name, "rb"))){
        printf("Can not open the picture %s !", pic_name);
        return;
    }
    if(!(f_file = fopen(file_name, "rb"))){
        printf("Can not open the file %s !", file_name);
        return;
    }
    if(!(f_merged = fopen(merged_name, "wb"))){
        printf("Can not open the file %s !", file_name);
        return;
    }
    while(!(feof(f_pic))){
        ch = fgetc(f_pic);
        fputc(ch, f_merged);
    }
    fclose(f_pic);
    while(!(feof(f_file))){
        ch = fgetc(f_file);
        fputc(ch, f_merged);
    }
    fclose(f_file);
    fclose(f_merged);

    system("pause");

    return 0;
}

打印:

请输入要合成的图片和文件的名称:
Picture: 520表白.jpg
File: 让人无法拒绝的8句表白.pdf
Result: 520.m

操作示例:

[video(video-CnvYVFdW-1589971519454)(type-bilibili)(url-https://player.bilibili.com/player.html?aid=370655137)(image-https://ss.csdn.net/p?http://i0.hdslb.com/bfs/archive/f6ae5c3d70882ddc4bf1b08224e9ad7127da721b.jpg)(title-小程序实现封装表白)]

2.字符串读写函数fgets和fputs

fgets函数调用形式如:

fgets(str,n,fp);

函数作用: 从fp所指的文件中读出n-1个字符送入字符数组str中,因为在最后加一个'\0'

返回值: str的首地址。

fputs函数调用方式:

fputs(str,fp);

函数作用: 其意义是把字符串str写入fp所指的文件之中。

返回值: 输入成功,返回值为0; 输入失败,返回EOF。

字符串读出练习:

#include <stdio.h>
#include <stdlib.h>

#define LEN 21

int main(){
    FILE *fp;
    char buffer[LEN];
    if(!(fp = fopen("test.txt", "rt"))){
        printf("\nCannot open file");
        return;
    }
    fgets(buffer, LEN, fp);
    printf("%s\n", buffer);
    fclose(fp);

    return 0;
}

打印:

I love c!

字符串写入练习:

#include <stdio.h>
#include <stdlib.h>

#define LEN 20

int main(){
    FILE *fp;
    char ch, buffer[LEN];
    if(!(fp = fopen("test.txt", "at+"))){
        printf("\nCannot open file");
        return;
    }
    printf("Please input a string:\n");
    fgets(buffer, LEN, stdin);
    fputs(buffer, fp);
    rewind(fp);
    printf("\nTHe file is:\n");
    ch = fgetc(fp);
    while(ch != EOF){
        putchar(ch);
        ch = fgetc(fp);
    }
    printf("\n");
    fclose(fp);

    return 0;
}

打印:

Please input a string:
I'm Corley!!

THe file is:
I love c!
I'm Corley!!

这里使用fgets(buffer, LEN, stdin)而不是scanf("%s", buffer)是因为scanf()函数默认遇到空格或回车键就会判断为输入结束,如果字符串中有空格或换行符就会导致输入不完整; 使用rewind();是将文件内部指针移到文件开头,便于后边从头读出文件。

3.数据块读写函数fread和fwrite

数据块读写函数函数调用:

fread(buffer, size, count, fp);
fwrite(buffer, size, count, fp); 

参数说明:

  • buffer 是一个指针: 对fread 来说,它是读入数据的存放地址; 对fwrite来说,是要输出数据的地址(均指起始地址);
  • size 要读写的字节数。
  • count 要进行读写的数据项的字节数。
  • fp 文件型指针。

若有如下结构类型:

struct student_type{
    char name[10];
    int num;
    int age;
    char addr[30];} stud[40];

可以用fread和fwrite来进行数据的操作:

for(i = 0; i < 40; i++>{
    fread(&stud[i], sizeof(struct student_type), 1, fp);
})
for(i = 0; i < 40; i++>{
    fwrite(&stud[i], sizeof(struct student_type), 1, fp);
})

练习: 从键盘输入4个学生的有关数据,然后把它们以二进制的格式存储到磁盘文件中。 代码如下:

#include <stdio.h>
#include <stdlib.h>

#define SIZE 4

struct student{
    char name[10];
    int num;
    int age;
    char addr[20];
} stu[SIZE];

int main(){
    void save();
    int i;
    printf("Please input the student's name, num, age and address:\n");
    for(i = 0; i < SIZE; i++){
        scanf("%s %d %d %s", &stu[i].name, &stu[i].num, &stu[i].age, &stu[i].addr);
    }
    save();

    return 0;
}

void save(){
    FILE *fp;
    int i;
    if(!(fp = fopen("student_list", "wb"))){
        printf("\nCannot open file");
        return;
    }

    for(i=0;i < SIZE; i++){
        if(fwrite(&stu[i], sizeof(struct student), 1, fp) != 1){
            printf("File write error!\n");
            fclose(fp);
        }
    }
}

打印:

Please input the student's name, num, age and address:
Corley 101 18 Road1
Jack 102 20 Road2
Shirley 103 19 ROad3
Tom 104 17 Road4

此时发现在与源程序同级的目录下多了一个文件student_list。

数据块读出练习:

#include <stdio.h>
#include <stdlib.h>

#define SIZE 4

struct student{
    char name[10];
    int num;
    int age;
    char addr[20];
} stu[SIZE];

int main(){
    void load();
    int i;
    load();
    printf("      name   num   age    address\n");
    for(i = 0; i < SIZE; i++){
        printf("%10s %5d %5d %10s\n", stu[i].name, stu[i].num, stu[i].age, stu[i].addr);
    }

    return 0;
}

void load(){
    FILE *fp;
    int i;
    if(!(fp = fopen("student_list", "rb"))){
        printf("Cannot open file\n");
        return;
    }

    for(i = 0;i < SIZE; i++){
        fread(&stu[i], sizeof(struct student), 1, fp);
    }

    fclose(fp);
}

打印:

      name   num   age    address
    Corley   101    18      Road1
      Jack   102    20      Road2
   Shirley   103    19      ROad3
       Tom   104    17      Road4

4.格式化读写函数fprintf和fscanf

函数调用:

fprintf(文件指针, 格式字符串, 输出表列);
fscanf(文件指针, 格式字符串, 输入表列); 

函数功能: 从磁盘文件中按格式读入或输出字符。

例如:

fprintf(fp, "%d, %6.2f", i, t);
fscanf(fp, "%d, %f", &i, &t);

5.顺序读写和随机读写

顺序读写: 位置指针按字节位置顺序移动。

随机读写: 读写完上一个字符(字节)后,并不一定要读写其后续的字符(字节),而可以读些文件中任意位置上所需要的字符(字节)。

四、文件的定位

1.fseek函数

一般用于二进制文件。

函数调用形式:

fseek(文件类型指针, 位移量, 起始点);

函数功能: 改变文件的位置指针。

参数说明: 起始点: 文件位置|标识|数字 ---|---|--- 文件开头 | SEEK_SET | 0 文件当前位置 | SEEK_CUR | 1 文件末尾 | SEEK_END | 2

位移量: 以起始点为基点,向前移动的字节数。

使用举例: fseek(fp, 100L, 0);将位置指针移到离文件头100个字节处; fseek(fp, 50L, 1);将位置指针移到离当前位置50个字节处; fseek(fp, 50L, 2);将位置指针从文件末尾处向后退10个字节; fseek(fp, i*sizeof(struct stu), 0);将位置指针移到离文件头i*sizeof(struct stu)距离处。

练习: 从前面的学生文件student_list中读出第2个学生的数据。 代码如下:

#include <stdio.h>
#include <stdlib.h>

#define SIZE 4

struct student{
    char name[10];
    int num;
    int age;
    char addr[20];
} stu;

int main(){
    FILE *fp;
    int i = 1;
    if(!(fp = fopen("student_list", "rb"))){
        printf("Cannot open file\n");
        return;
    }
    rewind(fp);
    fseek(fp, i*sizeof(struct student), 0);
    fread(&stu, sizeof(struct student), 1, fp);
    printf("      name   num   age    address\n");
    printf("%10s %5d %5d %10s\n", stu.name, stu.num, stu.age, stu.addr);

    return 0;
}

打印;

      name   num   age    address
      Jack   102    20      Road2

2.ftell函数

函数作用: 得到流式文件中的当前位置,用相对于文件开头的位移量来表示。

返回值: 返回当前位置,出错时返回-1L。

应用举例:

i = ftell(fp);
if(i == -1L){
    printf(“error\n”);
}    

五、出错的检测

1.ferror函数

调用形式:

ferror(fp);

返回值: 返回0,表示未出错; 返回非0,表示出错。

注意: 在调用一个输入输出函数后立即检查ferror函数的值,否则信息会丢失。 在执行fopen函数时,ferror函数的初始值自动置为0。

2.clearerr函数

调用形式:

clearerr(fp);

函数作用: 使文件错误标志和文件结束标志置为0。

只要出现错误标志,就一直保留,直到对同一文件调用clearerr函数或rewind函数,或任何其他一个输入输出函数。

六、文件操作小结

1.文件操作

分类 函数名 功能
打开文件 fopen() 打开文件
关闭文件 fclose() 关闭文件
文件定位 fseek() 改变文件位置指针的位置
... rewind() 使文件位置指针重新至于文件开头
... ftell() 返回文件位置指针的当前值
文件状态 feof() 若到文件末尾,函数值为真
... ferror() 若对文件操作出错,函数值为真
... clearerr() 使ferror()feof()函数值置零

2.文件读写

函数名 功能
fgetc(), getc() 从指定文件取得一个字符
fputc(), putc() 把字符输出到指定文件
fgets() 从指定文件读取字符串
fputs() 把字符串输出到指定文件
getw() 从指定文件读取一个字(int型)
putw() 把一个字输出到指定文件
fread() 从指定文件中读取数据项
fwrite() 把数据项写到指定文件中
fscanf() 从指定文件按格式输入数据
fprintf() 按指定格式将数据写到指定文件中

本文原文首发来自博客专栏C语言学习,由本人转发至https://www.helloworld.net/p/2myC76hvgFAD,其他平台均属侵权,可点击https://blog.csdn.net/CUFEECR/article/details/106242057查看原文,也可点击https://blog.csdn.net/CUFEECR浏览更多优质原创内容。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这