程序设计基础

第十二章 文件

第12章 文件

12.1 素数文件
12.2 用户信息加密和校验
12.3 文件综合应用:资金帐户管理

本章要点

  • 什么是文件?C文件是如何存储的?
  • 什么是文件缓冲系统?工作原理如何?
  • 什么是文本文件和二进制文件?
  • 怎样打开、关闭文件?
  • 怎样编写文件读写程序?
  • 怎样编写程序,实现简单的数据处理?

12.1 素数文件

例12-1. 系数文件

从2开始依次找出500个素数,将这些素数存入文本文件prime.txt中 Found

12.1.1 程序解析

                        
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
int prime(int n)  /* 见例5-5 */
{
    ......
}
int main()
{
    int n=2, count=0;
    FILE *fp;  /* 定义文件指针 */

    if((fp=fopen("prime.txt", "w"))==NULL){  /* 打开文件 */
        printf("File open error!\n");
        exit(0);
    }
    while(count<500){
        if(prime(n)!=0){  /* 写入文件 */
            count++;
            fprintf(fp, "%d ", n);
        }
        n++;
    }
    if(fclose(fp)){  /* 关闭文件 */
        printf("Can not close file\n");
        exit(0);
    }
    return 0;
}
                        
                    

12.1.2 文件的概念

操作系统中的文件是指驻留在外部介质(如磁盘等)中的一个有序数据集
文件类型主要包含
1. 程序文件,如源文件、目标程序、可执行程序等
2. 数据文件,用于输入输出,如文本文件、图像文件、声音文件等
文件所具有的特点包含以下三点:
1. 数据可永久保存
2. 数据长度不定
3. 数据按顺序存取

12.1.3 文本文件和二进制文件

C语言中的文件是由一个个字节数据组成的数据流
具有两种基本形式
ASCII码文本文件--字符流二进制文件--二进制流,其中二进制文件直接将内容数据以二进制形式保存
例如,整数1234以不同形式保存时
以文本文件保存时,保存为49 50 51 52 保存为4个字符
以二进制文件保存时,保存为04D2 为整数1234对应的二进制形式

12.1.4 缓冲文件系统

CPU和内存的速度远高于外部存储的速度,将数据直接从内存写入外部存储效率很低

缓冲文件系统执行过程

向磁盘输出数据时,数据先写入缓冲区,等缓冲区装满后,将缓冲区写入磁盘
从磁盘读入数据时,先一次性从磁盘文件将一批数据读入到缓冲区,再从缓冲区逐个读入数据到变量

缓冲文件与文件类型指针

用文件指针指示文件缓冲区中具体读写的位置
FILE *fp;
同时使用多个文件时,每个文件都有缓冲区,用不同的文件指针分别指示

12.1.5 文件结构与文件类型指针

FILE为结构类型,用typedef定义在stdio.h文件中

                        
typedef struct{
    short level;  /* 缓冲区使用量  */
    unsigned flags;  /* 文件状态标志 */
    char fd;  /* 文件描述符 */
    short bsize;  /* 缓冲区大小 */
    unsigned char *buffer;  /* 文件缓冲区的首地址 */
    unsigned char *curp;  /* 指向文件缓冲区的工作指针 */
    unsigned char hold;  /* 其他信息 */
    unsigned istemp;
    short token;
}FILE;
                        
                    

自定义类型typedef

typedef用于
(1). 将C语言中已有的类型(包括已定义过的自定义类型)重新命名
(2). 新的名称可以代替已有数据类型
(3). 常用于简化对复杂的数据类型定义的描述
typedef <已有类型名> <新类型名>;
typedef int INTEGER
    int i, j; ==> INTEGER i, j;
typedef int* POINT
    int* pt; ==> POINT pt;

typedef的使用方法

定义变量 int i;
用typedef将变量名命名为新类型名 typedef int INTEGER;
用新类型名定义变量 INTEGER i;

typedef int NUM[10];
NUM a <==> int a[10];

文件类型指针

FILE *fp;
文件类型指针指向文件缓冲区,通过移动指针实现对文件的操作

12.1.6 文件控制块FCB

文件控制块描述

  • 文件控制块(File Control Block, FCB),操作系统中对文件进行操作控制都是通过FCB实现,实际处理的是FCB列表
  • 一个文件对应一个FCB
  • 文件缓冲区由程序中fopen语句动态创建
  • 打开文件时,FCB的内容信息被复制到文件缓冲区保存,用文件指针指向文件缓冲区就可实现对文件数据的操作

12.1.7 文件处理步骤

