--- title: "12-c与文件读写" created: 2025-11-25 tags: - 项目筑基 --- # c与文件读写 对于通讯录系统进行一个改进 加上文件读写功能 成为真正意义上的通讯录 两种思路 增删改查分别对文件内容进行改写 代码工程量可能很大 但是程序执行起来需要改写的地方会少一些 对于数据量庞大的项目可以采用这种 但是一般都用连接数据库了 之前试过的连接mysql已经可以实现这种操作 就不再多此一举了 本来文件读写就是了解性的东西 实际用不太到了 然后一种思路就是后面摸索出来发现前面做了很多无用功 其实对于这种通讯录啊什么的 这种小数据的东西 直接在程序执行前 将文件里的内容全部读入到链表当中 然后再在链表中完成所有操作 最后将文件原有内容清空 用处理后的数据覆盖上去就OK了 对于之前的程序基本没什么改变 就是添加了一个读模块和写模块 接下来分析实现 ### 准备 ```c #define _CRT_SECURE_NO_WARNINGS ``` 防止c语法中的文件读写函数报错 ### 打开 首先在主函数中 ![[cfile-image1.png]] ```c FILE* fp; ``` 变量 fp 是一个指向 FILE 结构体的指针,用于在代码中操作文件。在这里,fp 被赋值为 fopen() 函数的返回值,即文件句柄。如果文件打开成功,fp 将指向打开的文件;否则它将为 NULL。 ```c (fp = fopen("text1.txt", "r")) == NULL ``` 尝试打开名为 "text1.txt" 的文件,并将其句柄存储在变量 fp 中。函数 fopen() 的第一个参数是文件名,第二个参数是文件打开模式。在这里,模式是 "r",表示以只读模式打开文件。 如果 fopen() 函数返回值为 NULL,则说明文件打开失败,即该文件不存在或无法被打开。因此,代码使用等号运算符将 fp 的值与 NULL 进行比较,以检查文件是否已经成功打开。如果 fp 等于 NULL,意味着文件打开失败,条件表达式的结果为真,程序将进入 if 代码块执行创建文件的操作。否则,程序将跳过该 if 代码块,继续向下执行。 如果打开失败 有可能是因为该文件不存在 所以就调用CreateFile();函数去生成一个新文件 ```c void CreateFile() { FILE* fp = NULL; if ((fp = fopen("text1.txt", "w")) == NULL) { printf("无法创建文件!\n"); exit(1); } else { printf("文件创建成功!\n"); } fclose(fp); } ``` 定义一个 FILE 类型的指针变量 fp,并将其初始化为 NULL。 使用fopen()函数以写入模式打开文件"text1.txt" 将其句柄存储在变量 fp 中 如果打开文件失败,则会打印错误消息并调用 exit() 函数以终止程序。 如果文件打开成功,函数打印一条成功创建文件的消息 并使用 fclose() 函数关闭文件 (以w模式即写入模式打开文件有创建效果 然后如果以w模式一打开就关闭 就会清除文件内所有数据 之后的清除操作原理也是如此 还有一个a模式 追加模式 可以理解为 w就是清空重新写 不写就等于清空 a是不清空 在原有数据后面继续加数据) 然后如果创建了该文件 再重新打开 还是失败的话 就是未知原因了 等待报错就好了 #### fopen() ![[cfile-image2.png]] 第一个参数 filename 是一个字符串,表示要打开的文件的路径和文件名。如果文件位于当前目录下,则可以直接使用文件名,例如:"myfile.txt"。如果文件位于其他目录下,则需要使用文件的完整路径,例如:"C:\mydir\myfile.txt"(或者"C:\\mydir\\myfile.txt" 需要转义字符) 第二个参数 mode 是一个字符串,表示打开文件的模式。 常用的模式有以下几种: "r":以只读模式打开文件。如果文件不存在,则打开失败。 "w":以写入模式打开文件。如果文件不存在,则创建文件。如果文件已经存在,则清空文件中原有的数据。 "a":以追加模式打开文件。如果文件不存在,则创建文件。如果文件已经存在,则将新数据添加到文件末尾。 "rb"、"wb"、"ab":以二进制模式打开文件。在 Windows 系统中,使用二进制模式打开文件时需要指定 "b" 模式。 fopen() 函数返回一个指向 FILE 结构体的指针。如果打开文件失败,则返回 NULL。在使用完文件后,需要使用 fclose() 函数关闭文件,以释放文件资源。 ```c FILE *fp = fopen("myfile.txt", "r"); if (fp == NULL) { // 处理文件打开失败的情况 } fclose(fp); ``` ### 读入链表 ![[cfile-image3.png]] 跟着思路来 打开了文件之后 将文件里的内容全部读入到链表当中 然后利用链表去进行一系列操作 ```c void ReadFile(List *pList) { ``` 使用fopen函数打开文件text1.txt 以只读方式读取 返回一个指向FILE结构的指针fp 如果打开失败 则输出一条错误信息并退出程序 ```c FILE* fp; if ((fp = fopen("text1.txt", "r")) == NULL) { printf("打开文件失败!\n"); exit(0); } Node* p = NULL; ``` 使用fgets函数读取一行数据 将数据存储在buf数组中 每次最多读取1024个字符 直到读到文件末尾为止 如果读取成功 则进入循环体 否则退出循环 ```c char buf[1024]; // 用于存储每一行读入的数据 while (fgets(buf, 1024, fp) != NULL) { ``` 使用strtok函数将每一行数据解析成五个子串 分别对应姓名、性别、年龄、电话、地址 然后将这些子串存储到一个节点中 该节点的person字段为一个结构体 包含姓名、性别、年龄、电话、地址五个字段 ```c p = new Node; char* token = strtok(buf, " "); p->person.m_Name = token; token = strtok(NULL, " "); p->person.m_Sex = atoi(token); token = strtok(NULL, " "); p->person.m_Age = atoi(token); token = strtok(NULL, " "); p->person.m_Phone = token; token = strtok(NULL, "\n"); p->person.m_Addr = token; if (pList->head == NULL) { pList->head = p; pList->nail = p; } else { pList->nail->next = p; pList->nail = p; } } pList->nail->next = NULL; } ``` 从文件 "text1.txt" 中读取数据,并将其添加到链表 pList 中 打开文件 "text1.txt" 并使用 fgets() 函数逐行读取文件,每次读取一行数据并将其存储在缓冲区 buf 中 然后,函数将从缓冲区中提取每个字段,并将这些字段的值存储在一个新的节点 p 中,然后将这个节点添加到链表 pList 中。最后,函数将链表的尾指针 nail 指向最后一个节点。 需要注意的是,这个函数假设文件中的每一行数据都由五个字段组成,这些字段用空格分隔。然而,这个函数并没有检查文件格式是否正确,如果文件格式不正确,那么函数可能会读取到错误的数据,从而导致程序出现错误。因此,如果你想要使用这个函数来读取数据,需要确保文件的格式正确。同时,在读取完文件后,需要关闭文件以释放文件资源,可以使用 fclose() 函数来关闭文件 #### fgets() 从文件中读取一行数据 ![[cfile-image4.png]] 第一个参数 str 是一个字符数组,用于存储读取的数据。 第二个参数 n 是一个整数,表示要读取的数据的最大长度。 第三个参数 stream 是一个指向已打开的文件的指针。 fgets() 函数每次读取一行数据,并将其存储在 str 中。 如果读取成功,则返回 str 指向的字符串的地址。 如果已经到达文件末尾,则返回 NULL。 ![[cfile-image5.png]] 在代码中,fgets(buf, 1024, fp) 表示从已打开的文件 fp 中读取一行数据,并将其存储在 buf 中。如果读取成功,则继续执行循环体中的代码;如果已经到达文件末尾,则跳出循环。因此,fgets(buf, 1024, fp) != NULL 的意思是,只要还能从文件中读取一行数据,就继续执行循环体中的代码。 #### strtok() 将字符串分割成子字符串 ![[cfile-image6.png]] str 是待分割的字符串,delim 是一个包含分割符的字符串。 在第一次调用时,strtok() 函数会将 str 中的第一个子字符串返回,并将 str 中的指针指向下一个子字符串的起始位置。在后续调用时,可以将 str 参数设置为 NULL,strtok() 函数会从上一次调用的位置继续分割字符串。 在代码中,strtok(buf, " ") 表示从 buf 中以空格为分隔符分割出第一个子字符串,并返回其指针。 第二个参数 " " 表示用空格作为分隔符,即将 buf 中的字符串按空格分割成若干个子字符串。 通过多次调用 strtok() 函数,可以将每一行数据分割成五个字段,并分别赋值给结构体 Person 的成员变量。 还能用其他符号: ```c char str[] = "Hello, world! How are you?"; ``` ![[cfile-image7.png]] ```c char* token = strtok(str, " ,!"); while (token != NULL) { printf("%s\n", token); token = strtok(NULL, " ,!"); } ``` 将字符串"Hello, world! How are you?"按照空格和逗号进行分解,并输出每个token ### 清空文件 ![[cfile-image8.png]] 在结束程序时 要把文件中原来的数据清空 然后写入这次执行完成后得到的数据 首先就得清空 ```c //使用文件操作中的"w"模式打开文件,然后立即关闭文件,这样就会清空文件内容 void ClearFile(const char* filename) { FILE* fp = fopen(filename, "w"); if (fp == NULL) { printf("打开文件失败!\n"); exit(0); } fclose(fp); } ``` 原理前面讲过 不再重复 ### 写入文件 ```c void WriteFile(List* pList) { FILE* fp; if ((fp = fopen("text1.txt", "w")) == NULL) { cout << "文件打开失败!" << endl; exit(0); } Node* p = pList->head->next; while (p) { fprintf(fp, "%s %d %d %s %s\n", p->person.m_Name.c_str(), p->person.m_Sex, p->person.m_Age, p->person.m_Phone.c_str(), p->person.m_Addr.c_str()); p = p->next; } fclose(fp); } ``` 将链表中的每个节点的信息按照指定格式写入文件中。 使用fopen()函数打开文件并以写的方式打开 使用fprintf()函数将每个节点中的信息格式化写入文件中,最后关闭文件。 在while循环中 首先使用fprintf()函数将节点中的姓名、性别、年龄、电话、地址按照指定格式写入文件中,使用c_str()函数将string类型转换为C风格字符串 将指针p指向下一个节点,继续写入文件。 最后,使用fclose()函数关闭文件。 #### c_str() C++标准库中的一个函数,可以将string类型的字符串转换为C风格的字符串(即以'\0'结尾的char数组),用于与C语言库函数交互。 返回const char*指针,指向string对象中的字符数组,注意是const指针,因此不能修改c_str()返回的C字符串中的字符,否则会导致未定义的行为。 ### 代码 整体逻辑和实现分析完毕 以下是完整代码 ```c #define _CRT_SECURE_NO_WARNINGS #include #include #include #include//解决system不明确问题 using namespace std; struct Person { string m_Name; int m_Sex; int m_Age; string m_Phone; string m_Addr; }; typedef struct addressbooks { Person person; struct addressbooks* next; }Node; typedef struct _list { Node* head; Node* nail; }List; void button(int x, int y, int w, int h, TCHAR* text) { setbkmode(TRANSPARENT); setfillcolor(BROWN); fillroundrect(x, y, x + w, y + h, 10, 10); settextstyle(30, 0, "黑体"); TCHAR text_[100]; for (int i = 0; i < 100; i++) text_[i] = text[i]; int tx = x + (w - textwidth(text)) / 2; int ty = y + (h - textheight(text)) / 2; outtextxy(tx, ty, text); } void initMenu() { initgraph(300, 600, EX_SHOWCONSOLE); setbkcolor(RGB(77, 95, 107)); cleardevice(); IMAGE image; loadimage(&image,"星之卡比.jpg"); putimage(0, 0, &image); TCHAR A[] = "显示联系人"; TCHAR B[] = "查询联系人"; TCHAR C[] = "添加联系人"; TCHAR D[] = "关闭通讯录"; button(50, 125, 200, 50, A); button(50, 225, 200, 50, B); button(50, 325, 200, 50, C); button(50, 425, 200, 50, D); } void InitList(List* pList) { Node* L = new Node; pList->head = L; pList->nail = pList->head; pList->nail->next = NULL; } void addPerson(List* plist) { Node* p = new Node; string name; cout << "请输入姓名:" << endl; cin >> name; p->person.m_Name = name; cout << "请输入性别:(1男2女)" << endl; int sex = 0; while (true) { cin >> sex; if (sex == 1 || sex == 2) { p->person.m_Sex = sex; break; } cout << "输入有误,请重新输入" << endl; } cout << "请输入年龄:" << endl; int age = 0; while (true) { cin >> age; if (age >= 0 && age <= 100) { p->person.m_Age = age; break; } cout << "输入有误,请重新输入" << endl; } cout << "请输入联系电话:" << endl; string phone; cin >> phone; p->person.m_Phone = phone; cout << "请输入家庭住址:" << endl; string address; cin >> address; p->person.m_Addr = address; p->next = NULL; plist->nail->next = p; plist->nail = p; cout << "添加成功" << endl; system("pause"); system("cls"); } void showPerson(List* pList) { if (pList->head->next == 0) { cout << "当前记录为空" << endl; } else { Node* p; for (p = pList->head->next; p; p = p->next) { cout << "姓名:" << p->person.m_Name << "\t\t"; cout << "性别:" << (p->person.m_Sex == 1 ? "男" : "女") << "\t\t"; cout << "年龄:" << p->person.m_Age << "\t\t"; cout << "电话:" << p->person.m_Phone << "\t\t"; cout << "住址:" << p->person.m_Addr << endl; } } system("pause"); system("cls"); } Node* isExist(List* plist, string name) { Node* p; p = plist->head->next; while (p && p->person.m_Name != name) { p = p->next; } if (p == NULL) { cout << "查无此人" << endl; } return p; } void show(Node* p) { cout << "姓名:" << p->person.m_Name << "\t\t"; cout << "性别:" << (p->person.m_Sex == 1 ? "男" : "女") << "\t\t"; cout << "年龄:" << p->person.m_Age << "\t\t"; cout << "电话:" << p->person.m_Phone << "\t\t"; cout << "住址:" << p->person.m_Addr << endl; } void showmodify() { cout << "*********************" << endl; cout << "*****(1)修改姓名*****" << endl; cout << "*****(2)修改性别*****" << endl; cout << "*****(3)修改年龄*****" << endl; cout << "*****(4)修改电话*****" << endl; cout << "*****(5)修改住址*****" << endl; cout << "*********************" << endl; } void modifyPerson(List* plist,string name) { Node* p, * q, * x; x = isExist(plist, name); if (x) { for (q = plist->head->next; q; q = q->next) { if (q->person.m_Name == name) { show(q); } } showmodify(); int button = 0; cin >> button; for (p = plist->head->next; p; p = p->next) { if (p == x) { switch (button) { case 1: { string name; cout << "请输入姓名:" << endl; cin >> name; p->person.m_Name = name; break; } case 2: { cout << "请输入性别:(1男2女)" << endl; int sex = 0; while (true) { cin >> sex; if (sex == 1 || sex == 2) { p->person.m_Sex = sex; break; } cout << "输入有误,请重新输入" << endl; } break; } case 3: { cout << "请输入年龄:" << endl; int age = 0; while (true) { cin >> age; if (age >= 0 && age <= 100) { p->person.m_Age = age; break; } cout << "输入有误,请重新输入" << endl; } break; } case 4: { cout << "请输入联系电话" << endl; string phone; cin >> phone; p->person.m_Phone = phone; break; } case 5: { cout << "请输入家庭住址" << endl; string address; cin >> address; p->person.m_Addr = address; break; } } } } cout << "修改成功!" << endl; } system("pause"); system("cls"); } void deletePerson(List* plist,string name) { Node* p, * q, * x; x = isExist(plist, name); if (x) { for (q = plist->head, p = plist->head->next; p; q = p, p = p->next) { if (p->person.m_Name == name) { q->next = p->next; delete(p); cout << "删除成功" << endl; break; } } } system("pause"); system("cls"); } void UDMenu() { cleardevice(); setbkcolor(RGB(77, 95, 107)); cleardevice(); IMAGE image; loadimage(&image, "卡比2.jpg"); putimage(0, 0, &image); TCHAR A[] = "修改联系人"; TCHAR B[] = "删除联系人"; TCHAR C[] = "确定"; button(50, 175, 200, 50, A); button(50, 275, 200, 50, B); button(50, 375, 200, 50, C); } void operate(List* plist,string name) { ExMessage msg; while (true) { if (peekmessage(&msg, EX_MOUSE)) { switch (msg.message) { case WM_LBUTTONDOWN: { if (msg.x >= 50 && msg.x <= 250 && msg.y >= 175 && msg.y <= 225) {//修改联系人 modifyPerson(plist, name); break; } if (msg.x >= 50 && msg.x <= 250 && msg.y >= 275 && msg.y <= 325) {//删除联系人 deletePerson(plist, name); break; } if (msg.x >= 50 && msg.x <= 250 && msg.y >= 375 && msg.y <= 425) { initMenu(); return; } } } } } } void findPerson(List* plist) { cout << "请输入要查找的联系人:" << endl; string name; cin >> name; Node* p, * x; x = isExist(plist, name); if (x) { for (p = plist->head->next; p; p = p->next) { if (p->person.m_Name == name) { show(p); cout << "是否要进行修改、删除操作?" << endl; UDMenu(); operate(plist,name); break; } } } system("pause"); system("cls"); } void certain() { cout << "是否要清空通讯录记录?" << endl; cout << "***************" << endl; cout << "****(0)取消****" << endl; cout << "****(1)确定****" << endl; cout << "***************" << endl; } void CreateFile() { FILE* fp = NULL; if ((fp = fopen("text1.txt", "w")) == NULL) { printf("无法创建文件!\n"); exit(1); } else { printf("文件创建成功!\n"); } fclose(fp); } void ReadFile(List *pList) { FILE* fp; if ((fp = fopen("text1.txt", "r")) == NULL) { printf("打开文件失败!\n"); exit(0); } Node* p = NULL; char buf[1024]; // 用于存储每一行读入的数据 while (fgets(buf, 1024, fp) != NULL) { p = new Node; char* token = strtok(buf, " "); p->person.m_Name = token; token = strtok(NULL, " "); p->person.m_Sex = atoi(token); token = strtok(NULL, " "); p->person.m_Age = atoi(token); token = strtok(NULL, " "); p->person.m_Phone = token; token = strtok(NULL, "\n"); p->person.m_Addr = token; if (pList->head == NULL) { pList->head = p; pList->nail = p; } else { pList->nail->next = p; pList->nail = p; } } pList->nail->next = NULL; } void WriteFile(List* pList) { FILE* fp; if ((fp = fopen("text1.txt", "w")) == NULL) { cout << "文件打开失败!" << endl; exit(0); } Node* p = pList->head->next; while (p) { fprintf(fp, "%s %d %d %s %s\n", p->person.m_Name.c_str(), p->person.m_Sex, p->person.m_Age, p->person.m_Phone.c_str(), p->person.m_Addr.c_str()); p = p->next; } fclose(fp); } //使用文件操作中的"w"模式打开文件,然后立即关闭文件,这样就会清空文件内容 void ClearFile(const char* filename) { FILE* fp = fopen(filename, "w"); if (fp == NULL) { printf("打开文件失败!\n"); exit(0); } fclose(fp); } void Close(List* plist) { certain(); int button = 0; cin >> button; if (button == 1) { Node* p, * q; p = plist->head->next; while (p) { q = p->next; delete(p); p = q; } plist->head->next = NULL; plist->nail = plist->head; ClearFile("text1.txt"); cout << "通讯录已清空,成功退出" << endl; } else if (button == 0) { ClearFile("text1.txt"); WriteFile(plist); cout << "成功退出" << endl; } system("pause"); system("cls"); } int main() { List abs; InitList(&abs); initMenu(); FILE* fp; // 打开文件 if ((fp = fopen("text1.txt", "r")) == NULL) { CreateFile(); // 如果文件不存在,则创建文件 fp = fopen("text1.txt", "r"); // 重新打开文件 if (fp == NULL) { printf("无法打开文件!\n"); exit(1); } } //读入文件 ReadFile(&abs); ExMessage msg; while (true) { if (peekmessage(&msg, EX_MOUSE)) { switch (msg.message) { case WM_LBUTTONDOWN: { if (msg.x >= 50 && msg.x <= 250 && msg.y >= 125 && msg.y <= 175) {//显示联系人 showPerson(&abs); break; } if (msg.x >= 50 && msg.x <= 250 && msg.y >= 225 && msg.y <= 275) {//查询联系人(修改?删除?) findPerson(&abs); break; } if (msg.x >= 50 && msg.x <= 250 && msg.y >= 325 && msg.y <= 375) { addPerson(&abs); break; } if (msg.x >= 50 && msg.x <= 250 && msg.y >= 425 && msg.y <= 475) { Close(&abs); goto END; } } } } } END: cout << "欢迎下次使用" << endl; getchar(); closegraph(); return 0; } ``` 注意 图片需要自行添加到同级目录 如果在本地电脑的vs 就添加到这个项目文件夹里面 ![[cfile-image9.png]] (txt文件也会在这里) 如果是打包发人 就需要放在exe同级文件中 ![[cfile-image10.png]] ## 附件 > 📄 c与文件读写.docx(原 word 笔记):[[c与文件读写.docx|c与文件读写.docx]] --- ⬅️ [[11-C语言|C语言]] 🏠 [[00-编程语言|00-编程语言]] ➡️ [[13-C语言与MySQL连接|C语言与MySQL连接]]