【C语言】通讯录2.0版本——动态内存版本,万字详解

该文章详细介绍了如何使用C语言实现一个通讯录管理系统,包括创建结构体表示联系人信息,初始化通讯录,增加、删除、查找和修改联系人,以及对通讯录进行排序。文章特别强调了通讯录的动态扩容机制,通过realloc函数在需要时扩展内存,避免空间不足的问题。此外,还提供了菜单驱动的用户交互界面,方便用户操作。
摘要由CSDN通过智能技术生成

目录

零,写在前面:

一,通讯录的实现

      1,通讯录的前期准备

        (1),菜单实现

        (2),联系人结构体的创建

        (3),实现菜单选项功能

        (4),关于全局变量的定义

2,通讯录的实现

        (1),初始化通讯录

        (2),增加联系人

        (3),打印通讯录

        (4),查找联系人

        (5),修改联系人

        (6),删除联系人

        (7),排序通讯录

二,通讯录的优化

        1,通讯录结构体改进

        2,初始化结构体

        3,增容

        4,释放内存

三,总结


零,写在前面:

        在上一篇文章中我们已经运用所学的结构体,完成了一个基础版的通讯录,但是我们知道这个版本的通讯录还是存在着许多的缺陷。那我们就优化一下我们的通讯录。让他实现动态内存增长。

一,通讯录的实现:

        1,通讯录的前期准备:

                (1),菜单实现

        首先一个通讯录要建立一个菜单,菜单是能够与用户实现交互的,这是很重要的,并且菜单包括了这个通讯录的基本功能,一便于用户可以快捷操作。

void menu()
{
	printf("=====================================\n");
	printf("============1.增加联系人=============\n");
	printf("============2.删除联系人=============\n");
	printf("============3.查找联系人=============\n");
	printf("============4.修改联系人=============\n");
	printf("============5.排序通讯录=============\n");
	printf("============6.打印通讯录=============\n");
	printf("============0.退出通讯录=============\n");
	printf("=====================================\n");
}

        (2),联系人结构体的创建

我们将利用结构体来实现我们所需要的通讯录功能以及联系人信息,所以我们这里将要用到两个结构体。

typedef struct PeoInfo
{
	char name[NAME_MAX];
	char sex[SEX_MAX];
	int age;
	char tele[TELE_MAX];
	char addr[ADDR_MAX];
}Peo;
typedef struct Contact
{
	Peo con[PON_MAX];
	int sz;//记录数量
}contact;

        第二个结构体是用来构建我们的通讯录的,con是一个结构体数组,sz是用来记录联系人存储数量的。

        (3),实现菜单选项功能

        我们会根据菜单里的选项来实现所对应的功能,比如按1就是增加联系人,按0就是退出通讯录,我选择利用枚举的方式来实现,将操作变成数字,再利用switch语句来实现个功能的选择与实现。

enum Option//利用枚举变量来定义
{
	exit,//0
	add,//1
	del,//2
	search,//3
	modify,//4
	sort,//5
	print//6
};
void test()
{
	contact con;//建立结构体
	InitCon(&con);
	int input = 0;
	do {
		menu();
		scanf("%d", &input);
		switch (input)
		{
		case add:
			AddCon(&con);
			break;
		case del:
			DelCon(&con);
			break;
		case search:
		SearchCon(&con);
			break;
		case modify:
			ModifyCon(&con);
			break;
		case sort:
			SortCon(&con);
			break;
		case print:
			PrintCon(&con);
			break;
		case exit:
			printf("退出程序,欢迎使用!\n");
			break;
		default:
			printf("没有找到此数字匹配的操作!!\n");
		}
	} while (input);
}

        (4),关于全局变量的定义

#define NAME_MAX 20//姓名的长度
#define SEX_MAX 5//性别的长度
#define TELE_MAX 12//电话号码的长度
#define ADDR_MAX 30//地址长度
#define PON_MAX 1000//通讯录的大小

2,通讯录的实现

        (1),初始化通讯录

        刚才我们定义了一个通讯录的结构体,但是我们不难发现里面其实都是随机数,因此我们要对他进行初始化,防止后面出现的失误。我们只需要对通讯录结构体里的用户信息的结构体进行初始化就好了,这里我们是使用memset函数,要记得引用头文件<string.h>哟,sz的初始化就是全部初始化成0就好了。

