10.file-input-and-output

文件

  • 程序文件。包括源程序文件,这种文件的内容是程序代码

  • 数据文件。供程序运行时读写的数据

操作系统把各种设备都统一作为文件来处理。

文件一般指存储在外部介质上数据的集合。

流(数据流)

数据的输入输出.流表示了信息从源到目的地的流动.

文件名

文件路径+文件名主干+文件后缀

文件的分类

  • ASCII文件,在存储前需要转换,文本文件

  • 二进制文件,存储在内存的数据的映象,不需要加以转换输出到外存,称为映像文件.

文件缓冲区

ANSI C标准采用“缓冲文件系统”处理数据文件。

所谓缓冲文件系统是指系统自动地在内存区为程序中每一个正在使用的文件开辟一个文件缓冲区从内存向磁盘输出数据必须先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘去。

如果从磁盘向计算机读入数据,则一次从磁盘文件将一批数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(给程序变量) 。

程序与磁盘之间交互,不是立即完成,系统或程序可根据需要设置缓冲区,以提高存取效率

文件类型指针

对于操作系统而言,关键的概念是”文件指针”.每个被使用的文件都在内存中开辟一段存储单元,用来存放文件的有关信息.这些信息是保存在一个结构体变量中的.该结构体类型变量是由系统定义的,取名为FILE.有几个文件就建立几个这样的结构体变量,分别存放各文件的有关信息.同时返回对应的FILE结构指针.

FILE结构体类型在stdio.h文件中定义如下:

Typedef struct
{
  short level;            //缓冲区”满”或”空”的程度//
  unsigned flags;         //文件状态标志//
  char fd;               //文件描述符//
  unsigned char hold;     //如无缓冲区不读取字符//
  short bsize;            //缓冲区大小//
  unsigned char *buffer;   //数据缓冲区的位置//
  unsigned char *curp;    //指针,当前指向//
  unsigned istemp;       //临时文件,指示器//
  short token;           //用于有效性检查//
}FILE;

定义一个指向文件型数据的指针变量

FILE *fp;

可以使fp指向某一个文件的文件信息区(是一个结构体变量),通过该文件信息区中的信息就能够访问该文件.

通过文件指针变量能够找到与它关联的文件,如果有n个文件,应设n个指针变量,分别指向n个FILE类型变量,以实现对n个文件的访问.

通常将这种指向文件信息区的指针变量简称为指向文件的指针变量

并不是指向外部介质上的数据文件的开头,而是指向内存中的文件信息区的开头.

打开与关闭文件

"打开"指为文件建立相应的信息区(用来存放有关文件的信息)和文件缓存区(用来暂时存放输入输出的数据).

打开文件的同时,一般指定一个指针变量指向该文件,建立起指针变量与文件之间的联系.

用fopen函数打开数据文件

fopen(文件名,使用文件方式)

FILE *fp;
fp=fopen("a1","r");

实现失败返回NULL.

使用文件方式.

打开方式含义指定文件不存在时指定文件存在时

r

只读方式打开文本文件

出错

正常打开

w

只写方式打开文本文件

建立新文件

文件原有内容丢失

a

追加方式打开文本文件

建立新文件

在原有内容末尾追加

r+

读/写方式打开文本文件

出错

正常打开

w+

读/写方式创建新的文本文件

建立新文件

文件原有内容丢失

a+

读/追加方式建立新的文本文件

建立新文件

在原有内容末尾追加

rb

只读方式打开二进制文件

出错

正常打开

wb

只写方式打开二进制文件

建立新文件

文件原有内容丢失

ab

追加方式打开二进制文件

建立新文件

在原有内容末尾添加

rb+

读/写方式打开二进制文件

出错

正常打开

wb+

读/写方式创建新的二进制文件

建立新文件

文件原有内容丢失

ab+

读/追加方式创建新的二进制文件

建立新文件

在原有内容末尾追加

带b区别在于换行的处理

