磁盘上的文件是文件。
在程序设计的过程当中,我们一般谈的文件有两种:程序文件、数据文件(从文件的功能角度来分)。
包括源程序文件(.c),目标文件(Windows环境后缀为.obj),可执行文件(windows环境后缀为.exe)。
文件的内容不一定是程序,而程序运行时读写的数据,如:程序运行需要从中读取数据的文件,或者输出内容的文件。
我们文件操作主要是说的数据文件。
一个问价要有一个唯一的文件标识,方便用户识别和引用。
文件名包括三个部分:文件路径 + 文件名主干 + 文件后缀
举例:c:\code\test.c
为了方便,文件标识被称为文件名。
文件类型
根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
总结:外存中不转化就是二进制文件,转化就是文本文件。
一个数据在内存中的存储形式
字符一律是以ASCII的形式存储,数值型数据也可以使用ASCII形式存储,也可以使用二进制形式存储。
如果有整数10000,如果以ASCII码形式存储输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节。
代码测试:
int main(){
int a = 10000;
FILE *pf = fopen("test.txt","wb");
fwrite(&a,4,1,pf);//二进制的形式写在文件中
fclose(pf);
pf = NULL;
return 0;
}
这时在文件目录下会产生一个test.txt
的文件。直接打开内容是无法阅读的。但是程序以二进制阅读的时候会产生以下结果。
但是如果使用文本的方式存储打开文件后直接就是10000的形式。
ANSIC标准采用“缓冲文件系统”处理数据文件的,缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读取数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序区(程序变量等)。缓冲区的大小根据c编译系统决定。
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
每个使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等。)。这些信息时保存在一个结构体变量中的。该结构体类型是有系统声明的,取名为FILE。
不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。
每当打开一个文件的时候,系统会根据文件的情况会自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节。
一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。
创建一个FILE*的指针变量
FILE* pf;//文件指针变量的定义
定义pf是一个指向FILE类型数据的指针变量。可以使用pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能访问该文件。也就是说,通过文件指针变量就能找到与它相关联的文件。
理解:就像数组一样,通过首元素地址就可以管理数组。
文件读写之前应该先打开文件,使用结束之后应该关闭文件。
在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。
ANSIC规定使用fopen函数打开文件,fclose来关闭文件。
FILE * fopen(const char * filename,const char * mode);
int fclose(FILE * stream);
打开文件的方式:
文件使用方式 | 含义 | 如果指定文件不存在 |
---|---|---|
“r” 只读 | 打开一个已经存在的文本文件 | 报错 |
“w” 只写 | 打开一个文本文件,使用之后会将原来的覆盖 | 建立一个新的文件 |
“a” 追加 | 向文本文件尾添加数据 | 报错 |
“rb” 二进制形式读 | 打开一个二进制文件 | 报错 |
“wb” 二进制形式写 | 打开一个二进制文件 | 建立一个新的文件 |
“ab” 二进制追加 | 向一个二进制文件尾添加数据 | 出错 |
“r+” 读写 | 打开一个文本文件,为了读写 | 出错 |
“w+” 读写 | 建立一个新的文件,为了读写 | 建立一个新的文件 |
“a+” 读写 | 打开一个文件,在文件尾读写 | 建立一个新的文件 |
“rb+” 读写 | 打开一个二进制文件 | 出错 |
“wb+” 读写 | 新建一个新的二进制文件 | 建立一个新的文件 |
“ab+” 读写 | 打开一个二进制文件,在文件尾部进行读和写 | 建立一个新的文件 |
"r"表示的是读,"w"表示的是写,“a"表示追加,”+"表示为了读写
文件操作
int main() {
FILE *pf = fopen("test.txt","r");//将r换位其他的文件使用方式测试
if (pf == NULL){
printf("%s",strerror(errno));
return 0;
}
//打开成功
//读写文件
//关闭文件 -- 类似于动态内存中的free
fclose(pf);
pf = NULL;
return 0;
}
相对于当前运行文件,或者当前工程的文件目录的,相对的路径。
..表示上一级目录
.表示当前目录
/表示进入下一级目录
从此电脑(我的电脑的目录开始),一般是以C、D、F、G盘开头的文件目录
功能 | 函数名 | 适用于 |
---|---|---|
字符输入函数 | fgetc | 所有输入流 |
字符输出函数 | fputc | 所有输出流 |
文本行输入函数 | fgets | 所有输入流 |
文本行输出函数 | fputs | 所有输出流 |
格式化输入函数 | fscanf | 所有输入流 |
格式化输出函数 | fprintf | 所有输出流 |
二进制输入 | fread | 文件 |
二进制输入 | fwrite | 文件 |
实例:
/* fgetc example: money counter */
#include
int main ()
{
FILE * pFile;
int c;
int n = 0;
pFile=fopen ("myfile.txt","r");
if (pFile==NULL) perror ("Error opening file");
else
{
do {
c = fgetc (pFile);
if (c == '$') n++;
} while (c != EOF);
fclose (pFile);
printf ("The file contains %d dollar sign characters ($).\n",n);
}
return 0;
}
实例:
/* fputc example: alphabet writer */
#include
int main ()
{
FILE * pFile;
char c;
pFile = fopen ("alphabet.txt","w");
if (pFile!=NULL) {
for (c = 'A' ; c <= 'Z' ; c++)
fputc ( c , pFile );
fclose (pFile);
}
return 0;
}
实例:
/* fgets example */
#include
int main()
{
FILE * pFile;
char mystring [100];
pFile = fopen ("myfile.txt" , "r");
if (pFile == NULL) perror ("Error opening file");
else {
if ( fgets (mystring , 100 , pFile) != NULL )
puts (mystring);
fclose (pFile);
}
return 0;
}
实例:
/* fputs example */
#include
int main ()
{
FILE * pFile;
char sentence [256];
printf ("Enter sentence to append: ");
fgets (sentence,256,stdin);
pFile = fopen ("mylog.txt","a");
fputs (sentence,pFile);
fclose (pFile);
return 0;
}
实例:
/* fscanf example */
#include
int main ()
{
char str [80];
float f;
FILE * pFile;
pFile = fopen ("myfile.txt","w+");
fprintf (pFile, "%f %s", 3.1416, "PI");
rewind (pFile);
fscanf (pFile, "%f", &f);
fscanf (pFile, "%s", str);
fclose (pFile);
printf ("I have read: %f and %s \n",f,str);
return 0;
}
实例:
/* fprintf example */
#include
int main ()
{
FILE * pFile;
int n;
char name [100];
pFile = fopen ("myfile.txt","w");
for (n=0 ; n<3 ; n++)
{
puts ("please, enter a name: ");
gets (name);
fprintf (pFile, "Name %d [%-10.10s]\n",n+1,name);
}
fclose (pFile);
return 0;
}
实例:
/* fread example: read an entire file */
#include
#include
int main () {
FILE * pFile;
long lSize;
char * buffer;
size_t result;
pFile = fopen ( "myfile.bin" , "rb" );
if (pFile==NULL) {fputs ("File error",stderr); exit (1);}
// obtain file size:
fseek (pFile , 0 , SEEK_END);
lSize = ftell (pFile);
rewind (pFile);
// allocate memory to contain the whole file:
buffer = (char*) malloc (sizeof(char)*lSize);
if (buffer == NULL) {fputs ("Memory error",stderr); exit (2);}
// copy the file into the buffer:
result = fread (buffer,1,lSize,pFile);
if (result != lSize) {fputs ("Reading error",stderr); exit (3);}
/* the whole file is now loaded in the memory buffer. */
// terminate
fclose (pFile);
free (buffer);
return 0;
}
实例:
/* fwrite example : write buffer */
#include
int main ()
{
FILE * pFile;
char buffer[] = { 'x' , 'y' , 'z' };
pFile = fopen ("myfile.bin", "wb");
fwrite (buffer , sizeof(char), sizeof(buffer), pFile);
fclose (pFile);
return 0;
}
输入流是从文件向计算机导入信息,输出流是从计算机向文本导出信息。
使用时注意函数返回值。
个人样例感受。
int main(){
int ch = fgetc(sedin);
fputc(ch,stdout);
return 0;
}
typedef struct S{
int n;
float score;
char arr[10];
}S;
int main(){
S s = {0};
fscanf(stdin,"%d %f %s",&(s.n),&(s.score),s.arr);
fprintf(stdout,"%d %.2f %s",s.n,s.score,s.arr);
return 0;
}
我们日常使用的键盘屏幕也是输入输出流。一般讲从键盘输入,输出到屏幕上。但是键盘和屏幕都是外部设备。
键盘被称之为 标准输入设备;屏幕是标准输出设备。对于两个设备,是程序默认打开的两个流设备。
程序默认打开的三个流
stdin(键盘)、stdout(屏幕)、stderr。这三个流是默认打开的。
scanf/fscanf/sscanf
printf/fprintf/sprintf
scanf/printf是针对标准输入/流输出流的格式化输入/输出语句
fscanf/fprintf是针对所有输入/输出流的格式化输入/输出语句
sscanf是从字符串中读取格式化的数据
sprintf是把格式化数据存储到字符串中。
typedef struct S{
int n;
float score;
char arr[10];
}S;
int main(){
S s = {100,3.14f,"abcdef"};
S tmp = {0};
char buf[1024] = {0};
//把格式化的数据转换成字符串存储到buf
sprintf(buf,"%d %f %s",s.n,s.score,s.arr);
// printf("%s\n",buf);
// 从buf中读取格式化的数据到tmp中。
sscanf(buf,"%d %f %s",&(s.n),&(s.score),s.arr);
printf("%d %f %s",s.n,s.score,s.arr);
return 0;
}
根据文件指针的位置和偏移量来定位文件指针。
int fseek(FILE* stream,long int offset,int origin);
int fseek(文件指针,偏移量,文件指针的当前位置)
origin可以写成SEEK_CUR(文件指针的当前位置)、SEEK_END(文件指针的末尾位置)、SEEK_SET(文件指针的其实位置)。
有了这个函数,就可以随机读取文件内容了。
#include
int main ()
{
FILE * pFile;
pFile = fopen ( "example.txt" , "wb" );
fputs ( "This is an apple." , pFile );
fseek ( pFile , 9 , SEEK_SET );
fputs ( " sam" , pFile );
fclose ( pFile );
return 0;
}
返回文件指针相对于其实位置的偏移量
long int ftell(FFILE * stream);
#include
int main ()
{
FILE * pFile;
long size;
pFile = fopen ("myfile.txt","rb");
if (pFile==NULL) perror ("Error opening file");
else
{
fseek (pFile, 0, SEEK_END); // non-portable
size=ftell (pFile);
fclose (pFile);
printf ("Size of myfile.txt: %ld bytes.\n",size);
}
return 0;
}
让文件指针的位置回到文件的起始位置。
void rewind(FILE* stream);
使用
#include
int main ()
{
int n;
FILE * pFile;
char buffer [27];
pFile = fopen ("myfile.txt","w+");
for ( n='A' ; n<='Z' ; n++)
fputc ( n, pFile);
rewind (pFile);
fread (buffer,1,26,pFile);
fclose (pFile);
buffer[26]='\0';
puts (buffer);
return 0;
}
在文件读取过程中,不能使用feof函数的返回值直接用来判断文件的是否结束。
而是应用于当前读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
理解:文件读取结束后,知道读取结束的原因。
文件读取结束判断:
文本文件读取是否结束,判断返回值是否为EOF
(fgetc),或者NULL
(fgets)
例如:
二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
例如:
正确使用的例子:
int main(){
int c;
FILE* fp = fopen("test.txt","r");
//perror
if(!fp){
//错误信息
perror("Failed");
return EXIT_FAILURE;
}
//打开成功处理
//fgetc当读取失败的时候或者遇到文件结束的时候,都会返回EOF
while((c = fgetc(fp))!=EOF){//读取结束后结束循环
putchar(c);
}
//判断结束原因
if (ferror(fp)) puts("I/o error when reading");
//feof一定是文件读取结束后才使用。
else if(feof(fp))
puts("End of file reached successfully");
//置空
fclose(fp);
fp = NULL;
}
feof不是判断结束,是判断结束的原因的。所以使用的条件是文件读取结束。