void InitCon(contact* pc)
{
	assert(pc);
	pc->sz = 0;
	memset(pc->con, 0, sizeof(pc->con));
}//初始化结构体

        (2),增加联系人

        先判断通讯录是否已经满了,如果满了就不能添加了,没有满才可以进行添加,就是访问通讯录结构体里的数组中的每个元素,在添加对应的值就好了。

void AddCon(contact* pc)
{
	assert(pc);
	if (pc->sz == PON_MAX)
	{
		printf("通讯录满了!!\n");
	}
	printf("请输入姓名:>\n");
	scanf("%s", pc->con[pc->sz].name);
	printf("请输入性别:>\n");
	scanf("%s", pc->con[pc->sz].sex);
	printf("请输入年龄:>\n");
	scanf("%d", &(pc->con[pc->sz].age));//注意年龄在这里是一个int类型
	printf("请输入电话号码:>\n");
	scanf("%s", pc->con[pc->sz].tele);
	printf("请输入地址:>\n");
	scanf("%s", pc->con[pc->sz].addr);
	pc->sz++;
	printf("此用户添加成功!\n");
}

        (3),打印通讯录

        打印很简单了,就是利用for循环遍历直接打印就好了。

void PrintCon(const contact* pc)
{
	assert(pc);
	printf("%-15s %-5s %-5s %-12s %-30s\n","姓名","性别","年龄","电话","地址");
	for (int i = 0; i < pc->sz; i++)
	{
		printf("%-15s %-5s %-5d %-12s %-30s\n", pc->con[i].name, pc->con[i].sex, pc->con[i].age, pc->con[i].tele, pc->con[i].addr);
	}
}

        (4),查找联系人

        首先要判断通讯里有没有联系人,然后在根据联系人的姓名去通讯录中查找,找到之后我们返回对应的下标,在打印对应的联系人信息,否则就返回-1。

int FindByName(const contact* pc, char* name)
{
	assert(pc && name);
	for (int i = 0; i < pc->sz; i++)
	{
		if (0 == strcmp(pc->con[i].name, name))
			return i;
    }
	return -1;
}//寻找或者删除联系人的下标
void SearchCon(const contact* pc)
{
	assert(pc);
	char name[NAME_MAX];
	printf("请输入需要寻找用户的名字:>\n");
	scanf("%s", name);
	int pos = FindByName(pc, name);//pos为要寻找的人的下标
	if (pos == -1)
	{
		printf("查无此人\n");
	}
	else
	{
		printf("%-15s %-5s %-5s %-12s %-30s\n", "姓名", "性别", "年龄", "电话", "地址");
		printf("%-15s %-5s %-5d %-12s %-30s\n", pc->con[pos].name, pc->con[pos].sex, pc->con[pos].age, pc->con[pos].tele, pc->con[pos].addr);
	}
}

        (5),修改联系人

        关于修改联系人信息,如果是直接替换,那么有很多本来不需要修改的信息也被修改了,就变得很没有必要,那么我们可以先找到要修改的联系人,在选择要修改的信息,这里就变和我们的菜单非常相似,我们利用枚举变量以及do...while循环来实现,再利用switch语句根据用户的选择跳到要修改的操作上。

enum Con
{
	ERRO,
    NAME,
	SEX,
	AGE,
	TELE,
	ADDR
};
void ModifyCon(contact* pc)
{
	assert(pc);
	char name[NAME_MAX];
	printf("请输入需要修改信息用户的名字:>\n");
	scanf("%s", name);
	int pos = FindByName(pc, name);//pos为要寻找的人的下标
	if (pos == -1)
	{
		printf("查无此人\n");
	}
	else
	{
		int num = 0;
		do {
			printf("请输入你想修改此用户的信息\n");
			printf(" 0.退出 1.姓名 2.性别 3.年龄 4.电话 5.地址:>\n");			
			scanf("%d", &num);
			switch (num)
			{
			case NAME:
				printf("请输入你想修改的姓名:>\n");
				scanf("%s", pc->con[pos].name);
				break;
			case SEX:
				printf("请输入你想修改的性别:>\n");
				scanf("%s", pc->con[pos].sex);
				break;
			case AGE:
				printf("请输入你想修改的年龄:>\n");
				scanf("%d", &(pc->con[pos].age));
				break;
			case TELE:
				printf("请输入你想修改的电话:>\n");
				scanf("%s", pc->con[pos].tele);
				break;
			case ADDR:
				printf("请输入你想修改的地址:>\n");
				scanf("%s", pc->con[pos].addr);
				break;
			case 0:
				printf("不修改退回界面\n");
				break;
			default:printf("无效操作数!\n");
			}
		} while (num);
	}
}

        (6),删除联系人

        我们可以利用刚才找到的下标,对他进行删除,再把剩余的元素往前移动,这里我们利用for循环实现,但是要注意越界哟。