用fclose函数关闭数据文件

fclose(文件指针);

fclose(fp);

先把缓冲区中的数据输出到磁盘文件,然后才撤销文件信息区.

顺序读写数据文件

怎样向文件读写字符

读写一个字符的函数.

函数名调用形式功能返回值

fgetc

fgetc(fp)

从fp指向的文件读入一个字符

读成功,带回所读的字符,失败了则返回文件结束标志EOF(即-1)

fputc

fputc(ch,fp)

把字符ch写到文件指针变量fp所指向的文件中

输出成功,返回值就是输出的字符,输出失败,则返回文件结束标志EOF(即-1)

从键盘输入一些字符,并逐个把它们送到磁盘上去,直到用户输入一个"#"为止.

#include <stdio.h>
#include <stdlib.h>
int main()
{
    FILE *fp;
    char ch, filename[10];
    printf("请输入所用的文件名:");
    scanf("%s", filename);
    getchar();//用来消化最后输入的回车符
    if ((fp = fopen(filename, 'w') == NULL))
    {
        printf("cannot open file\n");
        exit(0);//终止程序
    }
    printf("请输入一个准备存储到磁盘的字符串(以#结束):");
    ch = getchar();
    while (ch != '#')
    {
        fputc(ch, fp);
        putchar(ch);
        ch = getchar(); //再接收从键盘输入的一个字符
    }
    fclose(fp);
    putchar(10); //向屏幕输出一个换行符s
    return 0;
}

将一个磁盘文件中的信息复制到另一个磁盘文件中

#include <stdio.h>
#include <stdlib.h>
int main()
{
    FILE *in, *out;
    char ch, infile[10], outfile[10];
    printf("输入读入文件的名字:");
    scanf("%s", infile);
    printf("输入输出文件的名字:");
    scanf("%s", outfile);
    getchar(); //用来消化最后输入的回车符
    if ((in = fopen(infile, 'r') == NULL))
    {
        printf("cannot open file\n");
        exit(0);
    }
    if ((out = fopen(outfile, 'w') == NULL))
    {
        printf("cannot open file\n");
        exit(0);
    }

    ch = fgetc(in);
    while (!feof(in)) //如未遇到输入文件的结束标志
    {
        fputc(ch, out);
        putchar(ch); //显示在屏幕上
        ch = fgetc(in);
    }
    putchar(10); //显示完全部字符后换行
    fclose(in);
    fclose(out);
    return 0;
}

在访问磁盘文件时,是逐个字符(字节)进行的,为了知道当前访问到第几个字节,系统用"文件读写位置标记"来表示当前所访问的位置.开始时指向第1个字节,每访问完一个字节,当前读写位置就指向下一个字节.为了知道对文件的读写是否完成,只需看文件读写位置是否移到文件的末尾.

feof函数可以检测文件是否已被读取过.

怎样向文件读写一个字符串

fgets(str,n,fp);

作用是从fp所指向的文件中读入一个长度为n-1的字符串,并在最后加一个'\0'字符,然后把这n个字符存放到字符数组str中.

读写一个字符串的函数

函数名调用形式功能返回值

fgets

fgets(str,n,fp)

从fp指向的文件读入一个长度为(n-1)的字符串,存放到字符数组str中.

读成功,返回地址str,失败则返回NULL

fputs

fputs(str,fp)

把str所指向的字符串写到文件指针变量fp所指向的文件中

输出成功,返回0;否则返回非0值

fgets函数的原型为:

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

作用是从文件读入一个字符串.调用时可以写成下面的形式:

fgets(str,n,fp)

n是要求得到的字符个数,但实际上只从fp所指向的文件中读入n-1字符,然后在最后加一个'\0'字符,这样得到的字符串共有n个字符,把它们放到字符数组str中.

如果在读完n-1个字符之前遇到换行符'\n'或文件结束符EOF,读入即结束,但将所遇到的换行符"\n"也作为一个字符读入.执行成功返回值为str数组首元素的地址,如果一开始就遇到文件尾或读数据出错,则为NULL.

