通讯录(4)

本文介绍了C++实现的通讯录系统中批量删除功能的详细步骤,包括用户交互改进、错误处理和批量删除的算法实现。作者通过代码展示了如何读取用户输入,选择删除模式,以及如何处理超过30个删除项的情况。批量删除通过双指针方法实现,避免了越界和数据错误。目前代码仍需完善,如处理非数字输入和分类功能。
摘要由CSDN通过智能技术生成

前几篇已经写出了很多的功能实现,但不够完善。本篇要展示的也并非是全部。这期间我改了一些小细节,主要是为了输入后的提醒能够及时,功能实现更加贴近现实,以及其他目的我也就忘记了。前几天写了linux和c++笔记,懒了几天后(过年过年,有那个氛围,允许一下)今天又拿起通讯录写了起来,把之前的批量删除给补上了,经过简单测试,删除功能都正常。不过严谨肯定要有,只是我不算太清楚真正的测试方法,我只能给到更多人去使用来验证这个程序的实用性。接下来我慢慢说明批量删除的具体实现。

我的码云在这里

通讯录

本篇也会展示已经写出来的代码。

Book.c

#include "Contact.h"


void menu()
{
	printf("\n");
	printf("*************************************\n");
	printf("*******   1. 增加   2. 删除   *******\n");
	printf("*******   3. 查找   4. 修改   *******\n");
	printf("*******   5. 展示   6. 排序   *******\n");
	printf("*******   7. 分类   8. 保存   *******\n");
	printf("*******        0. 退出        *******\n");
	printf("*************************************\n");
	printf("\n");
}

enum Options
{
	Exit,
	Add,
	Del,
	Find,
	Modify,
	Show,
	Sort,
	//Classify,
	Save,
};

int main()
{
	int input = 0;
	Contact book;
	InitContact(&book);
	LoadContact(&book);
	do
	{
		menu();
		int n = 1;
		while (n)
		{
			printf("请选择: >");
			n = scanf("%d", &input);
			if (n != 1 || input < 0 || input > 8 || n == 0)
			{
				printf("没有对应选项,请重新选择\n");
				continue;
			}
			else
				break;
		}
		switch (input)
		{
		case Add:
			AddContact(&book);
			break;
		case Del:
			DelContact(&book);
			break;
		case Find:
			FindContact(&book);
			break;
		case Modify:
			ModifyContact(&book);
			break;
		case Show:
			ShowContact(&book);
			break;
		case Sort:
			SortContact(&book);
			break;
			/*case Classify:
				ClassifyContact(&book);
				break;*/
		case Save:
			SaveContact(&book);
			break;
		case Exit:
			SaveContact(&book);
			DestroyContact(&book);
			printf("退出通讯录\n");
			break;
		}
	} while (input);
	return 0;
}

这里的改动就在于选择哪个功能,以防止用户输错,所以错误就提醒一次,并重新输入。不过这里有个问题需要改动,如果用户输入了汉字等字符,那么程序就在显示完
没有对应选项,请重新选择一句话后退出通讯录,或者出现其它问题,其原因应当在于输入的格式是%d,所以字符自然会出错。至于这个如何解决,我还没有头绪,年后再去寻找办法。

后面的代码中一样会有这样的问题,用户输入的不是数字,程序出错。

Contact.h

#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>

#define MAX 100
#define NAME 20//姓名
#define GENDER 7//性别
#define ADDRESS 20//地址
#define WORK 10//职业
#define NUMBER 15//电话
#define SIZE 30//初始化时动态开辟的空间
#define ADD_SZ 10//增容时所用

typedef struct PeoInfo
{
	char name[NAME];
	char gender[GENDER];
	char address[ADDRESS];
	char work[WORK];
	char number[NUMBER];
	int age;
}PeoInfo;

typedef struct Contact
{
	PeoInfo* data;
	int sz;
	int capacity;
}Contact;


void LoadContact(Contact* ps);

void InitContact(Contact* ps);

void AddContact(Contact* ps);

void DelContact(Contact* ps);