void DelCon(contact* pc)
{
	assert(pc);
	char name[NAME_MAX];
	printf("请输入需要删除用户的名字:>\n");
	scanf("%s", name);
	int pos = FindByName(pc, name);//pos为要寻找的人的下标
	if (pos == -1)
	{
		printf("查无此人\n");
	}
	else
	{
		for (int i = pos; i < pc->sz - 1; i++)
		{
			pc->con[i] = pc->con[i + 1];
		}
		pc->sz--;
		printf("删除成功!\n");
	}
}

        (7),排序通讯录

        这里我们通过名字来排序,就是利用字典序。

void SortCon(contact* pc)
{
	if (pc->sz == 0)
	{
		printf("通讯录中没有联系人\n");
	}
	else
	{
		for (int i = 0; i < pc->sz - 1; i++)
		{
			for (int j = 0; j < pc->sz - 1 - i; j++)
			{
				if ((strcmp(pc->con[j].name, pc->con[j + 1].name)) > 0)
				{
					Peo temp = pc->con[j];
					pc->con[j] = pc->con[j + 1];
					pc->con[j + 1] = temp;
				}
			}
		}
		printf("排序成功!\n");
	}
}

二,通讯录的优化

        我们不难发现这个通讯录有一个致命的缺点,比如我们通讯里的初始大小设置为100个联系人,当我们存储满了的时候还像存储,编译器就会报错,我们就需要手动的增加我们的空间,所以我们为了避免这种很low的操作,我们可以利用动态内存分配来定义我们的通讯录结构体。

        1,通讯录结构体改进

        我们把之前的结构体里的数组改成指针数组,并且为了考虑这个通讯录来回增加删除的缘故,一个sz来记录元素个数肯定是不行的,我们需要设定一个最大值为数组的最大空间,当sz和这个最大值相等的时候我们就需要扩充这个数组。

typedef struct Contact
{
	Peo* con;
	int sz;//记录数量
	int max;//记录通讯录当前的最大容量
}contact;

        2,初始化结构体

        这个结构体初始化,我们需要把sz赋值成0,并且需要为con这个指针开辟一块空间,并且为max赋值一个初始值,这个值我们就用宏定义的我们通讯录的初始大小。

#define CON_MAX 3//通讯录初始化大小
void InitCon(contact* pc)
{
	assert(pc);
	pc->sz = 0;
	pc->max = CON_MAX;
    pc->con = (Peo*)malloc(sizeof(Peo) * pc->max);
	if (pc->con == NULL)
	{
		perror("InitContact::malloc");
		return;
	}
	memset(pc->con, 0, pc->max * sizeof(Peo));
}

        3,增容

        当我们增加联系人sz==max的时候,我们就需要扩容,我们可以利用realloc函数来实现扩容,每一次扩容两个空间。

void CheckCapacity(contact* pc)
{
	//增容的代码
	if (pc->sz == pc->max)
	{
		Peo* tmp = (Peo*)realloc(pc->sz, (pc->max + 2) * sizeof(Peo));
		if (tmp != NULL)
		{
			pc->sz = tmp;
		}
		else
		{
			perror("CheckCapacity::realloc");
			return;
		}
		pc->max += 2;
		printf("增容成功\n");
	}
}

        4,释放内存

void DestroyContact(contact* pc)
{
	free(pc->con);
	pc->con = NULL;
	pc->max = 0;
	pc->sz = 0;
	printf("销毁成功\n");
}

三,总结

        这个通讯录主要考察的是对于结构体的访问,还有结构体的创建,在编码时务必小心,最后放上完整源代码供大家参考。

#pragma once
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
 
//类型的声明
 
#define MAX 1000
 
#define NAME_MAX 20
#define SEX_MAX 5
#define TELE_MAX 12
#define ADDR_MAX 30
 
//通讯录初始状态的容量大小
#define DEFAULT_SZ 3
 
enum Option
{
	EXIT,//0
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SORT,
	PRINT
};
enum Con
{
	ERRO,
	NAME,
	SEX,
	AGE,
	TELE,
	ADDR
};
 
