用C语言来实现通讯录(顺序表结构)

在这里插入图片描述

通讯录

通讯录对于我们来说都不陌生,在我们早些年使用老人机的时候就认识到了,随着智能手机的普及通讯录变得花里胡哨和更加好看了。但是功能还是不变的,通讯录能够保存人的信息,比如名字、年龄、性别、电话、住址。
那么今天让我们来模拟实现通讯录的一些功能吧。

1.菜单与主框架

我们首先要搞个菜单模块,把我们想要实现的功能打印出来让我们看到,然后我们再去做选择。
框架: 我们定义三个文件,一个是头文件 Contact.h ,一个是Contact.c,一个是test.c
在头文件Contact.h 中我们把定义信息结构体,声明函数和引用库函数的头文件。
在源文件Contact.c 中实现我们定义的函数功能。
在源文件test.c 中放主函数和搭建主要框架。

所以我们先写test.c 中的main函数和框架,代码如下:

#define _CRT_SECURE_NO_WARNINGS 1
//包含我们定义的头文件
#include "Contact.h"

void menu() //菜单函数
{
	printf("**********************************\n");
	printf("***       通讯录管理系统       ***\n");
	printf("**********************************\n");
	printf("***       0.退出通讯录         ***\n");
	printf("***       1.增加联系人信息     ***\n");
	printf("***       2.删除联系人信息     ***\n");
	printf("***       3.查找联系人信息     ***\n");
	printf("***       4.修改联系人信息     ***\n");
	printf("***       5.显示联系人信息     ***\n");
	printf("***       6.排序联系人信息     ***\n");
	printf("**********************************\n\n");
}

