-
1 课件
-
2 视频
-
3 资料
-
4 练习
赵老师笔记----文件
文件操作分两部分的
第一部分:通过标准I/O方式对文件访问,今天讲
第二部分:通过系统调用的方式对文件访问,在系统编程部分讲解。
一、文件
1.什么是文件??
文件是用来存储程序、文档、音视频数据、图片,以及传输等等,在LINUX环境下一切都是文件。
文件都是可以通过程序来打开和读取,存储的。
2.文件的分类
磁盘文件:我们常用的,指一组相关的数据,存储在相应的介质上,访问或使用时调入内存。
设备文件:操作系统将与主机相连的输入、输出设备看作文件,通过这些文件进行输入或输出,对他们的操作等同于磁盘文件。
标准输入设备:键盘
标准输出设备:屏幕
其他的设备:打印机、触摸屏、摄像头、扬声器(音响)
LINUX环境下一切都是文件,一切的外边设备/dev目录下对应每一个设备文件
常规文件 -
目录 d
字符文件 c
块设备 b
套接字 s socket
管道 p
符号链接 l
从用户的角度来分:
文本文件:基于字符编码的文件,通过通用的程序记事本或其他程序打开,如歌词文件(lrc)
二进制文件:基于值编码的文件,存储的是二进制的数值或字符。。。自己根据具体的应用指定相应值为什么含义,必须按照编码格式
去读取并解释才有意义。
对于文本文件,因为它是基于固定字符定长,编译码容易,也可以通过通用程序解释或打开
而对于二进制文件,由于编码不固定,译码就困难,只有通过写入的格式去读取并解释才有意义
对于以上两类用户角度来分的文件在磁盘物理上都是以二进制的形式存储,都是以一种称为流的形式进行读取的,而且都是以字节流对文件进行读写。
3.对文件的操作方式
第一种:通过标准I/O库的方式访问文件,带缓冲
特点:有缓冲
内存 输出 磁盘
-->文件缓冲区 -->
程序 文件
数据
<-- 文件缓冲区 <--
输入
第二种:通过系统调用的方式,是不带缓冲的
4.缓冲方式:
①行缓冲 对于标准输入(stdin)、标准输出(stdout)
当缓冲区碰到换行符时刷新缓冲区
如何刷新行缓冲:
当无换行符时,不会输出,例:
#include "stdio.h"
int main(int argc, char *argv[])
{
printf("hello world!");
while(1);
return 0;
}
如何才能输出:
第一种,程序正常结束时,会自动刷新缓冲区。
第二种,给一个\n,行缓冲就会刷新缓冲区
第三种,强制人为刷新缓冲区,fflush(stdout)
#include "stdio.h"
int main(int argc, char *argv[])
{
printf("hello world!");
fflush(stdout);
while(1);
return 0;
}
第四种,缓冲区满时,也会刷新缓冲区。
②全缓冲 向文件读写数据
通过标准I/O库函数往普通文件读写数据时,是全缓冲的
并不会碰到换行符就刷新缓冲区,只有到缓冲区满时才刷新缓冲区,或者是程序结束时刷新缓冲区
③无缓冲 标准错误输出(stderr)、通过系统调用访问文件
在读写文件时,直接通过系统调用读取I/O的方式(read、write),对文件进行读写操作
就是无缓冲的,数据立马写入文件,或者马上读入到内存。
5.设置缓冲区的目的:
通过缓冲区可以减少进出内核的次数,提高效率。
以写文件为例:
应用程序空间-->内核空间-->驱动程序-->硬盘(或设备)
应用程序空间和内核空间是不同的空间,目的是为了保护内核
二、文件指针
文件指针是程序中用来标识文件的,在打开文件时就可以得到文件指针
定义文件指针的一般形式为:
FILE * 指针变量标识符;
FILE为大写,需要包含<stdio.h>
FILE是系统使用typedef定义出来的有关文件信息的一种结构体类型,结构中含有文件名、文件状态和文件当前位置等信息
typedef struct
{ short level; //缓冲区“满”或“空”的程度
unsigned flags; //文件状态标志
char fd; //文件描述符
unsigned charhold; //如无缓冲区不读取字符
short bsize; //缓冲区的大小
unsigned char *buffer; //数据缓冲区的位置
unsigned ar*curp; //指针,当前的指向
unsigned istemp; //临时文件,指示器
shorttoken; //用于有效性检查
}FILE;
用标准I/O访问时用文件指针
三个特殊文件指针:stdio.h定义了
stdin:标准输入,默认是当前终端的键盘
stdout:标准输出,默认是当前终端的屏幕
stderr:标准错误输出设备文件,默认是当前终端的屏幕
当初错时,可以用perror将stderr的内容打印到屏幕上。
用系统调用的方式去用文件描述符,
0、1、2
STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO。这些常数都定义在头文件<unistd.h>中
三、打开文件fopen
#include "stdio.h"
FILE *fopen (const char *path, const char *mode);
path:文件路径字符串
可以是绝对路径,也可以是相对路径
绝对路径:"/home/edu/share/day12/test.c"
相对路径:"./day12/test.c"
mode:字符串,代表是流形态
r:只读
r+:可读可写
w:只写 当文件不存在时,自动创建该文件;存在时,清空文件内容
w+:可读可写
前面几种都是从头开始读写
a:追加只写
a+:追加可读可写
当文件不存在时,自动创建该文件;存在时,将读写指针移到文件末,开始读写。
rb
wb
ab
rb+
wb+
ab+
加入一个b,表示是二进制文件,而非文本文件
返回值是FILE *
成功返回该文件的文件指针
失败返回NULL,调用fopen,记得一定要判断,看是否成功打开文件
四、关闭文件fclose
#include "stdio.h"
int fclose (FILE *stream);
stream:指向要关闭的文件
返回值:
关闭文件成功,返回值为0.
关闭文件失败,返回值非零.
五、文件写操作
#include "stdio.h"
size_t fwrite(const void * ptr,size_t size,size_t nmemb,FILE *stream);
stream:要写入的文件指针
ptr:要写入的数据指针
size:要写入每块的大小
nmemb:共写入的块数
写入的总大小是size*nmemb
返回值是实际写入的块数
#include "stdio.h"
#include "string.h"
int main(int argc, char *argv[])
{
FILE *fp;
char name[20];
fp=fopen("test.txt","r+");//保证文件存在
if (NULL==fp)
{
printf("cannot open this file!\n");
return 0;
}
printf("please input your name:\n");
scanf("%s",name);
fwrite(name,1,strlen(name),fp);
fclose(fp);
return 0;
}
六、文件读操作
#include "stdio.h"
size_t fread(void * ptr,size_t size,size_t nmemb,FILE *stream);
stream:要读入的文件指针
ptr:要读入的数据指针
size:要读入每块的大小
nmemb:共读入的块数
返回值是实际读入的块数,有可能返回值<nmemb,表示已读到文件末,或出错。
#include "stdio.h"
#include "string.h"
typedef struct stu
{
char name[20];
int num;
int age;
}STU;
int main(int argc, char *argv[])
{
FILE *fp;
STU boyin[2],boyout[2];
int i;
fp=fopen("test.txt","r+");
if (NULL==fp)
{
printf("cannot open this file!\n");
return 0;
}
for (i=0;i<2;i++)
{
printf("please input your name num age:\n");
scanf("%s %d %d",boyin[i].name,&boyin[i].num,&boyin[i].age);
}
fwrite(boyin,sizeof(STU),2,fp);
fclose(fp);
fp=fopen("test.txt","r+");
if (NULL==fp)
{
printf("cannot open this file!\n");
return 0;
}
fread(boyout,sizeof(STU),2,fp);
printf("***********************\n");
for (i=0;i<2;i++)
{
printf("%s,%d,%d\n",boyout[i].name,boyout[i].num,boyout[i].age);
}
fclose(fp);
return 0;
}
七、几组简单的字符/字符串读写文件
写:putc/fputc/fputs
int fputc(int c ,FILE *stream);
putc是一个宏定义,不是函数,作用同fputc
int fputs(const char * s ,FILE *stream);
例子:
#include "stdio.h"
#include "string.h"
int main(int argc, char *argv[])
{
FILE *fp;
char name[50]="I love C program!";
fp=fopen("test.txt","r+");
int i;
if (NULL==fp)
{
printf("cannot open this file!\n");
return 0;
}
/* for (i=0;i<strlen(name);i++)
fputc(name[i],fp); */
fputs(name,fp);
fclose(fp);
return 0;
}
读:fgetc/getc/fgets
int fgetc(FILE * stream);//读到文件末时,返回EOF,否则返回读到的字符。
int getc(FILE * stream);//同fgetc,是一个宏定义,不是函数
char *fgets(char * s ,int size,FILE *stream);//成功返回s指针,失败返回NULL,最多读size-1个字符,碰到换行符或文件末结束
例子:
#include "stdio.h"
#include "string.h"
int main(int argc, char *argv[])
{
FILE *fp;
char name[50];
fp=fopen("test.txt","r+");
int i;
if (NULL==fp)
{
printf("cannot open this file!\n");
return 0;
}
/* i=0;
do
{
name[i]=fgetc(fp);
}while(EOF!=name[i++]);
name[i]='\0'; */
fgets(name,50,fp);
fclose(fp);
printf("%s\n",name);
return 0;
}
八、随机读写文件操作
对文件的读写方式:
顺序读写:只能从文件头开始,顺序的读写各个数据
随机读写:可以任意的移动位置指针到文件的任意位置开始读写数据,随机读写
实现随机读写的关键是解决文件的定位
文件定位函数:
①rewind 复位读写位置
#include <stdio.h>
void rewind( FILE *stream );
作用:将文件指针移到由stream(流)指定的开始处, 同时清除和流相关的错误和EOF标记.
#include "stdio.h"
#include "string.h"
typedef struct stu
{
char name[20];
int num;
int age;
}STU;
int main(int argc, char *argv[])
{
FILE *fp;
STU boyin[2],boyout[2];
int i;
fp=fopen("test.zqy","rb+");
if (NULL==fp)
{
printf("cannot open this file!\n");
return 0;
}
for (i=0;i<2;i++)
{
printf("please input your name num age:\n");
scanf("%s %d %d",boyin[i].name,&boyin[i].num,&boyin[i].age);
}
fwrite(boyin,sizeof(STU),2,fp);
rewind(fp);
fread(boyout,sizeof(STU),2,fp);
printf("***********************\n");
for (i=0;i<2;i++)
{
printf("%s,%d,%d\n",boyout[i].name,boyout[i].num,boyout[i].age);
}
fclose(fp);
return 0;
}
②fseek 定位位置指针
#include <stdio.h>
int fseek( FILE *stream, long offset, int origin );
函数fseek()为给出的流设置位置数据. origin的值应该是下列值其中之一(在stdio.h中定义):
名称 说明
SEEK_SET 从文件的开始处开始搜索
SEEK_CUR 从当前位置开始搜索
SEEK_END 从文件的结束处开始搜索
移到默认方向是从文件开始往结束走,如果是反着定位,应该给offset(偏移量)给负值。
#include "stdio.h"
#include "string.h"
typedef struct stu
{
char name[20];
int num;
int age;
}STU;
int main(int argc, char *argv[])
{
FILE *fp;
STU boyin[2],boyout[2];
int i;
fp=fopen("test.zqy","rb+");
if (NULL==fp)
{
printf("cannot open this file!\n");
return 0;
}
for (i=0;i<2;i++)
{
printf("please input your name num age:\n");
scanf("%s %d %d",boyin[i].name,&boyin[i].num,&boyin[i].age);
}
fwrite(boyin,sizeof(STU),2,fp);
// fseek(fp,-sizeof(STU),SEEK_END);
fseek(fp,-sizeof(STU),SEEK_CUR);
// fseek(fp,sizeof(STU),SEEK_SET);
fread(&(boyout[0]),sizeof(STU),1,fp);
printf("***********************\n");
printf("%s,%d,%d\n",boyout[0].name,boyout[0].num,boyout[0].age);
fclose(fp);
return 0;
}
③ftell 测当前文件位置指针距离文件头有多少个字节
#include <stdio.h>
long ftell( FILE *stream );
#include "stdio.h"
#include "string.h"
typedef struct stu
{
char name[20];
int num;
int age;
}STU;
int main(int argc, char *argv[])
{
FILE *fp;
STU boyin[2],boyout[2];
int i;
fp=fopen("test.zqy","rb+");
if (NULL==fp)
{
printf("cannot open this file!\n");
return 0;
}
for (i=0;i<2;i++)
{
printf("please input your name num age:\n");
scanf("%s %d %d",boyin[i].name,&boyin[i].num,&boyin[i].age);
}
fwrite(boyin,sizeof(STU),2,fp);
fseek(fp,-sizeof(STU),SEEK_END);
i=ftell(fp);
if(i==sizeof(STU))
printf("刚好当前位置找到第二个结构体开始位置\n");
fread(&(boyout[0]),sizeof(STU),1,fp);
printf("***********************\n");
printf("%s,%d,%d\n",boyout[0].name,boyout[0].num,boyout[0].age);
fclose(fp);
return 0;
}
九、格式化读写文件函数
①fprintf 格式化写文件
#include <stdio.h>
int fprintf( FILE *stream, const char *format, ... );
fprintf()函数根据指定的format(格式)(格式)发送信息(参数)到由stream(流)指定的文件. fprintf()只能和printf()一样工作. fprintf()的返回值是输出的字符数,发生错误时返回一个负值.
#include "stdio.h"
#include "string.h"
int main(int argc, char *argv[])
{
FILE *fp;
char name[50]="I love C programming!\n";
int myage=37;
float degree=12.5;
fp=fopen("test.txt","r+");
int i;
if (NULL==fp)
{
printf("cannot open this file!\n");
return 0;
}
fprintf(fp,"%s %d %f",name,myage,degree);
fclose(fp);
return 0;
}
②fscanf 从文件格式化输入
#include <stdio.h>
int fscanf( FILE *stream, const char *format, ... );
函数fscanf()以scanf()的执行方式从给出的文件流中读取数据. fscanf()的返回值是事实上已赋值的变量的数,如果未进行任何分配时返回EOF.
#include "stdio.h"
#include "string.h"
int main(int argc, char *argv[])
{
FILE *fp;
char name[50];
int myage;
float degree;
fp=fopen("test.txt","r");
int i;
if (NULL==fp)
{
printf("cannot open this file!\n");
return 0;
}
fscanf(fp,"%s %d %f",name,&myage,°ree);
printf("%s %d %f",name,myage,degree);
fclose(fp);
return 0;
}
十、对比几个函数的使用
①一个一个字符读取
getc/fgetc/getchar
int fgetc(FILE * stream);//读到文件末时,返回EOF,否则返回读到的字符。stdin
int getc(FILE * stream);//同fgetc,是一个宏定义,不是函数
int getchar(void);//从标准输入文件获取一个字符
②一个一个字符写
putc/fputc/putchar
int fputc(int c ,FILE *stream);//stream=stdout
putc是一个宏定义,不是函数,作用同fputc
int putchar(int c);//向标准输出文件输出一个字符
③一次一行读
fgets/gets
char *fgets(char * s ,int size,FILE *stream);//成功返回s指针,失败返回NULL,最多读size-1个字符,碰到换行符或文件末结束
char *gets( char *str );
gets()函数从STDIN(标准输入)读取字符并把它们加载到str(字符串)里,直到遇到新行(\n)或到达EOF. 新行字符翻译为一个null中断符. gets()的返回值是读入的字符串,如果错误返回NULL
gets()这个函数不推荐使用,因为调用这在使用gets()时不能指定缓冲的长度,这样就可能造成缓冲越界,如果越界,会产生不可预料的后果。
gets与fgets另一个区别就是,gets并不讲新行符存入到缓冲区
④一次一行写
fputs/puts
int fputs(const char * s ,FILE *stream);
int puts( char *str );
函数puts()把str(字符串)写到STDOUT(标准输出)上. puts() 成功时返回非负值, 失败时返回EOF
⑤格式化输出
printf/fprintf/sprintf
⑥格式化输入
scanf/fscanf/sscanf
十一、几个不常用函数
①freopen
#include <stdio.h>
FILE *freopen( const char *fname, const char *mode, FILE *stream );
freopen()函数常用于再分配一个以存在的流给一个不同的文件和方式(mode).在调用本函数后,给出的文件流将会用mode(方式)指定的访问模式引用fname(文件名). freopen()的返回值是新的文件流,发生错误时返回NULL.
#include "stdio.h"
#include "string.h"
int main(int argc, char *argv[])
{
FILE *fp;
char name[50];
int myage;
float degree;
fp=fopen("test.zqy","r");
int i;
if (NULL==fp)
{
printf("cannot open this file!\n");
return 0;
}
fp=freopen( "test.txt","r+", fp);
fscanf(fp,"%s %d %f",name,&myage,°ree);
printf("%s %d %f",name,myage,degree);
fclose(fp);
return 0;
}
②fdopen
表头文件:#include<stdio.h>
定义函数:FILE * fdopen(int fildes,const char * mode);
fdopen取一个现存的文件描述符,并使一个标准的I / O流与该描述符相结合。此函数常用于由创建管道和网络通信通道函数获得的描述符。因为这些特殊类型的文件不能用标准I/O fopen函数打开,首先必须先调用设备专用函数以获得一个文件描述符,然后用fdopen使一个标准I/O流与该描述符相结合。
#include "stdio.h"
int main(int argc, char *argv[])
{
FILE * fp =fdopen(1,"w+");
fprintf(fp,"%s\n","hello!");
fclose(fp);
return 0;
}
③fsetpos
#include <stdio.h>
int fsetpos( FILE *stream, const fpos_t *position );
fsetpos()函数把给出的流的位置指针移到由position对象指定的位置. fpos_t是在stdio.h中定义的. fsetpos()执行成功返回0,失败时返回非零.
④fgetpos
#include <stdio.h>
int fgetpos( FILE *stream, fpos_t *position );
fgetpos()函数保存给出的文件流(stream)的位置指针到给出的位置变量(position)中. position变量是fpos_t类型的(它在stdio.h中定义)并且是可以控制在FILE中每个可能的位置对象. fgetpos()执行成功时返回0,失败时返回一个非零值.
⑤snprintf
snprintf(),为函数原型int snprintf(char *str, size_t size, const char *format, ...)。
将可变个参数(...)按照format格式化成字符串,然后将其复制到str中
(1) 如果格式化后的字符串长度 < size,则将此字符串全部复制到str中,并给其后添加一个字符串结束符('\0');
(2) 如果格式化后的字符串长度 >= size,则只将其中的(size-1)个字符复制到str中,并给其后添加一个字符串结束符('\0'),返回值为欲写入的字符串长度。
#include <stdio.h>
int main () {
char a[16];
size_t i;
i = snprintf(a, 13, "%012d", 12345); // 第 1 种情况
printf("i = %lu, a = %s\n", i, a); // 输出:i = 12, a = 000000012345
i = snprintf(a, 9, "%012d", 12345); // 第 2 种情况
printf("i = %lu, a = %s\n", i, a); // 输出:i = 12, a = 00000001
return 0;
}
strcpy() sprintf() strcat() 存在安全隐患, 其对应的安全版为:
strncpy() snprintf() strncat()
练习1
从一个文件(文本文件)中读取所有信息,写入另一个文件中
参考:r+ w+ fgetc fputc EOF
#include "stdio.h"
#include "string.h"
int main(int argc, char *argv[])
{
FILE * fp,*fs;
char s;
fp=fopen("test.txt","r");
fs=fopen("tests.txt","w");
while(1)
{
s=fgetc(fp);
if(s==EOF)
break;
fputc(s,fs);
}
fclose(fp);
fclose(fs);
return 0;
}
练习2:
将一个未知大小的文件(文本文件)全部读入内存,并显示在屏幕上
参考:fseek ftell rewind fread malloc
#include "stdio.h"
#include "string.h"
#include "stdlib.h"
typedef struct stu
{
char name[20];
int num;
int age;
}STU;
int main(int argc, char *argv[])
{
FILE *fp;
char *cp;
int i,j;
fp=fopen("test.txt","r");
if (NULL==fp)
{
printf("cannot open this file!\n");
return 0;
}
fseek(fp,0,SEEK_END);
i=ftell(fp);
cp=(char *)malloc(i*sizeof(char));
rewind(fp);
fread(cp,sizeof(char),i,fp);
for (j=0;j<i;j++)
putchar(cp[j]);
fclose(fp);
free(cp);
return 0;
}
十二、文件的出错检测
①文件结束检测函数feof
调用格式:feof(文件指针);
功能: 判断文件是否处于文件结束位置
常配合fgetc、fgets、fread等读函数判断
是否到文件结束
返回值: 文件未结束返回0,文件已结束返回非0
②读写文件出错检测函数ferror
调用格式:ferror(文件指针);
功能:
检查文件在用各种输入输出函数进行读写时是否出错
比如:以只读方式打开一个文件,调用写函数就会发生错误
只要出现错误标志,就一直保留,直到对同一文件调用clearerr函数或rewind函数,或任何其他一个输入输出函数。
返回值:
为0表示未出错,否则表示有错
练习1:
1.以只读、文本方式打开当前路径下一个叫test.txt文件
2.以只写、二进制方式打开同一个文件
3.以追加(a)、文本的方式打开同一个文件
4.同时以读/写、二进制方式打开同一个文件,要求若文件不存在,提示出错
5.同时以读/些、文本方式打开同一个文件,要求若文件不存在,创建此文件
6.若打开成功则关闭相应的文件
练习2:
将一个未知大小的文件(文本文件)全部读入内存,并显示在屏幕上
参考:fseek ftell rewind fread malloc