typedef struct PeoInfo
{
	char name[NAME_MAX];
	char sex[SEX_MAX];
	int age;
	char tele[TELE_MAX];
	char addr[ADDR_MAX];
} PeoInfo;
typedef struct Contact
{
	PeoInfo* data;//可以存放1000个人的信息
	int sz;//记录通讯中已经保存的信息个数
	int capacity;//记录通讯录当前的最大容量
}Contact;
//函数的声明
//初始化通讯录
void InitContact(Contact* pc);
//销毁通讯录
void DestroyContact(Contact* pc);
//增加联系人的信息
void AddContact(Contact* pc);
//打印通讯录中的信息
void PrintContact(const Contact* pc);
//删除指定联系人
void DelContact(Contact* pc);
//查找指定联系人
void SearchContact(const Contact* pc);
//保存通讯录的信息到文件
void SaveContact(const Contact* pc);
//通讯录排序
void SortCon(Contact* pc);
void ModifyCon(Contact* pc);
#define _CRT_SECURE_NO_WARNINGS 1
 
//动态的版本
//void InitContact(Contact* pc)
//{
//	assert(pc);
//	pc->sz = 0;
//	pc->capacity = DEFAULT_SZ;
//	pc->data = (PeoInfo*)malloc(pc->capacity * sizeof(PeoInfo));
//
//	if (pc->data == NULL)
//	{
//		perror("InitContact::malloc");
//		return;
//	}
//	memset(pc->data, 0, pc->capacity * sizeof(PeoInfo));
//}
void CheckCapacity(Contact* pc)
{
	//增容的代码
	if (pc->sz == pc->capacity)
	{
		PeoInfo* tmp = (PeoInfo*)realloc(pc->data, (pc->capacity + 2) * sizeof(PeoInfo));
		if (tmp != NULL)
		{
			pc->data = tmp;
		}
		else
		{
			perror("CheckCapacity::realloc");
			return;
		}
		pc->capacity += 2;
		printf("增容成功\n");
	}
}
void LoadContact(Contact* pc)
{
	//打开文件
	FILE* pf = fopen("contact.dat", "rb");
	if (pf == NULL)
	{
		perror("LoadContact::fopen");
		return;
	}
	//读文件
	PeoInfo tmp = { 0 };
	while (fread(&tmp, sizeof(PeoInfo), 1, pf))
	{
		CheckCapacity(pc);
		pc->data[pc->sz] = tmp;
		pc->sz++;
	}
 
	//关闭文件
	fclose(pf);
	pf = NULL;
}
//初始化通讯录 - 文件版本
void InitContact(Contact* pc)
{
	assert(pc);
	pc->sz = 0;
	pc->capacity = DEFAULT_SZ;
	pc->data = (PeoInfo*)malloc(pc->capacity * sizeof(PeoInfo));
 
	if (pc->data == NULL)
	{
		perror("InitContact::malloc");
		return;
	}
	memset(pc->data, 0, pc->capacity * sizeof(PeoInfo));
 
	//加载文件信息到通讯录中
	LoadContact(pc);
}
void DestroyContact(Contact* pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->capacity = 0;
	pc->sz = 0;
	printf("销毁成功\n");
}
void AddContact(Contact* pc)
{
	assert(pc);
 
	//静态版本
	//if (pc->sz == MAX)
	//{
	//	printf("通讯录已满,无法添加\n");
	//	return;
	//}
 
	//动态的版本
	CheckCapacity(pc);
 
	//录入信息
	printf("请输入名字:>");
	scanf("%s", pc->data[pc->sz].name);
	printf("请输入年龄:>");
	scanf("%d", &(pc->data[pc->sz].age));
	printf("请输入性别:>");
	scanf("%s", pc->data[pc->sz].sex);
	printf("请输入电话:>");
	scanf("%s", pc->data[pc->sz].tele);
	printf("请输入地址:>");
	scanf("%s", pc->data[pc->sz].addr);
 
	pc->sz++;
	printf("添加成功\n");
}
void PrintContact(const Contact* pc)
{
	assert(pc);
 
	int i = 0;
	printf("%-20s %-5s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
 
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-20s %-5d %-5s %-12s %-30s\n", pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);
	}
}
 
//找到了返回下标
//找不到返回-1
int FindByName(const Contact* pc, char name[])
{
	assert(pc);
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		if (0 == strcmp(pc->data[i].name, name))
		{
			return i;
		}
	}
 
	return -1;
}
 