文件处理有四个步骤:

  1. 定义文件指针
  2. 打开文件,文件指针指向磁盘文件缓冲区
  3. 文件处理,即文件读写操作
  4. 关闭文件

12.2 用户信息加密和校验

例12-2. 用户信息加密和校验

为了保障系统安全,通常采取用户帐号和密码登录系统。系统用户信息存放在一个文件中,系统帐号名和密码由若干字母与数字字符构成,因安全需要文件中的密码不能是明文,必须要经过加密处理。请编程实现: 输入5个用户信息(包含帐号名和密码)并写入文件f12-2.dat。要求文件中每个用户信息占一行,帐号名和加密过的密码之间用一个空格分隔。密码加密算法: 对每个字符ASCII码的低四位求反,高四位保持不变(即将其与15进行异或运算)

12.2.1 程序解析

                        
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
struct sysuser{  /* 定义系统用户账号信息结构 */
    char username[20];
    char password[8];
};
void encrypt(char *pwd)
{
    int i;
    /* 与15异或(00001111),实现低四位取反,高四位不变 */
    for(i=0; i<strlen(pwd); i++)
        pwd[i]=pwd[i]^15;
}
                        
                    

程序解析(2)

                        
int main()
{
    FILE *fp;  /* 定义文件指针 */
    int i;
    struct sysuser su;
    /* 打开文件,进行写入操作 */
    if((fp=fopen("f12-2.txt", "w"))==NULL){
        printf("File open error!\n");
        exit(0);
    }
    /* 将5位用户账号信息写入文件 */
    for(i=1; i<=5; i++){
        printf("Enter %dth sysuser(name password):", i);
        scanf("%s%s", su.username, su.password);  /* 输入账号和密码 */
        encrypt(su.password);  /* 加密处理 */
        fprintf(fp, "%s %s\n", su.username, su.password);  /* 写入文件 */
    }
    if(fclose(fp)){  /* 关闭文件 */
        printf("Can not close the file\n");
        exit(0);
    }
    return 0;
}
                        
                    

12.2.2 打开文件和关闭文件

                        
if((fp=fopen("f12-2.txt","w")) == NULL){
    printf("File open error!\n");
    exit(0);
}
                        
                    

fopen("文件名", "文件打开方式");
将文件指针与文件实体对应,程序对文件指针进行操作,指针fp代表磁盘文件
执行成功,fopen返回包含文件缓冲区等信息在内的FILE型地址,赋给文件指针fp
如果执行不成功,则返回一个NULL(空值)
如果不成功,执行exit(0)关闭所有打开的文件,终止程序的执行
其中,参数0表示程序正常结束,非0参数通常表示不正常的程序结束

文件打开方式

fp=fopen("f12-2.txt", "w");

文件打开方式参数表
文本文件(ASCII) 二进制文件(Binary)
使用方式 含义 使用方式 含义
"r" 打开只读文件 "rb" 打开只读文件
"w" 建立只写新文件 "wb" 建立只写新文件
"a" 打开添加写文件 "ab" 打开添加写文件
"r+" 打开读/写文件 "rb+" 打开读/写文件
"w+" 建立读/写新文件 "wb+" 建立读/写新文件
"a+" 打开读/写文件 "ab+" 打开读/写文件

文件读写与打开方式

                        
if 读文件
    指定的文件必须存在,否则出错;
if 写文件(指定的文件可以存在,也可以不存在)
    if 以"w"方式写
        if 该文件已经存在
            原文件将被删除并重新建立;
        else
            按指定的名字新建一个文件;
    else if 以"a"方式写
        if 该文件已经存在
            写入的数据将被添加到指定文件原有数据的后面,不会删去原来的内容;
        else
            按指定的名字新建一个文件(与"w"相同);
if 文件同时读和写
    使用"r+", "w+"或"a+"打开文件
                        
                    

关闭文件

                        
if(fclose(fp)){
    printf("Can not close the file!\n");
    exit(0);
}
                        
                    

fclose(文件指针);
将缓冲区中的数据写入磁盘扇区,确保写文件的正常完成
释放文件缓冲区单元和FILE结构体,使文件指针与具体文件脱钩
函数fclose()的返回值为0,表示正常关闭文件,非0值表示无法正常关闭文件

12.2.3 文件读写

例12-3. 复制用户文件

将例12-2的用户信息文件f12-2.txt文件备份一份,取名为文件f12-3.txt,说明,运算程序前请将文件f12-2.txt与源程序放在同一目录下