int main()
{
	Contact con; 
	//因为要改变栈上变量con的内容,所以要传地址实现
	InitContact(&con);//初始化函数
	LoadContact(&con);//加载保存联系人函数
	int input = 0;
	//do while 循环,实现调用各种函数
	do
	{
		menu();
		printf("请输入你的选择:");
		scanf("%d", &input);
		switch (input)
		{
		case EXIT:
			ConserveContact(&con);
			DestoryContact(&con);
			printf("退出通讯录\n");
			break;
		case ADD:
			AddContact(&con);
			break;
		case SUB:
			SubContact(&con);
			break;
		case SEARCH:
			SearchContact(&con);
			break;
		case MODIFY:
			break;
		case SHOW:
			ShowContact(&con);
			break;
		case SORT:
			SortContact(&con);
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);

	return 0;
}

2.定义信息结构体

当我们把框架写好了,接下来就是怎么描述一个人的信息呢?要用整形、字符型还是double型;这样都不行,因为一个人的信息有很多,单独靠一种类型是描述不了的。
所以我们要自定义类型,用到了结构体类型struct people 来描述人的信息;在结构体中我们可以定义数组来保存名字、电话、住址等信息,定义整形来保存年龄等等。

而定义了描述一个人的结构体类型还不行,我们要存放若干个人的信息呀,所以我们还要创建描述一个人结构体的指针,然后再所以内存开辟函数,我们要保存多少人就开辟够容纳的空间。
但是怎么知道开辟的空间不够了,要增容才行呢?所以我们再定义一个通讯录结构体类型
struct Contact
,在通讯录结构体中放描述人的结构体指针struct people* 和统计人总数与记录开辟空间大小。
所以头文件Contact.h 就可以写出来了,代码如下:

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

// 定义枚举类型,方便辨别case入口
enum p
{
	EXIT, 
	ADD,
	SUB,
	SEARCH,
	MODIFY,
	SHOW,
	SORT
};

// 名字、电话等数组元素最大个数
#define NAME_MAX 15
#define SEX_MAX 15
#define TELE_MAX 15
#define ADDR_MAX 15

// 把人的信息写成一个结构体类型
typedef struct people
{
	char name[NAME_MAX]; //名字
	int age;             //年龄
	char sex[SEX_MAX];   //性别
	char tele[TELE_MAX]; //电话
	char addr[ADDR_MAX]; //住址
}people;

struct Contact
{
	//创建一个信息结构体指针
	struct people* arr;  
	int sz;
	int capacity;
};
typedef struct Contact Contact;

//函数声明如下

//初始化通讯录
void InitContact(Contact* pc);

//显示联系人信息
void ShowContact(Contact* pc);

//增加联系人信息
void AddContact(Contact* pc);

//删除联系人信息
void SubContact(Contact* pc);

//查找联系人信息
void SearchContact(Contact* pc);

//修改联系人信息
void ModifyContact(Contact* pc);

//排序联系人信息
void SortContact(Contact* pc);

//保存联系人信息
void ConserveContact(Contact* pc);

//加载联系人信息
void LoadContact(Contact* pc);

//释放内存函数
void DestoryContact(Contact* pc);

图片分析如下:
在这里插入图片描述

3.初始化结构体

当我们创建好一个通讯录变量con ,因为是在栈区上创建的,所以里面存放的是随机值。那第一步就先进行初始化,把里面的信息结构体指针初始化为空指针,下标和容量都初始化为0。代码如下:

void InitContact(Contact* pc)
{
	pc->arr = NULL;
	pc->capacity = 0;
	pc->sz = 0;
}

4.增加联系人信息

我们知道怎么实现描述一个人的信息,接下来就是主要的功能模块实现了,
注意一点: 在增加联系人的过程中,我们要注意下标与容量相等时要扩容才行。
代码如下:

//扩容函数
people* BuyNewCapacity(Contact* pc)
{
	//当容量为0时就为2,其它就扩2倍
	pc->capacity = pc->capacity > 0 ? 2 * pc->capacity : 2;
	people* tmp = (people*)realloc(pc->arr, pc->capacity * sizeof(people));
	if (tmp == NULL)
	{
		printf("内存开辟失败\n");
		exit(-1);
	}
	//返回扩容的起始地址
	return tmp;
}

void AddContact(Contact* pc)
{
	if (pc->capacity == pc->sz)
	{
		people* tmp = BuyNewCapacity(pc);
		pc->arr = tmp;
	}
	printf("请输入名字:");
	scanf("%s", pc->arr[pc->sz].name);
	printf("请输入年龄:");
	scanf("%d", &pc->arr[pc->sz].age);
	printf("请输入性别:");
	scanf("%s", pc->arr[pc->sz].sex);
	printf("请输入电话:");
	scanf("%s", pc->arr[pc->sz].tele);
	printf("请输入地址:");
	scanf("%s", pc->arr[pc->sz].addr);

	pc->sz++;  //增加一个信息,下标也要自增1
	printf("添加成功\n");
}

5.删除联系人信息

删除联系人我们有三种情况,第一是正常删除,第二是通讯录一个人也没有就不能删除了,第三是找不到这个信息也删除不了。所以我们写一个找联系人的函数,找到了就返回下标,找不到就返回 -1。
代码如下:

int FindContact(Contact* pc)
{
	char name[NAME_MAX] = { 0 };
	printf("请输入名字:");
	scanf("%s", name);
	int i = 0;
	//遍历一遍
	for (i = 0; i < pc->sz; i++)
	{
		//利用库函数来判断字符串是否相等
		if (strcmp(name, pc->arr[i].name) == 0)
		{
			return i;
		}
	}
	return -1;
}


void SubContact(Contact* pc)
{
	if (pc->sz == 0)
	{
		printf("通讯录为空,不可再删除\n");
		return;
	}
	// 通讯录不为空就查找联系人
	int pos = FindContact(pc);
	if (pos == -1)
	{
		printf("你要删除的信息不存在\n");
		return;
	}
	else
	{
		//找到了,就一个一个往前面挪
		int i = 0;
		for (i = pos; i < pc->sz - 1; i++)
		{
			pc->arr[i] = pc->arr[i + 1];
		}
		// 要把下标自减1
		pc->sz--;
		printf("删除成功\n");
	}
}

6.查找联系人信息

如果通讯录为空就直接返回不用找了,如果查找函数返回-1就表示没有此人的信息,如果找到了就把这个人的信息打印出来即可。
代码如下:

void SearchContact(Contact* pc)
{
	if (pc->sz == 0)
	{
		printf("通讯录为空,找不到\n");
		return;
	}
	int pos = FindContact(pc);
	if (pos == -1)
	{
		printf("此联系人不存在\n");
		return;
	}
	else
	{
		printf("%-15s %-15s %-15s %-15s %-15s\n", 
		"名字", "年龄", "性别", "电话", "地址");
		printf("%-15s %-15d %-15s %-15s %-15s\n",
			pc->arr[pos].name,
			pc->arr[pos].age,
			pc->arr[pos].sex,
			pc->arr[pos].tele,
			pc->arr[pos].addr);
	}
}

7.修改联系人信息

找到了就重新输入一遍即可,代码如下:

void ModifyContact(Contact* pc)
{
	int pos = FindContact(pc);
	if (pos == -1)
	{
		printf("此联系人不存在\n");
		return;
	}
	else
	{
		printf("请输入名字:");
		scanf("%s", pc->arr[pos].name);
		printf("请输入年龄:");
		scanf("%d", &pc->arr[pos].age);
		printf("请输入性别:");
		scanf("%s", pc->arr[pos].sex);
		printf("请输入电话:");
		scanf("%s", pc->arr[pos].tele);
		printf("请输入地址:");
		scanf("%s", pc->arr[pos].addr);
		printf("修改成功\n");
	}
}

8.显示联系人信息

这个也很简单,显示就是打印出来即可,代码如下:

void ShowContact(Contact* pc)
{
	int i = 0;
	printf("%-15s %-15s %-15s %-15s %-15s\n", 
	"名字", "年龄", "性别", "电话", "地址");
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-15s %-15d %-15s %-15s %-15s\n",
			pc->arr[i].name,
			pc->arr[i].age,
			pc->arr[i].sex,
			pc->arr[i].tele,
			pc->arr[i].addr);
	}
	printf("\n");
}

