【C语言】可以不用,但不能不会的——文件操作
🎄什么是文件
🎅程序文件
🎅数据文件
🎅文件名
🎄文件类型
🎄文件缓冲区
🎄文件的打开和关闭
🎅文件指针
🎅文件打开与关闭
🎄文件的顺序读写
🎅字符输入函数 fgetc
🎅字符输出函数 fputc
🎅文本行输入函数 fgets
🎅文本行输出函数 fputs
🎅格式化输入函数 fscanf
🎅格式化输出函数 fprintf
🎅二进制输入 fread
🎅二进制输出 fwrite
🎄对比一组函数
🎄文件的随机读写
🎅fseek
🎅ftell
🎅rewind
🎄文件结束的判定
❗❗❗❗❗被错误使用的 feof
🐂🐸通讯录高阶版(动态内存+自定义类型+文件)
🎄什么是文件
磁盘上的文件是文件。
但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类)
🎅程序文件
包括源程序文件(后缀为.c)
目标文件(windows环境后缀为.obj)
可执行程序(windows环境后缀为.exe)
🎅数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
本章讨论的是数据文件。
在以前各章所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。
其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件。
🎅文件名
一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件名包含3部分:文件路径+文件名主干+文件后缀
例如: c:\code\test.txt
为了方便起见,文件标识常被称为文件名。
🎄文件类型
根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节。
(VS2013测试)。
🎄文件缓冲区
ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。
🎄文件的打开和关闭
🎅文件指针
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名FILE.
例如,VS2008编译环境提供的 stdio.h 头文件中有以下的文件类型申明:
不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。
每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节。
一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。
下面我们可以创建一个FILE*的指针变量:
FILE* pf;//文件指针变量
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。
比如:
🎅文件打开与关闭
文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。
在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。
ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件
FILE * fopen ( const char * filename, const char * mode );
该函数返回一个 FILE 指针。否则返回 NULL,且设置全局变量 errno 来标识错误。
filename – 这是 C 字符串,包含了要打开的文件名称。
mode – 这是 C 字符串,包含了文件访问模式,模式如下:
int fclose ( FILE * stream );
stream
– 这是指向 FILE 对象的指针,该 FILE 对象指定了要被关闭的流。- 如果流成功关闭,则该方法返回零。如果失败,则返回 EOF。
代码示例:
结果如下:
🎄文件的顺序读写
接下来依次展示使用实例:
🎅字符输入函数 fgetc
描述
C 库函数 int fgetc(FILE *stream) 从指定的流 stream 获取下一个字符(一个无符号字符),并把位置标识符往前移动。
声明
int fgetc(FILE *stream)
参数
stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了要在上面执行操作的流。
返回值
该函数以无符号 char 强制转换为 int 的形式返回读取的字符,如果到达文件末尾或发生读错误,则返回 EOF。
使用实例
🎅字符输出函数 fputc
描述
C 库函数 int fputc(int char, FILE *stream) 把参数 char 指定的字符(一个无符号字符)写入到指定的流 stream 中,并把位置标识符往前移动。
声明
int fputc(int char, FILE *stream)
参数
char – 这是要被写入的字符。该字符以其对应的 int 值进行传递。
stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符的流。
返回值
如果没有发生错误,则返回被写入的字符。如果发生错误,则返回 EOF,并设置错误标识符。
使用实例
🎅文本行输入函数 fgets
描述
C 库函数 char *fgets(char *str, int n, FILE *stream) 从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。
声明
char *fgets(char *str, int n, FILE *stream)
参数
str – 这是指向一个字符数组的指针,该数组存储了要读取的字符串。
n – 这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。
stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。
返回值
如果成功,该函数返回相同的 str 参数。如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。
使用实例
🎅文本行输出函数 fputs
描述
C 库函数 int fputs(const char *str, FILE *stream) 把字符串写入到指定的流 stream 中,但不包括空字符。
声明
int fputs(const char *str, FILE *stream)
参数
str – 这是一个数组,包含了要写入的以空字符终止的字符序列。
stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符串的流。
返回值
该函数返回一个非负值,如果发生错误则返回 EOF。
使用实例
🎅格式化输入函数 fscanf
描述
C 库函数 int fscanf(FILE *stream, const char *format, …) 从流 stream 读取格式化输入。
声明
int fscanf(FILE *stream, const char *format, ...)
参数
stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
format – 这是 C 字符串,包含了以下各项中的一个或多个:
空格字符、非空格字符 和 format 说明符。
format 说明符形式为 [=%[*][width][modifiers]type=],具体讲解如下:
fscanf 类型说明符:
附加参数 – 根据不同的 format 字符串,函数可能需要一系列的附加参数,每个参数包含了一个要被插入的值,替换了 format 参数中指定的每个 % 标签。参数的个数应与 % 标签的个数相同。
返回值
如果成功,该函数返回成功匹配和赋值的个数。如果到达文件末尾或发生读错误,则返回 EOF。
使用实例
🎅格式化输出函数 fprintf
描述
C 库函数 int fprintf(FILE *stream, const char *format, …) 发送格式化输出到流 stream 中。
声明
int fprintf(FILE *stream, const char *format, ...)
参数
stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
format – 这是 C 字符串,包含了要被写入到流 stream 中的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。format 标签属性是 %[flags][width][.precision][length]specifier,具体讲解如下:
附加参数 – 根据不同的 format 字符串,函数可能需要一系列的附加参数,每个参数包含了一个要被插入的值,替换了 format 参数中指定的每个 % 标签。参数的个数应与 % 标签的个数相同。
返回值
如果成功,则返回写入的字符总数,否则返回一个负数。
使用实例
🎅二进制输入 fread
描述
C 库函数 size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) 从给定流 stream 读取数据到 ptr 所指向的数组中。
声明
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
参数
ptr – 这是指向带有最小尺寸 size*nmemb 字节的内存块的指针。
size – 这是要读取的每个元素的大小,以 字节为单位 。
nmemb – 这是元素的个数,每个元素的大小为 size 字节。
stream – 这是 指向 FILE 对象的指针 ,该 FILE 对象 指定了一个输入流。
返回值
成功读取的元素总数会以 size_t 对象返回,size_t 对象是一个整型数据类型。如果总数与 nmemb 参数不同,如果返回 0 则可能发生了一个错误或者到达了文件末尾。
使用实例
🎅二进制输出 fwrite
描述
C 库函数 size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) 把 ptr 所指向的数组中的数据写入到给定流 stream 中。
声明
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
参数
ptr – 这是 指向要被写入的元素数组的指针。
size – 这是 要被写入的每个元素的大小,以字节为单位。
nmemb – 这是元素的个数,每个元素的 大小为 size 字节。
stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。
返回值
如果成功,该函数返回一个 size_t 对象,表示元素的 总数,该对象是一个整型数据类型。如果该数字与 nmemb 参数不同,则会显示一个错误。
使用实例
🎄对比一组函数
🎄文件的随机读写
再说接下来的操作之前,先明确一个东西:
文件偏移量(刚打开的文件偏移量为0)
🎅fseek
描述
C 库函数 int fseek(FILE *stream, long int offset, int whence) 设置流 stream 的文件位置为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数。
声明
int fseek(FILE *stream, long int offset, int whence)
参数
stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
offset – 这是相对 whence 的偏移量,以 字节为单位。
whence – 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:
返回值
如果成功,则该函数返回零,否则返回非零值。
使用实例
🎅ftell
描述
C 库函数 long int ftell(FILE *stream) 返回给定流 stream 的当前文件位置。
声明
long int ftell(FILE *stream)
参数
stream– 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
返回值
该函数返回位置标识符的当前值。如果发生错误,则返回 -1L,全局变量 errno 被设置为一个正值。
使用实例
🎅rewind
描述
C 库函数 void rewind(FILE *stream) 设置文件位置为给定流 stream 的文件的开头。
声明
void rewind(FILE *stream)
参数
stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
返回值
该函数 不返回任何值。
使用实例
🎄文件结束的判定
❗❗❗❗❗被错误使用的 feof
牢记:在文件读取过程中,不能 用feof函数的返回值直接用来判断文件的是否结束。
而是应用于当文件读取结束的时候,判断是读取失败结束,还是遇到文件尾结束。
🐂1. 文本文件读取是否结束,判断返回值是否为EOF (fgetc),或NULL(fgets)
例如:
fgetc判断是否为EOF.
fgets判断返回值是否为NULL.
🐂2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
例如:
fread判断返回值是否小于实际要读的个数。
正确的使用:
1.文本文件的例子:
2.二进制文件的例子:
🐂🐸通讯录高阶版(动态内存+自定义类型+文件)
contact.c
#include "contact.h" //静态初始化 //void InitContact(struct Contact* pc) //{ // pc->sz = 0;//默认没有信息 // memset(pc->data, 0, MAX*sizeof(struct PeoInfo)); // memset(pc->data, 0, sizeof(pc->data)); //} void CheckCapacity(struct Contact* pc) { if (pc->sz == pc->capacity) { struct PeoInfo* ptr = (struct PeoInfo*)realloc(pc->data, (pc->capacity + 2) * sizeof(struct PeoInfo)); if (ptr != NULL) { pc->data = ptr; pc->capacity += 2; printf("增容成功\n"); } else { printf("增容失败\n"); return; } } } //加载数据 void LoadContact(struct Contact* pc) { FILE*pf = fopen("contact.txt", "r"); if (NULL == pf) { perror("LoadContact::fopen"); return; } struct PeoInfo temp = { 0 }; while(EOF != fscanf(pf, "%s %d %s %s %s", temp.name, &temp.age, temp.sex, temp.tele, temp.addr)) { CheckCapacity(pc); pc->data[pc->sz] = temp; pc->sz++; } fclose(pf); pf = NULL; } //动态初始化 void InitContact(struct Contact* pc) { pc->sz = 0; pc->data = (struct PeoInfo*)malloc(DEFAULT_SZ * sizeof(struct PeoInfo)); pc->capacity = DEFAULT_SZ;//初始最大容量为3 //加载文件信息 LoadContact(pc); } //静态添加 //void AddContact(struct Contact* pc) //{ // if (pc->sz == MAX) // { // printf("通讯录满了\n"); // } // else // { // printf("请输入名字:>"); // scanf_s("%s", pc->data[pc->sz].name, 30); // printf("请输入年龄:>"); // scanf_s("%d", &(pc->data[pc->sz].age)); // printf("请输入性别:>"); // scanf_s("%s", pc->data[pc->sz].sex, 5); // printf("请输入电话:>"); // scanf_s("%s", pc->data[pc->sz].tele, 12); // printf("请输入地址:>"); // scanf_s("%s", pc->data[pc->sz].addr, 30); // // // printf("添加成功\n"); // pc->sz++; // ShowContact(pc); // } //} //动态添加 void AddContact(struct Contact* pc) { if (pc->sz == pc->capacity) { struct PeoInfo* ptr = (struct PeoInfo*)realloc(pc->data, (pc->capacity + 2) * sizeof(struct PeoInfo)); if (ptr != NULL) { pc->data = ptr; pc->capacity += 2; printf("增容成功\n"); } else { printf("增容失败\n"); return; } } //录入新增人的信息 printf("请输入名字:>"); scanf_s("%s", pc->data[pc->sz].name, 30); printf("请输入年龄:>"); scanf_s("%d", &(pc->data[pc->sz].age)); printf("请输入性别:>"); scanf_s("%s", pc->data[pc->sz].sex, 5); printf("请输入电话:>"); scanf_s("%s", pc->data[pc->sz].tele, 12); printf("请输入地址:>"); scanf_s("%s", pc->data[pc->sz].addr, 30); printf("添加成功\n"); pc->sz++; ShowContact(pc); } void DeletContact(struct Contact* pc) { printf("请输入需要删除的联系人姓名\n"); char name[30] = "0"; scanf_s("%s", name, 30); for (int i = 0; i < pc->sz; i++) { if (strcmp(name, pc->data[i].name) == 0) { for (int j = i; j < pc->sz-1; j++) { strcpy_s(pc->data[j].name, 30, pc->data[j + 1].name); strcpy_s(pc->data[j].sex, 5, pc->data[j + 1].sex); strcpy_s(pc->data[j].tele, 12, pc->data[j + 1].tele); strcpy_s(pc->data[j].addr, 30, pc->data[j + 1].addr); pc->data[j].age = pc->data[j + 1].age; } printf("删除成功\n"); (pc->sz)--; ShowContact(pc); } } } void ModifyContact(struct Contact* pc) { printf("请输入需要修改的联系人姓名\n"); char name[30] = "0"; scanf_s("%s", name, 30); for (int i = 0; i < pc->sz; i++) { if (strcmp(name, pc->data[i].name) == 0) { printf("请输入名字:>"); scanf_s("%s", pc->data[i].name, 30); printf("请输入年龄:>"); scanf_s("%d", &(pc->data[i].age)); printf("请输入性别:>"); scanf_s("%s", pc->data[i].sex, 5); printf("请输入电话:>"); scanf_s("%s", pc->data[i].tele, 12); printf("请输入地址:>"); scanf_s("%s", pc->data[i].addr, 30); printf("修改成功!\n"); ShowContact(pc); } } } void ShowContact(struct Contact* pc) { int i = 0; printf("序号\t%10s\t%10s\t%8s\t%15s\t%30s\n", "name", "age", "sex", "tele", "addr"); for (i = 0; i < pc->sz ; i++) { //打印每一个数据 printf("%d\t%10s\t%10d\t%8s\t%15s\t%30s\n", i + 1, pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr); } } void SearchContact(struct Contact* pc) { printf("请输入需要搜索的联系人姓名\n"); char name[30] = "0"; scanf_s("%s", name, 30); for (int i = 0; i < pc->sz; i++) { if (strcmp(name, pc->data[i].name) == 0) { printf("序号\t%10s\t%10s\t%8s\t%15s\t%30s\n", "name", "age", "sex", "tele", "addr"); printf("%d\t%10s\t%10d\t%8s\t%15s\t%30s\n", i + 1, pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr); return; } } printf("找不到联系人信息\n"); } void SortContact(struct Contact* pc) { struct PeoInfo temp; for (int j = 0; j < pc->sz - 1; j++) for (int i = 0; i < pc->sz - 1 - j; i++) { if (strcmp(pc->data[i].name, pc->data[i + 1].name) > 0) { temp = pc->data[i + 1]; pc->data[i + 1] = pc->data[i]; pc->data[i] = temp; } } ShowContact(pc); } void DestroyContact(struct Contact* pc) { free(pc->data); pc->data = NULL; pc->capacity = 0; pc->sz = 0; } void SaveContact(struct Contact* pc) { FILE* pf = fopen("contact.txt", "w"); if (NULL == pf) { perror("SaveContact::fopen"); return; } int i = 0; for (i = 0; i < pc->sz; i++) { fprintf(pf, "%s %d %s %s %s\n", pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr); } fclose(pf); pf = NULL; }
contact.h
#pragma once #define _CRT_SECURE_NO_WARNINGS #define NAME_MAX 30 #define SEX_MAX 5 #define TELE_MAX 12 #define ADDR_MAX 30 #define MAX 1000 #define DEFAULT_SZ 3 //默认大小为3 #include <string.h> #include <stdio.h> #include <string.h> #include <stdlib.h> //创建枚举变量 enum Option { EXIT, ADD, DEL, SEARCH, MODIFY, SHOW, SORT }; //描述人的信息 struct PeoInfo { char name[NAME_MAX]; int age; char sex[SEX_MAX]; char tele[TELE_MAX]; char addr[ADDR_MAX]; }; //通讯录-静态版本 //struct Contact //{ // struct PeoInfo data[MAX];//1000个人的数据存放在data数组中 // int sz;//记录当前通讯录有效信息的个数 //}; //动态增长的版本 struct Contact { struct PeoInfo* data; int sz;//通讯录中当前有效元素的个数 int capacity;//通讯录的当前最大容量 }; //初始化通讯录 void InitContact(struct Contact* pc); //增加联系人 void AddContact(struct Contact* pc); //删除联系人 void DeletContact(struct Contact* pc); //修改联系人信息 void ModifyContact(struct Contact* pc); //搜索联系人信息 void SearchContact(struct Contact* pc); //显示所有的联系人 void ShowContact(struct Contact* pc); //按姓氏排序联系人信息 void SortContact(struct Contact* pc); //销毁通讯录 void DestroyContact(struct Contact* pc); //保存通讯录信息 void SaveContact(struct Contact* pc); //加载文件信息 void LoadContact(struct Contact* pc); //增容 void CheckCapacity(struct Contact* pc);
通讯录(动态内存版本).c
#include "contact.h" void menu() { printf("******************************\n"); printf("**** 1. 添加 2. 删除 **\n"); printf("**** 3. 搜索 4. 修改 **\n"); printf("**** 5. 展示全部 6. 排序 **\n"); printf("**** 0. 退出 **\n"); printf("******************************\n"); } int main() { int input = 0; //创建一个通讯录 struct Contact con; //初始化通讯录 InitContact(&con); do { menu(); printf("请选择:>"); scanf_s("%d", &input); switch (input) { case ADD: AddContact(&con); break; case DEL: DeletContact(&con); break; case SHOW: ShowContact(&con); break; case MODIFY: ModifyContact(&con); break; case SEARCH: SearchContact(&con); break; case SORT: SortContact(&con); break; case EXIT: //销毁通讯录 SaveContact(&con); DestroyContact(&con); printf("保存成功\n退出通讯录\n"); break; default: printf("选择错误\n"); break; } } while (input); return 0; }