c语言-文件操作

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)

fgetcgetc 函数都是用于从文件中读取一个字符的函数,它们都需要包含头文件 stdio.h。它们的具体区别如下:

  1. fgetc(FILE *stream) 函数从指定的文件流(即由 fopen 函数返回的指针)中读取一个字符,并使文件指针后移一个位置。
int ch;
ch = fgetc(fp); // 从文件指针 fp 指向的文件中读取一个字符
  1. getc(FILE *stream) 函数与 fgetc 函数相似,也是从指定的文件流(即由 fopen 函数返回的指针)中读取一个字符。不同的是,getc 函数有时被定义为宏,这意味着它可以直接被编译器优化,而不需要调用函数。

使用这两个函数时需要注意以下几点:

  1. 如果达到文件结尾或读取失败,fgetcgetc 函数会返回 EOF(End Of File),其值为 -1。
  2. 需要检查返回值是否等于 EOF,以判断文件是否已经读取结束或读取出错,避免出现潜在的错误。
  3. 如果使用 fgetc 函数从标准输入 stdin 中读取一个字符,可以用 getchar() 函数代替,其作用与 fgetc(stdin) 相同。

总之,fgetcgetc 函数都是从文件中读取一个字符的函数,其大体相同,唯一的区别在于 getc 有可能被编译器定义为宏,两者的具体使用应该根据具体情况而定。

3.2写入单个字符(fputc和putc)

fputcputc 函数都是用于向文件中写入一个字符的函数,它们都需要包含头文件 stdio.h。它们的具体区别如下

  1. fputc(int c, FILE *stream) 函数将一个字符写入指定的文件流(即由 fopen 函数返回的指针),并使文件指针后移一个位置。
   int ch;
  fputc(ch, fp); // 向文件指针 fp 指向的文件中写入一个字符 ch
  1. putc(int c, FILE *stream) 函数与 fputc 函数相似,也是向指定的文件流(即由 fopen 函数返回的指针)中写入一个字符。不同的是,putc 函数有时被定义为宏,这意味着它可以直接被编译器优化,而不需要调用函数。
int ch;
putc(ch, fp); // 向文件指针 fp 指向的文件中写入一个字符 ch

使用这两个函数时需要注意以下几点:

  1. 如果成功写入字符到文件中,fputcputc 函数会返回写入的字符;如果发生写入失败,则返回 EOF(End Of File),其值为 -1。
  2. 需要检查返回值是否等于 EOF,以判断写入是否成功,避免出现潜在的错误。
  3. 如果使用 fputc 函数向标准输出 stdout 中写入一个字符,可以用 putchar() 函数代替,其作用与 fputc(c, stdout) 相同。

总之,fputcputc 函数都是向文件中写入一个字符的函数,其大体相同,唯一的区别在于 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_SETSEEK_CURSEEK_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打印错误的方式了。

案例:

这里偷个小懒,个人觉得这里的知识不是很重要,听一下了解一下就可以了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vC1UwBd1-1683818961223)(assets/image-20230502112021380.png)]

9.IO缓冲区


(struct Stu),1,fp) ; // 将读取到的数据,存放到结构体的指针,这里可以看fread的函数原型
printf(“%s(%d)的成绩是:%.2f\n”,sb.name,sb.num,sb.score ) ;

fclose(fp) ;

}


[外链图片转存中...(img-0IhgOSIq-1683818961222)]

[外链图片转存中...(img-pBbmR3WM-1683818961222)]

# 8.标准流和错误处理

这里就类似于Java种的抛出异常的那卦的知识,之前c语言中有错误的时候,都是用printf打印出错误,这时候又跑到了标准输出流去了,并不是错误输出流,本内容学习了之后就可以使用错误输出流来代替之前的printf打印错误的方式了。

[外链图片转存中...(img-iCnsc2jZ-1683818961223)]

案例:

这里偷个小懒,个人觉得这里的知识不是很重要,听一下了解一下就可以了。

[外链图片转存中...(img-vC1UwBd1-1683818961223)]

# 9.IO缓冲区

[外链图片转存中...(img-wKRwWJFs-1683818961224)]

[外链图片转存中...(img-EikWLyvZ-1683818961224)]
  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值