C语言:文件IO
文件操作
概述
-
什么是文件
文件是保存在外存储器(一般代指磁盘,U盘,移动硬盘等)的数据的集合
-
文件操作体现在哪几个方面
- 文件内容的读取
- 文件内容的写入
数据的读取和写入可被视为针对文件进行输入(Input)和输出(Output)操作,此时数据像水流一样从外存储器去流向内存,或者从内存流向外存储器,所以系统形象的成文件操作为文件流。
C语言程序对文件的操作采用“文件缓冲机制”。就是说在程序对文件的数据读写并不是直接操作文件中的数据,而是系统会为文件在内存中创建“文件缓冲区”,程序对文件的 操作,其实在缓冲区进行的。
-
文件的分类
根据数据的存储方式划分:
①文本文件(ASCII文件)
②二进制文件
-
文件的标识
①文件系统中:路径+文件名,举例:
d:/aaa/bbb.txt
,/home/yq/bbb.txt
②C语言程序中:文件指针(文件类型指针),语法:
FILE* 指针变量名;
-
文件操作的步骤:
①打开文件
②文件处理(读写文件)
③关闭文件
文件操作
文件的打开与关闭
打开文件
打开文件,让系统为文件创建文件缓冲区
- 函数名:fopen()
- 头文件:
#include <stdio.h>
- 函数原型:
FILE* fopen(const char* path,const char* mode);
- 函数功能:打开文件,并为文件创建缓冲区
- 函数参数:
- path:目标文件的路径
- mode:文件打开的方式(读-r,写-w,读写-rw)
- 返回值:
- 成功:返回文件指针FILE*(缓冲区首地址)
- 失败:返回NULL
关闭文件
文件关闭,文件使用完毕,一定要记得释放内存
- 函数名:fclose
- 头文件:
#include <stdio.h>
- 函数原型:
int fclose(FILE* fp);
- 函数功能:
- fp:已经打开的文件指针
- 返回值:
- 成功:返回0
- 失败:返回EOF(-1)
文件打开与关闭的案例
// 文件操作:文件的打开与关闭
#include <stdio.h>
int main()
{
// 在命令行执行./a.out的时候,传递一个需要打开的目录文件的地址
if(argc < 2)
{
printf("输入有误,请按照<%s 文件路径>格式输入\n",argv[0]);// ./a.out ./demo01.c
return -1;
}
// 根据提供的文件路径,打开文件
FILE* fp = fopen(argv[1],"r");
// 校验文件是否读取成功
if(!fp)
{
perror("文件打开失败!\n");
return -1;
}
puts("文件打开成功!");
// 关闭打开的文件
int ret = fclose(fp);
if(ret == -1)
{
perror("文件关闭失败!");
return -1;
}
puts("文件关闭成功!");
return 0;
}
文件的顺序读写
单字符读取
- 函数名:fgetc
- 头文件:
#include <stdio.h>
- 函数原型:
int fgetc(FILE* fp);
- 函数功能:从fp代表的文件中获取一个字符
- 函数参数:
- fp:我们需要操作的文件的指针
- 返回值:
- 成功:返回读取到的字符
- 失败:或者文件末尾,返回EOF(-1)
方式1(ASCII码):
#include <stdio.h>
int main(int argc, char *argv[])
{
// 在命令行执行./a.out的时候,传递一个需要打开的目录文件的地址
if (argc < 2)
{
printf("输入有误,请按照<%s 文件路径>格式输入\n", argv[0]); // ./a.out ./demo01.c
return -1;
}
// 根据提供的文件路径,打开文件(mode:r,w,rw)
FILE *fp = fopen(argv[1], "r");
// 校验文件是否读取成功
if (!fp)
{
perror("文件打开失败!\n");
return -1;
}
puts("文件打开成功!");
// 单字符读取文件
int re = 0;
// 循环读取文件中所有字符
while ((re = fgetc(fp)) != -1)
printf("%c", re);
// 关闭打开的文件
int ret = fclose(fp);
if (ret == -1)
{
perror("文件关闭失败!");
return -1;
}
puts("文件关闭成功!");
return 0;
}
方式2(ASCII值):
#include <stdio.h>
int main(int argc, char *argv[])
{
// 在命令行执行./a.out的时候,传递一个需要打开的目录文件的地址
if (argc < 2)
{
printf("输入有误,请按照<%s 文件路径>格式输入\n", argv[0]); // ./a.out ./demo01.c
return -1;
}
// 根据提供的文件路径,打开文件(mode:r,w,rw)
FILE *fp = fopen(argv[1], "r");
// 校验文件是否读取成功
if (!fp)
{
perror("文件打开失败!\n");
return -1;
}
puts("文件打开成功!");
// 单字符读取文件
char ch;
// 循环读取文件中所有字符
while ((ch = fgetc(fp)) != EOF)
printf("%c", ch);
// 关闭打开的文件
int ret = fclose(fp);
if (ret == -1)
{
perror("文件关闭失败!");
return -1;
}
puts("文件关闭成功!");
return 0;
}
多字符读取
-
函数名:fegets()
-
头文件:
#include <stdio.h>
-
函数原型:
char *fgets(char *buf,int size,FILE* fp);
-
函数功能:从fp代表的文件中获取size个字符(size大小以字节为单位),放置在buf代表的内存中
-
函数参数:
- buf:内存空间首地址用于存放读取的字节
- size:待读取的字符,实际读取size
- fp:已经打开文件的指针
-
返回值:
- 成功:返回buf
- 失败:或者文件末尾,返回NULL
-
案例:
#include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { // 在命令行执行./a.out的时候,传递一个需要打开的目录文件的地址 if (argc < 2) { printf("输入有误,请按照<%s 文件路径>格式输入\n", argv[0]); // ./a.out ./demo01.c return -1; } // 根据提供的文件路径,打开文件(mode:r,w,rw) FILE *fp = fopen(argv[1], "r"); // 校验文件是否读取成功 if (!fp) { perror("文件打开失败!\n"); return -1; } puts("文件打开成功!"); // 创建一个缓冲区(也就是每次读取字节的大小) char buf[64] = {0}; // 循环读取文件中所有字符 while (fgets(buf, 64, fp) != NULL) { printf("%s", buf); // 每读取一次,都需要清空缓冲区 memset(buf, 0, sizeof(buf)); } // 关闭打开的文件 int ret = fclose(fp); if (ret == -1) { perror("文件关闭失败!"); return -1; } puts("文件关闭成功!"); return 0; }
单字符写入
-
函数名:fputc
-
头文件:
#include <stdio.h>
-
函数原型:
int fputc(int c,FILE* fp);
-
函数功能:向fp指向的文件中写入一个字符c
-
函数参数:
- c:待写入的字符
- fp:已打开的文件指针
-
返回值:
- 成功:返回字符c
- 失败:返回EOF(-1)
-
案例:
#include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { // 在命令行执行./a.out的时候,传递一个需要打开的目录文件的地址 if (argc < 3) { printf("输入有误,请按照<%s 文件路径 文本数据>格式输入\n", argv[0]); // ./a.out ./ demo01.c hello return -1; } // 根据提供的文件路径,打开文件(mode:r,w,rw) FILE *fp = fopen(argv[1], "w"); // 校验文件是否读取成功 if (!fp) { perror("文件打开失败!\n"); return -1; } puts("文件打开成功!"); // 单字符写入 // 借助循环,一个字符一个字符写入 while (*argv[2] != '\0') { fputc(*argv[2], fp); // argv[2]++; // 指针偏移 } // 关闭打开的文件 int ret = fclose(fp); if (ret == -1) { perror("文件关闭失败!"); return -1; } puts("文件关闭成功!"); return 0; }
多字符的写入
-
函数名:fputs
-
头文件:
#include <stdio.h>
-
函数原型:
int fputs(const char* buf,FILE* fp);
-
函数功能:向fp指向的文件中写入一个字符数组buf
-
函数参数:
- buf:待写入的字符数组
- fp:已打开的文件指针
-
返回值:
- 成功:返回字符buf
- 失败:返回EOF(-1)
-
案例:
/** * 多字符写入 */ #include <stdio.h> int main(int argc, char **argv) { if (argc < 3) { printf("输入有误,请按照<%s 文件路径 文本数据>格式输入\n", argv[0]); return -1; } FILE *fp = fopen(argv[1], "w"); if (!fp) { perror("文件打开失败!"); return -1; } // 单字符写入 // ./a.out file1.txt I_Love_Your fputs(argv[2], fp); fclose(fp); return 0; }
综合案例:文件拷贝
实现从a文件读取内容,写入到b文件
代码:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
// 在命令行执行./a.out的时候,传递一个需要打开的目录文件的地址
if (argc < 2)
{
printf("输入有误,请按照<%s 被读文件路径 被写文件路径>格式输入\n", argv[0]); // ./a.out
./ demo01.c return -1;
}
// 根据提供的文件路径,打开文件(mode:r,w,rw)
FILE *fp_r = fopen(argv[1], "r");
FILE *fp_w = fopen(argv[2], "w");
// 校验文件是否读取成功
if (!fp_r || !fp_w)
{
perror("文件打开失败!\n");
return -1;
}
puts("文件打开成功!");
// 创建一个缓冲区(也就是每次读取字节的大小)
char buf[64] = {0};
// 循环读取文件中所有字符
while (fgets(buf, 64, fp_r) != NULL)
{
// 写入文件
fputs(buf, fp_w);
// 每读取一次,都需要清空缓冲区
memset(buf, 0, sizeof(buf));
}
// 关闭打开的文件
int ret1 = fclose(fp_r);
int ret2 = fclose(fp_w);
if (ret1 == -1 || ret2 == -1)
{
perror("文件关闭失败!");
return -1;
}
puts("文件关闭成功!");
return 0;
}
判别文件结束
- 函数名:feof(fp)
- 头文件:
#inlcude <stdio.h>
- 函数原型:
int feof(FILE* fp);
- 函数功能:在读fp指向的文件时判断是否遇到文件结束
- 函数参数:
- fp:已打开文件的指针
- 返回值:
- 文件未读取完毕:返回0
- 文件已读取完毕:返回非0
数据块的读写(二进制)
数据块的读取
-
函数名:fread
-
函数原型:
size_t fread(void* ptr,size_t size,size_t count);
-
函数功能:从fp代表的文件中以字节为单位(一个数据块)读取count个数据块存放在内存中
-
函数参数:
- ptr:内存空间首地址,用于存放读取到的数据(任意类型缓冲数据)
- size:数据块大小,以字节为单位
- count:待读取的数据块的个数
- fp:已打开的文件值
-
返回值:
- 成功:返回实际读取的字节数大小
- 失败:读取完毕,返回 < 0
-
案例:
#include <stdio.h> #define SIZE 4 // 存放学生的个数 // 创建学生结构体 struct Student { char name[20]; int num; int age; char addr[50]; } stus[SIZE]; int main(int argc, char *argv[]) { int i; FILE *fp; if ((fp = fopen("stu-list", "rb")) == NULL) // rb 以二进制读取 { perror("文件打开失败!"); return -1; } // 循环读取二进制读取 for (i = 0; i < SIZE; i++) { fread(&stus[i], sizeof(struct Student), 1, fp); // 将读取的数据输出到控制台 printf("%-10s%-4d%-4d%-20s\n", stus[i].name, stus[i].num, stus[i].age, stus[i].addr); } // 关闭文件 fclose(fp); return 0; }
数据块的写入
-
函数名:fwrite
-
函数原型:
size_t fwrite(const void* ptr,size_t size,size_t count,FILE* fp);
-
函数功能:向fp代表的文件中以size为一个数据块,写入count个数据块到fp
-
函数参数:
- ptr:内存空间首地址,用于存放读取到的数据(任意类型缓冲数据)
- size:数据块大小,以字节为单位
- count:待读取的数据块的个数
- fp:已打开的文件值
-
返回值:
- 成功:返回实际写入的字节数
- 失败:写入完毕,返回 < 0
-
案例:
// 数据块的写入:从键盘输入4个学生的有关数据,然后把他们存入到磁盘文件中 #include <stdio.h> #define SIZE 4 // 学生数量 // 创建学生结构体数组 struct Student { char name[20]; int num; int age; char addr[50]; } stus[SIZE]; /** * 保存学生信息到文件 */ int save() { FILE *fp; int i; // 打开文件 if ((fp = fopen("stu-list", "wb")) == NULL) // wb 二进制写入 { perror("文件打开失败!"); return -1; } // 写入数据 for (i = 0; i < SIZE; i++) { fwrite(&stus[i], sizeof(struct Student), 1, fp); } // 关闭文件 fclose(fp); return 0; } int main(int argc, char *argv[]) { int i; printf("请输入学生的信息:姓名,学号,年龄,住址\n"); for (i = 0; i < SIZE; i++) { scanf("%s%d%d%s", stus[i].name, &stus[i].num, &stus[i].age, stus[i].addr); // 保存信息到文件 save(); } return 0; }
文件的随机读写
-
说明:C语言允许程序员在读写文件内容的时候,可在指定位置上读写数据
-
文件随机读写的核心操作:文件位置指针的定位
-
文件位置指针移动方法:
-
rewind
-
头文件:
#include <stdio.h>
-
函数原型:
void rewind(FILE* fp);
-
函数功能:将文件位置指针定位到文件开头
-
函数参数:
- fp:已经打开文件的指针
-
返回值:空类型
-
-
fseek
-
头文件:
#include <stdio.h>
-
函数原型:
int fseek(FILE* fp,long offset,int whence);
-
函数功能:将文件位置指针定位到指定位置
-
函数参数:
-
fp:已经打开的文件的指针
-
offset:相对于参考位置的偏移量
-
whence:参考位置
-
SEEK_SET 或 0:表示文件头
-
SEEK_CUR 获1:表示当前读写的位置
-
SEEK_END 或2:表示文件尾
-
-
-
返回值:
-
成功:0
-
失败:-1
-
-
-
ftell
-
头文件: #include <stdio.h>
-
函数原型: long ftell(FILE* fp);
-
函数功能:获取文件位置指针当前位置
-
函数参数:
- fp:已经打开的文件指针
-
返回值:
-
成功:文件位置指针的当前位置
-
失败:-1
-
-
-
案例
案例1
// 有一个磁盘文件,第一次将它的内容通过控制台输出,第二次把它复制到另一个文件上。
#include <stdio.h>
int main(int argc, char *argv[])
{
// 创建两个指针,用来接收打开的文件
FILE *fp1, *fp2;
if (argc < 2)
{
perror("请使用<%s 被读取文件 被写入文件>的方式!\n");
return -1;
}
// 打开文件
fp1 = fopen(argv[1], "r");
fp2 = fopen(argv[2], "w");
// 第一次,读取文件内容通过控制台打印
while (!feof(fp1))
putchar(getc(fp1)); // 输出到控制台
// 将指针指向文件的头,否则下次读取文件,会读取不到内容
rewind(fp1);
// 第二次,读取文件内容将其拷贝至fp2对应的文件中
while (!feof(fp1))
putc(getc(fp1), fp2); // 输出到文件
// 关闭文件
fclose(fp1);
fclose(fp2);
return 0;
}
案例2
// 数据块写入案例:从键盘输入4个学生的有关数据,然后把他们存入到磁盘文件中
#include <stdio.h>
#define SIZE 10 // 学生数量
// 创建学生结构体数组
struct Student
{
char name[20];
int num;
int age;
char sex;
} stus[SIZE];
/**
* 保存学生信息到文件
*/
int save()
{
FILE *fp;
int i;
// 打开文件
if ((fp = fopen("stu-dat", "wb")) == NULL) // wb 二进制写入
{
perror("文件打开失败!");
return -1;
}
// 写入数据
for (i = 0; i < SIZE; i++)
{
fwrite(&stus[i], sizeof(struct Student), 1, fp);
}
// 关闭文件
fclose(fp);
return 0;
}
int main(int argc, char *argv[])
{
int i;
printf("请输入学生的信息:姓名,学号,年龄,性别\n");
for (i = 0; i < SIZE; i++)
{
scanf("%s%d%d%s", stus[i].name, &stus[i].num, &stus[i].age, &stus[i].sex);
// 保存信息到文件
save();
}
return 0;
}
案例3
// 需求:在磁盘文件上存储10个学生的数据,要求第1,3,5,7,9个学生数据输入计算机,并在屏幕上显示出来。
#include <stdio.h>
#include <stdlib.h>
// 定义一个学生结构体
typedef struct Student
{
char name[20];
int num;
int age;
char sex;
} Stu;
// 创建一个学生数组
Stu stu[10] = {0};
int main(int argc, char *argv[])
{
int i;
FILE *fp;
// 打开文件
if ((fp = fopen("stu-dat", "rb")) == NULL)
{
perror("文件打开失败!\n");
return -1;
}
// 循环取出数据
for (i = 0; i < 10; i += 2)
{
// 跳过对应位置,改变文件指针的指向
fseek(fp, i * sizeof(Stu), 0);
// 读数据
fread(&stu[i], sizeof(Stu), 1, fp);
// 输出到控制台
printf("%s,%d,%d,%c\n", stu[i].name, stu[i].num, stu[i].age, stu[i].sex);
}
// 关闭文件
fclose(fp);
return 0;
}
案例4
// 下列C程序的功能是,用“追加”的形式打开文件gg.txt,查看文件读写指针的位置,然后向文件写入“data”,再查看文件读写指针的位置。
#include <stdio.h>
int main(int argc, char *argv[])
{
long p;
FILE *fp;
if ((fp = fopen(argv[1], "a")) == NULL) // 此时的mode:a代表追加(a是append)
{
perror("文件打开失败!");
return -1;
}
// 获取当前位置
p = ftell(fp);
printf("p=%ld\n", p);
// 向文件添加数据
fputs("data", fp);
p = ftell(fp);
printf("p=%ld\n", p);
fclose(fp);
return 0;
}