C语言实验-动态顺序表实现简易通讯录

目录

一、前言

二、实现通讯录

1.定义联系人

2.定义通讯录顺序表

3.通讯录菜单及主函数

菜单与main函数

枚举主函数内部选项

fflush(stdin)清空缓冲区

4.初始化通讯录

5.新增联系人

判断增容

添加联系人

6.按姓名查找联系人

7.删除联系人

8.修改指定联系人的指定信息

9.查看所有联系人

10.按指定关键字对联系人进行排序

三、完整代码

Contact.cpp

Contact.h

Test.cpp 


一、前言

本文介绍用C语言模拟一个通讯录,用来存储联系人的信息。采取数据结构中的动弹顺序表来实现。

每个联系人的信息包括:姓名、性别、电话、住址。

通讯录的功能包括:

  1. 新增联系人信息
  2. 按姓名查找联系人
  3. 删除联系人信息
  4. 修改指定联系人的指定信息
  5. 查看所有联系人信息
  6. 按指定关键字排序联系人信息

二、实现通讯录

1.定义联系人

通讯录存放联系人的信息,因此顺序表中的每一个元素应是一个“联系人”的结构体类型。

代码如下:

typedef struct People
{
	char name[20];	//姓名
	char sex[6];	//性别
	char tel[15];	//电话
	char addr[50];	//住址
}People;

2.定义通讯录顺序表

采用顺序表来实现通讯录。一个通讯录包含:存储的元素内容、通讯录表长与通讯录表容量。表长用于记录已存储的联系人个数,表的容量用于控制通讯录能否正常使用、是否需要增容等。

代码如下:

typedef struct Contact 
{
	People* arr;
	int size;
	int capacity;
}Contact;

注意,这里的数组arr的类型是联系人类型,也就是People类型,因为它存放的内容是每一个联系人的信息。 


3.通讯录菜单及主函数

菜单与main函数

菜单包含了我们要实现的功能提示。将菜单封装成函数,在主函数中调用。

代码如下:

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

用户通过选择功能前不同的数字代号,来进入对应的功能。在主函数中,我们可以通过switch-case语句来实现多分支,如当用户输入数字1时,进入add功能。

枚举主函数内部选项

为了增强代码的可读性,我们也可以用枚举类型定义以上菜单中的各个功能。在如下的枚举中,从texit到sort这七个变量分别代表整型数字0到数字6.

enum Choice
{
	texit,    //0    exit与c语言函数重名,因此需更改一下变量名
	add,    //1
	del,    //2
	search,    //3
	modify,    //4
	show,    //5
	sort    //6
};

因此,在main函数的switch-case控制中,我们可以这样写:用枚举中定义的7个变量分别代替0到6这七个数字作为switch-case的入口。 

	int input = 0;

	do
	{
		menu();
		printf("请输入选项:>");
		scanf("%d", &input);

		switch (input)
		{
		case add:
			//add
			break;
		case del:
			//del
			break;
		case search:
			//search
			break;
		case modify:
			//modify
			break;
		case show:
			//show
			break;
		case sort:
			//sort
			break;
		case texit:
			//texit
			break;
		default:
			printf("请输入正确的选项!\n");
			break;
		}
		fflush(stdin);
		printf("\n");
	} while (input);
	
	return 0;
}

fflush(stdin)清空缓冲区

在上面代码中,在单个功能结束后调用了fflush(stdin)函数。该函数是C语言的清空缓冲区函数。

在输入input选项时,scanf("%d",&input)语句只有从缓冲区中读取到整型数字时,读取成功。但如果用户误触,没有输入整型数字,而输入了浮点数或其它非法数据,scanf会发生读取错误,即实际读入的数据与%d不匹配。而此时,缓冲区中的非法数据并没有因为scanf的读取而被清除。在第一次do-while循环结束、第二次循环进入时,scanf仍然会因为读取到缓冲区中的非法数据而发生读取错误。

只要缓冲区内的非法数据不清楚,scanf就永远无法读到正确的数据。这里可能会发生do-while的死循环,因此用fflush(stdin)进行规避。

C语言实验-动态顺序表实现简易通讯录_第1张图片 如输入7.3时,会发生死循环

C语言实验-动态顺序表实现简易通讯录_第2张图片 输入1.2,虽然不会发生死循环,但也会因为读取错误导致程序无法正常运行

