c语言学习(文件)

一、使用文件:

 

使用文件的话我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化。不至于程序退出后,下一次再进去就不存在了。

我们一般数据持久化的方法有,把数据存放在磁盘文件、存放到数据 库等方式。

什么是文件:

 磁盘上的文件是文件。

但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)。

 程序文件:

包括源程序文件(后缀为.c),

目标文件(windows环境后缀为.obj),

可执行程序(windows环境 后缀为.exe)。

 数据文件:

文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件, 或者输出内容的文件。(后面内容围绕数据文件展开)

文件名:

一个文件要有一个唯一的文件标识,以便用户识别和引用。

文件名包含3部分:

文件路径+文件名主干+文件后缀

例如: c:\code\test.txt

为了方便起见,文件标识常被称为文件名

二、文件的关闭和打开:

 文件指针:

缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。

每个被使用的文件都在内存中开辟了一个相应的文件信息区(文件信息区其实是一个结构体变量,该结构体类型名叫FILE),用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。

这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE.

例如,VS2013编译环境提供的 stdio.h 头文件中有以下的文件类型申明:

struct _iobuf {
        char *_ptr;
        int   _cnt;
        char *_base;
        int   _flag;
        int   _file;
        int   _charbuf;
        int   _bufsiz;
        char *_tmpfname;
       };
typedef struct _iobuf FILE;

a、只要文件发生变化,文件信息区会发生相应的变化

b、一般通过一个FILE的指针来维护这个FILE结构的变量。

创建一个FILE*的指针变量:

FILE* pf;//文件指针变量

定义pf是一个指向FILE类型数据的指针变量。

可以使pf指向某个文件的文件信息区(是一个结构体变 量)。

通过该文件信息区中的信息就能够访问该文件。

也就是说,通过文件指针变量能够找到与它关联的文件。

比如:

c语言学习(文件)_第1张图片

 文件的打开和关闭:

文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。

在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。

ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件。

int main()
{
	FILE*pf=fopen("Project22", "w");//创建指针接收,
	//如果打开失败会返回空指针,这时可以使用perror打印错误信息
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	//不用之后关闭文件,而一个进程或一个程序,它打开的文件数量是有限的,如果只打开不释放(不关闭文件),后面可能会打不开文件
	fclose(pf);
	pf = NULL;
	return 0;
}

当用fopen函数(返回类型是FILE*)去打开某个文件时,会创建一个信息区,并把信息区的起始地址返回,

 FILE* fopen(const char*filename,const char*mode),mode代表文件打开方式,打开方式如下:

文件使用方式           含义                            如果指定文件不存在
“r”(只读)   为了输入数据,打开一个已经存在的文本文件   出错
“w”(只写)   为了输出数据,打开一个文本文件             建立一个新的文件
“a”(追加)   向文本文件尾添加数据                      建立一个新的文件
“rb”(只读)  为了输入数据,打开一个二进制文件           出错
“wb”(只写)  为了输出数据,打开一个二进制文件           建立一个新的文件
“ab”(追加)  向一个二进制文件尾添加数据                 出错
“r+”(读写)  为了读和写,打开一个文本文件               出错
“w+”(读写)  为了读和写,建议一个新的文件               建立一个新的文件
“a+”(读写)  打开一个文件,在文件尾进行读写             建立一个新的文件
“rb+”(读写) 为了读和写打开一个二进制文件               出错
“wb+”(读写) 为了读和写,新建一个新的二进制文件         建立一个新的文件
“ab+”(读写) 打开一个二进制文件,在文件尾进行读和写     建立一个新的文件

int fclose(FILE*stream),

如果利用打开函数中的文件名是绝对路径,则要注意斜杠要再一个斜杆,避免被解读成转义字符

 三、文件的顺序读写

功能             函数名      适用于
字符输入函数      fgetc     所有输入流
字符输出函数      fputc     所有输出流
文本行输入函数    fgets     所有输入流
文本行输出函数    fputs     所有输出流
格式化输入函数    fscanf    所有输入流
格式化输出函数    fprintf   所有输出流
二进制输入        fread       文件
二进制输出        fwrite      文件

