目录
设计思路
代码实现
代码改造1
代码改造2
完整代码
代码仓库
设计思路
1. 通讯录存放的信息
这个通讯录保存的信息包括:名字,年龄,性别,电话,住址。
2. 通讯录的功能
1. 通讯录可以存放100个人的信息。
2. 增加联系人
3. 删除联系人
4. 修改联系人
5. 查询联系人
6. 显示所有人
3. 文件规划
我们准备三个文件完成这个项目。
test.c —— 负责测试
contact.h —— 负责函数和类型声明
contact.c —— 负责函数实现
4. 设计思路
1. 通讯录保存的信息有很多,我们可以设计一个结构体来存放这些信息。
2. 通讯录要存放100个人的信息,那我们可以创建一个存放这个结构体的数组。
代码实现
1. 设计存放信息的结构体
1. 我们先设计存放信息的结构体,因为这个结构体可能有多个文件会用到,所以我们统一放在头文件。
2. 名字,性别,电话,住址都是字符串所以用char数组,年龄用int。
3. 为了方便使用,用typedef重命名一下。
//通讯录保存的个人信息 typedef struct Info {char name[10]; //名字char sex[5]; //性别int age; //年龄char tele[12]; //电话char addr[20]; //住址 }Info;
4. 不同人有不同的个人信息,我们要把这些信息管理起来,所以我们要创建一个存放结构体的数组。
5. 我们还需要创建一个变量去记录数组元素的数量,以便于我们对数组信息的掌控。
6. 为了方便后续传参,我们再设计一个结构体把这两个变量作为结构体的成员。
//将Cont类型变量作为通讯录的基本单位 typedef struct Cont {Info data[100]; //存放个人信息结构体的数组int cout; //记录数组元素个数 }Cont;
2. 菜单实现
1. 设计一个菜单,利用do while 和 switch case配合,根据不同的功能选择不同的数字。
//菜单 void menu() {printf("-----------------------------\n");printf("--- 0.退出 --- 1.增加 -------\n");printf("-----------------------------\n");printf("--- 2.删除 --- 3.修改 -------\n");printf("-----------------------------\n");printf("--- 4.查询 --- 5.显示 -------\n");printf("-----------------------------\n"); }int main() {int input;do{menu();printf("请输入要执行的操作->");scanf("%d", &input);switch (input){case 0:printf("退出成功\n");break;case 1://增加break;case 2://删除break;case 3://修改break;case 4://查询break;case 5://显示break;default:printf("输入无效\n");break;}} while (input);return 0; }
3. 初始化通讯录
1. 初始化通讯录。利用memset将存放结构体的数组置0,计数也置为0。
void InitCont(Cont* cont) {//将数组置为0memset(cont, 0, sizeof(cont->data));//将计数置为0cont->cout = 0;return; }
4. 增加联系人
1. 先判断是否满了,满了就不能增加了。
2. 如果没满,输入信息,将计数作为数组下标存放,计数加1。
void AddInfo(Cont* cont) {if (cont->cout == DATA_MAX){printf("通讯录已满,不能新增\n");return;}//利用计数作为数组下标printf("姓名:");scanf("%s", cont->data[cont->cout].name);printf("性别:");scanf("%s", cont->data[cont->cout].sex);printf("年龄:");scanf("%d", &(cont->data[cont->cout].age));printf("电话:");scanf("%s", cont->data[cont->cout].tele);printf("住址:");scanf("%s", cont->data[cont->cout].addr);//增加一个元素,计数+1cont->cout++; }
5. 显示所有联系人
1. 不用修改数据所以参数可以加const。
2. 根据计数得到元素个数然后遍历打印信息。
3. %10s 表示输出宽度为10并且右对齐,%-10s 加个负号表示左对齐。
void ShowInfo(const Cont* cont) {printf("%-10s\t%-5s\t%-4s\t%-12s\t%-20s\n", "名字", "性别", "年龄", "电话", "住址");for (int i = 0; i < cont->cout; i++){printf("%-10s\t%-5s\t%-4d\t%-12s\t%-20s\n",cont->data[i].name,cont->data[i].sex,cont->data[i].age,cont->data[i].tele,cont->data[i].addr);} }
6. 删除联系人
1. 在实现删除之前我们先写一个另外的函数,这个函数是根据输入的姓名,返回它所对应的数组下标。
2. 将最后一个元素覆盖到要删除的地方,然后计数减减。
void DelInfo(Cont* cont) {printf("输入你要删除的名字->");char name[10];scanf("%s", name);//根据名字返回下标int index = FindByName(cont, name);//找不到if (index == -1){printf("没有这个人\n");return;}//找到了//将最后一个元素覆盖到要删除的地方,然后计数减减cont->data[index] = cont->data[cont->cout - 1];cont->cout--; }
7. 查询联系人
1. 查询不会修改数据所以参数可以加const。
2. 复用前面写的FindByName函数,通过名字返回下标,根据下标打印数据。
void SeleInfo(const Cont* cont) {printf("输入你要查询的名字->");char name[10];scanf("%s", name);int index = FindByName(cont, name);if (index == -1){printf("没有这个人\n");return;}printf("%-10s\t%-5s\t%-4s\t%-12s\t%-20s\n", "名字", "性别", "年龄", "电话", "住址");printf("%-10s\t%-5s\t%-4d\t%-12s\t%-20s\n",cont->data[index].name,cont->data[index].sex,cont->data[index].age,cont->data[index].tele,cont->data[index].addr); }
7. 修改联系人
1. 输入名字找到下标。
2. 利用scanf修改信息。
void ModInfo(Cont* cont) {printf("输入你要修改的名字->");char name[10];scanf("%s", name);int index = FindByName(cont, name);if (index == -1){printf("没有这个人\n");return;}//利用scanf修改信息printf("姓名:");scanf("%s", cont->data[index].name);printf("性别:");scanf("%s", cont->data[index].sex);printf("年龄:");scanf("%d", &(cont->data[index].age));printf("电话:");scanf("%s", cont->data[index].tele);printf("住址:");scanf("%s", cont->data[index].addr); }
代码改造1
随着动态内存管理的学习,我们可以对通讯录进行优化改造。
改造目标:
1. 通讯录的空间不要固定,大小是可以调整的。
2. 默认能存放3个人的信息,满了就每次增加2个人的空间。
.
第一步,
问题:数组大小写死了。
解决:将数组改成指针,这个指针指向动态开辟的空间。
问题:记录的变量只记录当前个数,不够。
解决:增加一个记录容量的变量。
typedef struct Cont {Info* data; //指向动态开辟的空间int cout; //记录当前元素个数int capa; //记录容量 }Cont;
第二步,
问题:结构体成员发生变化对应初始化函数也需要变化。
解决:指针指向malloc开辟的空间,容量初始化为3。
void InitCont(Cont* cont) {cont->capa = DEFAULT; cont->data = (Info*)malloc(DEFAULT*sizeof(Info));cont->cout = 0;return; }
第三步,
问题:原本的增加联系人函数放满就不能放了。
解决:增加一个检测容量的函数判断是否需要扩容,扩容考虑用realloc。
int CheckAdd(Cont* cont) {//比较当前个数与容量if (cont->cout == cont->capa) return 1;else return 0; }void AddInfo(Cont* cont) {if (CheckAdd(&cont)){//需要扩容Info* ptr = (Info*)realloc(cont->data, sizeof(Info)*(cont->capa+CHANGE));//扩容失败if (ptr == NULL){perror("realloc");return;}//扩容成功else{cont->data = ptr;cont->capa += CHANGE;}}//利用计数作为数组下标printf("姓名:");scanf("%s", cont->data[cont->cout].name);printf("性别:");scanf("%s", cont->data[cont->cout].sex);printf("年龄:");scanf("%d", &(cont->data[cont->cout].age));printf("电话:");scanf("%s", cont->data[cont->cout].tele);printf("住址:");scanf("%s", cont->data[cont->cout].addr);//增加一个元素,计数+1cont->cout++; }
第四步,
申请的空间记得释放。
void Des(Cont* cont) {free(cont->data);cont->data = NULL;cont->capa = cont->cout = 0; }
代码改造2
随着文件操作的学习,我们又可以对通讯录进行改造。
第一,我们想退出的时候把信息保存起来。
所以我们要保存通讯录信息到文件中。
void SaveCont(Cont* cont) {//打开文件FILE* pf = fopen("Cont.dat", "wb");if (pf == NULL){perror("fopen");return;}//将信息写入文件for(int i=0; i<cont->cout; i++) fwrite(cont->data + i, sizeof(Info), 1, pf);//关闭文件fclose(pf);pf = NULL; }
第二,下次运行程序的时候,在初始化的时候,我们可以把之前保存到文件的信息重新加载回通讯录中。
void LoadCont(Cont* cont) {//打开文件FILE* pf = fopen("Cont.dat", "rb");if (pf == NULL){perror("fopen");return;}//读文件Info tmp;while (fread(&tmp, sizeof(Info), 1, pf)){//赋值之前先判断有没有位置if (0 == CheckAdd(cont)) return;cont->data[cont->cout] = tmp;cont->cout++;}//关闭文件fclose(pf);pf = NULL; }void InitCont(Cont* cont) {assert(cont);cont->capa = DEFAULT; cont->data = (Info*)malloc(DEFAULT*sizeof(Info));if (cont->data == NULL){perror("malloc");return;}cont->cout = 0;//将文件信息加载到通讯录中LoadCont(cont);return; }
完整代码
1. contact.h
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>#define DATA_MAX 100
#define DEFAULT 3
#define CHANGE 2
//通讯录保存的个人信息
typedef struct Info
{char name[10]; //名字char sex[5]; //性别int age; //年龄char tele[12]; //电话char addr[20]; //住址
}Info;//将Cont类型变量作为通讯录的基本单位
//typedef struct Cont
//{
// Info data[DATA_MAX]; //存放个人信息结构体的数组
// int cout; //记录数组元素个数
//}Cont;
typedef struct Cont
{Info* data; //指向动态开辟的空间int cout; //记录当前元素个数int capa; //记录容量
}Cont;//初始化
void InitCont(Cont*);//增加联系人
void AddInfo(Cont*);//显示所有联系人
void ShowInfo(const Cont*);//删除联系人
void DelInfo(Cont*);//查询联系人
void SelInfo(const Cont*);//修改联系人
void ModInfo(Cont*);//释放空间
void Des(Cont*);//保存信息到文件中
void SaveCont(Cont*);
2. contact.c
#include "contact.h"//void InitCont(Cont* cont)
//{
// //将数组置为0
// memset(cont, 0, sizeof(cont->data));
// //将计数置为0
// cont->cout = 0;
//
// return;
//}
//将文件信息加载到通讯录中
int CheckAdd(Cont* cont);
void LoadCont(Cont* cont)
{//打开文件FILE* pf = fopen("Cont.dat", "rb");if (pf == NULL){perror("fopen");return;}//读文件Info tmp;while (fread(&tmp, sizeof(Info), 1, pf)){//赋值之前先判断有没有位置if (0 == CheckAdd(cont)) return;cont->data[cont->cout] = tmp;cont->cout++;}//关闭文件fclose(pf);pf = NULL;
}void InitCont(Cont* cont)
{assert(cont);cont->capa = DEFAULT; cont->data = (Info*)malloc(DEFAULT*sizeof(Info));if (cont->data == NULL){perror("malloc");return;}cont->cout = 0;//将文件信息加载到通讯录中LoadCont(cont);return;
}//void AddInfo(Cont* cont)
//{
// if (cont->cout == DATA_MAX)
// {
// printf("通讯录已满,不能新增\n");
// return;
// }
//
// //利用计数作为数组下标
// printf("姓名:");
// scanf("%s", cont->data[cont->cout].name);
// printf("性别:");
// scanf("%s", cont->data[cont->cout].sex);
// printf("年龄:");
// scanf("%d", &(cont->data[cont->cout].age));
// printf("电话:");
// scanf("%s", cont->data[cont->cout].tele);
// printf("住址:");
// scanf("%s", cont->data[cont->cout].addr);
//
// //增加一个元素,计数+1
// cont->cout++;
//}
//判断是否需要增容
int CheckAdd(Cont* cont)
{//当前个数和容量相等,需要增容if (cont->cout == cont->capa){Info* ptr = (Info*)realloc(cont->data, sizeof(Info) * (cont->capa + CHANGE));if (ptr == NULL) //增容失败{perror("realloc");return 0;}else //增容成功{cont->data = ptr;cont->capa += CHANGE;}}return 1;
}void AddInfo(Cont* cont)
{if(0 == CheckAdd(cont)) return; //增容失败等于0//利用计数作为数组下标printf("姓名:");scanf("%s", cont->data[cont->cout].name);printf("性别:");scanf("%s", cont->data[cont->cout].sex);printf("年龄:");scanf("%d", &(cont->data[cont->cout].age));printf("电话:");scanf("%s", cont->data[cont->cout].tele);printf("住址:");scanf("%s", cont->data[cont->cout].addr);//增加一个元素,计数+1cont->cout++;
}void ShowInfo(const Cont* cont)
{//打印提示行printf("%-10s\t%-5s\t%-4s\t%-12s\t%-20s\n", "名字", "性别", "年龄", "电话", "住址");//遍历数组打印信息for (int i = 0; i < cont->cout; i++){printf("%-10s\t%-5s\t%-4d\t%-12s\t%-20s\n",cont->data[i].name,cont->data[i].sex,cont->data[i].age,cont->data[i].tele,cont->data[i].addr);}
}//根据名字找到下标
int FindByName(const Cont* cont, char* name)
{//遍历数组for(int i=0; i<cont->cout; i++) if(strcmp(cont->data[i].name, name) == 0) return i;return -1;
}void DelInfo(Cont* cont)
{printf("输入你要删除的名字->");char name[10];scanf("%s", name);//根据名字返回下标int index = FindByName(cont, name);//找不到if (index == -1){printf("没有这个人\n");return;}//找到了//将最后一个元素覆盖到要删除的地方,然后计数减减cont->data[index] = cont->data[cont->cout - 1];cont->cout--;
}void SelInfo(const Cont* cont)
{printf("输入你要查询的名字->");char name[10];scanf("%s", name);int index = FindByName(cont, name);if (index == -1){printf("没有这个人\n");return;}printf("%-10s\t%-5s\t%-4s\t%-12s\t%-20s\n", "名字", "性别", "年龄", "电话", "住址");printf("%-10s\t%-5s\t%-4d\t%-12s\t%-20s\n",cont->data[index].name,cont->data[index].sex,cont->data[index].age,cont->data[index].tele,cont->data[index].addr);
}void ModInfo(Cont* cont)
{printf("输入你要修改的名字->");char name[10];scanf("%s", name);int index = FindByName(cont, name);if (index == -1){printf("没有这个人\n");return;}//利用scanf修改信息printf("姓名:");scanf("%s", cont->data[index].name);printf("性别:");scanf("%s", cont->data[index].sex);printf("年龄:");scanf("%d", &(cont->data[index].age));printf("电话:");scanf("%s", cont->data[index].tele);printf("住址:");scanf("%s", cont->data[index].addr);
}void Des(Cont* cont)
{free(cont->data);cont->data = NULL;cont->capa = cont->cout = 0;
}void SaveCont(Cont* cont)
{//打开文件FILE* pf = fopen("Cont.dat", "wb");if (pf == NULL){perror("fopen");return;}//将信息写入文件for(int i=0; i<cont->cout; i++) fwrite(cont->data + i, sizeof(Info), 1, pf);//关闭文件fclose(pf);pf = NULL;
}
3. test.c
#include "contact.h"//菜单
void menu()
{printf("-----------------------------\n");printf("--- 0.退出 --- 1.增加 -------\n");printf("-----------------------------\n");printf("--- 2.删除 --- 3.修改 -------\n");printf("-----------------------------\n");printf("--- 4.查询 --- 5.显示 -------\n");printf("-----------------------------\n");
}int main()
{Cont cont;InitCont(&cont); //初始化int input;do{menu();printf("请输入要执行的操作->");scanf("%d", &input);switch (input){case 0:SaveCont(&cont);Des(&cont);printf("退出成功\n");break;case 1:AddInfo(&cont);break;case 2:DelInfo(&cont);break;case 3:ModInfo(&cont);break;case 4:SelInfo(&cont);break;case 5:ShowInfo(&cont);break;default:printf("输入无效\n");break;}} while (input);return 0;
}
代码仓库
Contact/Contact · 林宇恒/code_c - 码云 - 开源中国 (gitee.com)