文件读写源程序

                        
#include<stdio.h>
#include<stdlib.h>
int main()
{
    FILE *fp1, *fp2;
    char ch;
    /* 打开文件, 读出数据 */
    if((fp1=fopen("f12-2.txt", "r"))==NULL){
        printf("File open error!\n");
        exit(0);
    }
    if((fp2=fopen("f12-3.txt", "w"))==NULL){
        printf("File open error!\n");
        exit(0);
    }
    while(!feof(fp1)){  /* 从fp1所指的文件中按字符顺序依次读取 */
        ch=fgetc(fp1);
        if(ch!=EOF)  /* 将字符ch写入fp2所指的文件 */
            fputc(ch, fp2);
    }
    if(fclose(fp1)){  /* 关闭文件 */
        printf("Can not close the file!\n");
        exit(0);
    }
    if(fclose(fp2)){  /* 关闭文件 */
        printf("Can not close the file!\n");
        exit(0);
    }
    return 0;
}
                        
                    

打开多个文件

                        
if((fp1=fopen("f12-2.txt", "r"))==NULL){
    printf("File open error!\n");
    exit(0);
}
if((fp2=fopen("f12-3.txt", "w"))==NULL){
    printf("File open error!\n");
    exit(0);
}
                        
                    

C语言中允许同时打开多个文件
不同的文件对应不同的文件指针
但不允许同一个文件在关闭前再次打开

文件读写函数

函数类型 函数名
字符读写函数 fgetc()/fputc()
字符串读写函数 fputs()/fgets()
格式化读写函数 fscanf()/fprintf()
二进制读写函数 fread()/fwrite()
检测文件结尾函数 feof()
检测文件读写出错函数 ferror()
清除末尾标志和出错标志函数 clearerr()
文件定位函数 fseek(), rewind(), ftell()

字符读写函数fgetc和fputc

                        
while(!eof(fp1)){
    ch=fgetc(fp1);
    if(ch!=EOF) fputc(ch, fp2);
}
                        
                    

函数fgetc()
ch=fgetc(fp);
从fp所指示的磁盘文件上读入一个字符到ch
要区分键盘字符输入函数getchar()

函数fputc()
fputc(ch, fp);
将一个字符ch写到fp所指示的磁盘文件上
返回值为-1(EOF),表示写文件失败; 若返回ch表示写文件成功

字符串读写函数fgets和fputs

函数fputs()
fputs(s, fp);
用来向指定的文本文件写入一个字符串
s为要写入的字符串,结束符'\0'不写入文件
函数返回值,若执行成功,返回所写的最后一个字符,否则返回EOF

字符串读写函数fgets和fputs

函数fgets()
fgets(s, n, fp);
从文本文件中读取字符串
s可以是字符数组名或字符指针,n为指定读入的字符个数,fp为文件指针
当函数被调用时,最多读取n-1个字符,并将读入的字符串存入s所指向内存地址开始的n-1个连续的内存单元中
当函数读取的字符达到指定的个数,或接收到换行符,或接收到文件结束标志EOF时,将在读取的字符后面自动添加一个'\0'字符;若有换行符,则将换行符保留(换行符在'\0'之前);若有EOF,则不保留
若函数执行成功,返回读取的字符串
若函数执行失败,则返回空指针,此时,s的内容不确定

文件读写示例

例12-4. 用户信息校验

例12-2的f12-2.txt文件保存着系统用户信息,编写一个函数checkUserValid()用于登录系统时校验用户的合法性
在程序运行时输入用户名和密码,然后在文件中查找该用户信息,如果用户名和密码在文件中找到,则表示用户合法,返回1,否则返回0
程序运行时,输入一个用户名和密码,调用checkUserValid()函数,如果返回1,则提示"Valid User!",否则输出"Invalid User!"

用户信息校验源程序(1)

                        
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct sysuser{
    char username[20];
    char password[8];
};
int checkUserValid(struct sysuser *psu);

void encrypt(char *pwd)
{
    int i;
    /* 与15异或(00001111),实现低四位取反,高四位不变 */
    for(i=0; i<strlen(pwd); i++)
        pwd[i]=pwd[i]^15;
}
int main()
{
    struct sysuser su;
    printf("Enter username: ");
    scanf("%s", su.username);
    printf("Enter password: ");
    scanf("%s", su.password);
    if(checkUserValid(&su)==1)  /* 调用函数进行校验 */
        printf("Valid user!\n");
    else
        printf("Invalide user!\n");
    return 0;
}
                        
                    