int SpecifyDel(Contact* ps);

int BatchDel(Contact* pb);

void FindContact(Contact* ps);

void SortContact(Contact* ps);

void ModifyContact(const Contact* ps);

void ShowContact(const Contact* ps);

//void ClassifyContact(const Contact* ps);

void DestroyContact(Contact* ps);

void SaveContact(Contact* ps);

两个文件都可以看到,我有一个分类函数没写,这也是之后需要完善的一部分。

接下来才是重点——批量删除

Contact.c(重点为批量删除)

1、删除主体展示

这里我会只展示删除部分的代码,其它都有小细节的改动,不算大改。

先看删除的主体

void DelContact(Contact* pd)
{
	assert(pd);
	int n = 0;
	int s = 1;
	int b = 1;
	int i = 0;
	int num = 0;
	char ret[20] = { '\0' };
	printf("进行删除前请仔细看好每个联系人的代号\n");
	ShowContact(pd);
	printf("请选择删除模式: 1、指定删除  2、批量删除\n");
	while (scanf("%d", &n) != EOF)
	{
		if (n == 1)
		{
			while (s)
			{
				s = SpecifyDel(pd);
				if (s != 0)
					++num;
				if (3 == num)
				{
					printf("程序自身已出现错误3次,强制退出,请重新开始\n");
					s = 0;
					num = 0;
				}
			}
			break;
		}
		else if (n == 2)
		{
			while (b)
			{
				b = BatchDel(pd);
				if (b != 0)
					++num;
				if (3 == num)
				{
					printf("程序自身已出现错误3次,强制退出,请重新开始\n");
					b = 0;
					num = 0;
				}
			}
			break;
		}
		else
		{
			printf("请重新输入\n");
			continue;
		}
	}
}

删除分为指定删除和批量删除。在两个删除里,都需要开文件指针,往回收站文本文件里写入数据;开指针的时候要检查是否为NULL;函数其他地方都不会出这样的错误,只是代码写得怎么样,所以我把两个函数都改成int类型,出错就返回1.实际上我们都知道,创建指针出错这种问题的概率极小,可以忽略掉,不过只是出于严谨吧。指定删除(SpecifyDel)用s接受返回值,批量删除(BatchDel)用b接收返回值。

在之前的代码中,选择删除后,会询问用户是否查看已存在的联系人,现在已经改过,直接显示一遍,无论是指定还是批量都需要用到前面的序列号,所以不如直接显示一遍更好。

2、批量删除

int BatchDel(Contact* pb)
{
	assert(pb);
	int num[30] = { 0 };
	int n = 0;
	int m = 1;
	int size = 0;
	printf("请输入要删除的序列号:     选择好后,输入 666 即可进行批量删除\n");
	while (m && scanf("%d", &n) != EOF)
	{
		if (n == 666)
			break;
		num[size] = n;
		if (size < 30)
			size++;
		if (size == 30)
		{
			printf("最多选择30个,是否开始删除? 0、是   1、否\n");
			scanf("%d", &m);
			if (m)
			{
				printf("您可以继续选择,但只会删除前30个\n");
			}
		}
	}
	InsertSort(num, size);
	FILE* bin = fopen("E://recycle bin.txt", "a");
	if (bin == NULL)
	{
		perror("ZDDel::fopen");
		printf("文件创建失败,请重新进行删除\n");
		return 1;
	}
	int x = pb->sz, y = 0, z = 0, ret = 0;
	while (x > 1)
	{
		while (x != num[z])
		{
			x--;
		}
		y = x - 1;
		while ((y + 1) == num[z] && z < size)
		{
			fprintf(bin, "姓名:%s  性别:%s  地址:%s  工作:%s  电话:%s  年龄:%d\n", pb->data[y].name, pb->data[y].gender, pb->data[y].address, pb->data[y].work, pb->data[y].number, pb->data[y].age);
			y--;
			z++;
		}
		y += 1;
		ret = x - y;
		for (int i = y; i < pb->sz - ret; i++)
		{
			pb->data[i] = pb->data[i + ret];
		}
		pb->sz = pb->sz - ret;
		x = y;
	}
	fclose(bin);
	bin = NULL;
	return 0;
}

