C语言实现通讯录,这里我使用多文件的形式,分别分为三个文件:
1.contact.h 这是头文件,用来包含其他头文件和进行函数声明以及宏定义。
2.main.c 主调函数,main函数的存在地方。
3.contact.c 提供通讯录的各种功能函数
下面来讲讲通讯录实现的基本思想,通讯录的实现肯定要用到结构体,因为一个人有很多信息,所以首先我们定义一个结构体struct person(名字可以改),用来存放单个人的信息,但是通讯录不止一个人,所以还得在定义一个结构体struct contact,用来存放整个通讯录的信息(人数和每个人的信息)。定义好后,就可以实现各种功能了,如:增、删、改、查、排序。以及将写好的通讯录保存在文件里。下面来实现整个通讯录。
1.contact.h
#pragma once//防止头文件被重复包含
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <windows.h>
#include <stdlib.h>
#include <string.h>
#define CAPA_CITY 3//初始化时,刚开始给的空间可以存放3个人
#define APP_VALUE 1//空间不够时每次可以追加存放1个人的空间
#define TARGET_FILE "contact.bin"//文件名
typedef struct person{//用来存放一个人的数据
char name[12];//姓名
char sex[12];//性别
int age;//年龄
char phone[12];//电话
char address[32];//地址
}struct_p;
typedef struct contact{//用来存放整个通讯录的信息
int cap;//通讯录中可以存放的最大人数
int size;//通讯录中实际存放的人数
struct_p people[0];//柔性数组,便于进行空间的扩容
}struct_c;
extern void initialize(struct_c **con);
extern void Freeupspace(struct_c **con);
extern void Add(struct_c **con);
extern void Delete(struct_c *con);
extern void Revise(struct_c *con);
extern void Inquire(con);
extern void Empty(struct_c *con);
extern void Show(struct_c *con);
extern void Sort(struct_c *con);
extern void SaveFile(struct_c *con);
使用柔性数组时要注意,此时数组的大小是0。使用柔性数组便于在空间不够时自动扩容。
2.main.c
#include "contact.h"
void Start(){
printf("#########################\n");
printf("##1.Add#######2.Delete###\n");
printf("##3.Revise####4.Inquire##\n");
printf("##5.Empty#####6.Show#####\n");
printf("##7.Sort######8.Quit#####\n");
printf("#########################\n");
}
int main(){
int select = 1;
struct_c *con;//首先定义一个结构体指针
initialize(&con);//进行初始化
while (select){//死循环,等待选择退出为止
Start();
int x = 0;
printf("请输入你的选择:\n");
scanf(" %d", &x);
switch (x){
case 1:
Add(&con);//添加联系人
break;
case 2:
Delete(con);//删除联系人
break;
case 3:
Revise(con);//修改某一个联系人
break;
case 4:
Inquire(con);//查找联系人
break;
case 5:
Empty(con);//清空所有联系人
break;
case 6:
Show(con);//显示全部联系人
break;
case 7:
Sort(con);//对全部联系人按照姓名进行排序(把姓名当做字符串)
break;
case 8:
select = 0;
SaveFile(con);//以二进制文件形式形式保存数据
printf("ByeBye\n");
break;
default:
printf("输入有误,请重新输入\n");
break;
}
}
Freeupspace(&con);//释放开辟的空间
system("pause");
return 0;
}
在主调函数中首先要用函数来初始化(在contact.c文件中再谈)。还要提供一个可以选择的页面,提示用户输入数字选择下一步要做什么,所以用到了while死循环和switch语句实现相应的功能。在每次退出时将数据保存到文件中。我们可以先编写完提示窗口(此时先不写函数的调用),先运行下程序出现如下结果然后在继续编写其他代码:
3.contact.c
在讲解这个文件之前我们先来看一段代码:
void GetMemory(char *p){
p = (char *)malloc(100);
}
int main(){
char *str=NULL;
GetMemory(str);
return 0;
}
这段代码是想在堆上开辟空间,然后让str指向这段空间。但是这里有一个非常大的bug,p是形参,p并不影响str,所以最终str并不能指向新开辟的这段空间。函数调用要发生形参实例化,形参的改变不影响实参,如果要影响实参,那么必须传入实参的地址。这里虽然传入的是指针,但是这里的指针此时就是实参,所以要传入指针str(实参)的地址,也就是&str。以上代码修改为:
void GetMemory(char **p){
*p = (char *)malloc(100);
}
int main(){
char *p = NULL;
GetMemory(&p);
return 0;
}
这样才是正确的结果。当然也可以通过修改返回值类型达到目的。在以下代码中,我会在函数中使用二级指针变量来完成初始化时空间的开辟以及空间的扩容。初始化通讯录以及实现各种功能的代码如下:
#include "contact.h"
void initialize(struct_c **con){//初始化
FILE *p = fopen(TARGET_FILE, "rb");//以二进制读的方式打开二进制文件
if (p == NULL){//打开失败,此时文件不存在,不用读取原始数据,直接在堆上开辟空间
*con = (struct_c *)malloc(sizeof(struct_c)+sizeof(struct_p)*CAPA_CITY);//因为柔性数组不占用空间,所以开辟空间时要加上sizeof(struct_p)*CAPA_CITY
if (con == NULL){//开辟空间失败,说明程序出现问题,此时退出
perror("malloc");
exit(1);
}
(*con)->cap = CAPA_CITY;//最多存放的人数
(*con)->size = 0;//一开始人数为0
printf("初始化成功!\n");
}
else{//文件存在,以读取文件方式打开,文件存在时初始化方案
struct_c t;//临时结构体,用来读出cap的值,来确定此时应该在堆上开辟多少空间
fread(&t, sizeof(struct_c), 1, p);
*con = (struct_c *)malloc(sizeof(struct_c)+sizeof(struct_p)*t.cap);//空间开辟完成
if (con == NULL){//开辟空间失败,说明程序出现问题,此时退出
perror("malloc");
exit(2);
}
fread((*con)->people,sizeof(struct_p),t.size,p );//把文件中联系人的信息读取到新开辟的空间中
(*con)->cap = t.cap;//更新堆空间中cap的值
(*con)->size = t.size;//更新堆空间中size的值
fclose(p);//关闭文件
printf("从文件中读取并初始化成功!\n");
}
}
static int Additionalspace(struct_c **con){//进行空间的扩容
struct_c *con1 = realloc(*con, sizeof(struct_c)+sizeof(struct_p)*(CAPA_CITY + APP_VALUE));//每次扩容一个人
if (con1 == NULL){
perror("realloc");
exit(2);
}
*con = con1;
(*con)->cap = (*con)->cap + 1;//更新最多可存储的人数
printf("追加空间成功!\n");
return 1;
}
void Add(struct_c **con){//添加联系人
if (((*con)->size) != ((*con)->cap) || Additionalspace(con)){
//开辟的空间没满直接进入,空间满了扩容成功后进入
struct_p *cop = (*con)->people +(*con)->size;
printf("请输入姓名:");
scanf(" %s",cop->name);
printf("请输入性别:");
scanf(" %s", cop->sex);
printf("请输入年龄:");
scanf(" %d", &(cop->age));
printf("请输入电话:");
scanf(" %s",cop->phone);
printf("请输入地址:");
scanf(" %s", cop->address);
(*con)->size++;//更新实际存放的人数
}
}
void Delete(struct_c *con){//删除联系人
Show(con);
struct_p *cop = con->people;
char name[12] = "0";
printf("请输入要删除的人的姓名:\n");
scanf(" %s", name);
printf("开始删除!\n");
for (int i = 0; i < con->size; i++){
if (strcmp(name, cop[i].name) == 0){
memmove(con->people + i, con->people + con->size, sizeof(struct_p));
con->size--;
printf("删除成功!\n");
return;
}
}
printf("这个人不存在,无法删除!\n");
}
void Revise(struct_c *con){//修改某一个联系人
struct_p *cop = con->people;
char name[12] = "0";
scanf(" %s", name);
printf("开始修改!\n");
for (int i = 0; i < con->size; i++){
if (strcmp(name, cop->name + i) == 0){
printf("请输入姓名:");
scanf(" %s", cop->name);
printf("请输入性别:");
scanf(" %s", cop->sex);
printf("请输入年龄:");
scanf(" %d", &(cop->age));
printf("请输入电话:");
scanf(" %s", cop->phone);
printf("请输入地址:");
scanf(" %s", cop->address);
printf("修改成功!\n");
return;
}
}
printf("这个人不存在,无法修改!\n");
}
static void Showsingle(struct_p *cop){//输出某一个联系人的信息,在Inquire和Show中要用到
printf("%s|%s|%d|%s|%s\n", cop->name, cop->sex, cop->age, \
cop->phone, cop->address);
}
void Inquire(struct_c *con){//查找联系人
struct_p *cop = con->people;
char name[12] = "0";
printf("请输入要查询的人的姓名:\n");
scanf(" %s", name);
for (int i = 0; i < con->size; i++){
if (strcmp(name, cop[i].name) == 0){
Showsingle(cop);
return;
}
}
printf("没有找到\n");
}
void Empty(struct_c *con){//清空所有联系人
printf("你真的要清空吗?\n");
printf("1.确定------2.再想想\n");
int select = 0;
scanf("%d", &select);
if (select == 1){
con->size = 0;
printf("清空成功!\n");
}
return;
}
void Show(struct_c *con){//显示全部联系人
struct_p *cop = con->people;
for (int i = 0; i < con-> size; i++){
Showsingle(cop+i);
}
}
static int Comchar(const void *str1, const void *str2){//无类型排序时需要写一个回调函数
const struct_p *p = (const struct_p *)str1;
const struct_p *q = (const struct_p *)str2;
return strcmp(p->name, q->name);
}
void Sort(struct_c *con){//对全部联系人按照姓名进行排序(把姓名当做字符串)
struct_p *cop = con->people;
qsort(cop, con->size, sizeof(struct_p), Comchar);
printf("排序成功!\n");
}
void SaveFile(struct_c *con){//以二进制文件形式形式保存数据
FILE *p = fopen(TARGET_FILE, "wb");
if (p == NULL){
perror("fopen");
return;
}
fwrite(con, sizeof(struct_c), 1, p);
fwrite(con->people, sizeof(struct_p), con->cap, p);
fclose(p);//关闭文件
}
void Freeupspace(struct_c **con){//释放开辟的空间
free(*con);
*con = NULL;
}
注意:
1.初始化时要在堆上开辟空间,所以要使用二级指针。第一次运行程序还没有文件,而且不会自动帮我们创建“contact.bin”文件,此时初始化直接在堆上开辟空间。程序运行结束(按8结束)后会保存文件,下次在运行程序就是文件存在时初始化方案了。
2.添加联系人时可能存在扩容空间,所以也要使用二级指针变量,每次扩容的人数可修改。
其他的功能实现都比较容易,第一次运行程序,先添加一个联系人,结果如下:
再次运行程序,输入选项6,显示全部联系人,出现如下结果,说明保存成功。