前言
在c编译器中,我们操作的数据都是存放在内存中的,有没有什么办法可以让我们操作电脑硬盘的数据呢?答案是可以的。
在C语言中,有一种特殊的指针,文件指针,它可以满足我们的需求,让我们可以通过内存来对电脑硬盘中的文件进行操作。
其实,当我们在编译器中使用某个电脑硬盘中的文件时,编译器会在内存中自动创建一个与文件信息相对应的文件信息区,我们可以用一个文件指针来指向文件信息区,通过操作文件信息区来间接的操作电脑硬盘里的文件
一、简单的文件操作小知识
1.创建文件指针变量
语法:FILE* 变量名;
大写的FILE*就代表文件指针类型,我们只要知道它依然是一个指针就可以了,变量名可以根据自己的使用习惯来命名。
2.文件的打开
打开文件:FILE* fopen(const char* filename,const cahr* mode);
参数解释:两个参数都可以看作是常量字符串,不可以被修改,第一个参数表示文件名,第二个参数表示的是打开文件的方式,返回类型为文件指针
那么什么是打开文件的方式呢?什么是文件名呢?
我们先介绍两种简单的方式,只读和只写
也就是说我们的mode参数,如果是”r",那么就是以只读的方式打开一个已经存在的文件,如果文件不存在(也就是第一个参数,文件名不存在),就会出错
那么什么是只读、只写?什么是输入、输出呢?
我们可以这样理解,内存数据和硬盘数据(文件在硬盘中)是两个独立的个体,只读的意思就是我们想要在内存中读取文件中的数据,这时候硬盘会向内存输入数据到内存中,这样我们才可以在内存中得到硬盘中的数据。而只写,是我们想要通过内存对硬盘中的数据进行写入,这时候内存会输出信息到硬盘,这数据才会被写入硬盘。
可以看到,只读和只写针对的都是硬盘数据(也就是文件),而输入输出也针对示是文件
文件名:一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件名包含3部分:文件路径+文件名主干+文件后缀 例如: c:\code\test.txt 为了方便起见,文件标识常被称为文件名。
加上文件路径可以在对应路径下操作文件,如果不加,则只会在当前项目的路径下操作文件,文件后缀是默认打开文件的方式
到这里我们可以明白,文件打开函数的两个参数都是常量字符串,第一个参数表示文件名,第二个参数表示打开文件的方式,返回类型是文件指针,我们可以用一个文件指针变量来指向打开文件时编译器自动产生的文件信息区,通过改变内存中的文件信息区的数据来改变硬盘中文件的数据
3.文件的关闭
函数:int fclose(FILE* stream)
参数解释,一个文件指针类型的变量,返回值为int,表示的是ASCIL码值
只要有文件的打开,就必须有文件的关闭以及对指向该文件的文件信息区的文件指针的置空操作
例如:
void test()
{
FILE* pf = fopen("text", "r");
//以只读的方式打开文件名为text的文件,但该文件名是瞎写的,它并不存在
//在只读方式下会出错
if (pf == NULL)
{
perror("fopen");
return ;
}
fclose(pf);//有打开文件的操作,必须有关闭文件的操作
pf = NULL;//也必须有对文件指针的置空操作
}
二、文件读写函数
1.fputc,fgetc
函数原型:int fputc(int c,FILE* stream);
f代表文件,put代表输出,c代表一个字符
c虽然是int类型,可实际上它表示的是ASCIL码值对应的内容,stream文件指针,返回类型也表示的是ASCIL码值对应的内容
函数功能:通过内存向文件输入一个字符
举例:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
void test()
{
FILE* pf = fopen("text.txt", "w");
//以只写的方式打开一个文件名为text,默认打开方式为txt的文件,用文件指针指向
//该文件产生的文件信息区,因为没有给具体的路径并且文件并不存在,
//所以会在当前项目下产生该文件
if (pf == NULL)//打开失败报错
{
perror("fopen");
return ;
}
else
{
for (int i = 0; i < 26; i++)
//利用循环和输出函数,在文件内写入26个字母
{
fputc('a' + i, pf);
}
fclose(pf);//关闭文件
pf = NULL;//置空
}
}
int main()
{
test();
return 0;
}
函数未编译项目路径下的内容如下:
编译后:
在项目路径下确实出现了text.txt文件,并且其中确实写入了26个字母
fgetc函数原型: int fgetc(FILE* stream);
f代表文件,get代表输入,c代表一个字符
参数解释,参数类型为文件指针,返回类型int表示对应的ASCIL码值
函数功能:在内存中读取硬盘文件的一个字符
我们利用fputc函数在文件text.txt写入了26个字母,现在我们用fgetc把他们读取出来
void test1()
{
FILE* pf = fopen("text.txt", "r");
//以只读的方式打开刚才创建好的文件并执行所对应文件信息区的位置
if (pf == NULL)
{
perror("fopen");
return ;
}
else
{
for (int i = 0; i < 26; i++)
{
int ch=fgetc(pf);
printf("%c ", ch);
}
fclose(pf);
pf = NULL;
}
}
int main()
{
test1();
return 0;
}
显然读取成功,需要留意的是fgetc每读取到文件中的一个字符后,文件指针会指向下一个字符,直到读取完所有内容
2.fputs,fgets
fputs函数原型:int fputs(cosnt cahr* string, FILE* stream)
参数解释:将字符串string写入到stream所指向的文件当中去,返回类型表示对应ASCIL码值表示的内容
函数功能:在文件中写入一行数据(字符串),字符串末尾如果不添加\n,那么第二次调用时写入的内容会在第一行的后面,添加\n可实现换行
举例:
void test2()
{
FILE* pf = fopen("text.txt", "w");
if (pf == NULL)
{
perror("fopen");
return;
}
else
{
fputs("hello bit\n", pf);
fputs("hello world\n", pf);
/*char arr[30] = { 0 };
fgets(arr,10, pf);
printf("%s\n",arr);*/
fclose(pf);
pf = NULL;
}
}
int main(0
{
test2();
return 0;
}
结果如下:
因为添加了换行符,所以在文件内写入了两行内容
fgets函数原型:char* fgets(char string,int n, FILE*stream);
参数解释:从stream所指向的文件中读取n个字符放到string中,返回string的地址
举例:读取将刚才写入的两行内容
void test1()
{
FILE* pf = fopen("text.txt", "r");
if (pf == NULL)
{
perror("fopen");
return ;
}
else
{
for (int i = 0; i < 26; i++)
{
int ch=fgetc(pf);
printf("%c", ch);
}
fclose(pf);
pf = NULL;
}
}
int main()
{
test1();
return 0;
}
结果如下:
3.fprintf,fsacnf
fprintf函数原型:
int fprintf( FILE *stream,const char *format [, argument ]...);
printf函数原型:int printf( const char *format[, argument]... );
参数解释:相比printf函数的参数,fprintf函数只是多了一个指向文件的函数指针,而fprintf之所以被称为格式化输出函数,是因为在对结构体类型的数据写入时,结构体成员互不相同,有自己的输出格式,面对这种情况,就得使用fprintf函数,
例如:
struct S//创建结构体类型
{
char name[20];
int age;
float hight;
};
int main()
{
struct S s = { "zhangsan",18,190.0 };
//创建结构体变量并且初始化
FILE* pf = fopen("text.txt", "w");
//以只写的方式打开文件
if (pf == NULL)//打开失败报错
{
perror("fopen");
return 1;
}
fprintf(pf, "%s %d %f\n", s.name, (s.age), (s.hight));
将结构体变量的内容写入文件text.txt中
}
写入成功
fscanf函数原型:
int fscanf( FILE *stream, const char *format [, argument ]... );
scanf函数原型:int scanf( const char *format [,argument]... );
参数解释:相比scanf函数的参数,fscanf函数只是多了一个指向文件的函数指针,而fscanf之所以被称为格式化输入函数,是因为在对结构体类型的数据读取时,结构体成员互不相同,有自己的输出格式,面对这种情况,就得使用fscanf函数
例如:我们将刚才写入文件的结构体读取出来
struct S
{
char name[20];
int age;
float hight;
};
int main()
{
struct S s = { "zhangsan",18,190.0 };
FILE* pf = fopen("text.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fscanf(pf,"%s%d%f", s.name, &(s.age), &(s.hight));//读文件
printf("%s %d %f\n", s.name, (s.age), (s.hight));
//读取成功后输出在屏幕上
/*fprintf(pf, "%s %d %f\n", s.name, (s.age), (s.hight));*/
return 0;
}
4.fwrite,fread
fwrite函数原型:
size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );
函数解释:将指针buffer指向的数据以二进制的形式,写入到stream所指向的文件中去,每个数据的大小为size,一共写入count个数据,返回值是实际写入的数据的个数
例如:将结构体s的信息以二进制形式写入text.txt中
struct S
{
char name[20];
int age;
float hight;
};
int main()
{
struct S s = { "zhangsan",18,190.0 };
FILE* pf = fopen("text.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fwrite(&s, sizeof(s), 1, pf);
}
zhangsan后的不认识的字符就是其他数据以二级制在文本中的存储,而zhangsan之所以没有变化,是因为它以二进制的形式存储和以文本的形式存储是一样的
fread函数原型:
size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
函数解释:和fwrite完全相同的使用方法,功能是读取文件中存储的二进制数据,返回值是实际读取到的数据的个数
例如:读取刚才写入文件的二进制数据
struct S
{
char name[20];
int age;
float hight;
};
int main()
{
struct S s = { "zhangsan",18,190.0 };
FILE* pf = fopen("text.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fread(&s, sizeof(s), 1, pf);
//读取文件中的二进制数据
printf("%s %d %f\n", s.name, (s.age), (s.hight));
//读取后打印在屏幕上
}
三、小结
我们简单讲了C语言的文件指针,以及如何通过文件指针对文件进行读写操作,还有一些文件读写的函数,文件的打开和关闭。下一讲将更深入的去学习C语言里的文件操作。