C语言:动态通讯录

本通讯录的最终目标:
1. 可以动态存放联系人的信息
2. 人的信息:名字,性别,年龄,电话,住址
3. 增加联系人
4. 删除联系人
5. 查找联系人
6. 修改联系人
7. 排序(名字 / 年龄)
8. 清除所有联系人

前面介绍了第一版本的通讯录,是直接开辟1000块空间内存来供使用,但是这样就会导致一个问题:如果人太少,要存入的联系人只有10个,造成空间浪费,又或者人太多,需要存进2000个人,内存中可存储的空间不够等问题。这时候就要有一个能够能够随存入联系人数量的增加而增大空间的通讯录,所以下面代码就来实现通讯录版本二之动态通讯录。

本程序是对之前通讯录进行修改,所以大体思路并未改变,下面对程序修改之处进行介绍:

contact.h:

#pragma once

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

#define NAME_MAX 20
#define SEX_MAX 5
#define TELE_MAX 15
#define ADDR_MAX 30

#define DEFAULT_SZ 3

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;   //存放数据
	int sz;   //通讯录中有效信息个数
	int capacity;   //记录当前通讯录最大容量
}Contact;

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

//增添联系人
void AddContact(Contact* con);

//删除联系人
void DelContact(Contact* con);

//查找联系人
void SearchContact(const Contact* con);

//修改联系人
void ModifyContact(Contact* con);

//排序联系人
void SortContact(Contact* con);

//打印联系人
void ShowContact(const Contact* con);

//清除所有联系人
void ClearContact(Contact* con);

//销毁通讯录
void DestroyContact(Contact* con);

动态通讯录在定义结构体的时候,应该增加一个元素来记录当前通讯录的最大容量,当达到这个最大容量的时候,就对动态通讯录进行扩容,增加内存空间,这样就能够很好地实现动态通讯录。 

test.c:

#define _CRT_SECURE_NO_WARNINGS 1

#include "dy_contact.h"

enum Oprion
{
	EXIT,
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SORT,
	SHOW,
	CLEAR
};

void menu()
{
	printf("***********************************\n");
	printf("******** 1. add     2. del   ******\n");
	printf("******** 3. search  4. modify******\n");
	printf("******** 5. sort    6. show  ******\n");
	printf("******** 7. clear   0. exit  ******\n");
	printf("***********************************\n");
}

