一.文件
文件有两种:程序文件、数据文件(从文件功能的角度来分类的)
程序文件:
源程序文件(后缀为 .c ) , 目标文件( windows 环境后缀为 .obj ) , 可执行程序( windows 环境
后缀为 .exe )
数据文件:
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,
或者输出内容的文件
这篇文章主要讨论的是数据文件
文件名:
文件标识常被称为文件名,以便用户识别和引用。
文件名包含 3 部分:文件路径 + 文件名主干 + 文件后缀
例如: D :\666\code\test.txt
二、文件的打开和关闭
文件指针打开与关闭
文件指针变量能够找到与它关联 的文件 。
文件指针变量创建方式:
FILE* pf;
ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件
1. fopen
FILE * fopen ( const char * filename, const char * mode );
2. fclose
int fclose ( FILE * stream );
打开方式:
文件使用方式 |
含义 |
如果指定文件不存在 |
“r” (只读) |
为了输入数据,打开一个已经存在的文本文件 |
出错 |
“w” (只写) |
为了输出数据,打开一个文本文件 |
建立一个新的文件 |
“a” (追加) |
向文本文件尾添加数据 |
建立一个新的文件 |
“rb” (只读) |
为了输入数据,打开一个二进制文件 |
出错 |
“wb” (只写) |
为了输出数据,打开一个二进制文件 |
建立一个新的文件 |
“ab” (追加) |
向一个二进制文件尾添加数据 |
出错 |
“r+” (读写) |
为了读和写,打开一个文本文件 |
出错 |
“w+” (读写) |
为了读和写,建议一个新的文件 |
建立一个新的文件 |
“a+” (读写) |
打开一个文件,在文件尾进行读写 |
建立一个新的文件 |
“rb+” (读写) |
为了读和写打开一个二进制文件 | 出错 |
“wb+” (读写) |
为了读和写,新建一个新的二进制文件 |
建立一个新的文件 |
“ab+” (读写) |
打开一个二进制文件,在文件尾进行读和写 |
建立一个新的文件 |
使用:
int main()
{
//创建文件指针变量
//打开文件
FILE* pf = fopen("test.txt", "w");//如果不加文件路径,则默认将test.txt写到与该代码相同地址
if (pf == NULL);
{
printf("%s\n", strerror(errno));
return 0;
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
三、文件的顺序读取
功能 |
函数名 |
适用于 |
字符输入函数 |
fgetc |
所有输入流 |
字符输出函数 |
fputc |
所有输出流 |
文本行输入函数 |
fgets |
所有输入流 |
文本行输出函数 |
fputs |
所有输出流 |
格式化输入函数 |
fscanf |
所有输入流 |
格式化输出函数 |
fprintf |
所有输出流 |
二进制输入 |
fread |
文件 |
二进制输出 |
fwrite |
文件 |
fputc -> int fputc( int c, FILE *stream );
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//写文件
char ch = 0;
for (ch = 'a'; ch <= 'z'; ch++)
{
fputc(ch, pf);//一个一个将a - z写入test.txt
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fgetc -> int fgetc( FILE *stream );
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");//只读文件
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//读文件
int ch = 0;
while ((ch = fgetc(pf)) != EOF)//文件无数据输入时,fgetc返回EOF
{
printf("%c ", ch);//一个一个读取文件指针pf指向文件中的数据
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fputs -> int fputs( const char *string, FILE *stream );
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//写文件
fputs("hello world\n", pf);//将字符串写入test.txt中
fputs("hahahahahaha\n", pf);//
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fgets -> char *fgets( char *string, int n, FILE *stream );
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//读文件
char buf[100] = { 0 };
fgets(buf, 5, pf);
printf("%s", buf);
fgets(buf, 5, pf);//接着上面的地方往后读取5个
printf("%s", buf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
用代码实现将文件的内容拷贝一份:
int main()
{
//创建文件指针变量
//打开文件
FILE* pf = fopen("test.txt", "r");//只读
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
FILE* pf2 = fopen("test2.txt", "w");//只写
if (pf2 == NULL)
{
printf("%s\n", strerror(errno));
fclose(pf);
pf = NULL;
return 0;
}
//拷贝文件test.txt内容到test2.txt中
int ch = 0;
while ((ch = fgetc(pf)) != EOF)//从pf从一个个读取
{
fputc(ch, pf2);//一个个输出至pf2中
}
//关闭文件
fclose(pf);
pf = NULL;
fclose(pf2);
pf2 = NULL;
return 0;
}
fprintf -> int fprintf( FILE *stream, const char *format [, argument ]...);
struct stu
{
char name[20];
int age;
char sex[5];
};
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//写格式化的数据
struct stu N[2] = { {"张三",18,"男"},{"李四",19,"男"}};
int i = 0;
for (i = 0; i < 2; i++)
{
fprintf(pf, "%s %d %s\n", N[i].name, N[i].age, N[i].sex);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fscanf -> int fscanf( FILE *stream, const char *format [, argument ]... );
struct stu
{
char name[20];
int age;
char sex[5];
};
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//读格式化的数据
struct stu N[2] = { 0 };
int i = 0;
for (i = 0; i < 2; i++)
{
fscanf(pf, "%s %d %s\n", N[i].name, &N[i].age, N[i].sex);
printf("%s %d %s\n", N[i].name, N[i].age, N[i].sex);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fwrite -> size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );
以二进制的方式写入
struct stu
{
char name[20];
int age;
char sex[5];
};
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "wb");//wb , 为了输出数据,打开一个二进制文件
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//二进制的写入
struct stu N[2] = { {"张三",18,"男"},{"李四",19,"男"}};
fwrite(N,sizeof(struct stu),2,pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
fread -> size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
以二进制的方式读取
struct stu
{
char name[20];
int age;
char sex[5];
};
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "rb");//rb , 为了输入数据,打开一个二进制文件
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//二进制的读取
struct stu N[2] = { 0 };
fread(N,sizeof(struct stu),2,pf);
int i = 0;
for (i = 0; i < 2; i++)
{
printf("%s %d %s\n", N[i].name, N[i].age, N[i].sex);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
sscanf和sprintf
struct stu
{
char name[20];
int age;
char sex[5];
};
int main()
{
struct stu S = { "张三",18,"男" };
struct stu tmp = { 0 };
char buf[100] = { 0 };
sprintf(buf, "%s ,%d ,%s", S.name, S.age, S.sex);//将S中的格式化数据转换为字符串存入buf
printf("%s\n", buf);
sscanf(buf, "%s ,%d ,%s", tmp.name, &tmp.age, tmp.sex);//将buf中的字符串提取出格式化数据存入tmp中
printf("%s ,%d ,%s\n", tmp.name, tmp.age, tmp.sex);
return 0;
}
对比以下一组函数
scanf 从标准输入流(stdin)上进行格式化输入的函数
printf 向标准输出流(stdout)上进行格式化输出的函数
fscanf 可以从标准输入流(stdin)/指定的文件流 上读取格式化数据
fprintf 把数据按照格式化的方式输出到标准输出流(stdout)/指定的文件流
sscanf 可以从一个字符串中提取(转化)出格式化数据
sprintf 把一个格式化的数据转换为字符串
结合文件知识改良通讯录
将信息保存在文件中
test.c
#include "contact.h"
void menu()
{
printf("*************************************\n");
printf("***** 1.add 2.del *****\n");
printf("***** 3.search 4.modify *****\n");
printf("***** 5.sort 6.show *****\n");
printf("***** 0.exit *****\n");
printf("*************************************\n");
}
int main()
{
int input = 0;
conact con;
Initconact(&con);
do {
menu();
printf("请选择要进行的操作:");
scanf("%d", &input);
switch (input)
{
case 1:
Addconact(&con);
break;
case 2:
Delconact(&con);
break;
case 3:
{
char name[10] = "";
printf("请输入需要查询的名字:");
scanf("%s", name);
int n = Findconact(&con, name);
if (n != -1)
{
printf("% -10s\t% -5s\t% -5s\t% -15s\t% -20s\n", "名字", "性别", "年龄", "电话", "地址");
printf("%-10s\t%-5s\t%-5d\t%-15s\t%-20s\n",
con.date[n].name, con.date[n].sex, con.date[n].age,
con.date[n].tele, con.date[n].addr);
}
else
{
printf("找不到此联系人\n");
}
break;
}
case 4:
Changeconact(&con);
break;
case 5:
Sortconact(&con);
break;
case 6:
Showconact(&con);
break;
case 0:
Destroyconact(&con);
Saveconact(&con);
printf("退出通讯录\n");
break;
default:
printf("输入错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
contact.c
#include "contact.h"
void check(conact* pc)
{
assert(pc);
if (pc->sz == pc->capacity)
{
//增加容量
PeoInfo* p = (PeoInfo*)realloc(pc->date, (pc->capacity + 2) * sizeof(PeoInfo));
if (p != NULL)
{
pc->date = p;
pc->capacity += 2;
printf("增容成功\n");
}
else
{
printf("check()::%s\n", strerror(errno));
}
}
}
void Loaddownconact(conact* pc)
{
FILE* pf = fopen("conact.txt", "rb");
if (pf == NULL)
{
printf("Initconact:open for reading :()%s\n", strerror(errno));
return;
}
PeoInfo buf = { 0 };
while (fread(&buf, sizeof(PeoInfo), 1, pf))
{
check(pc);
pc->date[pc->sz] = buf;
pc->sz++;
}
fclose(pf);
pf = NULL;
}
void Saveconact(conact* pc)
{
FILE* pf = fopen("contact.txt", "wb");
if (pf == NULL)
{
printf("Saveconact::()%s\n", strerror(errno));
return;
}
int i = 0;
for (i = 0; i < pc->sz; i++)
{
fwrite(pc->date + i, sizeof(PeoInfo), 1, pf);
}
fclose(pf);
pf = NULL;
}
void Destroyconact(conact* pc)
{
assert(pc);
free(pc->date);
pc->date = NULL;
pc->sz = 0;
pc->capacity;
}
void Initconact(conact* pc)
{
assert(pc);
pc->sz = 0;
PeoInfo* p = (PeoInfo*)malloc(Default_Max * sizeof(PeoInfo));
if (p != NULL)
{
pc->date = p;
}
else
{
printf("Initconact()::%s\n", strerror(errno));
return;
}
pc->capacity = Default_Max;
}
void Addconact(conact* pc)
{
assert(pc);
check(pc);
printf("请输入名字:");
scanf("%s", pc->date[pc->sz].name);
printf("请输入性别:");
scanf("%s", pc->date[pc->sz].sex);
printf("请输入年龄:");
scanf("%d", &pc->date[pc->sz].age);
printf("请输入电话:");
scanf("%s", pc->date[pc->sz].tele);
printf("请输入地址:");
scanf("%s", pc->date[pc->sz].addr);
pc->sz++;
printf("添加联系人成功\n");
}
void Showconact(const conact* pc)
{
assert(pc);
printf("%-10s\t%-5s\t%-5s\t%-15s\t%-20s\n", "名字", "性别", "年龄", "电话", "地址");
int i = 0;
for (i = 0; i < pc->sz; i++)
{
printf("%-10s\t%-5s\t%-5d\t%-15s\t%-20s\n",
pc->date[i].name, pc->date[i].sex, pc->date[i].age,
pc->date[i].tele, pc->date[i].addr);
}
}
int Findconact(const conact* pc, char name[])
{
assert(pc);
int i = 0;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(pc->date[i].name, name) == 0)
{
return i;
}
}
return -1;
}
void Changeconact(conact* pc)
{
printf("请输入需要修改的联系人:");
char name[20] = "";
scanf("%s", name);
int n = Findconact(pc, name);
if (n != -1)
{
Showconact(pc);
printf("请输入修改后的名字:");
scanf("%s", pc->date[n].name);
printf("请输入修改后的性别:");
scanf("%s", pc->date[n].sex);
printf("请输入修改后的年龄:");
scanf("%d", &pc->date[n].age);
printf("请输入修改后的电话:");
scanf("%s", pc->date[n].tele);
printf("请输入修改后的地址:");
scanf("%s", pc->date[n].addr);
printf("修改成功\n");
}
else
{
printf("找不到该联系人\n");
}
}
void Delconact(conact* pc)
{
assert(pc);
if (pc->sz == 0)
{
printf("通讯录为空\n");
return;
}
printf("请输入要删除的联系人:");
char name[20] = "";
int i = 0;
scanf("%s", name);
int n = Findconact(pc, name);
if (n != -1)
{
for (i = n; i < pc->sz - 1; i++)
{
pc->date[i] = pc->date[i + 1];
}
pc->sz--;
printf("删除成功\n");
}
else
{
printf("找不到联系人\n");
return;
}
}
void Sortconact(conact* pc)
{
int i = 0;
int j = 0;
for (i = 0; i < pc->sz - 1; i++)
{
for (j = 0; j < pc->sz - 1 - i; j++)
{
if (strcmp(pc->date[j].name, pc->date[j + 1].name) > 0)
{
char t[Name_Max] = "";
strcpy(t, pc->date[j].name);
strcpy(pc->date[j].name, pc->date[j + 1].name);
strcpy(pc->date[j + 1].name, t);
}
}
}
printf("排序完成\n");
}
contact.h
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#define Max 1000
#define Name_Max 20
#define Sex_Max 5
#define Addr_Max 30
#define Tele_Max 12
#define Default_Max 3
typedef struct PeoInfo
{
char name[Name_Max];
char sex[Sex_Max];
char addr[Addr_Max];
char tele[Tele_Max];
int age;
}PeoInfo;
//通讯录的结构体
typedef struct conact
{
//PeoInfo data[MAX];
PeoInfo* date;//存放数据
int sz;//通讯录中有效信息个数
int capacity;//记录当前通讯录的最大容量
}conact;
void Initconact(conact* pc);
void Addconact(conact* pc);
void Showconact(const conact* pc);
void Delconact(conact* pc);
int Findconact(const conact* pc, char name[]);
void Changeconact(conact* pc);
void Sortconact(conact* pc);
void Destroyconact(conact* pc);
void Saveconact(conact* pc);
void Loaddownconact(conact* pc);
四、文件的随机读写
fseek
int fseek ( FILE * stream, long int offset, int origin );
根据文件指针的位置和偏移量来定位文件指针
SEEK_CUR 从当前位置
SEEK_END 从结尾位置
SEEK_SET 从起始位置
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//test.txt里存有 abcdefghijklmnopqrstuvwxyz
char ch = 0;
ch = fgetc(pf);//a
ch = fgetc(pf);//b
ch = fgetc(pf);//c
ch = fgetc(pf);//d
fseek(pf, 2, SEEK_CUR);//SEEK_CUR 令文件指针在当前位置往后跳过两个位置, e f
ch = fgetc(pf);//g
ch = fgetc(pf);//h
fseek(pf, 2, SEEK_SET);//SEEK_SET 令文件指针从第一位往后跳过两个位置, a b
ch = fgetc(pf);//c
fseek(pf, -2, SEEK_END);//SEEK_END 令文件指针从最后一位往前数的第两个位置, z y,上面都是跳,只有这个是数的,而且只能填负数
ch = fgetc(pf);//y
fclose(pf);
pf = NULL;
return 0;
}
ftell
long int ftell ( FILE * stream );
返回一个数值,即文件指针相对于起始位置的偏移量
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//test.txt里存有 abcdefghijklmnopqrstuvwxyz
char ch = 0;
int ret = 0;
ch = fgetc(pf);//a
ch = fgetc(pf);//b
ch = fgetc(pf);//c
ch = fgetc(pf);//d
ret = ftell(pf);
printf("%d\n", ret);//ret = 4,相对于起始位置,偏移量为4 a - d
fclose(pf);
pf = NULL;
return 0;
}
rewind
void rewind ( FILE * stream );
让文件指针的位置回到文件的起始位置
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//test.txt里存有 abcdefghijklmnopqrstuvwxyz
char ch = 0;
ch = fgetc(pf);//a
ch = fgetc(pf);//b
ch = fgetc(pf);//c
ch = fgetc(pf);//d
rewind(pf);//令文件指针指向起始位置a
ch = fgetc(pf);//a
fclose(pf);
pf = NULL;
return 0;
}
五、文件读取结束的判定
1. 文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )
例如:
fgetc 判断是否为 EOF .
fgets 判断返回值是否为 NULL .
2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
例如:
fread判断返回值是否小于实际要读的个数。
feof的正确用法
在文件读取过程中,不能用feof函数的返回值直接用来判断文件的是否结束。 而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
//test.txt里存有 abcdefghijklmnopqrstuvwxyz
char ch = 0;
while ((ch = fgetc(pf)) != EOF)
{
putchar(ch);
}
if (ferror(pf))
{
printf("文件读取失败\n");
}
else if(feof(pf))
{
printf("遇到文件尾结束\n");
}
fclose(pf);
pf = NULL;
return 0;
}
文件缓存区
总结:因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。如果不做,可能导致读写文件的问题。