这里对fflush(stdin)函数作简要介绍,并且提醒一下大家scanf的数据读取问题。


4.初始化与销毁通讯录

初始化

初始化通讯录可以有两个版本:初始化并分配顺序表空间或仅初始化,不分配顺序表空间(分配空间的任务在对顺序表进行插入时再执行)。

代码如下:

版本一:初始化并分配顺序表空间

#define DEFAULT_SZ 3

void Init(Contact* con)
{
	assert(con);

	con->arr = (People*)malloc(DEFAULT_SZ * sizeof(People));
	if (con->arr == NULL)
	{
		perror("Init()");
		return;
	}
	con->size = 0;
	con->capacity = DEFAULT_SZ;
}

版本二:仅初始化不分配空间

void Init(Contact* con)
{
	con->arr = NULL;
	con->size = con->capacity = 0;
}

*下面将采取版本二对应的思路实现剩余代码。

销毁

//7-销毁
void destroyContact(Contact *con)
{
	assert(con);
	free(con->arr);
	con->arr = NULL;
	con->size = con->capacity = 0;
	printf("销毁成功!\n");
}

在exit之前,必须先销毁,否则会造成内存泄漏。 


5.新增联系人

判断增容

若表长size与表容量capacity相等时,意味着顺序表满,应当增容。

增容代码如下:

//判断增容
static void CheckCapacity(Contact* con)
{
	assert(con);

	if (con->capacity == con->size)    //判断通讯录是否已满
	{
        //由于初始化表时,并未给表分配空间,因此若capacity为0,则应先分配空间
		int newCapacity = con->capacity == 0 ? 4 : 2 * con->capacity;

        //realloc原地增容
		People* tmp = (People*)realloc(con->arr, sizeof(People) * newCapacity);
		if (tmp == NULL)
		{
			perror("CheckCapacity");
			exit(-1);
		}
		else
		{
			con->arr = tmp;
			con->capacity = newCapacity;
		}
	}
}

增容的思路是当表满后,顺序表的容量扩展为原表的两倍。但由于初始化通讯录顺序表时,并未给顺序表分配空间,因此最开始表capacity为0.此时2*0结果还是0,无法达到增容的效果,程序会出错。因此先对newCapacity进行判断,若newCapacity为0,则先给它赋一个初始值。若newCapacity不为0,那么扩容到原来的两倍。

此外,扩容应当是在原数组的基础上扩张。因此要用realloc来原地扩容。realloc的原理是当原数组后还有连续可用空间时,直接在原数组后开辟空间,仍然返回原数组地址。

若用malloc,则会重新开辟一片内存空间,原数组中的内容会丢失,因此不妥。

添加联系人

在判断增容过后,就能安全地添加联系人了。我们通过con->arr[con->size].成员名 来输入联系人信息。

con是一个指向通讯录的指针,通讯录中的arr数组存储的是联系人People的信息。

con->arr即可访问到通讯录中的People信息。

由于是顺序表,通过con->size即可访问表中元素。表中每个元素都是People类型,因此con->arr[con->size]表示的正是一个People类型的结构体,这时通过成员访问符 . 即可对相应的信息进行输入。

添加联系人的代码如下:

//1-添加
void addContact(Contact *con)
{
	//判断增容
	CheckCapacity(con);

	//添加内容
	printf("姓名:");
	scanf(" %s", con->arr[con->size].name);
	printf("性别:");
	scanf(" %s", con->arr[con->size].sex);
	printf("电话:");
	scanf(" %s", con->arr[con->size].tel);
	printf("住址:");
	scanf(" %s", con->arr[con->size].addr);

	con->size++;    //添加完毕后个数要+1
	printf("添加联系人成功!\n");
}

6.按姓名查找联系人

toFindName是我们要查找的联系人姓名,由用户输入。将用户输入的待查找姓名与通讯录中的所有联系人姓名挨个比较,若查找到了,就返回该联系人在通讯录中的下标,若没有找到,返回-1。

代码如下:

//3-查找 按姓名
int searchContact(Contact *con,const char *toFindName)
{
	assert(con);

	for (int i = 0; i < con->size; i++)
	{
		if (strcmp(con->arr[i].name, toFindName) == 0)
		{
			return i;
		}
	}
	return -1;
}

7.删除联系人

删除联系人的函数中,先对通讯录进行了判空,以保证在执行运行操作时候通讯录非空。

调用了查找函数,来判断要删除的联系人在表中是否存在。若存在,则执行删除操作:与顺序表的删除一样,将该联系人后面的所有联系人(从第i+1到第n)依次向前移动1位,将要删除的联系人信息覆盖删除。

在删除完毕后必须将个数size减1.

代码如下:

//2-按姓名删除
void delContact(Contact* con,const char *toDelName)
{
	assert(con);
	if (con->size == 0)
	{
		printf("通讯录为空!删除失败!\n");
		return;
	}

	if (searchContact(con, toDelName) != -1)
	{
		for (int i = searchContact(con, toDelName); i < con->size-1; i++)
		{
			con->arr[i] = con->arr[i + 1];
		}
		con->size--;
		printf("删除成功!\n");
	}
	else
	{
		printf("该联系人不存在,删除失败!\n");
	}
}

8.修改指定联系人的指定信息

通过姓名检索指定的要更改其信息的联系人,仍然调用查找函数实现检索。

在找到目标联系人的信息后,我们通过相应的选择来决定要更改该联系人的哪一信息(姓名/性别/电话/住址)。注意,此处联系人信息均应输入字符串,对字符串的赋值不能直接用等号,而应用strcpy()

代码如下:

//4-修改 按姓名
void modifyContact(Contact* con)
{
	assert(con);
	if (con->size == 0)
	{
		printf("通讯录为空!修改失败!\n");
		return;
	}

	char toModifyName[20] = { 0 };
	printf("请输入要更改的联系人的姓名:");
	scanf(" %s", toModifyName);

	int modifyI = searchContact(con, toModifyName);
	if (modifyI != -1)
	{
		printf("请输入您要修改的信息项:\n");
		printf("0.姓名:\n");
		printf("1.性别:\n");
		printf("2.电话:\n");
		printf("3.住址:\n");
		
		int input = 0;
		scanf(" %d", &input);

		printf("请输入修改后的信息:");
		char tmp[50] = { 0 };
		scanf(" %s", tmp);

		switch (input)
		{
		case 0:
			strcpy(con->arr[modifyI].name, tmp);
			break;
		case 1:
			strcpy(con->arr[modifyI].sex, tmp);
			break;
		case 2:
			strcpy(con->arr[modifyI].tel, tmp);
			break;
		case 3:
			strcpy(con->arr[modifyI].addr, tmp);
			break;
		}		
		printf("修改成功!\n");
	}
	else
	{
		printf("修改失败!联系人不存在!\n");
	}
}

9.查看所有联系人

注意打印格式,%-20s 的含义为打印一个字符串,该字符串左对齐(-号)并占20个比特位(若不足20个比特位则右侧补空格)。

代码如下:

//5-展示
void showContact(Contact *con)
{
	printf("%-20s", "姓名");
	printf("%-20s", "性别");
	printf("%-20s", "电话");
	printf("%-20s", "住址");
	printf("\n");
	
	for(int i = 0; i < con->size; i++)
	{
		printf("%-20s", con->arr[i].name);
		printf("%-20s", con->arr[i].sex);
		printf("%-20s", con->arr[i].tel);
		printf("%-20s", con->arr[i].addr);
		printf("\n");
	}
}

打印后的对齐效果如下:

C语言实验-动态顺序表实现简易通讯录_第3张图片


10.按指定关键字对联系人进行排序

实现思路与修改指定联系人的指定信息类似。在排序时我们可以采用c库函数qsort()

注意qsort实现结构体排序时,比较函数cmp的书写。需先将e1和e2强制类型转换为结构体指针(People*),再通过结构体指针访问联系人信息关键字。

//比较函数
static int cmp_name(const void* e1, const void* e2)
{
	//本质是字符串比较->使用strcmp函数
	return strcmp(((People*)e1)->name, ((People*)e2)->name);
}

static int cmp_sex(const void* e1, const void* e2)
{
	return strcmp(((People*)e1)->sex, ((People*)e2)->sex);

}

static int cmp_tel(const void* e1, const void* e2)
{
	return strcmp(((People*)e1)->tel, ((People*)e2)->tel);
}

