0.什么是文件
1.文本文件和二进制文件
c语言中主要有两种文件,文本文件和二进制文件。
2.打开和关闭文件
2.1打开文件
2.1.1fopen()函数
FILE *fp; // 定义文件指针
fp = fopen("filename", "mode"); // 打开文件
其中,第一个参数是文件名(包括路径),第二个参数是文件打开模式,可以是以下几种之一:
"r"
:- 只读方式打开文件,文件指针指向文件开头,从文件头开始读取。
- 该文件必须存在
"w"
:- 写方式打开文件,如果文件不存在则新建一个文件
- 如果文件已经存在则清空文件内容,并指向文件开头。
"a"
:- 追加方式打开文件,如果文件不存在则新建一个文件,指向文件结尾
- 如果文件已经存在则指向文件结尾。
"r+"
:- 读写方式打开文件,文件指针指向文件开头。
- 该文件必须存在
- 该模式不会清除原有内容,只覆盖重新写入的内容,例如原来100个字节,写入10个,则只覆盖前10个字节。
"w+"
:- 读写方式打开文件,如果文件不存在则新建一个文件
- 如果文件已经存在则清空文件内容,并指向文件开头。
"a+"
:读和追加方式打开文件,如果文件不存在则新建一个文件,指向文件结尾
如果文件已经存在则指向文件结尾。
"b"
:- 与上面6种模式可结合(”rb”,”wb”,”ab”,”r+b”,”w+b”,”a+b”)
- 描述的含义一样,只不过操作的对象是二进制文件
2.2关闭文件
fclose(fp); // 关闭文件
3.读写单个字符
3.1读取单个字符(fgetc和getc)
fgetc
和 getc
函数都是用于从文件中读取一个字符的函数,它们都需要包含头文件 stdio.h
。它们的具体区别如下:
fgetc(FILE *stream)
函数从指定的文件流(即由fopen
函数返回的指针)中读取一个字符,并使文件指针后移一个位置。
int ch;
ch = fgetc(fp); // 从文件指针 fp 指向的文件中读取一个字符
getc(FILE *stream)
函数与fgetc
函数相似,也是从指定的文件流(即由fopen
函数返回的指针)中读取一个字符。不同的是,getc
函数有时被定义为宏,这意味着它可以直接被编译器优化,而不需要调用函数。
使用这两个函数时需要注意以下几点:
- 如果达到文件结尾或读取失败,
fgetc
和getc
函数会返回 EOF(End Of File),其值为 -1。- 需要检查返回值是否等于 EOF,以判断文件是否已经读取结束或读取出错,避免出现潜在的错误。
- 如果使用
fgetc
函数从标准输入stdin
中读取一个字符,可以用getchar()
函数代替,其作用与fgetc(stdin)
相同。总之,
fgetc
和getc
函数都是从文件中读取一个字符的函数,其大体相同,唯一的区别在于getc
有可能被编译器定义为宏,两者的具体使用应该根据具体情况而定。
3.2写入单个字符(fputc和putc)
fputc
和 putc
函数都是用于向文件中写入一个字符的函数,它们都需要包含头文件 stdio.h
。它们的具体区别如下
fputc(int c, FILE *stream)
函数将一个字符写入指定的文件流(即由fopen
函数返回的指针),并使文件指针后移一个位置。
int ch;
fputc(ch, fp); // 向文件指针 fp 指向的文件中写入一个字符 ch
putc(int c, FILE *stream)
函数与fputc
函数相似,也是向指定的文件流(即由fopen
函数返回的指针)中写入一个字符。不同的是,putc
函数有时被定义为宏,这意味着它可以直接被编译器优化,而不需要调用函数。
int ch;
putc(ch, fp); // 向文件指针 fp 指向的文件中写入一个字符 ch
使用这两个函数时需要注意以下几点:
- 如果成功写入字符到文件中,
fputc
和putc
函数会返回写入的字符;如果发生写入失败,则返回EOF
(End Of File),其值为 -1。 - 需要检查返回值是否等于
EOF
,以判断写入是否成功,避免出现潜在的错误。 - 如果使用
fputc
函数向标准输出stdout
中写入一个字符,可以用putchar()
函数代替,其作用与fputc(c, stdout)
相同。
总之,fputc
和 putc
函数都是向文件中写入一个字符的函数,其大体相同,唯一的区别在于 putc
有可能被编译器定义为宏,两者的具体使用应该根据具体情况而定。
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE *fp1 ;
FILE *fp2 ;
int ch ;
if((fp1 = fopen("hello.txt","r")) == NULL)
{
printf("文件打开失败") ;
exit(EXIT_FAILURE);
}
if((fp2 = fopen("fishc.txt","w")) == NULL)
{
printf("文件打开失败") ;
exit(EXIT_FAILURE);
}
while((ch = fgetc(fp1)) != EOF)
{
fputc(ch,fp2) ; //将读取到的文件,写入fp2对应的文件中
}
fclose(fp1) ;
fclose(fp2) ;
}
4.读写字符串
4.1读取字符串(fgets)
fgets
函数用于从一个文件流中读取一行内容,其函数原型如下:
char *fgets(char *s, int size, FILE *stream);
它接受三个参数:
s
:指向一个字符数组,用于存储读取到的数据。size
:表示读取字符数的最大值。当读取到\n
或者读取字符数达到了size-1
,就会停止读取。stream
:指向一个文件流,用于指定从哪个文件读取。
fgets
函数会依次读取每个字符,直到遇到换行符 \n
或者读取字符数达到上限(由 size
指定)也可以说是,然后将读取到的字符存储到 s
指向的缓冲区中,并在字符串结尾添加一个空字符 \0
,表示字符串的结束。如果遇到文件结尾 EOF,或者读取过程中出现错误,fgets
将停止读取并返回 NULL。
==补充==:如果在读取字符的过程中遇到EOF,则eof指示器被设置;如果还么读入任何字符就遇到这种EOF,则s参数指向的位置保持原来的内容,函数返回NULL,例如字符串的末尾有换行符,此时就来到读取下一行了,但是下一行什么也没有,是空的,此时就会指向上一行的内容。只有打印的时候会,查看文件内容还是实质的内容。
4.2写入字符串(fputs)
fputs
函数用于将一个字符串写入到指定的文件流中,其函数原型如下:
int fputs(const char *s, FILE *stream);
它接受两个参数:
s
:指向要写入的字符串。stream
:指向要写入的文件流。
fputs
函数会将指定的字符串 s
写入到 stream
指定的文件流中,并返回一个非负数值表示写入的字符数,非0值
(不包括结尾的空字符 \0
)。如果写入过程出现错误,则返回一个负数表示错误码,返回EOF。
4.3feof()
用于检查文件指针所指向的文件是否已经达到文件末尾。该函数通过测试文件流上的文件结束标识符来确定文件是否结束
如果结束则返回非零值,否则返回零。
feof()
的函数原型如下:
int feof(FILE *stream);
其中 stream
是一个文件指针,指向需要被检查的文件。
当文件读取完毕后,调用 feof()
函数会返回一个非零值;而在文件读取过程中调用 feof()
函数将会返回零。
通常情况下,我们在使用 fgets()
或者 fscanf()
等函数在一个循环中读取文件内容时,可以利用 feof()
函数来判断循环何时应该停止
#include<stdio.h>
#include<stdlib.h>
#define MAX 1024
int main()
{
FILE *fp ;
char buffer[MAX] ;
if((fp = fopen("lines.txt","w")) == NULL)
{
printf("文件打开失败\n") ;
exit(EXIT_FAILURE) ;
}
// 向这个文件中写入数据
fputs("Lines one : hello world\n",fp);
fputs("lines two : hello ljh\n",fp) ;
fputs("lines three : i love fishc.com\n ",fp);
fclose(fp) ;
if((fp = fopen("lines.txt","r")) == NULL)
{
printf("文件打开失败\n") ;
exit(EXIT_FAILURE) ;
}
// 读取数据
while(!feof(fp)) // 当文件到达末尾的时候,返回非零,所以我们取反,就是没有到达末尾的时候就while循环,到达末尾了就是非零的取反为0,此时就跳出循环了
{
fgets(buffer,MAX,fp) ;
printf("%s",buffer) ;
}
}
5.格式化读写文件
5.1fscanf读
fscanf() 函数的原型为:
int fscanf(FILE *stream, const char* format, ...);
它的第一个参数是要读取的文件流,第二个参数是格式化字符串,后面跟着可选的参数列表,对应格式化字符串中的占位符。fscanf() 将按照格式化字符串中指定的格式从文件流中读取数据,并将其存储到相应的参数中。
例如,下面的代码从文件中读取一个整数和一个浮点数:
int i;
float f;
FILE *fp = fopen("data.txt", "r");
fscanf(fp, "%d%f", &i, &f);
fclose(fp);
5.2fprintf写
fprintf() 函数的原型为:
int fprintf(FILE *stream, const char* format, ...);
它的第一个参数是要写入的文件流,第二个参数是格式化字符串,后面跟着可选的参数列表,对应格式化字符串中的占位符。fprintf() 将按照格式化字符串中指定的格式将数据写入到文件流中。
例如,下面的代码向文件中写入一个字符串和一个整数:
char str[] = "Hello, World!";
int i = 123;
FILE* fp = fopen("data.txt", "w");
fprintf(fp, "%s %d\n", str, i);
fclose(fp);
案例
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{
FILE *fp ;
struct tm *p ;
time_t t ;
time(&t) ;
p = localtime(&t) ;
if((fp = fopen("date.txt","w")) == NULL)
{
printf("打开文件失败") ;
exit(EXIT_FAILURE) ;
}
fprintf(fp,"%d-%d-%d",1900 + p->tm_year, 1 + p->tm_mon, p->tm_mday) ;//因为年份是从1900开始的,所以要加上,月份是从0开始的,要加1
fclose(fp) ;
int year , month ,day ;
if((fp = fopen("date.txt","r")) == NULL)
{
printf("打开文件失败l\n") ;
exit(EXIT_FAILURE) ;
}
fscanf(fp,"%d-%d-%d",&year,&month,&day) ;
printf( "%d-%d-%d\n",year,month,day) ;
fclose(fp) ;
}
6.二进制读写文件
6.1fread读
6.2fwrite写
函数原型:
size_t fread ( void *ptr, size_t size, size_t count, FILE *fp );
size_t fwrite ( void * ptr, size_t size, size_t count, FILE *fp );
对参数的说明:
- ptr 为内存区块的指针,它可以是数组、变量、结构体等。
- fread() 中的 ptr 用来存放读取到的数据
- fwrite() 中的 ptr 用来存放要写入的数据。
- size:表示每个数据块的字节数。
- count:表示要读写的数据块的块数。
- fp:表示文件指针。
- 理论上,每次读写 size*count 个字节的数据
返回值:返回成功读写的块数,也即 count。如果返回值小于 count:
- 对于 fwrite() 来说,肯定发生了写入错误,可以用 ferror() 函数检测。
- 对于 fread() 来说,可能读到了文件末尾,可能发生了错误,可以用 ferror() 或 feof() 检测。
案例:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Date
{
int year ;
int month ;
int day ;
} ;
struct Book
{
char name[40] ;
char author[40] ;
char publisher[40] ;
struct Date date ;
} ;
int main()
{
FILE *fp ;
struct Book *book_for_write , *book_for_read ;
book_for_write = (struct Book *)malloc(sizeof(struct Book)) ;
book_for_read = (struct Book *)malloc(sizeof(struct Book)) ;
// ..申请内存空间判断
//写一些要存放进fp文件的数据
strcpy(book_for_write->name,"学习笔记") ;
strcpy(book_for_write->author,"ljh") ;
strcpy(book_for_write->publisher,"未来出版社") ;
book_for_write->date.year = 2023 ;
book_for_write->date.month = 5 ;
book_for_write->date.day = 2 ;
if((fp = fopen("file.txt","w")) == NULL)
{
printf("打开文件失败") ;
exit(EXIT_FAILURE) ;
}
//将上面的文件写进fp中
fwrite(book_for_write,sizeof(struct Book),1,fp) ;
fclose(fp) ;
if((fp = fopen("file.txt","r")) == NULL)
{
printf("打开文件失败") ;
exit(EXIT_FAILURE) ;
}
// 从文件fp中读取数据,存放到book_for_read中
fread(book_for_read,sizeof(struct Book),1,fp) ;
printf("书名:%s\n",book_for_read->name) ;
printf("作者:%s\n",book_for_read->author) ;
printf("出版社:%s\n",book_for_read->publisher) ;
// 打印结果
printf("出版日期:%d-%d-%d\n",book_for_read->date.year,book_for_read->date.month,book_for_read->date.day) ;
fclose(fp) ;
}
打开该文件来看就是乱码,因为是二进制文件
7.随机读写文件
7.1ftell
用于获取当前文件流位置的函数
函数原型:
long int ftell(FILE *stream);
需要注意的是,ftell()
函数返回的位置信息仅在二进制模式下是有意义的。在文本模式下,由于可能存在换行符等特殊字符的转换,因此返回的位置信息可能与实际位置不同。如果需要获取文本文件的真实字符数,请使用 fgetpos()
函数和 fsetpos()
函数配合使用。
案例:
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE *fp ;
if((fp = fopen("hello.txt","w")) == NULL)
{
printf("文件打开失败\n") ;
exit(EXIT_FAILURE) ;
}
printf("%ld\n",ftell(fp)) ; // 刚开始该文件是空的,所以打印结果为0
fputc('F',fp) ;
printf("%ld\n",ftell(fp)) ; // 写入了一个字符'f',之后,打印结果为1
fputs("fishc",fp) ;
printf("%ld\n",ftell(fp)) ; // 写入了之后,打印的结果是累加的和6,
fclose(fp) ;
}
7.2rewind
rewind()
函数是 C 标准库中用于将文件流位置指针重置到文件开头的函数。它的原型为:
void rewind(FILE *stream);
该函数将文件流 stream
的位置指针重置到文件开头。它不返回任何值。
案例:用上面的代码,rewind之后,发现,再写入数据,之前的数据就全部没有了。
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE *fp ;
if((fp = fopen("hello.txt","w")) == NULL)
{
printf("文件打开失败\n") ;
exit(EXIT_FAILURE) ;
}
printf("%ld\n",ftell(fp)) ; // 刚开始该文件是空的,所以打印结果为0
fputc('F',fp) ;
printf("%ld\n",ftell(fp)) ; // 写入了一个字符'f',之后,打印结果为1
fputs("fishc",fp) ;
printf("%ld\n",ftell(fp)) ; // 写入了之后,打印的结果是累加的和6,
rewind(fp) ; //rewind
fputs("hello",fp) ; // 插入数据,会直接覆盖原始数据
fclose(fp) ;
}
7.3fseek随机读取任意位置的数据
fseek()
函数是 C 标准库中用于移动文件流位置指针的函数。它的原型为:
int fseek(FILE *stream, long int offset, int origin);
该函数将文件流 stream
的位置指针移动 offset
个字节,移动方向由参数 origin
指定。参数 origin
可以取三个值:SEEK_SET
、SEEK_CUR
和 SEEK_END
,分别表示从文件开头、从当前位置和从文件末尾开始移动。
如果移动成功,则返回 0;否则返回非零值。
需要注意的是,如果将文件流位置指针移动到不可读或不可写的位置,可能会导致文件读写失败。因此,在使用 fseek()
函数时,需要确保移动的位置正确,并检查函数返回值以确保移动是否成功。
案例:
#include <stdio.h>
#include<stdlib.h>
#define N 4
struct Stu
{
char name[24] ;
int num ;
float score ;
} stu[N] , sb ;
int main()
{
FILE *fp ;
int i ;
if((fp = fopen("score.txt","wb")) == NULL)
{
printf("打卡文件失败") ;
exit(EXIT_FAILURE) ;
}
printf("请开始录入成绩 格式:姓名,学号,成绩\n") ;
for(i = 0 ; i < N ; i++)
{
scanf("%s %d %f",stu[i].name,&stu[i].num,&stu[i].score) ;
}
fwrite(stu,sizeof(struct Stu),N,fp) ;
fclose(fp) ;
if((fp = fopen("score.txt","rb")) == NULL)
{
printf("打卡文件失败") ;
exit(EXIT_FAILURE) ;
}
fseek(fp,sizeof(struct Stu),SEEK_SET) ;
fread(&sb,sizeof(struct Stu),1,fp) ; // 将读取到的数据,存放到结构体的指针,这里可以看fread的函数原型
printf("%s(%d)的成绩是:%.2f\n",sb.name,sb.num,sb.score ) ;
fclose(fp) ;
}
8.标准流和错误处理
这里就类似于Java种的抛出异常的那卦的知识,之前c语言中有错误的时候,都是用printf打印出错误,这时候又跑到了标准输出流去了,并不是错误输出流,本内容学习了之后就可以使用错误输出流来代替之前的printf打印错误的方式了。
案例:
这里偷个小懒,个人觉得这里的知识不是很重要,听一下了解一下就可以了。