什么是文件:
磁盘上的文件就是文件
程序文件:
源程序文件test.c 目标文件test.obj 可执行程序test.exe
数据文件:
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要 从中读取数据的文件,或者输出内容的文件,如:data.txt
一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件名包含3部分:文件路径+文件名主干+文件后缀
例如: c:\code\test.txt
为了方便起见,文件标识常被称为文件名。
每一个打开的文件都会后一个与它匹配的文件信息区,存储文件信息
这些信息保存在一个结构体变量中,如:struct FILE f;。该结构体类型是有系统声明的,取名FILE.
例如,VS2008编译环境提供的 stdio.h 头文件中有以下的文件类型声明:
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节。
一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。
ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件。
#include
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
// 输出:
// fopen: No such file or directory
// 因为文件不存在
return -1;
}
// 读文件
// 关闭文件
fclose(pf);
pf = NULL;
return 0;
}
绝对路径
#include
int main()
{
FILE* pf = fopen("C:\\Users\\Chloe\\Desktop\\data.txt", "r"); // 绝对路径
// FILE* pf = fopen("data.txt", "r"); // 相对路径
if (pf == NULL)
{
perror("fopen");
return -1;
}
// 读文件
// 关闭文件
fclose(pf);
pf = NULL;
return 0;
}
#include
int main()
{
FILE* pf = fopen("C:\\Users\\Chloe\\Desktop\\data.txt", "w"); // 会生成一个文件
if (pf == NULL)
{
perror("fopen");
return -1;
}
// 读文件
// 关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fputc - 字符输出函数 写一个字符
fgetc - 字符输入函数 读一个字符
int fgetc( FILE *stream );
#include
int main()
{
FILE* pf = fopen ("C:\\Users\\Chloe\\Desktop\\data.txt", "w");
if (NULL == pf)
{
perror("fopen");
return -1;
}
// 写文件
fputc('b', pf); // 在data.txt中写入bit
fputc('i', pf);
fputc('t', pf);
// 关闭文件
fclose(pf);
pf = NULL;
return 0;
}
流:高度抽象的概念
C语言的程序,只要运行起来,就默认打开了三个流:
stdout - 标准输出流
stdin - 标准输入流
stderr - 标准错误流
类型都是 FILE*
#include
int main()
{
fputc('b', stdout);
fputc('i', stdout);
fputc('t', stdout);
}
int fputc( int c, FILE *stream );
把文件内容改成abcdef 会输出什么?
#include
int main()
{
FILE* pf = fopen ("C:\\Users\\Chloe\\Desktop\\data.txt", "r");
if (NULL == pf)
{
perror("fopen");
return -1;
}
// 读文件
int ch = fgetc(pf);
printf("%c\n", ch); // a
ch = fgetc(pf);
printf("%c\n", ch); // b
ch = fgetc(pf);
printf("%c\n", ch); // c
// 关闭文件
fclose(pf);
pf = NULL;
return 0;
}
也可以从标准输入读,也就是键盘
#include
int main()
{
int ch = fgetc(stdin);
printf("%c\n", ch);
ch = fgetc(stdin);
printf("%c\n", ch);
ch = fgetc(stdin);
printf("%c\n", ch);
}
#include
int main()
{
fputc('b', stdout);
// putchar printf("%c", ch);
int ch = fgetc(stdin);
// getchar scanf("%c", ch);
printf("%c\n", ch);
}
int fputs( const char *string, FILE *stream );
把数据放进流里
#include
int main()
{
FILE* pf = fopen("data.txt", "w");
if (NULL == pf)
{
perror("fopen");
return -1;
}
// 写文件
// 写一行数据
fputs("hello world\n", pf);
fputs("hello bit\n", pf);
fclose(pf);
pf = NULL;
}
char *fgets( char *string, int n, FILE *stream );
想要读5个字符,但实际只读了4个字符,因为后面后有一个 \0
改成20:
int fprintf( FILE *stream, const char *format [, argument ]...);
与 printf 函数对比
示例:
#include
struct S
{
int n;
double d;
};
int main()
{
struct S s = { 100, 3.14 };
FILE* pf = fopen("data.txt", "w");
if (NULL == pf)
{
perror("fopen");
return -1;
}
// 写文件
fprintf(pf, "%d %lf", s.n, s.d);
// 关闭文件
fclose(pf);
pf = NULL;
return 0;
}
int fscanf( FILE *stream, const char *format [, argument ]... );
示例:
#include
struct S
{
int n;
double d;
};
int main()
{
struct S s = { 0 };
FILE* pf = fopen("data.txt", "r");
if (NULL == pf)
{
perror("fopen");
return -1;
}
// 读文件
fscanf(pf, "%d %lf", &(s.n), &(s.d));
printf("%d %lf", s.n, s.d);
// 关闭文件
fclose(pf);
pf = NULL;
return 0;
}
示例:
wb”(只写) 为了输出数据,打开一个二进制文件
#include
struct S
{
int n;
double d;
char name[10];
};
int main()
{
struct S s = { 100, 3.24, "zhangsan"};
FILE* pf = fopen("data.txt", "wb");
if (NULL == pf)
{
perror("fopen");
return -1;
}
// 以二进制方式写
fwrite(&s, sizeof(s), 1, pf);
// 关闭文件
fclose(pf);
pf = NULL;
return 0;
}
文件内输出一堆二进制
示例:
#include
struct S
{
int n;
double d;
char name[10];
};
int main()
{
struct S s = { 100, 3.24, "zhangsan" };
FILE* pf = fopen("data.txt", "rb");
if (NULL == pf)
{
perror("fopen");
return -1;
}
// 读文件 - 以二进制方式读
fwrite(&s, sizeof(s), 1, pf);
// 打印文件
printf("%d %lf %s\n", s.n, s.d, s.name); // 100 3.240000 zhangsan
// 关闭文件
fclose(pf);
pf = NULL;
return 0;
}
scanf/fscanf/sscanf
printf/fprintf/sprintf
scanf:从标准输入流(键盘)读取格式化的数据
printf:把格式化的数据输出到标准输出(屏幕)上
fscanf:从所有的输入流读取格式化的数据
fprintf:把格式化的数据输出到所有输出流(屏幕 / 文件)上
剩下 sscanf 和 sprintf 是什么意思?
从一个字符串里读格式化的数据
写格式化的数据到字符串里
举例:
#include
struct S
{
int n;
double d;
char name[10];
};
int main()
{
char arr[100] = { 0 };
struct S s = { 100, 3.14, "zhangsan" };
// 把一个格式化的数据转换成字符串
sprintf(arr, "%d %lf %s\n", s.n, s.d, s.name);
// 打印
printf("%s\n", arr);
return 0;
}
如何从arr里解析出格式化的结构体放进tmp:
#include
struct S
{
int n;
double d;
char name[10];
};
int main()
{
char arr[100] = { 0 };
struct S s = { 100, 3.14, "zhangsan" };
struct S tmp = { 0 };
// 把一个格式化的数据转换成字符串
sprintf(arr, "%d %lf %s\n", s.n, s.d, s.name);
// 以字符串形式打印
printf("%s\n", arr); // 100 3.140000 zhangsan
// 从arr中的字符串中提取一个格式化的数据
sscanf(arr, "%d %lf %s", &(tmp.n), &(tmp.d), tmp.name);
// 按照格式化的形式打印的
printf("%d %lf %s\n", tmp.n, tmp.d, tmp.name); // 100 3.140000 zhangsan
return 0;
}
文件的版本:
【Contact】结构体+动态内存管理+文件存储实现简易通讯录代码
int fseek ( FILE * stream, long int offset, int origin );
根据文件指针的位置和偏移量来定位文件指针
#include
int main()
{
FILE* pf = fopen("data.txt", "r");
if (NULL == pf)
{
perror("fopen");
return -1;
}
// 读文件 随机读写
// 1、读a
//int ch = fgetc(pf);
//printf("%c\n", ch); // a
// 2、如果第一次就要读取'c'
fseek(pf, 2, SEEK_SET); // 从起始位置偏移2指向c
int ch = fgetc(pf);
printf("%c\n", ch); // c
// 3、读'b'
// 读完c 指向d 要读取b需要从当前位置开始偏移-2
fseek(pf, -2, SEEK_CUR);
ch = fgetc(pf);
printf("%c\n", ch); // b
fclose(pf);
pf = NULL;
return 0;
}
long int ftell ( FILE * stream );
返回文件指针相对于起始位置的偏移量
计算文件指针相较于起始位置的偏移量:
#include
int main()
{
FILE* pf = fopen("data.txt", "r");
if (NULL == pf)
{
perror("fopen");
return -1;
}
// 读取'c'
fseek(pf, 2, SEEK_SET);
int ch = fgetc(pf);
printf("%c\n", ch);
// 读'b'
fseek(pf, -2, SEEK_CUR);
ch = fgetc(pf);
printf("%c\n", ch);
// 计算偏移量
// 读取b后指向c 偏移量较起始位置为2
int ret = ftell(pf);
printf("%d\n", ret); // 2
fclose(pf);
pf = NULL;
return 0;
}
void rewind ( FILE * stream );
让文件指针的位置回到文件的起始位置
#include
int main()
{
FILE* pf = fopen("data.txt", "r");
if (NULL == pf)
{
perror("fopen");
return -1;
}
// 读取'c'
fseek(pf, 2, SEEK_SET);
int ch = fgetc(pf);
printf("%c\n", ch);
// 读'b'
fseek(pf, -2, SEEK_CUR);
ch = fgetc(pf);
printf("%c\n", ch);
// 计算偏移量
int ret = ftell(pf);
printf("%d\n", ret); // 2
// 让指针回到起始位置
// 再次从起始位置打印 还是a
rewind(pf);
ch = fgetc(pf);
printf("%c\n", ch); // a
fclose(pf);
pf = NULL;
return 0;
}
十进制10000的二进制存储:
ASCII形式 / 二进制形式
5字节 4字节
#include
int main()
{
FILE* pf = fopen("data.txt", "wb");
if (NULL == pf)
{
perror("fopen");
return -1;
}
// 以二进制形式写文件
int a = 10000;
fwrite(&a, 4, 1, pf);
fclose(pf);
pf = NULL;
return 0;
}
字符输入函数fgetc返回int,遇到错误或文件结束返回EOF
如果用fgetc读取文件,可以判断fgetc的返回值是否是EOF,来判定文件是否读取结束
文本行输入函数fgets返回起始地址,遇到错误或文件结束返回NULL
#include
int main()
{
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return -1;
}
int ch = 0;
while ((ch = fgetc(pf)) != EOF)
{
printf("%c ", ch);
}
fclose(pf);
pf = NULL;
return 0;
}
feof不是用来判断文件是否读取结束的
文本文件的例子:
#include
#include
int main(void)
{
int c; // 注意:int,非char,要求处理EOF
FILE* fp = fopen("test.txt", "r");
if (!fp) {
perror("File opening failed");
return EXIT_FAILURE;
}
//fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
while ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环
{
putchar(c);
}
//判断是什么原因结束的
if (ferror(fp))
puts("I/O error when reading");
else if (feof(fp))
puts("End of file reached successfully");
fclose(fp);
fp = NULL;
}
二进制文件的例子:
#include
enum { SIZE = 5 };
int main(void)
{
double a[SIZE] = { 1.0,2.0,3.0,4.0,5.0 };
double b = 0.0;
size_t ret_code = 0;
FILE* fp = fopen("test.bin", "wb"); // 必须用二进制模式
fwrite(a, sizeof(*a), SIZE, fp); // 写 double 的数组
fclose(fp);
fp = fopen("test.bin", "rb");
// 读 double 的数组
while ((ret_code = fread(&b, sizeof(double), 1, fp)) >= 1)
{
printf("%lf\n", b);
}
if (feof(fp))
printf("Error reading test.bin: unexpected end of file\n");
else if (ferror(fp)) {
perror("Error reading test.bin");
}
fclose(fp);
fp = NULL;
}
总结:
feof是当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束
ferror 用途:文件读取结束了,判断是不是遇到错误后读取结束
测试代码:
#include
#include
//VS2013 WIN10环境测试
int main()
{
FILE* pf = fopen("test.txt", "w");
fputs("abcdef", pf); // 先将代码放在缓冲区
printf("睡眠10s 已经写数据了 打开test.txt文件 发现文件没有内容\n");
Sleep(10000);
printf("刷新缓冲区\n");
fflush(pf); // 刷新缓冲区时 才将输出缓冲区的数据写到文件(磁盘)
printf("再睡眠10s 此时 再次打开test.txt文件 文件有内容了\n");
Sleep(10000);
// 注:fclose关闭文件的时候 也会刷新缓冲区
pf = NULL;
return 0;
}
结论:
因为有缓冲区的存在,C语言在操作文件时,需要做刷新缓冲区或者在文件操作结束时关闭文件(fclose关闭文件的时候 也会刷新缓冲区),否则可能导致读写文件问题