用户信息校验源程序(2)

                        
/* 校验用户信息的合法性,成功返回1,否则返回0 */
int checkUserValid(struct sysuser *psu)
{
    FILE *fp;
    char usr[30], usr1[30], pwd[10];
    int check=0;  /* 检查结果变量,初始化为0 */
    /* 连接生成待校验字符串 */
    strcpy(usr,psu->username);  /* 复制psu->username到usr1 */
    strcpy(pwd,psu->password);  /* 复制psu->password到pwd */
    encrypt(pwd);  /* 调用例12-2的encrypt对密码进行加密 */
    /* 连接usr、空格、pwd和\n构成新字符串usr,用于在文件中逐行检查 */
    strcat(usr, " "); strcat(usr,pwd); strcat(usr,"\n");
    /* 打开文件"f12-2.txt"读入 */
    if((fp=fopen("f12-2.txt","r"))==NULL){
        printf("File open error!\n");
        exit(0);
    }
    /* 从文件读入用户信息数据,遍历判断是否存在 */
    while(!feof(fp)){
        fgets(usr1,30,fp);  /* 读入一行用户信息作为一个字符串到usr1 */
        if(strcmp(usr,usr1)==0){  /* 比较判断usr与usr1是否相同 */
            check=1;
            break;
        }
    }
    if(fclose(fp)){
        printf("Can not close the file!\n"); exit(0);
    }  /* 关闭文件 */
    return check;
}
                        
                    

格式化文件读写fscanf和fprintf

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

实现指定格式的输出输出函数

                        
FILE *fp;
int n;
float x;
fp=fopen("a.txt", "r");
fscanf(fp, "%d%f", &n, &x);  /* 表示从文件a.txt分别读入整型数到变量a,浮点数到变量x */
fp=fopen("b.txt", "w");
fprintf(fp, "%d%f", n, x);  /* 表示将变量n和变量x的值写入文件b.txt */
                        
                    

数据块读写函数fread()和fwrite()

fread(buffer, size, count, fp);
从二进制文件中读入一个数据块到变量
fwrite(bufffer, size, count, fp);
向二进制文件中写入一个数据块
其中,buffer为指针,表示存放数据的首地址
size表示数据块的字节数,count表示要读写的数据块块数,fp为文件指针

12.2.4 其他相关函数

feof(fp);
判断fp指针是否已经到文件末尾,若已经到文件结束位置,返回1,否则返回0

rewind(FILE *fp)
定位文件指针,使文件指针指向读写文件的首地址,即打开文件时文件指针所指向的位置

fseek(fp, offset, from);
将文件指针fp从from位置移动offset,其中,offset为偏移量,类型为long,from表示起始位置,文件首部、当前位置、文件尾部分别对应0, 1, 2,或者常量SEEK_SET, SEEK_CUR, SEEK_END
如fseek(fp, 20L, 0); /* 将文件位置指针移动到离文件首20字节处 */
fseek(fp, -20L, SEEK_END); /* 将文件位置指针移动到离文件尾部前20字节处 */

其他相关函数(2)

ftell(fp);
获取当前文件指针的位置,即相对于文件开头的位移量(字节数),若函数出错,返回-1L

ferror(fp)
用来检查文件在用各种输入输出函数进行读写时是否出错,若返回值为0,表示未出错,否则表示有错,其中文件指针必须为已经定义过的

clearerr(fp)
用来清除出错标志和文件结束标志,使其值为0

文件综合应用:个人资金账户管理

例12-6. 个人资金账户管理

资金账户信息统一放在随机文件中,该随机文件的数据项包括:记录ID,发生日期,发生事件,发生金额(正表示收,负表示支出)和余额。每发生一笔收支,文件要增加一条记录,并计算一次余额
程序要实现三个功能:
(1). 创建资金账户文件并添加收入或支出记录
(2). 输出所有记录,显示资金账户的明细收支流水信息
(3). 查询最后一条记录,获账户余额

12.3.1 顺序文件和随机文件

按照C程序对文件访问的特点来分,文件可分为顺序访问文件和随机访问文件,简称为顺序文件和随机文件。前面介绍的所有例子都进行的是顺序访问,通过使用fprintf()或fputs()函数创建的数据记录长度并不是完全一致的,这种记录长度不确定的文件访问称为顺序访问。而随机访问文件要求文件中单个记录的长度固定,可直接访问,这样速度快,并且无需通过其他记录查找特定记录。因此随机文件适合银行系统、航空售票系统、销售点系统和其他需要快速访问特定数据的事务处理系统。