fputs函数的原型为:

char *fputs(char * str,FILE *fp);

其作用是将str所指向的字符串输出到fp所指向的文件中.调用时可以写成下面的形式:

fputs("China",fp);

第一个参数可以是字符串常量,字符数组名或字符型指针.字符串末尾的'\0'不输出.输出成功,函数值为0,不成功则为EOF(-1).

从键盘读入若干个字符串,对它们按字母大小的顺序排序,然后把排好序的字符串送到磁盘文件中保存.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
    FILE *fp;
    char str[3][10], temp[10];
    int i, j, k, n = 3;
    printf("Enter strings:\n");
    for (i = 0; i < n; i++)
        gets(str[i]);
    for (i = 0; i < n - 1; i++) //用选择法对字符串排序
    {
        k = i;
        for (j = i + 1; j < n; j++)
        {
            if (strcmp(str[k], str[j]) > 0)
                k = j;
            if (k != i)
            {
                strcpy(temp, str[i]);
                strcpy(str[i], str[k]);
                strcpy(str[k], temp);
            }
        }
    }

    if ((fp = fopen("D:\\CC\\string.dat", "w")) == NULL)
    {
        printf("can't open file!\n");
        exit(0);
    }

    printf("\nThe new sequence:\n");
    for (i = 0; i < n; i++)
    {
        fputs(str[i], fp);
        fputs("\n", fp);
        printf("%s", str[i]);
    }
    fclose(fp);
    return 0;
}

用格式化的方式读写文本文件

fprintf(文件指针,格式化字符串,输出表列);
fscanf(文件指针,格式化字符串,输入表列);//从磁盘文件上读入ASCII字符
---
fprintf(fp,"%d,%6.2f",i,f);

因为在输入时要将文件中的ASCII码转换为二进制形式再保存在内存变量中,在输出时又要将内存中的二进制形式转换成字符,要花费更多的时间,因此,在内存与磁盘交换数据的情况下,最好不用fprintf和fscanf函数,而用fread和fwrite函数.

用二进制方式向文件读写一组数据

允许用fread函数从文件读一个数据块,用fwrite函数向文件写一个数据块.在读写时以二进制形式进行的.在向磁盘写数据时,直接将内存中的一组数据原封不动,不加转换地复制到磁盘文件上,读入时也是将磁盘文件中若干字节的内容一批读入内存.

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

buffer:

  • fread:用来存放从文件读入的数据的存储区的地址

  • fwrite:把此地址开始的存储区中的数据向文件输出(指起始地址)

size:要读的每个数据块的字节数 count:要读的数据块的个数 fp:文件指针

如:

fread(f,4,10,fp);
//f是一个float型数组名(代表数组首元素地址),从fp所指向的文件读入10个4个字节的数据,存储到数组f中

fread或fwrite执行成功返回count(输入或输出数据项的个数).

从键盘输入10个学生的有关数据,然后把它们转存到磁盘文件上.

#include <stdio.h>
#define SIZE 10
struct Student_type
{
    char name[10];
    int num;
    int age;
    char addr[15];
} stu[SIZE];

void save()
{
    int i;
    FILE *fp;
    if ((fp = fopen("D:\\CC\\string.dat", "wb")) == NULL)//二进制文件
    {
        printf("can't open file!\n");
        exit(0);
    }
    for (i = 0; i < SIZE; i++)
        if ((fwrite(&stu[i], sizeof(struct Student_type), 1, fp) != 1))
            printf("file write error!\n");

    fclose(fp);
}