int  fputc(int c,FILE*stream)

int fgetc(FILE*stream)

a、该函数在读取后,指针会加1指向下一个字符

b、如果读取正常则会返回字符的ascii码值作为一个整数,如果读取失败或遇到文件结束的时候会返回EOF(-1)。

fgetc为什么用int接收,

函数以unsigned char的方式读取文件流,并扩展为一个整数再返回,然而我们要按照返回值为-1(EOF)来判断是否读取结束或失败,而unsigned char显然无法表示-1,所以要设置合适的类型去接收(进行强制转换),而如果是char类型去接收字节的话,显然遇到字符0xff时,只接收一个字节,并认为最高位为符号位(接收为0xff,和EOF比较时整型提升为0xffffffff和EOF相等)无法很好的判断读取请况,会出现提前退出循环的情况(只用一个字节去判断而不能确定后面是否还有内容),用int接收,遇到字符0xff时则为(0x000000ff),可以很好的判断出并不是文件结束,而当接收-1时,unsigned char类型时为11111111(255)整型提升后为0x000000ff,用char接收为0xffffffff,用int接收为0xffffffff,所以我们可以发现,如果只是单纯的判断是否为EOF时,char和int类型显然都是可以的,但int类型可以更好的判断是否读取结束。

使用fgetc从文件流中读取数据:

int main()
{
	FILE*pf=fopen("Project111.c", "w");
	//FILE* pf= fopen("Project111.c", "r");

	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件//先用"w"写文件
	fputc('h',pf );
	fputc('a', pf);
	fputc('h', pf); 
	fputc('a', pf);
	fputc('h', pf);
	fputc('a', pf);
    //读文件,用"r"
	//int n = fgetc(pf);
	//printf("%c", n);
	//n = fgetc(pf);
	//printf("%c", n);
	//n = fgetc(pf);
	//printf("%c", n);
    //关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

c语言学习(文件)_第2张图片

fgetc从标准输入流读取数据:

int main()
{
	int n=fgetc(stdin);
	printf("%c", n);
	n=fgetc(stdin);
	printf("%c", n);
	n=fgetc(stdin); 
	printf("%c", n);
	n=fgetc(stdin);
	printf("%c", n);
	return 0;
}

c语言学习(文件)_第3张图片

 如果读到文本结束,再往下读取会得到-1

按照行来写:

int fputs(const char*string,FILE* stream)

char*fgets(char*string,int n,FILE*stream)

:string表示从stream中读取数据放到string

  n表示最大能读取的字符个数(其实读n-1个,最后为'\0'),

int main()
{
	char shu[11] = { 0 };
	FILE* pf = fopen("Project111.c", "r");//虽然这里是r,但我是先用w写完后再用r读取
		if (pf == NULL)
		{
			perror("fopen");
			return 1;
		}
	//写文件
	fputs("yes!",pf);
	fgets(shu,9,pf);
	printf("%s\n",shu);
	return 0;

}

c语言学习(文件)_第4张图片

流:c语言学习(文件)_第5张图片

 所以我们再打开文件时,向文件里写数据,

c程序,只要运行起来,就默认打开了3个流:(它们的类型都是FILE*)

stdin  - 标准输入流 -  键盘

stdout - 标准输出流 - 屏幕

stderr - 标准错误流 - 屏幕

所以当我们把fput的形参指向标准输出流时,则会在屏幕上打印;

c语言学习(文件)_第6张图片

int fprintf(FILE*stream,const char* format[,argument].....);(....是表示可变参数的意思)

int printf(const char* format[,argument].....);和fprintf相比只是多了FILE*stream,所以用的方式差不多

同理:

int scanf(const char* format[,argument].....);

int fscanf(FILE*stream,const char* format[,argument].....);

struct bo
{
	char a[11];
	int c;
	float d;
};
int main()
{
	struct bo b = { "forget",11,7.77};
	//FILE* pf = fopen("Project111.c", "w");//写完之后读文件
	FILE* pf = fopen("Project111.c", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	fprintf(pf, "%s %d %f", b.a, b.c, b.d);
	//读
	fscanf(pf, "%s %d %f", b.a, &(b.c), &(b.d));
	//打印
	fprintf(stdout, "%s %d %f\n", b.a, b.c, b.d);
	//等价于:
	//printf( "%s %d %f", b.a, b.c, b.d);

	fclose(pf);
	pf = NULL;
	return 0;
}

二进制形式:

size_fwrite(const void*buffer,size_t  size,size_t  count,FILE* stream)

(将count个size大小的buffer写到流里去)

(buffer,写入对象的指针,size每个元素数据的类型大小(单位为字节),count最多要写入多少这样的元素)

这种形式写入时,我们一般除了字符形式(字符串以二进制形式或文本形式写进去都是一样的)在看的懂外其它可能都看不懂(看起来为乱码的形式),但是用fread同样可以很好的读出:

size_fread(const void*buffer,size_t  size,size_t  count,FILE* stream)

(从流(stream)里面读取count个大小为size的数据放到buffer)

struct bo
{
	char a[11];
	int c;
	float d;
};
int main()
{
	struct bo b = { "tomorrow",17,9.77};
	//FILE* pf = fopen("Project111.c", "w");//写完之后读文件
	FILE* pf = fopen("Project111.c", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	fwrite(&b, sizeof(struct bo),1,pf);
	//读
	fread(&b, sizeof(struct bo), 1, pf);
	//打印
	fprintf(stdout, "%s %d %f\n", b.a, b.c, b.d);
	//等价于:
	//printf( "%s %d %f", b.a, b.c, b.d);

	fclose(pf);
	pf = NULL;
	return 0;
}

看到这个博客觉得挺好的:【C 语言】文件操作 ( fwrite 函数 )_韩曙亮的博客-CSDN博客_fwrite

根据数据的组织形式,数据文件被称为文本文件或者二进制文件。

a、二进制文件:数据在内存中以二进制的形式存储,如果不加转换的输出到外存,

b、如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文 本文件。

int main()
{
	int a = 10000;//00000000 00000000 00100111 00010000
	FILE* pf = fopen("Project111.txt", "r");
	fwrite(&a, 4, 1, pf);//先用w写入后再读的这里我是
	fread(&a, 4, 1,pf);
	fprintf(stdout,"%d", a);

	fclose(pf);
	pf = NULL;
	return 0;

c语言学习(文件)_第7张图片

尽管二进制数不太可读,但是我们发现用fread可以很好的读取

 在一个数据在文件中的存储,字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。

如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而 二进制形式输出,则在磁盘上只占4个字节(VS2013测试)。


00110001 00110000 00110000 00110000 00110000
———————— ———————— ———————— ———————— ————————
    1       0         0       0         0     -在内存中的ASCII形式


00000000 00000000 00100111 00010000  -二进制
   00       00       27       10      -十六进制
  10 27 00 00  - 小端模式存储
 

 四、文件的随机读写:

1、fseek根据文件指针的位置和偏移量来定位文件指针。

int fseek ( FILE * stream, long int offset, int origin ); 

offset:偏移量,单位为字节,负数为往左,正数往右偏移

oringin:起始位置有三种请况选择:

SEEK_CUR:当前位置开始偏移

SEEK_END:文件末尾开始偏移

SEEK_SET:文件起始位置开始偏移

2、ftell

long int ftell ( FILE * stream );

返回文件指针相对于起始位置的偏移量

3、rewind

void rewind ( FILE * stream );

让文件指针的位置回到文件的起始位置

上述函数使用如下图所示:

int main()
{
	FILE* pf = fopen("Project111.txt", "r");//里面的内容是I am not happy now!
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读取文件
	int file = fgetc(pf);
	printf("%c\n", file);
	int n = ftell(pf);//查看偏移量
	printf("%d\n", n);//1,这时指针指向下一个字符

	rewind(pf);//让指针回到起始位置,所以下面又从起始开始打印
	while ((file = fgetc(pf)) != EOF)
	{
		printf("%c", file);
	}

	fseek(pf,-1, SEEK_CUR);//让指针指向当前位置开始算偏移量为-1的字符
	file = fgetc(pf);
	printf("\n%c", file);
	fclose(pf);
	pf = NULL;
	return 0;
}

c语言学习(文件)_第8张图片

补充:

scanf:针对标准输入(-stdin)的格式化的输入语句

fscanf:针对所有输入流的格式化的输入语句 -stdin\文件

sscanf:从一个字符串读取一个格式化的数据

int  sscanf (const char*bufferr,const char* format[,argument]....); 

printf:针对标准输出(-stdout)的格式化输出语句 

fprintf:针对所有输出流的格式化输出语句   -stdout\文件

sprintf:把一个格式化的数据,转换成字符串 

int  sprintf(char*bufferr,const char* format[,argument]....); 

struct bo
{
	char a[11];
	int c;
	float d;
};
int main()
{
	struct bo b = { "today",1,3.3};
	char buffer[99] = { 0 };//把字符串的信息写入buffer
	struct bo temp = { 0 };//将刚刚的字符串存放在这个结构体变量中
	sprintf(buffer, "%s %d %f", b.a, b.c, b.d);
	printf("%s\n",buffer);
	//从buffer字符串中还原出一个结构体变量
	sscanf(buffer, "%s %d %f", temp.a, &(temp.c), &(temp.d));
	printf("%s %d %f", temp.a, temp.c, temp.d);//虽然和上面的结果相同,但这里打印的格式是%s %d %f,而上面则是字符串形式
	return 0;
}

c语言学习(文件)_第9张图片

 

五、文件读取结束的判定

牢记:在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束

1、文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )

例如: fgetc 判断是否为 EOF . fgets 判断返回值是否为 NULL

 2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。

例如: 

fread函数在读取的时候,返回的是实际读取到的完整元素的个数,

如果发现读取到的完整的元素的个数小于指定的元素个数,那就是最后一次读取

在文本文件中即使函数返回EOF,也不能确定到底时因为文本读取结束,还是因为读取操作错误,

第一种请况:可用feof函数检测是否时读取结束而返回EOF

int feof(FILE*stream)

(站在光标所在位置,向后看看还有没有字符。如果有,返回0;如果没有,返回非0。它并不会读取相关信息,只是查看光标后是否还有内容。)

返回非零,说明时读取文本结束而结束,否则返回0

feof()原理和用法_konghouy的博客-CSDN博客_feof

第二种请况,可用ferror函数检测

int ferror(FILE*stream)

返回值为非零,表示有错误发生,否则返回0

int main()
{
	char p[] = "I am hungry!";
	int i = sizeof(p);
	int a = 0;
	FILE* pf = fopen("Project111.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	for(a=0;a

c语言学习(文件)_第10张图片

 

六、 文件缓冲区

a、 ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。

b、从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。

c、如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。

d、缓冲区的大小根 据C编译系统决定的。

c语言学习(文件)_第11张图片

 

因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文 件。 如果不做,可能导致读写文件的问题。

#include 
#include 
//VS2013 WIN10环境测试
int main()
{
 FILE*pf = fopen("test.txt", "w");
 fputs("abcdef", pf);//先将代码放在输出缓冲区
 printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");
 Sleep(10000);
 printf("刷新缓冲区\n");
 fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)
 //注:fflush 在高版本的VS上不能使用了
 printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");
 Sleep(10000);
 fclose(pf);
 //注:fclose在关闭文件的时候,也会刷新缓冲区
 pf = NULL;
 return 0;
}

你可能感兴趣的:(学习)