void DelContact(Contact* pc)
{
	assert(pc);
 
	if (pc->sz == 0)
	{
		printf("通讯录已空,无法删除\n");
		return;
	}
	//删除
	//1. 找到
	char name[NAME_MAX] = { 0 };
	printf("请输入要删除人的名字:>");
	scanf("%s", name);
	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要删除的人不存在\n");
		return;
	}
	//2. 删除
	int j = 0;
	for (j = pos; j < pc->sz - 1; j++)
	{
		pc->data[j] = pc->data[j + 1];
	}
 
	pc->sz--;
	printf("删除成功\n");
}
 
void SearchContact(const Contact* pc)
{
	char name[NAME_MAX] = { 0 };
	printf("请输入要查找人的名字:>");
	scanf("%s", name);
	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要查找的人不存在\n");
		return;
	}
	printf("%-20s %-5s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
	printf("%-20s %-5d %-5s %-12s %-30s\n", pc->data[pos].name, pc->data[pos].age, pc->data[pos].sex,
		pc->data[pos].tele, pc->data[pos].addr);
}
 
 
void SaveContact(const Contact* pc)
{
	FILE* pf = fopen("contact.dat", "wb");
	//回来本地建立一个contact.dat的记事本
	if (pf == NULL)
	{
		perror("SaveContact::fopen");
		return;
	}
	//写文件
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);
	}
 
	//关闭文件
	fclose(pf);
	pf = NULL;
}
void SortCon(Contact* pc)
{
	if (pc->sz == 0)
	{
		printf("通讯录中没有联系人\n");
	}
	else
	{
		for (int i = 0; i < pc->sz - 1; i++)
		{
			for (int j = 0; j < pc->sz - 1 - i; j++)
			{
				if ((strcmp(pc->data[j].name, pc->data[j + 1].name)) > 0)
				{
					PeoInfo temp = pc->data[j];
					pc->data[j] = pc->data[j + 1];
					pc->data[j + 1] = temp;
				}
			}
		}
		printf("排序成功!\n");
	}
}
 
void ModifyCon(Contact* pc)
{
	assert(pc);
	char name[NAME_MAX];
	printf("请输入需要修改信息用户的名字:>\n");
	scanf("%s", name);
	int pos = FindByName(pc, name);//pos为要寻找的人的下标
	if (pos == -1)
	{
		printf("查无此人\n");
	}
	else
	{
		int num = 0;
		do {
			printf("请输入你想修改此用户的信息\n");
			printf(" 0.退出 1.姓名 2.性别 3.年龄 4.电话 5.地址:>\n");
			scanf("%d", &num);
			switch (num)
			{
			case NAME:
				printf("请输入你想修改的姓名:>\n");
				scanf("%s", pc->data[pos].name);
				break;
			case SEX:
				printf("请输入你想修改的性别:>\n");
				scanf("%s", pc->data[pos].sex);
				break;
			case AGE:
				printf("请输入你想修改的年龄:>\n");
				scanf("%d", &(pc->data[pos].age));
				break;
			case TELE:
				printf("请输入你想修改的电话:>\n");
				scanf("%s", pc->data[pos].tele);
				break;
			case ADDR:
				printf("请输入你想修改的地址:>\n");
				scanf("%s", pc->data[pos].addr);
				break;
			case 0:
				printf("不修改退回界面\n");
				break;
			default:printf("无效操作数!\n");
			}
		} while (num);
	}
}
 
void menu()
{
	printf("*****************************************\n");
	printf("**** 1.增加联系人    2.删除联系人    ****\n");
	printf("**** 3.查找联系人    4.修改联系人    ****\n");
	printf("**** 5.通讯录排序    6.打印通讯录    ****\n");
	printf("****            0.退出               ****\n");
	printf("******************************************\n");
}
 
void test()
{
	int input = 0;
	Contact con;
	InitContact(&con);
 
	do
	{
		menu();
		printf("请输入你的选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case ADD:
			AddContact(&con);
			break;
		case DEL:
			DelContact(&con);
			break;
		case SEARCH:
			SearchContact(&con);
			break;
		case MODIFY:
			ModifyCon(&con);
			break;
		case SORT:
			SortCon(&con);
			break;
		case PRINT:
			PrintContact(&con);
			break;
		case EXIT:
			SaveContact(&con);
			DestroyContact(&con);
			printf("感谢使用\n");
			break;
		default:
			printf("非法输入\n");
			break;
		}
	} while (input);
}
 
int main()
{
	test();
	return 0;
}
 
 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值