C语言文件操作
什么是文件
磁盘上的文件是文件。
但是在程序设计中,我们的文件一般有两种:程序文件、数据文件(从文件功能的角度分类的)。
程序文件
包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。
数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
之前了解的理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。
其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件。
文件名
一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件名包括3部分:文件路径+文件名主干+文件后缀
例如:c:\code\test.c
为了方便起见,文件标识常被称为文件名
文件的打开和关闭
文件指针
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名
字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统
声明的,取名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;
不同的C编译器的FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。
下面我们可以创建一个FILE*的指针变量:
FILE*pf//文件指针变量
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过文件信息区中的信息就能访问该文件。而就是说通过文件指针变量就可能够找到与他关联的文件。
比如:
文件的打开和关闭
文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。
在编写程序的时候,在打开文件的同时,都会返回一个FILE*指针变量指向该文件,也相当于建立的指针和文件的关系。
ANSIC规定使用fopen打开文件,使用fclose关闭文件
fopen和fclose
//打开文件
FILE*fopen(const char* filename, const char*mode);
//关闭文件
int fclose(FILE*stream);
文件的打开方式:
注意:打开方式中的“w”打开文件时会将原来文件中的内容覆盖掉。
举个例子:
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "w");
//文件操作
if (NULL == pf)
{
perror("fopen");
return 1;
}
else
{
printf("打开成功\n");
}
//读文件
//....
//关闭文件
fclose(pf);
pf = NULL;//置为空指针NULL,防止野指针
return 0;
}
文件的顺序读写
可能看了这个表有一些疑惑?为啥fgetc是读取而不是写入,而fputc是写入不是读取。
因为这些函数的命名是站在内存的角度取命名,而写入读取是站在其他角度来看的(比如文件)。详解看下图:
fgetc函数(读)
函数原型:
int fgetc(FILE* stream);
- 函数功能:从流中读取字符,即从stream所指定的文件中取得下一个字符。这里需要注意,在每取完一个字符时stream会自动向下移动一个字节。这样编成时,程序员就不用再stream控制了。这种功能在许多读写函数中都有体现。
- 返回值:返回所得到的字符;若读入错误,返回EOF。
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
//文件操作
if (NULL == pf)
{
perror("fopen");
return 1;
}
else
{
printf("打开成功\n");
}
//读文件
int i = 0;
char ch[30] = {0};
for (i = 0; i < 26; i++)
{
ch[i] = fgetc(pf);
}
printf("%s", ch);
//关闭文件
fclose(pf);
pf = NULL;//置为空指针NULL,防止野指针
return 0;
}
运行结果:
fputc(写)
函数原型:
int fputc(int character,FILE*stream);
- 函数功能:将字符character,写入到stream流中去
- 返回值:fputc 写入成功时返回写入的字符,失败时返回 EOF
例如:
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "w");
//文件操作
if (NULL == pf)
{
perror("fopen");
return 1;
}
else
{
printf("打开成功\n");
}
//写文件
char ch = 0;
for (ch ='a'; ch <= 'z'; ch++)
{
fputc(ch, pf);//ch为要写入的字符,pf为要写入的文件指针
}
//关闭文件
fclose(pf);
pf = NULL;//置为空指针NULL,防止野指针
return 0;
}
接下来我们打开test.txt文件查看:
fgets(读)
函数原型:
char * fgets ( char * str, int num, FILE * stream );
- 函数功能:从streaml流中最多读取num个字符放到str所指向的地址处
返回值:读取成功返回str的指针,读取失败返回NULL
fgets函数会从stream中读取字符,直到遇到下列三种情况之一会停止下来:
读取了n-1个字符(最多读取n个字符但是函数fgets会在末尾添加一个终止符’\0’,所以相当于读取了n-1个字符);
读取到了换行符’\n’;
读取到了文件结束符EOF。
如果读取成功,则返回一个指向str的指针,否则返回NULL。另外,fgets会将读取到的字符串缓存到str中,并自动在末尾添加一个终止符’\0’。
例子:
#include<stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "r");
if (NULL == pf)
{
perror("fopen");
return 1;
}
char ch[100] = {0};
fgets(ch,100, pf);
printf(ch);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fputs(写)
函数原型:
int fputs(const char*str,FILE*stream);
- 函数功能:将字符串str的内存写入到stream流中去
- 返回值:成功时,将返回非负值。
出错时,该函数返回 EOF 并设置错误指示器(ferror)。
例如:
#include<stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "w");
if (NULL == pf)
{
perror("fopen");
return 1;
}
//写文件
fputs("hello world!\n", pf);
char ch[20] = { "你好!!\n" };
fputs(ch, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
运行后test.txt文件夹:
C语言的三种标准流
C语言程序运行的时候会默认打开三个流;
stdin - 标准输入(键盘) 类型FILE*
stdout - 标准输出(屏幕) 类型FILE*
stderr - 标准错误(屏幕) 类型FILE*
这三个流和前面描述的文件流是一样的。
直接上代码使用方法例子1:
#include<stdio.h>
int main()
{
int ch = fgetc(stdin);//从键盘流中读取到ch
fputc(ch,stdout);//将ch中的数据写到屏幕上去
return 0;
}
例子2:
#include<stdio.h>
struct S
{
char name[20];
int age;
float score;
};
int main()
{
struct S s = { 0 };
fscanf(stdin, "%s %d %f", s.name, &(s.age), &(s.score));//从键盘读取
fprintf(stdout, "%s %d %f\n", s.name, s.age, s.score);//输出到屏幕上
}
运行结果:
例2中的fprintf,fscanf的用法其实和printf,scanf的用法一样
fscanf(读)与 fprintf(写)
fscanf函数原型:
int fscanf(FILE* stream,const char*format,...);
scanf函数原型:
int scanf(const char*format,...);
fprintf函数原型:
int fprintf ( FILE * stream, const char * format, ... );
printf函数原型:
int printf ( const char * format, ... );
所以fprintf和fscanf的用法我们都比较好理解了,就是多了一个FILE*类型的流,他们的功能就是:
fpritnf:将格式化数据写入流stream
fscanf:从流stream中读取格式化数据
直接上代码:
#include <stdio.h>
int main()
{
char str[80];
float f;
//打开文件方式读和写
FILE* pf = fopen("myfile.txt", "w+");
if (NULL == pf)
{
perror("fopen");
return 1;
}
//写文件
fprintf(pf, "%f %s", 3.1416, "PI");
rewind(pf);//文件流pf关联的位置指示器设置为文件的开头。
//读文件
fscanf(pf, "%f %s", &f,str);
//关闭文件
fclose(pf);
pf = NULL;
//打印
printf("I have read: %f and %s \n", f, str);
return 0;
}
运行截图:
fwrite(二进制写入文件)
函数原型:
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
函数功能:
从ptr所指向的内存中向stream中写入count个size大小的内存
返回值:
返回成功写入的元素总数。
如果此数字与 count 参数不同,则写入错误阻止函数完成。在这种情况下,将为流设置错误指示器(ferror)。
如果大小或计数为零,则该函数返回零,错误指示器保持不变。
size_t 是无符号整数类型。
直接上代码演示:
struct S
{
char name[20];
int age;
float score;
};
#include<stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "wb");//以二进制形式打开文件
if (NULL == pf)
{
perror("fopen");
return;
}
struct S s = { "张三",23,95 };
//以二进制形式写文件
fwrite(&s, sizeof(struct S), 1, pf);//从s所指向的内存读取数据写入到文件pf中去
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
文件夹中的内容:
证明已经将数据以二进制的形式写进去了,接下来我们就来研究用二进制读取
fread(二进制读取文件)
函数原型:
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
函数功能:
从stream流中读取count个size大小的空间放入到ptr所指向的内存处
返回值:
返回成功读取的元素总数。
如果此数字与 count 参数不同,则表示读取时发生读取错误或到达文件末尾。在这两种情况下,都会设置正确的指标,可以分别用 ferror 和 feof 进行检查。
如果大小或计数为零,则该函数返回零,并且流状态和 ptr 指向的内容保持不变。
size_t 是无符号整数类型。
直接上代码:
struct S
{
char name[20];
int age;
float score;
};
#include<stdio.h>
int main()
{
FILE* pf = fopen("test.txt", "rb");//以二进制形式打开文件
if (NULL == pf)
{
perror("fopen");
return;
}
struct S temp = { 0 };
//以二进制形式写文件
fread(&temp, sizeof(struct S), 1, pf);//从文件pf中读取数据放到temp指针所指向的内存
printf("%s %d %f\n", temp.name, temp.age, temp.score);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
运行结果: