数据结构:用顺序表和单链表实现通讯录(上)

本文详细介绍了顺序表和链表在通讯录应用中的实现,包括它们的优缺点,如空间效率、插入删除操作的复杂度,并展示了如何使用这两种数据结构来构建通讯录功能,如添加、删除和查找联系人。
摘要由CSDN通过智能技术生成

前言

首先简要介绍顺序表和链表的概念和区别以作区分。

顺序表:逻辑上是线性的,物理性质上也是线性的。逻辑是线性的(连续的)体现在它可以通过第一个数找到接下来的数。物理性质上的线性体现在分配给它的内存是连续的。它本质上就像一个数组,可以通过下标来访问成员。

单链表:这里说的单链表是指不带头单向不循环链表。链表和顺序表是不同的。链表在逻辑上是线性的,但在物理性质上是非线性的。需要的时候申请一块内存,但这块内存和其他内存不一定连续,它可能是散落在四周的,只是通过记住下一个节点的位置,使这些单独存在的空间有了联系。它们在逻辑上就像一条串好的线。一个结点接一个结点。就像火车一样,需要的时候装个车厢,不需要就把车厢撤下来。

顺序表和链表的对比

顺序表:

缺点:

1.申请空间一般按2的倍数申请,但是在使用的时候可能只需要用几个,因此会造成一定程度的空间浪费。

2.每次删除一个数,就要移动这个数后面的其他所有数。删除的数剧靠前,数据总量庞大时,效率太低了。

3.每次内存满了以后就要扩容(realloc),扩容导致性能效率低。

优点:

1.释放内存可以一次性释放。方便。

2.知道一个数的下标,就能访问相邻的两个数。

链表:

缺点:

1.销毁链表时,释放申请的内存属于申请了多少块就要释放多少。

2.单链表只能记住下一个结点的地址,没办法通过当前的地址找到上一个。找上一个结点时要一个个去找。

优点:

1.需要添加一个结点就申请一块空间。不会造成空间浪费。

2.每次删除一个数,只需要改动这个数相邻的两个结点的指针,相对来说影响很小。

3.单链表是申请(malloc)新的空间,不存在扩容。

在实践中,单链表和顺序表都会用,主要看应用场景是什么。

顺序表实现通讯录

1.分析

通讯录的功能有添加联系人,删除联系人,查找联系人,修改联系人,展示通讯录,退出通讯录六个功能。这次需要实现的就六个功能。

建立三个文件。sqcontact.c、sqcontact.h、test.c

sqcontact.h主要放头文件声明,函数声明,宏等。

sqcontact.c主要写函数的具体实现,函数的定义。

test.c主要写通讯录功能的测试,属于测试文件。

2.先写头文件,sqcontact.h

用注释说明了。

头文件里主要写你想要实现哪些功能,就声明哪些函数。先写的时候可以不完整,后面想到再加。

#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:6031)
#pragma once//防止被重复引用
#define NAME_MAX 100
#define SEX_MAX 10
#define TEL_MAX 14
#define ADDR_MAX 100
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>

typedef struct personinfo   //联系人是一个结构体,这个结构体里面包含有五个信息。
{
	char name[NAME_MAX];
	char sex[SEX_MAX];
	int age;            //其他信息是数组,因为单个char内存太小不够写,但年龄只需要整型就够
	char tel[TEL_MAX];
	char addr[ADDR_MAX];
}personinfo;         //把struct personinfo重命名为personinfo


typedef struct seqlist   //整个通讯录是一个结构体,这个结构体里面有联系人数组
{
	personinfo* info;//这是一个联系人数组,里面有各个联系人信息
	int capacity;//这个是通讯录的空间大小,可以装多少联系人
	int size; //这个是通讯录的实际大小,装了多少个联系人
}contact;

void Menu();       //菜单的声明  下面是各个功能实现的函数。
//初始化通讯录
void Initcontact(contact* con);//传一级指针是因为要改变con的内容,下面保持接口一致性
//扩容
void Creatcapacity(contact* con);
//添加联系人
void Addcontact(contact* con);
//删除联系人
void Delcontact(contact* con);
//展示联系人
void Showcontact(contact* con);
//查找联系人
void Findcontact(contact* con);
//修改联系人
void Modifycontact(contact* con);
//销毁通讯录
void Destorycontact(contact* con);

2.再写sqcontact.c文件

这里首先要包含头文件的内容。然后去实现头文件的那些函数。

写完一些功能的时候可以测试一下这些代码是否无误。

#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:6031)
#include "sqcontact.h"

void Menu()
{
	printf("\n");
	printf("***************************\n");
	printf("1.添加联系人\n");
	printf("2.删除联系人\n");
	printf("3.修改联系人\n");
	printf("4.展示联系人\n");
	printf("5.查找联系人\n");
	printf("0.退出通讯录\n");
	printf("***************************\n\n");
}

//初始化通讯录
void Initcontact(contact* con)
{
	con->info = NULL;           //初始化通讯录就是把里面的指针置为NULL,其他赋值。
	con->capacity = 0;          //也可以把指针赋值,只是为了避免它成为野指针。
	con->size = 0;
}

//扩容
void Creatcapacity(contact* con)   
{
	if (con->capacity == con->size)//如果容量和实际数据相等(包括0),就需要扩容。
	{
		int newcapacity = con->capacity == 0 ? 4 : (2 * con->capacity);
		personinfo* ptr = (personinfo*)realloc(con->info, sizeof(personinfo) * newcapacity);
		if (ptr == NULL)
		{
			perror("realloc failured");
			exit(1);
		}
		con->info = ptr;
		con->capacity = newcapacity;
	}//还有容量则什么都不做
}
//尾插
void Addback(contact* con, personinfo* x)
{
	//一种是直接尾插,一种是头插
	if (con->size==0)  //头插,因为联系人里没有数据
	{
		Creatcapacity(con);//尾插的时候要看一下空间够不够
		con->info[0] = *x;
		con->size++;
	}
	else//尾插,联系人中有数据
	{
		Creatcapacity(con);
		con->info[con->size] = *x;
		con->size++;
	}
}

//添加联系人 包含扩容,尾插
void Addcontact(contact* con)
{
	personinfo x;  //创建一个联系人结构体,填充内容,再把它传给尾插的函数。
	Creatcapacity(con);   //这句可以不写,后面尾插的时候扩容了的
	printf("请输入您要添加的联系人姓名:\n");
	scanf("%s", x.name);   //数组名是首元素地址,所以不用再取地址了
	printf("请输入您要添加的联系人性别:\n");
	scanf("%s", x.sex);
	printf("请输入您要添加的联系人年龄:\n");
	scanf("%d", &x.age);  //注意,这里要取地址。这个是整型
	printf("请输入您要添加的联系人电话:\n");
	scanf("%s", x.tel);
	printf("请输入您要添加的联系人地址:\n");
	scanf("%s", x.addr);

	Addback(con, &x);
	printf("联系人添加成功!\n");
	Showcontact(con);//添加成功以后再展示一遍当前通讯录
}

//指定位置删除联系人
void Del(contact* con, int pos)
{
	for (int i = pos; i < con->size-1; i++)//i如果是最后一个,下标为size-1的数,那么
	{
		con->info[i] = con->info[i + 1];
	}
	con->size--;//通过size--,最后一个数也被删掉了,这个逻辑就完整了。(访问不了等于删掉
}
//删除联系人包含 找联系人 删除联系人
void Delcontact(contact* con)
{
	assert(con);
	printf("请输入您要删除的联系人:\n");
	char name[NAME_MAX];
	scanf("%s", name);

	for (int i = 0; i < con->size; i++)
	{
		if (strcmp(con->info[i].name, name) == 0)//找联系人
		{
			//任意位置删除联系人
			Del(con, i);
			printf("联系人删除成功\n\n");
			Showcontact(con);
			return;
		}
	}
	printf("该联系人不存在!\n");

}

//展示联系人
void Showcontact(contact* con)
{
	if (con == NULL)
	{
		return;
	}
	printf("当前联系人如下:\n");
	printf("%-4s %-4s %-4s %-4s %-4s\n", "姓名", "性别", "年龄", "电话", "地址");
	for (int i = 0; i < con->size; i++)
	{
		printf("%-4s ", con->info[i].name);
		printf("%-4s ", con->info[i].sex);
		printf("%-4d ", con->info[i].age);
		printf("%-4s ", con->info[i].tel);
		printf("%-4s ", con->info[i].addr);
		printf("\n");
	}
}

//查找联系人  这个代码和前面有很多重复部分,找联系人。直接复制之前的代码再修改一下
void Findcontact(contact* con)
{
	assert(con);
	printf("请输入您要查找的联系人:\n");
	char name[NAME_MAX];
	scanf("%s", name);

	for (int i = 0; i < con->size; i++)
	{
		if (strcmp(con->info[i].name, name) == 0)
		{
			printf("%-4s %-4s %-4s %-4s %-4s\n", "姓名", "性别", "年龄", "电话", "地址");
			printf("%-4s ", con->info[i].name);
			printf("%-4s ", con->info[i].sex);
			printf("%-4d ", con->info[i].age);
			printf("%-4s ", con->info[i].tel);
			printf("%-4s ", con->info[i].addr);
			printf("\n");
			return;
		}
	}
	printf("该联系人不存在!\n");
}

//修改联系人  找联系人,再改联系人。这个代码和前面代码有很多重复部分。直接复制过来再改改
void Modifycontact(contact* con)
{
	assert(con);
	printf("请输入您要修改的联系人:\n");
	char name[NAME_MAX];
	scanf("%s", name);

	for (int i = 0; i < con->size; i++)
	{
		if (strcmp(con->info[i].name, name) == 0)
		{
			printf("请输入您要修改的联系人姓名:\n");
			scanf("%s", con->info[i].name);
			printf("请输入您要修改的联系人性别:\n");
			scanf("%s", con->info[i].sex);
			printf("请输入您要修改的联系人年龄:\n");
			scanf("%d", &con->info[i].age);
			printf("请输入您要修改的联系人电话:\n");
			scanf("%s", con->info[i].tel);
			printf("请输入您要修改的联系人地址:\n");
			scanf("%s", con->info[i].addr);
			printf("联系人修改成功!\n");
			Showcontact(con);
			return;
		}
	}
	printf("该联系人不存在!\n");
}

//销毁通讯录
void Destorycontact(contact* con)
{
	assert(con);  //不能释放已经释放的动态内存
	free(con->info); //释放内存空间
	con->info = NULL;//再把指针置为空
	con->size = con->capacity = 0;//其他变为0
}

3.写test.c文件

可以先实现一部分功能以后就写test.c文件测试一下那些功能是否能顺利运行。

首先还是包含一个头文件。初始化和创建通讯录不能在do while循环里。测试文件相比比较简单。

#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:6031)
#include "sqcontact.h"


int main()
{
	contact s1;
	Initcontact(&s1);
	int num = 0;
	do
	{
		Menu();
		printf("请输入您要进行的操作:\n");
		scanf("%d", &num);
		switch (num)
		{
		case 1: Addcontact(&s1);
			break;
		case 2: Delcontact(&s1);
			break;
		case 3: Modifycontact(&s1);
			break;
		case 4: Showcontact(&s1);
			break;
		case 5: Findcontact(&s1);
			break;
		case 0: Destorycontact(&s1);
			break;
		default: printf("输入错误,请重新输入!\n");
			break;
		}

	} while (num);

	return 0;
}

小提示

写代码的时候第一次运行发现报错很多也别慌,看报错的内容一个个找原因就好。

主要是逻辑上没有错误,没有遗漏情况,没有数组越界,没有释放空指针等。然后是代码有没有问题,函数名是否写错,重复定义,漏写头文件,取地址,类型错误等等。

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值