int main()
{
	int input = 0;
	Contact con = { 0 };   //通讯录
	//初始化通讯录
	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:
			ModifyContact(&con);
			break;
		case SORT:
			SortContact(&con);
			break;
		case SHOW:
			ShowContact(&con);
			break;
		case CLEAR:
			ClearContact(&con);
			break;
		case EXIT:
			DestroyContact(&con);
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

test.c代码中与之前的区别主要就是要在初始化的时候开辟动态内存空间、在输入0退出程序的时候,会将通讯录删除,其实仔细想的话,确实是需要这一步的,因为在整个工程都没有结束的时候,动态开辟内存也并未结束,所以只能是在整个程序结束的时候才能将动态开辟的内存释放掉。

contact.c:

#define _CRT_SECURE_NO_WARNINGS 1

#include "dy_contact.h"

void InitContact(Contact* con)
{
	assert(con);
	con->sz = 0;
	PeoInfo* tmp = (PeoInfo*)malloc(DEFAULT_SZ * sizeof(PeoInfo));
	if (tmp != NULL)
	{
		con->data = tmp;
	}
	else
	{
		printf("InitContact()::%s\n", strerror(errno));
		return;
	}
	con->capacity = DEFAULT_SZ;
}

void check_capacity(Contact* con)
{
	assert(con);
	if (con->sz == con->capacity)
	{
		//增加容量
		PeoInfo* tmp = (PeoInfo*)realloc(con->data, (con->capacity + 2) * sizeof(PeoInfo));
		if (tmp != NULL)
		{
			con->data = tmp;
			con->capacity += 2;
			printf("增容成功!\n");
		}
		else
		{
			printf("check_capacity()::%s\n", strerror(errno));
		}
	}
}

void AddContact(Contact* con)
{
	assert(con);
	check_capacity(con);
	//输入联系人
	printf("请输入名字:>");
	scanf("%s", con->data[con->sz].name);
	printf("请输入性别:>");
	scanf("%s", con->data[con->sz].sex);
	printf("请输入年龄:>");
	scanf("%d", &(con->data[con->sz].age));
	printf("请输入电话号码:>");
	scanf("%s", con->data[con->sz].tele);
	printf("请输入住址:>");
	scanf("%s", con->data[con->sz].addr);

	con->sz++;
	printf("添加联系人成功!\n");
}

int FindContact(const Contact* con, char name[])
{
	assert(con && name);
	int i = 0;
	for (i = 0; i < con->sz; i++)
	{
		if (strcmp(con->data[i].name, name) == 0)
		{
			return i;
		}
	}
	return -1;
}

void ShowContact(const Contact* con)
{
	assert(con);
	printf("%-10s\t%-5s\t%-5s\t%-13s\t%-20s\n", "名字", "性别", "年龄", "电话", "地址");
	int i = 0;
	for (i = 0; i < con->sz; i++)
	{
		printf("%-10s\t%-5s\t%-5d\t%-13s\t%-20s\n",
			con->data[i].name, con->data[i].sex, con->data[i].age, con->data[i].tele, con->data[i].addr);
	}
}

void SearchContact(const Contact* con)
{
	assert(con);
	char searchname[NAME_MAX] = { 0 };
	if (con->sz == 0)
	{
		printf("通讯录为空,无法查找\n");
		return;
	}
	printf("请输入查找人的名字:>");
	scanf("%s", searchname);
	int ret = FindContact(con, searchname);
	if (ret == -1)
	{
		printf("未能找到该联系人\n");
		return;
	}
	else
	{
		printf("找到了:>\n");
		printf("%-10s\t%-5s\t%-5s\t%-13s\t%-20s\n", "名字", "性别", "年龄", "电话", "地址");
		printf("%-10s\t%-5s\t%-5d\t%-13s\t%-20s\n",
			con->data[ret].name, con->data[ret].sex, con->data[ret].age, con->data[ret].tele, con->data[ret].addr);
	}
}

void DelContact(Contact* con)
{
	assert(con);
	char delname[NAME_MAX] = { 0 };
	if (con->sz == 0)
	{
		printf("通讯录为空,无法删除\n");
		return;
	}
	printf("请输入删除人名字:>");
	scanf("%s", delname);
	int ret = FindContact(con, delname);
	if (ret == -1)
	{
		printf("通讯录中暂无此人,无法删除\n");
		return;
	}
	else
	{
		int i = 0;
		for (i = ret; i < con->sz - 1; i++)
		{
			con->data[i] = con->data[i + 1];
		}
		con->sz--;
		printf("删除联系人成功!\n");
	}
}

void ModifyContact(Contact* con)
{
	assert(con);
	char modifyname[NAME_MAX] = { 0 };
	char mod[5] = { 0 };
	char modform[50] = { 0 };
	if (con->sz == 0)
	{
		printf("通讯录为空,无法修改\n");
		return;
	}
	printf("请输入修改人的名字:>");
	scanf("%s", modifyname);
	int ret = FindContact(con, modifyname);
	if (ret == -1)
	{
		printf("通讯录中暂无此人,无法修改\n");
		return;
	}
	else
	{
		printf("请选择修改的选项(名字/性别/年龄/电话/地址):>");
		scanf("%s", mod);
		if (strcmp(mod, "名字") == 0)
		{
			printf("请输入修改后的名字:>");
			scanf("%s", modform);
			strcpy(con->data[ret].name, modform);
			printf("修改名字成功!\n");
		}
		else if (strcmp(mod, "性别") == 0)
		{
			printf("请输入修改后的性别:>");
			scanf("%s", modform);
			strcpy(con->data[ret].sex, modform);
			printf("修改性别成功!\n");
		}
		else if (strcmp(mod, "电话") == 0)
		{
			printf("请输入修改后的电话:>");
			scanf("%s", modform);
			strcpy(con->data[ret].tele, modform);
			printf("修改电话成功!\n");
		}
		else if (strcmp(mod, "地址") == 0)
		{
			printf("请输入修改后的地址:>");
			scanf("%s", modform);
			strcpy(con->data[ret].addr, modform);
			printf("修改地址成功!\n");
		}
		else if (strcmp(mod, "年龄") == 0)
		{
			printf("请输入修改后的年龄:>");
			scanf("%s", modform);
			int newage = atoi(modform);
			con->data[ret].age = newage;
			printf("修改年龄成功!\n");
		}
		else
		{
			printf("选择错误\n");
		}
	}
}

int cmp_people_by_name(const void* e1, const void* e2)
{
	return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}

int cmp_people_by_age1(const void* e1, const void* e2)
{
	return ((PeoInfo*)e1)->age - ((PeoInfo*)e2)->age;
}

int cmp_people_by_age2(const void* e1, const void* e2)
{
	return ((PeoInfo*)e2)->age - ((PeoInfo*)e1)->age;
}

void SortContact(Contact* con)
{
	assert(con);
	char sort[5] = { 0 };
	char sortway[5] = { 0 };
	if (con->sz == 0)
	{
		printf("通讯录为空,无法排序\n");
		return;
	}
	else
	{
		printf("请选择排序方式(名字/年龄):>");
		scanf("%s", sort);
		if (strcmp(sort, "名字") == 0)
		{
			qsort(con->data, con->sz, sizeof(con->data[0]), cmp_people_by_name);
			printf("排序成功!\n");
		}
		else if (strcmp(sort, "年龄") == 0)
		{
			printf("请选择递增or递减:>");
			scanf("%s", sortway);
			if (strcmp(sortway, "递增") == 0)
			{
				qsort(con->data, con->sz, sizeof(con->data[0]), cmp_people_by_age1);
				printf("排序成功!\n");
			}
			else if (strcmp(sortway, "递减") == 0)
			{
				qsort(con->data, con->sz, sizeof(con->data[0]), cmp_people_by_age2);
				printf("排序成功!\n");
			}
			else
			{
				printf("选择错误\n");
			}
		}
		else
		{
			printf("选择错误\n");
		}
	}
}

void ClearContact(Contact* con)
{
	assert(con);
	if (con->sz == 0)
	{
		printf("通讯录为空,无需清除\n");
		return;
	}
	else
	{
		memset(con->data, 0, sizeof(con->data));
		con->sz = 0;
		printf("清除所有联系人成功!\n");
	}
}

void DestroyContact(Contact* con)
{
	free(con->data);
	con->data = NULL;
	con->capacity = 0;
	con->sz = 0;
}

整个动态通讯录大致就可以这样实现出来了,接下来就是介绍之前说到的模拟实现atoi函数:

对于atoi函数,总体来说情况还是比较多的,比如:

1. 传入的是空指针,会报错
2. 传入的是空字符串,会非法返回0
3. 传入的是空白字符,在开头会直接跳过,在字符串中间会结束
4. 传入的是含有+-号的字符,会识别打印
5. 传入的是非数字字符,会非法返回
6. 传入的是超大数字,会非法返回0

所以,通过上面的几种情况,就可以就可以编出一个较为完善的代码。首先需要用枚举类型来定义非法和合法两种情况,并一开始就给其赋为非法,把所有非法的情况都一一列举后剩下的就是合法的情况,那时候再将其赋为合法就可以。这段代码比较难以思考到的就是应该在计算的时候就把正负包含进去,同时还需要将计算值类型改成long long类型(最后再强制类型转换成int类型),原因是如果计算值是int类型,当传入的是超大值的时候,可能会发生整型截断,从而返回值一定会是在最大或最小整型的范围内,那就一定不会非法返回0了,与上面情况不符;所以这时候就会想到使用long long类型,而且一并将正负号给带上,这样传入超大数字的时候,就不会出现整型截断的情况,然后再让其与最大或最小整型比较,如果是在最大或最小整型之外,则会非法返回;如果是在这个范围之内,则是合法返回,并将long long类型强制类型转换为int类型,从而就可以模拟实现atoi。下面是代码:

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

enum State
{
	INVALID,//非法
	VALID   //合法
};

enum State status = INVALID;

int my_atoi(const char* str)
{
	assert(str);
	//空字符串
	if (*str == '\0')
	{
		return 0;
	}
	//空白字符(跳过)
	while (isspace(*str))
	{
		str++;
	}
	int flag = 1;
	if (*str == '+')
	{
		flag = 1;
		str++;
	}
	else if (*str == '-')
	{
		flag = -1;
		str++;
	}
	long long n = 0;
	while (isdigit(*str))
	{
		n = n * 10 + flag * (*str - '0');
		if (n > INT_MAX || n < INT_MIN)
		{
			return 0;
		}
		str++;
	}
	if (*str == '\0' || *str == ' ')
	{
		//合法返回
		status = VALID;
		return (int)n;
	}
	return (int)n;
}

int main()
{
	int ret = my_atoi("123  4");
	if (status == VALID)
	{
		printf("%d\n", ret);
	}
	else
	{
		printf("非法返回\n");
	}
	return 0;
}

整个动态通讯录的完整代码放在这里面,有兴趣可以看看:

蔡欣致/C - C++ 项目 - Gitee.comhttps://gitee.com/zkcxz/c-----c---project/tree/master/dy_contact/dy_contact

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蔡欣致

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值