文件是我们使用电脑时接触最多的存储信息的东西,那么我们在学习C语言时为什么要用到文件呢?文件有什么用呢?我们怎么使用代码对文件进行各种操作呢?接下来,我会用最通俗的语言告诉你,一起来思考吧~!
一、文件
(一)、文件的概述
数据存入文件可以保存的更久,可以持久的保存我们需要的数据。文件有两种:程序文件和数据文件。
1.程序文件:程序文件指:源文件(.c)以及程序在编译过程产生的文件。
2.数据文件:提供数据、保存数据。⽐如程序运⾏需要从中读取数据的⽂件,或者输出内容的⽂件。在这里我们主要讨论的是数据文件。
(二)、二进制文件和文本文件
二进制文件是指内存二进制的数据不加转换直接存入文件中。(乱码)
文本文件是指存储的是ASCII码。
举例:
二、文件的打开和关闭
我们对文件的操作如下:
1.流和标准流
(1).流
我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输⼊输出
操作各不相同,为了⽅便程序员对各种设备进⾏⽅便的操作,我们抽象出了流的概念,我们可以把流
想象成流淌着字符的河。(个人理解:把数据写到外部设备上,把外部设备的数据获取到内存里)
如果从定义来构思应该是这样的,如果没有流的话,我们可能需要很多流程,但是有了流存在中间的操作就省去了,我们直接把数据写/读给流,也就是简化了流程。
(2).标准流
我们还有一个疑问:每次我们编程时为什么没有打开流呢?而是直接运行?那是因为我们C语言程序在启动时就默认打开三个流:
- stdin:标准输入流,在大多数的环境中从键盘中输入。
- stdiout标准被初流,大多数的环境中输出到显示器界面。
- stderr标准错误流,大多数韩静中输出到显示器界面。
默认打开这三个流,我们就可以直接使用我们的“printf”、"scanf"等函数了。
(二)、文件指针
c语言用FILE*文件指针类型的文件来维护各种操作的,所以文件指针对于我们来说及其重要。
使用文件的时候,我们要先打开文件—>内存中创建了一个和文件相关的文件信息区。这个文件信息区保存了很多类型的信息,并且保存在一个被命名问FILE的结构体中。
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
上述代码是在VS2013中stdio.h头文件中观看的,可以很清晰的看到,有各种类型的信息,被封装在FILE的结构体中。
--------------------------我是一条普通的分割线QWQ----------------------------------------------------------------------------------
我们一般用FILE的指针类型来维护这个FILE结构的变量,所以当我们定义一个FILE*类型的指针时,通过⽂件指针变量能够间接找到与它关联的⽂件。
(三)、fopen函数
我们要想文件里面读取内容或者写入内容时就需要先打开文件,才能进行操作,那么我们就要从打开文件开始学习。
fopen()函数的功能是打开文件。
我们可以看这个函数的参数类型有两个:一个是文件的名字,另一个是打开方式,和参数返回值是一个文件指针类型。
那么打开方式有哪几种呢?在这里我列一些常用。
扩展:有时候我们可能很难区分读和写的含义,读是指从硬盘(文件)输入/读到内存中,写是指从内存中输出/写到硬盘(文件)上。
那么现在我们就用几个实例来带大家试试fopen()函数吧。
如果我们要先用“只读”®的形式打开一个文件,当我没有在当前路径下创建是,我们可以观察是否会返回空指针,我们可以用assert或者if语句进行判断,这里我们用了assert。注:包含头文件(#include<assert.h>)
#include<assert.h>
int main()
{
FILE* p = fopen("data.txt", "r");
assert(p);
return 0;
}
当我使用fopen的"r"时,程序并没有在我当前文件夹中发现名叫data.txt的文件,所以直接报警,也就是说,打开失败返回了NULL。那么我们如果现在时用"w"时,他会不会自动创建呢?
int main()
{
FILE* p = fopen("data.txt", "w");
if (p == NULL)
{
perror("fopen");
return 1;
}
return 0;
}
最后发现我们创建成功了,所以我们也就验证了"r"和"w"的区别,如果你想在某个路径下建立,将路径写的更完整。
(四)、fclose函数
有打开肯定就会有关闭,所以fopen要和fclose函数配合使用,**当我们不用指针的时候及时关闭文件指针所指向的内存,并且置空。**即:
这样可以是代码更加健壮,也不会出现后续的问题,避免了野指针的存在!!
三、文件的顺序读写
当我们打开某个文件时,我们希望将某个内容或数据从内存输出(写)到文件中保存,或者将文件保存的数据输入(读)到内存里提供我们使用。所以我们就需要一个顺序读写的函数帮助我们去获得或储存数据。
(1)、fputc函数
通过图中我们可以看出fputc函数的参数类型和返回值,他会返回一个整型,并且一次只能写一个字符。
int main()
{
FILE* p = fopen("data.txt", "w");
if (p==NULL)
{
perror("fopen");
return 1;
}
fputc('A', p);
fclose(p);
p = NULL;
return 0;
}
最后会发现我们写入成功,注意只能一次写一个字符!!!!
(2)、fgetc函数
fgetc函数的参数类型是文件指针类型,返回值是一个整型(字符的ASCII码值),也就是从文件里输入到内存中,如果读取失败则会返回EOF(-1)。
int main()
{
FILE* p = fopen("data.txt", "r");
if (p==NULL)
{
perror("fopen");
return 1;
}
int ch=fgetc( p);
printf("%c", ch);
fclose(p);
p = NULL;
return 0;
}
扩展:读取失败有两种情况:1.遇到了文件末尾读取失败 2.遇到错误会读取失败。 无论是遇到文件末尾还是遇见错误,都会返回EOF(-1)。
(3)、fputs函数
如果我们要输出一句话/一串字符串时,可以用fputs他可以将字符串输出到文件里。
这个函数的返回类型是一个整型,参数类型有两个:一个是指向字符串的指针,一个是文件指针类型。
int main()
{
FILE* p = fopen("data.txt", "w");
if (p == NULL)
{
perror("fopen");
return 1;
}
fputs("hellow world!!",p);
fclose(p);
p = NULL;
return 0;
}
从上述例子看出,我们确实将一句话/一段字符串输出到了文件中。
(4)、fgets函数
从这个函数的返回类型和参数类型来看,比较复杂,可以通过图来理解。返回了str的地址,注:其实并没有读取num个,而是num-1个,因为他会自动补充\0。
int main()
{
int arr[20] = { 0 };
FILE* p = fopen("data.txt", "r");
if (p == NULL)
{
perror("fopen");
return 1;
}
int *ptr=fgets(arr, 6,p);
printf("%s", ptr);
fclose(p);
p = NULL;
return 0;
}
如果我要取出num个,实际上只拿出了num-1个字符,这就是fgets的特殊性,他会自动补充\0。
注:一定要把接收数据的存储空间(str)设置大,否则会出现栈溢出错误。
(5)、fwrite函数
fwrite表示以二进制从内存中输出到文件中,即:乱码
int main()
{
int arr[] = {1,2,3,4,5};
FILE* p = fopen("data.txt", "wb");
if (p == NULL)
{
perror("fopen");
return 1;
}
fwrite(arr, sizeof(arr[0]), 3, p);
fclose(p);
p = NULL;
return 0;
}
(5)、fread函数
fread与fwrite的函数相似,只是第一个参数不同。
int main()
{
int arr[20] = {0};
FILE* p = fopen("data.txt", "rb");
if (p == NULL)
{
perror("fopen");
return 1;
}
fread(arr, sizeof(arr[0]), 2, p);
for (int i = 0; i < 2; i++)
{
printf("%d", arr[i]);
}
fclose(p);
p = NULL;
return 0;
}
会发现我们刚开始将123存入后,通过fread函数读出了二进制文件的内容。
四、文件的随机读写
刚刚我们讲了顺序读写,是为了让数据按照顺序来进行读数据和写数据,那么如果我们想要从某个段数据中间开始读写呢?这个时候就需要随机读写的存在了。(随机读写要配合顺序读写使用)
(1)、fseek函数
在你使用fseek函数时,必须要了解文件的内容,再能惊醒随机读写,因为通过函数的参数我们可以发现,他需要我们提供偏移量,也就是要从哪里开始读取,而第三个参数取值有特定的模式(三个)。如
int main()
{
int arr[20] = { 0 };
FILE* p = fopen("data.txt", "r");
if (p == NULL)
{
perror("fopen");
return 1;
}
fseek(p, 1, SEEK_SET);
int ch = fgetc(p);
printf("%c", ch);
fclose(p);
p = NULL;
return 0;
}
注意如果我要从文档结尾插入的话,一定要是负数。
如图:
(2)、ftell函数
**ftell函数的作用是告诉当前文件指针距离开始的偏移量多少。**ftell的函数和返回值也很容易理解,这里就不和大家过多赘述了,如图:
(2)、rewind函数
rewind函数是让文件指针回到其实的位置。如图所示:
五、文件读取结束的判定
(一)、被错误使用的feof函数
feof函数的作用当文件读取结束时,判断是读取结束的原因是否遇到文件结尾!
牢记:在文件读取过程中,不能用feof函数的返回值直接来判断文件的是否结束。
判断方式:
①文本文件:判断EOF(fgetc)/NULL(fgets)、
②二进制文件:判断返回值是否小于实际要读的格式,即最后一次读取。
如果ferror函数表示是遇到错误后结束!
int main()
{
FILE* p = fopen("data.txt", "r");
if (p == NULL)
{
perror("fopen");
return 1;
}
int ch = 0;
while ((ch = fgetc(p)) != EOF)
{
putchar(ch);
}
if (feof(p))
{
printf("\n遇到结尾正常结束");
}
else if (ferror(p))
{
printf("\n遇到错误结束");
}
fclose(p);
p = NULL;
return 0;
}
证明本代码是正常遇到结尾结束的。
以上就是我对文件和文件操作的个人理解和知识点整理,欢迎大家观看以及指出错误,谢谢~!