static int cmp_addr(const void* e1, const void* e2)
{
	return strcmp(((People*)e1)->addr, ((People*)e2)->addr);
}



//6-排序
void sortContact(Contact *con)
{
	assert(con);

	printf("要按哪一项排序?\n");
	printf("0.姓名:\n");
	printf("1.性别:\n");
	printf("2.电话:\n");
	printf("3.住址:\n");

	int input = 0;
	scanf(" %d", &input);
	switch (input)
	{
	case 0:
		qsort(con->arr, con->size, sizeof(con->arr[0]), cmp_name);
		break;
	case 1:
		qsort(con->arr, con->size, sizeof(con->arr[0]), cmp_sex);
		break;
	case 2:
		qsort(con->arr, con->size, sizeof(con->arr[0]), cmp_tel);
		break;
	case 3:
		qsort(con->arr, con->size, sizeof(con->arr[0]), cmp_addr);
		break;
	}
	printf("排序成功!\n");
}

三、完整代码

Contact.cpp

#define _CRT_SECURE_NO_WARNINGS 1

#include"Contact.h"

//初始化
void Init(Contact* con)
{
	con->arr = NULL;
	con->size = con->capacity = 0;
}

//判断增容
static void CheckCapacity(Contact* con)
{
	assert(con);

	if (con->capacity == con->size)
	{
		int newCapacity = con->capacity == 0 ? 4 : 2 * con->capacity;
		People* tmp = (People*)realloc(con->arr, sizeof(People) * newCapacity);
		if (tmp == NULL)
		{
			perror("CheckCapacity");
			exit(-1);
		}
		else
		{
			con->arr = tmp;
			con->capacity = newCapacity;
		}
	}
}