我们如何看待这些代码?

现实中我们可以看到的是,选择批量删除后,我们要选择删除哪几个,选中后点击删除即可。放到数组做的通讯录来说,就是选中的数字转换成可对应的数组的元素,然后全部删除掉,再也访问不到它们。

问题是,正如上方代码一样,用户选中删除哪几个后,程序如何开始删除?这里我没有想到解决办法,本来是想输入数字后让用户输入“删除”二字,但不知如何让数字和字符共存,我想到的就是检测到字符后就转换成数字,但还没有实现。所以我先用666来结束循环。

我设定好最多选择30个,也就写了个循环来控制数量。结束后就是批量删除的开始。我的代码思路使用了双指针思路,只不过这里是双变量。双变量之前,先把选好的序号排列一下,否则无论后面如何操作,无序的序号组都是一个麻烦。

起初我排了升序,x代表序号,从1开始走,遇到选中的序号就停下来,用y来继续遍历,把遇到的序号放入回收站文件里,碰到非选中的序号就结束循环,然后后面的元素往前覆盖,控制好步数即可。但是走错了,很麻烦。在我开始写覆盖的代码时就发现了一个问题,如果选中的是3和5呢?y经历过3的位置后,会停在4的位置,这时候开始覆盖,4会来到x停下的位置,5会在其后,那么y是不是又得再来一次?从前往后走不好控制,容易控制不住,出现越界问题,删错数据。所以我改为从后往前,x初始值设为通讯录sz的大小,也就是联系人数量的大小。从后往前走,遇到一个选中的序列号后,循环还是交给y,让y继续往前走,遇到非选中的序号就停下,然后后覆盖前,这里就不需要担心是否还有临近的序号要删除。覆盖完后,x也得来到y的附件,继续往前删除。不过这里就出现了一个问题,要想真正做到不担心附近是否有选中序号,序号组就需要降序,让x先遇到大的序号,这样小的序号都在左边,就不会出现问题。另一个要解决的就是边界问题,或者说这也是删除数据常出现的问题之一。

	int x = pb->sz, y = 0, z = 0, ret = 0;
	while (x > 1)
	{
		while (x != num[z])
		{
			x--;
		}
		y = x - 1;
		while ((y + 1) == num[z] && z < size)
		{
			fprintf(bin, "姓名:%s  性别:%s  地址:%s  工作:%s  电话:%s  年龄:%d\n", pb->data[y].name, pb->data[y].gender, pb->data[y].address, pb->data[y].work, pb->data[y].number, pb->data[y].age);
			y--;
			z++;
		}
		y += 1;
		ret = x - y;
		for (int i = y; i < pb->sz - ret; i++)
		{
			pb->data[i] = pb->data[i + ret];
		}
		pb->sz = pb->sz - ret;
		x = y;
	}

这一部分就是我经过不断调试后才成功的代码。这里有一个考虑,假如选择1 4 6,那么x遇到6后停下,此时y = 5,进入循环后,y–, = 4,这时候循环会退出,然后开始往前覆盖。这也就说明一个现象,序号为5时,也就是下标为4的元素不是选中的序号,所以x应当从4开始继续往前走,所以最后有一行x = y。

代码运行到这里,也要注意指针的回收。

这就是做好的批量删除的思路。实际通讯录中批量删除如何实现我并不清楚,这仅仅是我自己能够想到的思路。之前做过以空间换时间,再创建一个结构体,删除时用它来存储删除后的联系人,然后再拷贝回去,这条思路也可,不过在数组上,我认为双变量更简单。

关于还没写到的代码,有输入非数字的解决,分类函数,程序开始删除等问题,年后会继续改善。现在写出来的代码算是整体的框架已经完成,剩下的是修改以及更有效的代码实现。年后继续更新,可去我的码云看完整的代码。

给各位看官拜年了!

结束。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值