12.3.2 个人资金账户管理

                        
#include "stdio.h"
#include "stdlib.h"
#include "process.h"
long size;					/* 用来保存sizeof(struct LogData) */
struct LogData{				/* 记录的结构 */
    long logid;				/* 记录ID */
    char logdate[11];		/* 记录发生日期 */
    char lognote[15];		/* 记录事件说明 */
    double charge;			/* 发生费用:正表示收入,负表示支出 */
    double balance;			/* 余额 */
};

int inputchoice()			/* 选择操作参数 */
{
   	int mychoice;

   	printf("\nEnter your choice:\n");
   	printf("1 - Add a new cash LOG.\n2 - List All Cash LOG.\n");
   	printf("3 - Query Last Cash LOG.\n0 - End program.\n");
   	scanf("%d", &mychoice);

   	return mychoice;
}

long getLogcount(FILE *cfptr) 		/* 获取文件记录总数 */
{
	long begin, end, logcount;

	fseek(cfptr, 0L, SEEK_SET);
	begin = ftell(cfptr);
	fseek(cfptr, 0L, SEEK_END);
	end = ftell(cfptr);
	logcount = (end-begin)/size;

	return logcount;
}
                        
                    

12.3.2 个人资金账户管理(2)

                        
void ListAllLog(FILE *cfptr) 		/* 列出所有收支流水记录 */
{
   	struct LogData log;

   	fseek(cfptr,0L,SEEK_SET);			/* 定位指针到文件开始位置 */
   	fread(&log,size,1,cfptr);
    printf("logid  logdate lognote charge balance\n");
   	while(!feof(cfptr)){
		printf("%6ld %-11s %-15s %10.2lf %10.2lf\n", log.logid, log.logdate, log.lognote, log.charge, log.balance);
    	fread(&log, size, 1, cfptr);
  	}
}

void QueryLastLog(FILE *cfptr) 		/* 查询显示最后一条记录 */
{
	struct LogData log;
	long logcount;

	logcount = getLogcount(cfptr);
	if(logcount > 0){				/* 表示有记录存在 */
	    fseek(cfptr, size*(logcount-1), SEEK_SET);	/* 定位最后记录 */
	    fread(&log, size, 1, cfptr);				/* 读取最后记录 */
	    printf("The last log is:\n");
	    printf("logid:%-6ld\nlogdate:%-11s\nlognote:%-15s\n", log.logid, log.logdate, log.lognote);
		printf("charge:%-10.2lf\nbalance:%-10.2lf\n", log.charge, log.balance);   /* 显示最后记录内容 */
	}else{
		printf("no logs in file!\n");
	}
}
                        
                    

12.3.2 个人资金账户管理(3)

                        
void AddNewLog(FILE *cfptr) 	/* 添加新记录 */
{
	struct LogData log, lastlog;
    long logcount;

	printf("Input logdate(format:2006-01-01):");
	scanf("%s", log.logdate);
	printf("Input lognote:");
	scanf("%s", log.lognote);
	printf("Input Charge:Income+ and expend-:");
	scanf("%lf", &log.charge);
	logcount = getLogcount(cfptr);	/* 获取记录数 */

	if(logcount>0){
		fseek(cfptr,size*(logcount-1),SEEK_SET);
		fread(&lastlog, size, 1, cfptr);  /* 读入最后记录 */
		log.logid = lastlog.logid + 1;	 /* 记录号按顺序是上次的号+1 */
		log.balance = log.charge + lastlog.balance;
	}else{								/* 如果文件是初始,记录数为0 */
		log.logid = 1;
		log.balance = log.charge;
	}
	rewind(cfptr);
	printf("logid= %ld\n", log.logid);
	fwrite(&log, sizeof(struct LogData), 1, cfptr);	 /* 写入记录 */
}
                        
                    

12.3.2 个人资金账户管理(4)

                        
int main()
{
	FILE *fp;
	int choice;

	if((fp = fopen("cashbox.txt", "ab+")) == NULL){
      	printf("can not open file cashbox.txt!\n");
      	exit(0);
 	}
	size = sizeof(struct LogData);
	while((choice = inputchoice()) != 0){
      	switch(choice){
	      	case 1:                 /*添加新记录*/
		     	AddNewLog(fp);
				 break;
	      	case 2:                 /*列出所有收支流水记录*/
		    	ListAllLog(fp);
				break;
		  	case 3:                 /*查询最后的余额*/
		     	QueryLastLog(fp);
				break;
		  	default:
		     	printf("Input Error.");break;
      	}
   	}
    if(fclose(fp) != 0){
        printf( "Can not close the file!\n" );
        exit(0);
    }
   	return 0;
}