//1-添加
void addContact(Contact *con)
{
	//判断增容
	CheckCapacity(con);

	//添加内容
	printf("姓名:");
	scanf(" %s", con->arr[con->size].name);
	printf("性别:");
	scanf(" %s", con->arr[con->size].sex);
	printf("电话:");
	scanf(" %s", con->arr[con->size].tel);
	printf("住址:");
	scanf(" %s", con->arr[con->size].addr);

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

//2-按姓名删除
void delContact(Contact* con,const char *toDelName)
{
	assert(con);
	if (con->size == 0)
	{
		printf("通讯录为空!删除失败!\n");
		return;
	}

	if (searchContact(con, toDelName) != -1)
	{
		for (int i = searchContact(con, toDelName); i < con->size-1; i++)
		{
			con->arr[i] = con->arr[i + 1];
		}
		con->size--;
		printf("删除成功!\n");
	}
	else
	{
		printf("该联系人不存在,删除失败!\n");
	}
}

//3-查找 按姓名
int searchContact(Contact *con,const char *toFindName)
{
	assert(con);

	for (int i = 0; i < con->size; i++)
	{
		if (strcmp(con->arr[i].name, toFindName) == 0)
		{
			return i;
		}
	}
	return -1;
}

//4-修改 按姓名
void modifyContact(Contact* con)
{
	assert(con);
	if (con->size == 0)
	{
		printf("通讯录为空!修改失败!\n");
		return;
	}

	char toModifyName[20] = { 0 };
	printf("请输入要更改的联系人的姓名:");
	scanf(" %s", toModifyName);

	int modifyI = searchContact(con, toModifyName);
	if (modifyI != -1)
	{
		printf("请输入您要修改的信息项:\n");
		printf("0.姓名:\n");
		printf("1.性别:\n");
		printf("2.电话:\n");
		printf("3.住址:\n");
		
		int input = 0;
		scanf(" %d", &input);

		printf("请输入修改后的信息:");
		char tmp[50] = { 0 };
		scanf(" %s", tmp);

		switch (input)
		{
		case 0:
			strcpy(con->arr[modifyI].name, tmp);
			break;
		case 1:
			strcpy(con->arr[modifyI].sex, tmp);
			break;
		case 2:
			strcpy(con->arr[modifyI].tel, tmp);
			break;
		case 3:
			strcpy(con->arr[modifyI].addr, tmp);
			break;
		}		
		printf("修改成功!\n");
	}
	else
	{
		printf("修改失败!联系人不存在!\n");
	}
}

//5-展示
void showContact(Contact *con)
{
	printf("%-20s", "姓名");
	printf("%-20s", "性别");
	printf("%-20s", "电话");
	printf("%-20s", "住址");
	printf("\n");
	
	for(int i = 0; i < con->size; i++)
	{
		printf("%-20s", con->arr[i].name);
		printf("%-20s", con->arr[i].sex);
		printf("%-20s", con->arr[i].tel);
		printf("%-20s", con->arr[i].addr);
		printf("\n");
	}
}


static int cmp_name(const void* e1, const void* e2)
{
	//本质是字符串比较->使用strcmp函数
	return strcmp(((People*)e1)->name, ((People*)e2)->name);
}

static int cmp_sex(const void* e1, const void* e2)
{
	return strcmp(((People*)e1)->sex, ((People*)e2)->sex);

}

static int cmp_tel(const void* e1, const void* e2)
{
	return strcmp(((People*)e1)->tel, ((People*)e2)->tel);
}

static int cmp_addr(const void* e1, const void* e2)
{
	return strcmp(((People*)e1)->addr, ((People*)e2)->addr);
}

//6-排序
void sortContact(Contact *con)
{
	assert(con);

	printf("要按哪一项排序?\n");
	printf("0.姓名:\n");
	printf("1.性别:\n");
	printf("2.电话:\n");
	printf("3.住址:\n");

	int input = 0;
	scanf(" %d", &input);
	switch (input)
	{
	case 0:
		qsort(con->arr, con->size, sizeof(con->arr[0]), cmp_name);
		break;
	case 1:
		qsort(con->arr, con->size, sizeof(con->arr[0]), cmp_sex);
		break;
	case 2:
		qsort(con->arr, con->size, sizeof(con->arr[0]), cmp_tel);
		break;
	case 3:
		qsort(con->arr, con->size, sizeof(con->arr[0]), cmp_addr);
		break;
	}
	printf("排序成功!\n");
}

//7-销毁
void destroyContact(Contact *con)
{
	assert(con);
	free(con->arr);
	con->arr = NULL;
	con->size = con->capacity = 0;
	printf("销毁成功!\n");
}

Contact.h

#define _CRT_SECURE_NO_WARNINGS 1

#include
#include
#include
#include


enum Choice
{
	texit,
	add,
	del,
	search,
	modify,
	show,
	sort
};

typedef struct People
{
	char name[20];	//姓名
	char sex[6];	//性别
	char tel[15];	//电话
	char addr[50];	//住址
}People;

typedef struct Contact 
{
	People* arr;
	int size;
	int capacity;
}Contact;

//初始化
void Init(Contact* con);

//1-添加
void addContact(Contact* con);

//5-展示
void showContact(Contact* con);

//2-按姓名删除
void delContact(Contact* con, const char* toDelName);

//3-查找 按姓名
int searchContact(Contact* con, const char* toFindName);

//4-修改 按姓名
void modifyContact(Contact* con);

//6-排序
void sortContact(Contact* con);

//7-销毁
void destroyContact(Contact* con);

Test.cpp 

#define _CRT_SECURE_NO_WARNINGS 1

#include"Contact.h"

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

int main()
{
	Contact contact;
	Init(&contact);
	int input = 0;
	printf("通讯录初始化成功!\n");

	do
	{
		menu();
		printf("请输入选项:>");
		scanf(" %d", &input);

		switch (input)
		{
		case add:
		{
			addContact(&contact);
			break;
		}
		case del:
		{
			printf("请输入要删除的联系人姓名:>");
			char name[20] = { 0 };
			scanf(" %s", name);
			delContact(&contact, name);
			break;
		}
		case search:
		{
			printf("请输入要查找的联系人姓名:>");
			char name[20] = { 0 };
			scanf(" %s", name);
			searchContact(&contact, name);
			break;
		}
		case modify:
			modifyContact(&contact);
			break;
		case show:
			showContact(&contact);
			break;
		case sort:
			sortContact(&contact);
			break;
		case texit:
			printf("退出程序!通讯录销毁!\n");
			destroyContact(&contact);    //注意要销毁
			break;
		default:
			printf("请输入正确的选项!\n");
			break;
		}
		fflush(stdin);
		printf("\n");
	} while (input);
	
	return 0;
}

你可能感兴趣的:(#,C实现数据结构,课设项目合集,c语言,数据结构,算法,顺序表,动态规划)