9.排序联系人信息

这个就要用到我们的qsort库函数了,这个库函数能排序很多种类型的信息,详解请看这篇博文:qsort函数的解析 其中通讯录我们针对的是对名字或者年龄的排序。有兴趣的小伙伴也可以增加其他种类的排序,除了年龄外都是对字符串之间的排序。
代码如下:

int compar1(const void* a, const void* b)
{
	return strcmp(((people*)a)->name, ((people*)b)->name);
}

int compar2(const void* a, const void* b)
{
	return ((people*)a)->age - ((people*)b)->age;
}

void SortContact(Contact* pc)
{
	int input = 0;
	do
	{
		printf("请选择要排序的类型:\n");
		printf("1.名字大小排序   2.年龄大小排序   0.退出排序\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			qsort(pc->arr, pc->sz, sizeof(people), compar1);
			printf("名字大小排序成功\n");
			break;
		case 2:
			qsort(pc->arr, pc->sz, sizeof(people), compar2);
			printf("年龄大小排序成功\n");
			break;
		case 0:
			printf("退出排序\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);
}

10.保存和加载联系人信息

当我们输入联系人信息结束通讯录后,如果结束前不保存,那么下一次再执行程序就没有上一次输入的信息了,这样不就是一次性的通讯录么,这样的通讯录谁都不敢用呀。
所以我们要在程序结束前保存信息到文件里面,然后重新执行程序在初始化阶段把文件里面信息加载到内存当中。文件使用可以参考这一篇博文:C语言中的文件操作
代码如下:

//保存联系人信息
void ConserveContact(Contact* pc)
{
   //以二进制写的方式打开文件
	FILE* pf = fopen("contact.txt", "wb");
	if (pf == NULL)
	{
		perror("erron ");
		return;
	}
	int i = 0;
	//把信息保存到文件当中
	for (i = 0; i < pc->sz; i++)
	{
		fwrite(pc->arr + i, sizeof(people), 1, pf);
	}
	fclose(pf);
	pf = NULL;
}


//加载联系人信息
void LoadContact(Contact* pc)
{
	FILE* pf = fopen("Contact.txt", "rb");
	if (pf == NULL)
	{
		perror("erron ");
		exit(-1);
	} 
	// 因为初始化还没有开辟空间
	// 先开辟一点大小的空间
	if (pc->capacity == pc->sz)
	{
		people* tmp = BuyNewCapacity(pc);
		pc->arr = tmp;
	}
	// 从文件中把信息加载到变量当中
	while (fread(pc->arr + pc->sz, sizeof(people), 1, pf))
	{
		pc->sz++;
		//如果空间满了就增容
		if (pc->capacity == pc->sz)
		{
			people* tmp = BuyNewCapacity(pc);
			pc->arr = tmp;
		}
	}
	fclose(pf);
	pf = NULL;
}

11.释放内存空间

当我们结束通讯录之前除了要保存联系人的信息外,还要把在堆上开辟的内存空间还给操作系统。代码如下:

void DestoryContact(Contact* pc)
{
	free(pc->arr);
	pc->arr = NULL;
	pc->capacity = 0;
	pc->sz = 0;
}

程序执行的部分结果:
在这里插入图片描述
在这里插入图片描述
以上就是我的通讯录全部内容了,其中前面我只有把头文件Contact.h 和源文件test.c 放在同一段代码块中,实现函数功能的源文件Contact.c 文件我拆开来分析了。如果想要方便拷贝Contact.c 文件的内容,阔以移步到gitee上获取。
在这里插入图片描述
三个源文件内容链接:
https://gitee.com/fait-juyuan/c-language/tree/master/test_10_4/test_10_4

评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值