int main()
{
    void save();
    int i;
    printf("Enter datas of student:\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;
}

文本文件和二进制文件

  • 数据的存储方式

    ​ 文本方式: 数据以字符方式(ASCII代码)存储到文件中.如整数12,送到文件时占2个字节,而不是4个字节.以文本方式保存的数据便于阅读

    ​ 二进制方式:数据按在内存的存储状态原封不动地复制到文件.如整数12,送到文件时和内存中一样占4个字节.

  • 文件的分类

    ​ 文本文件(ASCII文件):文件全部为ASCII字符

    ​ 二进制文件:按二进制方式把在内存中的数据复制到文件的,称为二进制文件,即映像文件.

  • 文件的打开方式

    ​ 文本方式: 不带b的方式,读写文件时对换行符进行转换

    ​ 文本方式: 带b的方式,读写文件时对换行符不进行转换

  • 文件读写函数

    ​ 文本读写函数:用来向文本文件读写字符数据的函数(如fgetc,fgets,fputc,fputs,fscanf,fprintf等)

    ​ 二进制读写函数:用来向二进制文件读写二进制数据的函数(如getw,putw,fread,fwrite等)

随机读写数据文件

文件位置标记及其定位

文件位置标记

文件位置标记用来指示"接下来要读写的下一个字符的位置"

文件位置标记的定位

1️⃣ 用rewind函数使文件位置标记指向开头.同时feof函数的值会回复为0(假).

有一个磁盘文件,内有一些信息.要求第1次将它的内容显示在屏幕上,第2次把它复制到另一文件上.

#include <stdio.h>

int main()
{
    FILE *fp1, *fp2;
    char ch;
    fp1 = fopen("file1.dat", "r"); //不含路径默认在源文件所在目录
    fp2 = fopen("file2.dat", "w");
    ch = getc(fp1);
    while (!feof(fp1)) //或者 while(ch!=EOF)
    {
        putchar(ch);
        ch = getc(fp1);
    }
    putchar(10);
    rewind(fp1);
    ch = getc(fp1);
    while (!feof(fp1))
    {
        fputc(ch, fp2);
        ch = getc(fp1);
    }
    fclose(fp1);
    fclose(fp2);
    return 0;
}

2️⃣用fseek函数改变文件位置标记

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

起始点:

起始点名字用数字代表

文件开始位置

SEEK_SET

0

文件当前位置

SEEK_CUR

1

文件末尾位置

SEEK_END

2

位移量:以"起始点"为基点,应是long型数据(在数字的末尾加一个字母L,就表示是long型);

fseek(fp,100L,0);//将文件位置标记先前移到离文件开头100个字节处.

3️⃣用ftell函数测定文件位置标记的当前位置.

函数得到流式文件中文件位置标记的当前位置.调用函数时出错(如不存在fp指向的文件),返回值为-1L.

随机读写

在磁盘文件上存有10个学生的数据.要求将第1,3,5,7,9个学生数据输入计算机,并在屏幕上显示出来.

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

struct Student_type
{
    char name[10];
    int num;
    int age;
    char addr[15];
} stu[10];

int main()
{
    FILE *fp;
    int i;
    if ((fp = fopen("D:\\CC\\string.dat", "wb")) == NULL)
    {
        printf("can't open file!\n");
        exit(0);
    }

    for (i = 0; i < 10; i += 2)
    {
        fseek(fp, i * sizeof(struct Student_type), 0);
        fread(&stu[i], sizeof(struct Student_type), 1, fp);
        printf("%-10s %4d %4d %-15s\n", stu[i].name, stu[i].num, stu[i].age, stu[i].addr);
    }

    fclose(fp);
    return 0;
}

文件读写的出错检测

检查输入输出函数调用时可能出现的错误.

ferror函数

ferror(fp);

如果返回值为0,表示未出现错误.

如果返回一个非零值,表示出错.

对同一个文件每一次调用输入输出函数,都会产生一个新的ferror函数值.因此在调用一个输入输出函数后立即检查ferror的值,否则信息会丢失.

在执行fopen函数时,ferror函数的初始值自动置为0.

clearerr函数

使文件出错标记和文件结束标记置为0.

Last updated