IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 单链表 和 顺序表 实现的 通讯录 -> 正文阅读

[C++知识库]单链表 和 顺序表 实现的 通讯录

🐬前言

学了数据的存储结构,是时候该拿出来遛一遛了~
收获:将进一步加深对单链表,顺序表的理解。也可以以这个为模板去设计,图书管理系统,学生信息管理系统等,当然啦这些都是比较粗糙的、简陋的。

我们可以在日后学了更多的知识,再将其改造升级,我想这是一件比较有意义的事情,因为它见证了你的成长不是吗?

🍑通讯录

不管是实现任何项目,请记住一句话,思想大于实现

🍉通讯录的逻辑

实现的逻辑要正确,才能开始下手。
这样设计对吗(单链表的设计)?

typedef struct PeopleInfo
{
	char name[20];
	int age;
	char tele[12];
	char address[30];
}People;

typedef struct Contact
{
	People contacts;//联系人
	int lenth;//通讯录的长度
	struct Contact* next;//指针域
}Contact;

错的,上面实现的是每个通讯录中,存放一个联系人,这样的通讯录存在多个。(顺序表设计的时候也要注意到这点)尽管这样写也是可以实现,但仍然是一个失败品。

typedef struct PeopleInfo
{
	char name[20];
	int age;
	char tele[12];
	char address[30];
	struct People* next;//指向下一个联系人
}People;

typedef struct Contact
{
	int lenth;//通讯录的长度
	struct People* Con;//指针域
}Contact;

通讯录的正确逻辑是一个通讯录中存多个联系人。也可以添加一些通讯录的属性,比如通讯录的中当前存放联系人的个数和存放最大的联系人的个数。

🍉通讯录存储结构的选择

实现逻辑定下来了,该选取存储的结构了

不管选取哪种结构的,数据最终都是在内存中存储的,最后程序退出的时候,这些内容都不会存在了。

如果你想将你的数据持久化,学一些文件操作即可。将这里的数据都存储进文件中。但是嘞,正儿八经的工作中都是用数据库的。

🍊顺序表

🍎数组

想到多个联系人,一般都可以想到通过数组的形式来存储数据,数组本质上是一种顺序存储(地址连续)。

不过这种设计方式,存在一个弊端,大小被固定死了,如果把数组一开始的空间开辟的足够大,那又太浪费空间了。
这样设计出来的可以称为静态版本的通讯录

🍎在堆区开辟的空间

还存在哪些顺序存储的结构呢?

这里将涉及到动态内存的部分知识。
以下设计出来的通讯录为动态版本的通讯录

在堆区开辟的多个联系人的空间,这块空间根据联系人结构体的大小而定。
比如说刚开始设计通讯录的大小为3个人,那就先开辟三个联系人结构体大小的空间,这三块空间的地址是连续的。
当存满三个,再去扩容。如何扩容呢,与等下介绍的函数realloc有关

🍎柔性数组

可能有人没有听过这个概念(在c99中引入),什么是柔性数组呢?
柔性数组必须和结构体连用,并且柔性数组必须作为结构体的最后一个成员,它[]中写0或者不写。结构体中除了柔性数组外至少还得有一个成员

例如:

struct S
{
	int a;
	char data[];
    //char data[0]; 
};

这就是一个柔性数组。柔性数组虽然是在这个结构体中,但是求结构体的大小sizeof(struct S)时,柔性数组的大小是不会算进去的。

怎么分配空间呢?也是通过动态内存函数去分配空间。
一般先把结构体的空间分配出来,再分配柔性数组的空间。一般这样写

struct S* s = (struct S*)malloc(sizeof(struct S)+sizeof(char)*10)

sizeof(struct S)是为结构体分配的空间,sizeof(char)*10就是为柔性数组分配的空间。
在存储的时候它们地址也是连续的。

简单了解了下这些概念,接下来介绍三个函数realloc,malloc,free。下文会使用。

realloc,malloc都是在堆区中开辟空间,free是释放在堆区开辟的空间。

malloc

在这里插入图片描述

返回值是在堆区开辟空间的起始地址。因为地址类型是void*,所以需要根据具体情况去强制类型转换成一样的地址类型。
形参为需要开辟空间的大小

realloc

在这里插入图片描述

对原来已经在堆区开辟的空间,进行增加或者减少。
第一个形参是起始地址(原先在堆区开辟空间的起始地址)
第二个形参,扩容后或减少后的总大小。比如原先有10字节的空间,扩容2个字节,第二个形参就为12。
返回值也是堆区开辟空间的起始地址。

使用realloc进行扩容或者缩减时,原有的数据是不会改变的。

realloc进行扩容,原有空间的后面如果没有足够大的空间进行扩容,那会在堆区重新开辟一块空间(空间是扩容后的),同样的原有的数据不会改变,但地址发生改变

free

在这里插入图片描述

只能释放在堆区的空间,参数为在堆区开辟空间的起始地址

🍊单链表

单链表实现的通讯录就没有必要扩容,增加一个联系人,开辟一个节点,非常节约空间。

实现项目的时候,代码比较多采用多文件的形式。

🍑单链表实现通讯录

基于之前一篇博客单链表的基本操作和一些复杂操作来实现通讯录的。这里的任何单链表中的操作都在那篇博客中,所以只会介绍实现逻辑以及注意事项。

🍒进入页面设计

这里的使用了贪吃蛇博客中介绍的任意位置输出。

在这里插入图片描述

代码效果,看起来比较有那味了😄

通讯录

开始的时候有个字符向中间汇聚,这个实现也比较简单,准备两个数组,左下标和右下标,交换,打印。
页面在添加一些通讯录的操作。。

void Opertion()
{
	gotoxy(32, 3);
	printf("Opertion:");
	gotoxy(42, 4);
	printf("0.Exit     1.Add      2.Delete  3.Search");
	gotoxy(42, 6);
	printf("4.Modify   5.Order    6.Show    7.Clear");
}


void OpenContact()
{

	char arr[] = "Welcome back to  Contact";
	char arr1[] = "                          ";
	int left = 0;//左下标
	int right = strlen(arr) - 1;//右下标
	while (left < right)
	{
		arr1[left] = arr[left];
		arr1[right] = arr[right];
		gotoxy(44, 2);
		printf("%s", arr1);
		Sleep(1);
		left++;
		right--;
	}
	Opertion();
	printf("\n");
}

gotoxy(44, 2);每次打印都将光标移到(44,2)这个位置。所以就不需要清屏的操作,每次都从(44,2)打印,原有数据会被覆盖。打印速度太快没看到效果的话,加就加个Sleep(1);停屏操作。

🍒通讯录属性的设计

enum Op
{
	Exit,
	Add,
	Delete,
	Search,
	Modify,
	Order,
	Show,
	Clear,
};

typedef struct PeopleInfo
{
	char name[20];
	int age;
	char tele[12];
	char address[30];
	struct PeopleInfo* next;
}People;

typedef struct Contact
{
	int lenth;//通讯录的长度
	People* next;//当作头指针
}Contact;
extern Contact* Con;

项目中使用一些有特定含义的常量,并且这些常量有内在的联系,推荐使用枚举,不用宏。

对于上面两个结构体要理解,这和单链表操作中的不同,这两个是不同类型的结构体,一不小心很容易出错。

所以在写的时候,我会向熟悉的转换,也就是向单链表操作中那样使用,这样写起来会简单许多。可能这样说不太理解,看下面的图片。

在这里插入图片描述

这样设计,写来还是比较轻松的。浪费一个节点,浪费的节点充当头节点。

🍒主函数的设计

cHead可以设成全局变量,在调试的时候,可以观察到添加删除等操作之后,节点之间是否连接起来。

//Contact* cHead = (Contact*)malloc(sizeof(Contact));
int main()
{
	int input = 0;
	Contact* cHead = (Contact*)malloc(sizeof(Contact));
	if (NULL == cHead)
	{
		perror("AollocPeopleInfo");
		return 1;
	}
	memset(cHead, 0, sizeof(Contact));

	cHead->next = AollocPeopleInfo();
	People* pEnd = cHead->next;
	if (NULL == cHead->next)
	{
		perror("AollocPeopleInfo");
		return 1;
	}

	OpenContact();
	do
	{
		Input();
		gotoxy(32, 8);
		scanf("%d", &input);
		gotoxy(32, 8);
		printf("  ");
		switch (input)
		{
		case Exit :
			printf("通讯录关闭");
			break;
		case Add :
			AddContact(&pEnd);
			cHead->lenth++;
			break;
		case Delete :
			DelContact(cHead,&pEnd);
			cHead->lenth--;
			break;
		case Search:
			SerContact(cHead);
			break;
		case Modify:
			ModContact(cHead);
			break;
		case Order:
			OrderContact(cHead);
			break;
		case Show:
			ShowContact(cHead);
			break;
		case Clear:
			ClearContact(cHead);
			break;
		default:
			Error();
			break;
		}
		gotoxy(32, 3);
		printf("   现有联系人:%d ", cHead->lenth);
	} while (input);

	free(cHead->data);
	free(cHead);
	cHead = NULL;
	gotoxy(32, 12);
	return 0;
}

🍒AddContact

这里采用的是尾插法添加。
memset这个函数是用来对数据进行初始化。在这篇博客内存函数、字符串函数中有介绍,也提到了其他的常用的函数。

People* AollocPeopleInfo()
{
	People* p = (People*)malloc(sizeof(People));
	//初始化
	memset(p, 0, sizeof(People));
	return p;
}
//添加信息
void AddInput()
{
	gotoxy(32, 7);
	printf("请分别输入姓名 年龄 电话 地址的信息\n");
}
//清理添加痕迹
void CleanAdd()
{
	gotoxy(32, 7);
	printf("                                           ");
	gotoxy(32, 8);
	printf("                                           ");
	gotoxy(32, 9);
	printf("                     ");
}
//添加
void AddContact(People** end)
{
	People* p = AollocPeopleInfo();
	AddInput();
	gotoxy(32, 8);
	scanf("%s%d%s%s", p->name, &p->age,
		p->tele, p->address);
	gotoxy(32, 9);
	printf("添加成功");
	Sleep(50);
	CleanAdd();
	(*end)->next = p;
	*end = p;
}

🍒ShowContact

这里的i作用是用来清理所有打印出来的结果。

//清理展示的效果
void CleanShow(int i)
{
	while (i > 0)
	{
		gotoxy(32, 7 + i);
		printf("                                                                    ");
		i--;
	}
}
//展示
void ShowContact(Contact* head)
{
	People* p = head->next->next;//指向第一个节点
	
	static int i = 1;
	gotoxy(32, 7);
	printf("%-20s\t%-3s\t%-12s\t%-30s\t", "姓名", "年龄", "电话", "地址");
	if (0 == head->lenth)
	{
		gotoxy(32, 8);
		printf("通讯录为空");
		Sleep(500);
		CleanShow(i);
		return;
	}
	while (p)
	{
		gotoxy(32, 7 + i);
		printf("%-20s\t%-3d\t%-12s\t%-30s\t", p->name,
			p->age, p->tele, p->address);
		p = p->next;
		i++;
	}
	Sleep(3000);
	CleanShow(i);
	i = 1;
}

🍒DelContact

删除联系人,也可以根据人名进行删除,这里图了个简单。不管是哪种,都需要两个指针,一个去找位置,一个是记录前驱。

根据人名删除:字符串的比较需要用到strcmp(),删除人的名字需要用字符数组存起来
在循环中遍历,每一次都要与删除人的名字比较。找到了,前驱指向下个节点,在释放删除节点。

void DelContact(Contact* head)
{

	gotoxy(32, 7);
	printf("请输入删除哪一个联系人                     ");
	int x = 0;
	gotoxy(32, 8);
	scanf("%d", &x);
	gotoxy(32, 8);
	printf("     ");
	if (x < 1 || x > head->lenth)
	{
		gotoxy(32, 7);
		printf("位置错误,退回界面                ");
		Sleep(500);
		return;
	}
	People* p = head->next->next;//第一个节点开始 找删除位置
	People* p1 = head->next;//记录删除位置的前驱
	while (x - 1)
	{
		p = p->next;
		p1 = p1->next;
		x--;
	}
	p1->next = p->next;
	free(p);
	DelSet();
}

真能这样写吗?这样写,会有bug,挺难发现的,要结合前后的操作去理解。
假设删除最后一个联系人,再去添加新联系人,会成功(链表能连接)吗?

不会,为什么呢?添加联系人我们使用的是尾插法。注意在删除最后一个联系人的时候,尾指针指向的是这个删除的节点,指向没有改变。也就是说下一次添加新联系人时,尾指针仍然指向的是这个删除的节点。

在这里插入图片描述

正确的是这样的

在这里插入图片描述

所以需要改进

	DelContact(cHead,&pEnd);
void DelContact(Contact* head,People** end)
{

	gotoxy(32, 7);
	printf("请输入删除哪一个联系人                     ");
	int x = 0;
	gotoxy(32, 8);
	scanf("%d", &x);
	gotoxy(32, 8);
	printf("     ");
	if (x < 1 || x > head->lenth)
	{
		gotoxy(32, 7);
		printf("位置错误,退回界面                ");
		Sleep(500);
		return;
	}
	People* p = head->next->next;//第一个节点开始 找删除位置
	People* p1 = head->next;//记录删除位置的前驱
	int flag = 0;
	if (x == head->lenth)
		flag = 1;
	while (x - 1)
	{
		p = p->next;
		p1 = p1->next;
		x--;
	}
	p1->next = p->next;
	if (flag)
	{
		*end = p1;
	}
	free(p);
	DelSet();
}

🍒SerContact

查找联系人。
同样也可以根据人名查找。
这里不要删除,只用一个指针即可,找到了打印就完事了。

//查找展示和清理
void ShowCleanSear(People* p)
{
	//先清除原来的痕迹
	gotoxy(32, 7);
	printf("                                                                    ");
	gotoxy(32, 7);
	printf("%-20s\t%-3s\t%-12s\t%-30s\t", "姓名", "年龄", "电话", "地址");
	gotoxy(32, 8);
	printf("%-20s\t%-3d\t%-12s\t%-30s\t", p->name,
		p->age, p->tele, p->address);
	Sleep(2000);
	gotoxy(32, 7);
	printf("                                                                    ");
	gotoxy(32, 8);
	printf("                                                                    ");
}
void SerContact(Contact* head)
{
	gotoxy(32, 7);
	printf("请输入查找哪一个联系人                     ");
	int x = 0;
	gotoxy(32, 8);
	scanf("%d", &x);
	gotoxy(32, 8);
	printf("     ");
	if (x < 0 || x > head->lenth)
	{
		gotoxy(32, 7);
		printf("位置错误,退回界面                ");
		Sleep(500);
		return;
	}
	People* p = head->next;//从头节点开始 找 查找位置
	while (x)
	{
		p = p->next;
		x--;
	}
	ShowCleanSear(p);//展示找的记录,并清理痕迹
}

🍒ModContact

对联系人信息进行修改。
先得找才能修改,基于上面找的操作加以改进就可以了。

//清楚修改的痕迹
void CleanMod()
{
	int i = 0;
	while (i < 3)
	{
		gotoxy(32, 7 + i);
		printf("                                                                ");
		i++;
	}
}

//修改
void ModContact(Contact* head)
{
	gotoxy(32, 7);
	printf("请输入要修改哪一个联系人                     ");
	int x = 0;
	gotoxy(32, 8);
	scanf("%d", &x);
	gotoxy(32, 8);
	printf("      ");
	if (x < 0 || x > head->lenth)
	{
		gotoxy(32, 7);
		printf("位置错误,退回操作界面                ");
		Sleep(500);
		return;
	}

	People* p = head->next;//找 查找位置
	while (x)
	{
		p = p->next;
		x--;
	}

	gotoxy(32, 7);
	printf("请输入修改信息                                  ");
	gotoxy(32, 8);
	printf("%-20s\t%-3s\t%-12s\t%-30s\t", "姓名", "年龄", "电话", "地址");
	gotoxy(32, 9);
	scanf("%s%d%s%s", p->name, &p->age,
		p->tele, p->address);

	Sleep(1000);
	CleanMod();//清楚痕迹
	gotoxy(32, 7);
	printf("修改成功");
	Sleep(500);
	CleanMod();//清楚痕迹
}

🍒OrderContact

这个应该是第二难的吧,第一难的我觉得是刚刚那个bug,那个一不注意真难发现。

这里的排序和单链表基本操作和一些复杂操作博客中的略有不同,但大同小异,那篇博客中的稍微复杂,这里稍微简单。

这里是对节点中的任意成员排序,那篇博客中是对节点中的结构体的任意成员排序

这里的排序是由qsort的介绍,以及模拟实现发散而来。

单链表与顺序表不同,地址的不连续,参数的设计也有所不同。

单链表基本操作和一些复杂操作的博客中解释了,这里就不浪费笔墨了。

int int_cmp(const void* e1, const void* e2)
{
	return ((People*)e1)->age - ((People*)e2)->age;
}
int name_cmp(const void* e1, const void* e2)
{
	return strcmp(((People*)e1)->name, ((People*)e2)->name);
}
//排序
void InsertSort(Contact* head, int(*cmp)(const void*, const void*))
{
	People* t1 = head->next->next;//插入节点的前驱
	People* t = t1->next;//插入节点
	People* p = NULL;//p1的前驱
	People* p1 = NULL; //遍历插入节点前面所有的节点

	int flag = 1;
	while (t)
	{
		flag = 1;
		People* p = head->next;//p1的前驱
		People* p1 = head->next->next; //遍历插入节点前面所有的节点
		while (p1 != t && flag)
		{
			if (cmp(p1, t) > 0)
			{
				People* t3 = t;
				t = t->next;//指向下个节点
				t1->next = t;//连接

				p->next = t3;
				t3->next = p1;
				flag = 0;
			}
			p = p->next;
			p1 = p1->next;
		}
		if (flag)
		{
			t = t->next;
			t1 = t1->next;
		}

	}
	
}

void OrderSet()
{

	gotoxy(32, 7);
	printf("排序成功                                                      ");
	gotoxy(32, 8);
	printf("             ");
	gotoxy(32, 9);
	printf("               ");
	Sleep(500);
	gotoxy(32, 7);
	printf("                                                   ");

}
void BeginSet()
{
	gotoxy(32, 7);
	printf("1.对年龄排序                                                  ");
	gotoxy(32, 8);
	printf("2.对姓名排序");
	gotoxy(32, 9);
	printf("请选择:");
}
void OrderContact(Contact* head)
{
	BeginSet();
	int b = 0;
	scanf("%d", &b);
	int i = 1;
	int flag = 1;
	while (flag)
	{
		switch (b)
		{
		case 1:
			InsertSort(head, int_cmp);
			flag = 0;
			break;
		case 2:
			InsertSort(head, name_cmp);
			flag = 0;
			break;
		default:
			gotoxy(32, 9 + i++);
			printf("选择错误");
			break;
		}
	}
	OrderSet();
}

🍒ClearContact

清空数据的时候不能忘记把属性置为0
清空数据很容易,只要把堆区开辟的空间释放掉即可,但是这样写了以后,我们得考虑到主函数中也存在free(head->next),同一块空间不可释放2次否则会报错的。因此需要调整。

局部代码

	int flag = 1;//标记ClearContact中是否释放空间过。
	//switch中
			case Clear:
			ClearContact(cHead);
			flag = 0;
   	if(flag)
		free(cHead->next);
void ClearContact(Contact* head) 
{
	free(head->next);
	head->lenth = 0;
	gotoxy(32, 7);
	printf("通讯录已清空               ");
	Sleep(1000);
	gotoxy(32, 7);
	printf("              ");
}

💥完成代码

因为用到了任意位置的输出(它头文件包含c++的内容),所以得用.cpp的后缀名文件。

💢Contact.h

#pragma once
#include<stdio.h>
#include<windows.h>
#include<iostream>
#include<conio.h>
#include<string.h>
#include<stdlib.h>
#pragma warning(disable:4996)


enum Op
{
	Exit,
	Add,
	Delete,
	Search,
	Modify,
	Order,
	Show,
	Clear,
};

typedef struct PeopleInfo
{
	char name[20];
	int age;
	char tele[12];
	char address[30];
	struct PeopleInfo* next;
}People;

typedef struct Contact
{
	int lenth;//通讯录的长度
	People* next;//当作头指针
}Contact;
//extern Contact* Con; 用全局变量的时候申请一下

//函数声明
extern void gotoxy(int x, int y);
extern People* AollocPeopleInfo();
extern void AddContact(People** end);
extern void DelContact(Contact* head, People** end);
extern void SerContact(Contact* head);
extern void ModContact(Contact* head);
extern void OrderContact(Contact* head);
extern void ShowContact(Contact* head);
extern void ClearContact(Contact* head);

💢Contact.cpp

#include"Contact.h"

//开辟联系人(节点)并初始化
People* AollocPeopleInfo()
{
	People* p = (People*)malloc(sizeof(People));
	//初始化
	memset(p, 0, sizeof(People));
	return p;
}
//添加信息
void AddInput()
{
	gotoxy(32, 7);
	printf("请分别输入姓名 年龄 电话 地址的信息\n");
}
//清理添加痕迹
void CleanAdd()
{
	gotoxy(32, 7);
	printf("                                           ");
	gotoxy(32, 8);
	printf("                                           ");
	gotoxy(32, 9);
	printf("                     ");
}
//清理展示的效果
void CleanShow(int i)
{
	while (i > 0)
	{
		gotoxy(32, 7 + i);
		printf("                                                                                ");
		i--;
	}
}
//展示
void ShowContact(Contact* head)
{
	People* p = head->next->next;//指向第一个节点
	
	static int i = 1;
	gotoxy(32, 7);
	printf("%-20s\t%-3s\t%-12s\t%-30s\t", "姓名", "年龄", "电话", "地址");
	if (0 == head->lenth)
	{
		gotoxy(32, 8);
		printf("通讯录为空");
		Sleep(500);
		CleanShow(i);
		return;
	}
	while (p)
	{
		gotoxy(32, 7 + i);
		printf("%-20s\t%-3d\t%-12s\t%-30s\t", p->name,
			p->age, p->tele, p->address);
		p = p->next;
		i++;
	}
	Sleep(3000);
	CleanShow(i);
	i = 1;
}
//添加
void AddContact(People** end)
{
	People* p = AollocPeopleInfo();
	AddInput();
	gotoxy(32, 8);
	scanf("%s%d%s%s", p->name, &p->age,
		p->tele, p->address);
	gotoxy(32, 9);
	printf("添加成功");
	Sleep(50);
	CleanAdd();
	(*end)->next = p;
	*end = p;
}
//删除成功的设置
void DelSet()
{
	gotoxy(32, 9);
	printf("删除成功");
	Sleep(500);
	gotoxy(32, 9);
	printf("        ");
}
//删除
void DelContact(Contact* head,People** end)
{

	gotoxy(32, 7);
	printf("请输入删除哪一个联系人                     ");
	int x = 0;
	gotoxy(32, 8);
	scanf("%d", &x);
	gotoxy(32, 8);
	printf("     ");
	if (x < 1 || x > head->lenth)
	{
		gotoxy(32, 7);
		printf("位置错误,退回界面                ");
		Sleep(500);
		return;
	}
	People* p = head->next->next;//第一个节点开始 找删除位置
	People* p1 = head->next;//记录删除位置的前驱
	int flag = 0;
	if (x == head->lenth)
		flag = 1;
	while (x - 1)
	{
		p = p->next;
		p1 = p1->next;
		x--;
	}
	p1->next = p->next;
	if (flag)
	{
		*end = p1;
	}
	free(p);
	DelSet();
}
//查找展示和清理
void ShowCleanSear(People* p)
{
	//先清除原来的痕迹
	gotoxy(32, 7);
	printf("                                                                    ");
	gotoxy(32, 7);
	printf("%-20s\t%-3s\t%-12s\t%-30s\t", "姓名", "年龄", "电话", "地址");
	gotoxy(32, 8);
	printf("%-20s\t%-3d\t%-12s\t%-30s\t", p->name,
		p->age, p->tele, p->address);
	Sleep(2000);
	gotoxy(32, 7);
	printf("                                                                    ");
	gotoxy(32, 8);
	printf("                                                                    ");
}
//查找
void SerContact(Contact* head)
{
	gotoxy(32, 7);
	printf("请输入查找哪一个联系人                     ");
	int x = 0;
	gotoxy(32, 8);
	scanf("%d", &x);
	gotoxy(32, 8);
	printf("     ");
	if (x < 0 || x > head->lenth)
	{
		gotoxy(32, 7);
		printf("位置错误,退回界面                ");
		Sleep(500);
		return;
	}
	People* p = head->next;//从头节点开始 找 查找位置
	while (x)
	{
		p = p->next;
		x--;
	}
	ShowCleanSear(p);//展示找的记录,并清理痕迹
}
//清楚修改的痕迹
void CleanMod()
{
	int i = 0;
	while (i < 3)
	{
		gotoxy(32, 7 + i);
		printf("                                                                ");
		i++;
	}
}

//修改
void ModContact(Contact* head)
{
	gotoxy(32, 7);
	printf("请输入要修改哪一个联系人                     ");
	int x = 0;
	gotoxy(32, 8);
	scanf("%d", &x);
	gotoxy(32, 8);
	printf("      ");
	if (x < 0 || x > head->lenth)
	{
		gotoxy(32, 7);
		printf("位置错误,退回操作界面                ");
		Sleep(500);
		return;
	}

	People* p = head->next;//找 查找位置
	while (x)
	{
		p = p->next;
		x--;
	}

	gotoxy(32, 7);
	printf("请输入修改信息                                  ");
	gotoxy(32, 8);
	printf("%-20s\t%-3s\t%-12s\t%-30s\t", "姓名", "年龄", "电话", "地址");
	gotoxy(32, 9);
	scanf("%s%d%s%s", p->name, &p->age,
		p->tele, p->address);

	Sleep(1000);
	CleanMod();//清楚痕迹
	gotoxy(32, 7);
	printf("修改成功");
	Sleep(500);
	CleanMod();//清楚痕迹
}

int int_cmp(const void* e1, const void* e2)
{
	return ((People*)e1)->age - ((People*)e2)->age;
}
int name_cmp(const void* e1, const void* e2)
{
	return strcmp(((People*)e1)->name, ((People*)e2)->name);
}
//排序
void InsertSort(Contact* head, int(*cmp)(const void*, const void*))
{
	People* t1 = head->next->next;//插入节点的前驱
	People* t = t1->next;//插入节点
	People* p = NULL;//p1的前驱
	People* p1 = NULL; //遍历插入节点前面所有的节点

	int flag = 1;
	while (t)
	{
		flag = 1;
		People* p = head->next;//p1的前驱
		People* p1 = head->next->next; //遍历插入节点前面所有的节点
		while (p1 != t && flag)
		{
			if (cmp(p1, t) > 0)
			{
				People* t3 = t;
				t = t->next;//指向下个节点
				t1->next = t;//连接

				p->next = t3;
				t3->next = p1;
				flag = 0;
			}
			p = p->next;
			p1 = p1->next;
		}
		if (flag)
		{
			t = t->next;
			t1 = t1->next;
		}

	}
	
}

void OrderSet()
{

	gotoxy(32, 7);
	printf("排序成功                                                      ");
	gotoxy(32, 8);
	printf("             ");
	gotoxy(32, 9);
	printf("               ");
	Sleep(500);
	gotoxy(32, 7);
	printf("                                                   ");

}
void BeginSet()
{
	gotoxy(32, 7);
	printf("1.对年龄排序                                                  ");
	gotoxy(32, 8);
	printf("2.对姓名排序");
	gotoxy(32, 9);
	printf("请选择:");
}
void OrderContact(Contact* head)
{
	BeginSet();
	int b = 0;
	scanf("%d", &b);
	int i = 1;
	int flag = 1;
	while (flag)
	{
		switch (b)
		{
		case 1:
			InsertSort(head, int_cmp);
			flag = 0;
			break;
		case 2:
			InsertSort(head, name_cmp);
			flag = 0;
			break;
		default:
			gotoxy(32, 9 + i++);
			printf("选择错误");
			break;
		}
	}
	OrderSet();
}
void ClearContact(Contact* head) 
{
	free(head->next);
	head->lenth = 0;
	gotoxy(32, 7);
	printf("通讯录已清空               ");
	Sleep(1000);
	gotoxy(32, 7);
	printf("              ");
}

💢main.cpp

#include"Contact.h"

void gotoxy(int x, int y)
{
	COORD pos;
	HANDLE hOutput;
	pos.X = x;//水平方向
	pos.Y = y;//垂直方向
	hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	//获取缓冲区中的数据,地址赋值给句柄
	SetConsoleCursorPosition(hOutput, pos);
	/*定位光标位置的函数,坐标为GetStdHandle()返回标准的输出的句柄,
	也就是获得输出屏幕缓冲区的句柄,并赋值给对象 pos*/

	/*隐藏光标操作 */
	CONSOLE_CURSOR_INFO cursor;
	cursor.bVisible = FALSE;
	cursor.dwSize = sizeof(cursor);
	SetConsoleCursorInfo(hOutput, &cursor);
}

void Opertion()
{
	gotoxy(32, 3);
	printf("Opertion:");
	gotoxy(42, 4);
	printf("0.Exit     1.Add      2.Delete  3.Search");
	gotoxy(42, 6);
	printf("4.Modify   5.Order    6.Show    7.Clear");
}

void OpenContact()
{

	char arr[] = "Welcome back to  Contact";
	char arr1[] = "                          ";
	int left = 0;//左下标
	int right = strlen(arr) - 1;//右下标
	while (left < right)
	{
		arr1[left] = arr[left];
		arr1[right] = arr[right];
		gotoxy(44, 2);
		printf("%s", arr1);
		Sleep(1);
		/*if (right - left != 1)
			system("cls");*/
		left++;
		right--;
	}
	Opertion();
	printf("\n");
}

void Input()
{
	gotoxy(32, 7);
	printf("请输入执行功能按键:                                  ");
}
void CleanInput()
{
	gotoxy(32, 7);
	printf("                   ");
}
void Error()
{
	gotoxy(32, 7);
	printf("输入错误重新输入");
	gotoxy(32, 7);
	printf("                ");
}

//Contact* cHead = (Contact*)malloc(sizeof(Contact));
int main()
{
	int input = 0;
	Contact* cHead = (Contact*)malloc(sizeof(Contact));
	if (NULL == cHead)
	{
		perror("AollocPeopleInfo");
		return 1;
	}
	memset(cHead, 0, sizeof(Contact));

	cHead->next = AollocPeopleInfo();
	People* pEnd = cHead->next;
	if (NULL == cHead->next)
	{
		perror("AollocPeopleInfo");
		return 1;
	}
	int flag = 1;
	OpenContact();
	do
	{
		Input();
		gotoxy(32, 8);
		scanf("%d", &input);
		gotoxy(32, 8);
		printf("  ");
		flag = 1;
		switch (input)
		{
		case Exit :
			printf("通讯录关闭");
			break;
		case Add :
			AddContact(&pEnd);
			cHead->lenth++;
			break;
		case Delete :
			DelContact(cHead,&pEnd);
			cHead->lenth--;
			break;
		case Search:
			SerContact(cHead);
			break;
		case Modify:
			ModContact(cHead);
			break;
		case Order:
			OrderContact(cHead);
			break;
		case Show:
			ShowContact(cHead);
			break;
		case Clear:
			ClearContact(cHead);
			flag = 0;
			break;
		default:
			Error();
			break;
		}
		gotoxy(32, 3);
		printf("   现有联系人:%d ", cHead->lenth);
	} while (input);

	if(flag)
		free(cHead->next);
	free(cHead);
	cHead = NULL;
	gotoxy(32, 12);
	return 0;
}

🍑顺序表实现通讯录

柔性数组形式的和堆区开辟空间的略有不同,大部分一样,数组形式的比较简单就不介绍了。

🍓通讯录属性设计

🍉堆区开辟连续空间:

在设计结构体属性的时候,增加的当前联系人个数,是非常有必要的。

因为这个是顺序存储,又因为指针与数组访问方式是打通的,所以指针可以通过下标的形式去访问每一个联系人。
这个下标从何而来呢?不就是当前联系人的个数吗?
一开始设置成0,每增加一个联系人当前联系人的个数就加一,满了在扩容。

enum op
{
	Exit,
	Add,
	Delete,
	Search,
	Modify,
	Order,
	Show,
	Clear,
};
typedef struct PeopleInfo
{
	char name[20];
	int age;
	char tele[12];
	char address[30];
}People;

typedef struct Contacts
{
	int count;//当前
	int total;//总
	People* data;
}Contacts;

🍉柔性数组

enum op
{
	Exit,
	Add,
	Delete,
	Search,
	Modify,
	Order,
	Show,
	Clear,
};
typedef struct PeopleInfo
{
	char name[20];
	int age;
	char tele[12];
	char address[30];
}People;

typedef struct Contacts
  {
	int count;//当前
	int total;//总
	People data[0];
   }Contacts;

🍉数组形式

enum op
{
	Exit,
	Add,
	Delete,
	Search,
	Modify,
	Order,
	Show,
	Clear,
};
typedef struct PeopleInfo
{
	char name[20];
	int age;
	char tele[12];
	char address[30];
}People;

typedef struct Contacts
  {
	int count;//当前
	People data[1000];
   }Contacts;

🍓主函数的设计

柔性数组的形式和堆区开辟连续空间的形式,部分地方略有差异。
这里的X是一个#define定义的标识符,大小为3

void menu()
{
	printf("**************************************\n");
	printf("********0.Eixt     1.Add    **********\n");
	printf("********2.Delete   3.Search **********\n");
	printf("********4.Modify   5.Order  **********\n");
	printf("********6.Show     7.Clear  **********\n");
	printf("**************************************\n");
}

堆区开辟连续空间

这里的Con不是指针,是一个结构体变量,在传参的时候需要传址调用。因为通过增删查改等操作,可能会改变其内部成员。
还有一个原因,形参是实参的一份临时拷贝,假设通讯录很大,如果传值调用,这个形参要开辟同样大的空间,太浪费空间了,而传址调用只需要开辟4个字节(32位平台下)。

int main()
{
	Contacts Con = { NULL,0,0 };
	Con.data = (People*)malloc(sizeof(People) * X);
	memset(Con.data, 0, sizeof(People) * X);
	Con.total = 0;

	int x = 0;
	do
	{
		menu();
		scanf("%d", &x);
		switch (x)
		{
		case Exit:
			printf("退出通讯录");
			break;
		case Add:
			 AddContact(&Con);
			break;
		case Delete:
			DelContact(&Con);
			break;
		case Search:
			SerContact(&Con);
			break;
		case Modify:
			ModContact(&Con);
			break;
		case Order:
			OrderContact(&Con);
			break;
		case Show:
			ShowContact(&Con);
			break;
		case Clear:
			ClearContact(&Con);
			break;
		default:
			printf("输入错误\n");
			break;
		}

	} while (x);

	return 0;
}

柔性数组

Con是一个指针变量,在不需要改变指针变量空间中存放的地址时,传指针变量就可以操作指针指向的内容。这里Add、Clear操作略有不同呢,这和realloc有关,也和形参有关。

堆区开辟一块连续空间

在这里插入图片描述

柔性数组

在这里插入图片描述

int main()
{
	Contacts* Con = (Contacts*)malloc(sizeof(Contacts) + sizeof(People) * X);
	Con->total = X;
	Con->count = 0;
	int x = 0;
	do
	{
		menu();
		scanf("%d", &x);
		printf("Main-->%p\n", Con);
		switch (x)
		{
		case Exit:
			printf("退出通讯录");
			break;
		case Add:
			Con = AddContact(Con);
			break;
		case Delete:
			DelContact(Con);
			break;
		case Search:
			SerContact(Con);
			break;
		case Modify:
			ModContact(Con);
			break;
		case Order:
			OrderContact(Con);
			break;
		case Show:
			ShowContact(Con);
			break;
		case Clear:
			Con = ClearContact(Con);
			break;
		default:
			printf("输入错误\n");
			break;
		}

	} while (x);
	free(Con);
	Con = NULL;
	return 0;
}

🍓AddContact

堆区开辟

int AddSpace(Contacts* Con)
{
	People* p = (People*)realloc(Con->data, sizeof(People) * (Con->count + X));
	if (NULL == p)
	{
		return 0;
	}
	Con->data = p;
	Con->total += X;
	memset(Con->data+Con->count, 0, sizeof(People) * X);
	return 1;
}
void AddContact(Contacts* Con)
{
	//判断是否满
	if (Con->count == Con->total)
	{
		if (AddSpace(Con))
			printf("增容成功\n");
		else
		{
			printf("开辟空间失败\n");
		}
	}
	printf("%-20s\t%-3s\t%-12s\t%-30s\t\n", "姓名", "年龄", "电话", "地址");
	scanf("%s",Con->data[Con->count].name );
	scanf("%d",&(Con->data[Con->count].age));
	scanf("%s", Con->data[Con->count].tele);
	scanf("%s", Con->data[Con->count].address);
	Con->count++;

	printf("添加成功\n");
}

柔性数组

Contacts* AddSpace(Contacts* Con)
{
	Contacts* p = (Contacts*)realloc(Con, sizeof(Contacts) + sizeof(People) * (Con->count + X));
	if (NULL == p)
	{
		perror("开辟失败AddSpace:");
		return 0;
	}
	printf("Space-->%p\n", p);
	printf("增容成功\n");
	p->total += X;
	memset(Con->data + Con->count, 0, sizeof(People) * X);
	return p;
}
Contacts* AddContact(Contacts* Con)
{
	//判断是否满
	if (Con->count == Con->total)
	{
		Con = AddSpace(Con);
		printf("Add-->%p\n", Con);

	}
	printf("%-20s\t%-3s\t%-12s\t%-30s\t\n", "姓名", "年龄", "电话", "地址");
	scanf("%s", Con->data[Con->count].name);
	scanf("%d", &(Con->data[Con->count].age));
	scanf("%s", Con->data[Con->count].tele);
	scanf("%s", Con->data[Con->count].address);
	Con->count++;
	printf("添加成功\n");
	return Con;
}

为什么两者不一样呢?
realloc有关,原来已经开辟空间的后面没有足够大的空间进行扩容时,会在堆区中重新找个位置,开辟一个增容后的空间,内部的数据不会改变。

需要深刻理解刚刚的两张图片

在这里插入图片描述

堆区开辟一块连续空间的形式:通过结构体中指针变量data来维护联系人这块空间的。而Con是指向这个结构体的dataCon指向目标的内部成员,因为函数调用传的是指针形式的realloc返回一个新的地址,data接收了,回到AddContact函数中,data还是指向这个新的空间。
对于data来说这是一个是传址调用

柔性数组:通过指针Con来维护联系人这块空间的,在AddSpace函数中,申请的空间需要交给Con来维护,假设realloc返回的是一个新的地址,如果代码和上面写的一样,回到AddContact中会有效吗?无效,为啥呢?指针变量Con空间中存储的地址发生改变了,对于指针Con来说这是传值调用,回到AddContactCon指向的并不会是新的地址,还是原来的地址。
两种解决方案,一个是返回地址(堆区),一个是传址调用。
这里改动了,不能忘记主函数那里,也是同样的道理。

🍓ClearContact

这里柔性数组形式要注意,堆区开辟形式比较简单

堆区开辟形式

void ClearContact(Contacts* Con)
{
	free(Con->data);
	Con->data = NULL;
	Con->count = 0;
	Con->total = 0;
	printf("已清空通讯录\n");
}

柔性数组
也可以使用realloc减少空间

Contacts* ClearContact(Contacts* Con)
{
	free(Con);
	Con = (Contacts*)malloc(sizeof(Contacts) + sizeof(People) * X);
	printf("After:Clear-->%p\n", Con);
	Con->total = X;
	Con->count = 0;
	printf("已清空通讯录\n");
	return Con;

}

其它操作,两种形式都是一样的

🍓DelContact

输入的人可能不了解数组下标是从0开始。因此x的合法值从1开始。输入的数x,遍历时x要-1。
也可以根据姓名去删除。设计一个函数要找到删除人的名字返回它对应的下标。
不管哪种都不能忘记处理没有联系人的情况

void DelContact(Contacts* Con)
{
	printf("请输入删除哪个联系人\n");
	if (0 == Con->count)
	{
		printf("通讯录--空\n");
		return;
	}
	int x = 0;
	int flag = 1;
	while (flag)
	{
		scanf("%d", &x);
		if (x > Con->count || x < 1)
			printf("输入错误,重新输入\n");
		else
			flag = 0;
	}
	//输入的x从1开始,数组下标从0开始,所以-1
	for (int i = x - 1; i < Con->count - 1; i++)
	{
		Con->data[i] = Con->data[i + 1];
	}
	Con->count--;
	printf("删除成功\n");
}

🍓SerContact

也可根据姓名查找,和删除操作中说法一样不再赘述。

void SerContact(Contacts* Con)
{
	if (0 == Con->count)
	{
		printf("通讯录--空\n");
		return;
	}
	printf("请输入要查找哪个联系人\n");
	int x = 0;
	int flag = 1;
	while (flag)
	{
		scanf("%d", &x);
		if (x > Con->count || x < 1)
			printf("输入错误,重新输入\n");
		else
			flag = 0;
	}
	printf("%-20s\t%-3s\t%-12s\t%-30s\t\n", "姓名", "年龄", "电话", "地址");
	printf("%-20s\t", Con->data[x - 1].name);
	printf("%-3d\t", (Con->data[x - 1].age));
	printf("%-12s\t", Con->data[x - 1].tele);
	printf("%-30s\t\n", Con->data[x - 1].address);

}

🍓ModContact

也可根据姓名修改,和删除操作中说法一样不再赘述。

void ModContact(Contacts* Con)
{
	if (0 == Con->count)
	{
		printf("通讯录--空\n");
		return;
	}
	printf("请输入要修改的哪个联系人的信息\n");
	int x = 0;
	int flag = 1;
	while (flag)
	{
		scanf("%d", &x);
		if (x > Con->count || x < 1)
			printf("输入错误,重新输入\n");
		else
			flag = 0;
	}
	printf("请输入:\n");
	printf("%-20s\t%-3s\t%-12s\t%-30s\t\n", "姓名", "年龄", "电话", "地址");
	scanf("%s", Con->data[x - 1].name);
	scanf("%d", &(Con->data[x - 1].age));
	scanf("%s", Con->data[x - 1].tele);
	scanf("%s", Con->data[x - 1].address);
	printf("修改成功\n");
}

🍓OrderContact

这是对qsort函数的实际应用,可别傻傻的再写排序函数。之前写的博客—qsort函数的介绍以及模拟实现
挺详细的,写了冒泡排序和插入排序去模拟qsort的功能

int age_cmp(const void* e1, const void* e2)
{
	return ((People*)e1)->age - ((People*)e2)->age;
}
int name_cmp(const void* e1, const void* e2)
{
	return strcmp(((People*)e1)->name, ((People*)e2)->name);
}
void OrderContact(Contacts* Con)
{
	if (0 == Con->count)
	{
		printf("通讯录--空\n");
		return;
	}
	printf("1.姓名排序,2.年龄排序\n");
	printf("请选择哪种排序\n");
	int x = 0;
	int flag = 1;
	while (flag)
	{
		scanf("%d", &x);
		switch (x)
		{
		case 1:
			qsort(Con->data, Con->count, sizeof(People), name_cmp);
			flag = 0;
			break;
		case 2:
			qsort(Con->data, Con->count, sizeof(People), age_cmp);
			flag = 0;
			break;
		default:
			printf("重新输入\n");
			break;
		}
	}
	printf("排序成功\n");
}

🍓ShowContactw

遍历的方法就可以。

void ShowContact(Contacts* Con)
{
	int i = 0;
	if (0 == Con->count)
	{
		printf("通讯录--空\n");
		return;
	}
	printf("%-20s\t%-3s\t%-12s\t%-30s\t\n", "姓名", "年龄", "电话", "地址");
	for (i = 0; i < Con->count; i++)
	{
		printf("%-20s\t", Con->data[i ].name);
		printf("%-3d\t", (Con->data[i].age));
		printf("%-12s\t", Con->data[i].tele);
		printf("%-30s\t\n", Con->data[i].address);
	}
}

💥完整代码

💢堆区开辟空间形式

🌊Contact.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

enum
{
	Exit,
	Add,
	Delete,
	Search,
	Modify,
	Order,
	Show,
	Clear,
};

typedef struct PeopleInfo
{
	char name[20];
	int age;
	char tele[12];
	char address[30];
}People;

typedef struct Contacts
{
	int count;//当前
	int total;//总
	People* data;
}Contacts;

#define X 3

extern void AddContact(Contacts* Con);
extern void DelContact(Contacts* Con);
extern void SerContact(Contacts* Con);
extern void DelContact(Contacts* Con);
extern void ModContact(Contacts* Con);
extern void OrderContact(Contacts* Con);
extern void ShowContact(Contacts* Con);
extern void ClearContact(Contacts* Con);
🌊main.c
#include"Contact.h"
void menu()
{
	printf("**************************************\n");
	printf("********0.Eixt     1.Add    **********\n");
	printf("********2.Delete   3.Search **********\n");
	printf("********4.Modify   5.Order  **********\n");
	printf("********6.Show     7.Clear  **********\n");
	printf("**************************************\n");
}


int main()
{
	Contacts Con = { NULL,0,0 };
	Con.data = (People*)malloc(sizeof(People) * X);
	memset(Con.data, 0, sizeof(People) * X);
	Con.total = 0;
	int x = 0;
	do
	{
		menu();
		scanf("%d", &x);
		switch (x)
		{
		case Exit:
			printf("退出通讯录");
			break;
		case Add:
			 AddContact(&Con);
			break;
		case Delete:
			DelContact(&Con);
			break;
		case Search:
			SerContact(&Con);
			break;
		case Modify:
			ModContact(&Con);
			break;
		case Order:
			OrderContact(&Con);
			break;
		case Show:
			ShowContact(&Con);
			break;
		case Clear:
			ClearContact(&Con);
			break;
		default:
			printf("输入错误\n");
			break;
		}

	} while (x);

	return 0;
}
🌊Contact.c
#include"Contact.h"

int AddSpace(Contacts* Con)
{
	People* p = (People*)realloc(Con->data, sizeof(People) * (Con->count + X));
	if (NULL == p)
	{
		return 0;
	}
	Con->data = p;
	Con->total += X;
	memset(Con->data+Con->count, 0, sizeof(People) * X);
	return 1;
}
void AddContact(Contacts* Con)
{
	//判断是否满
	if (Con->count == Con->total)
	{
		if (AddSpace(Con))
			printf("增容成功\n");
		else
		{
			printf("开辟空间失败\n");
		}
	}
	printf("%-20s\t%-3s\t%-12s\t%-30s\t\n", "姓名", "年龄", "电话", "地址");
	scanf("%s",Con->data[Con->count].name );
	scanf("%d",&(Con->data[Con->count].age));
	scanf("%s", Con->data[Con->count].tele);
	scanf("%s", Con->data[Con->count].address);
	Con->count++;

	printf("添加成功\n");
}
void DelContact(Contacts* Con)
{
	printf("请输入删除哪个联系人\n");
	if ( 0 == Con->count)
	{
		printf("通讯录--空\n");
		return;
	}
	int x = 0;
	int flag = 1;
	while (flag)
	{
		scanf("%d", &x);
		if (x > Con->count || x < 1)
		    printf("输入错误,重新输入\n");
		else
			flag = 0;
	}
	//1 1 2 3 4
	//输入的x从1开始,数组下标从0开始
	for (int i = x - 1; i < Con->count - 1; i++)
	{
		Con->data[i] = Con->data[i + 1];
	}
	Con->count--;
	printf("删除成功\n");
}
void SerContact(Contacts* Con)
{
	if (0 == Con->count)
	{
		printf("通讯录--空\n");
		return;
	}
	printf("请输入要查找哪个联系人\n");
	int x = 0;
	int flag = 1;
	while (flag)
	{
		scanf("%d", &x);
		if (x > Con->count || x < 1)
			printf("输入错误,重新输入\n");
		else
			flag = 0;
	}
	printf("%-20s\t%-3s\t%-12s\t%-30s\t\n", "姓名", "年龄", "电话", "地址");
	printf("%-20s\t", Con->data[x - 1].name);
	printf("%-3d\t", (Con->data[x - 1].age));
	printf("%-12s\t", Con->data[x - 1].tele);
	printf("%-30s\t\n", Con->data[x - 1].address);

}
void ModContact(Contacts* Con)
{
	if (0 == Con->count)
	{
		printf("通讯录--空\n");
		return;
	}
	printf("请输入要修改的哪个联系人的信息\n");
	int x = 0;
	int flag = 1;
	while (flag)
	{
		scanf("%d", &x);
		if (x > Con->count || x < 1)
			printf("输入错误,重新输入\n");
		else
			flag = 0;
	}
	printf("请输入:\n");
	printf("%-20s\t%-3s\t%-12s\t%-30s\t\n", "姓名", "年龄", "电话", "地址");
	scanf("%s", Con->data[x - 1].name);
	scanf("%d", &(Con->data[x - 1].age));
	scanf("%s", Con->data[x - 1].tele);
	scanf("%s", Con->data[x - 1].address);
	printf("修改成功\n");
}
int age_cmp(const void* e1, const void* e2)
{
	return ((People*)e1)->age - ((People*)e2)->age;
}
int name_cmp(const void* e1, const void* e2)
{
	return strcmp(((People*)e1)->name, ((People*)e2)->name);
}
void OrderContact(Contacts* Con)
{
	if (0 == Con->count)
	{
		printf("通讯录--空\n");
		return;
	}
	printf("1.姓名排序,2.年龄排序\n");
	printf("请选择哪种排序\n");
	int x = 0;
	int flag = 1;
	while (flag)
	{
		scanf("%d", &x);
		switch (x)
		{
		case 1:
			qsort(Con->data, Con->count, sizeof(People), name_cmp);
			flag = 0;
			break;
		case 2:
			qsort(Con->data, Con->count, sizeof(People), age_cmp);
			flag = 0;
			break;
		default:
			printf("重新输入\n");
			break;
		}
	}
	printf("排序成功\n");
}
void ShowContact(Contacts* Con)
{
	int i = 0;
	if (0 == Con->count)
	{
		printf("通讯录--空\n");
		return;
	}
	printf("%-20s\t%-3s\t%-12s\t%-30s\t\n", "姓名", "年龄", "电话", "地址");
	for (i = 0; i < Con->count; i++)
	{
		printf("%-20s\t", Con->data[i ].name);
		printf("%-3d\t", (Con->data[i].age));
		printf("%-12s\t", Con->data[i].tele);
		printf("%-30s\t\n", Con->data[i].address);
	}
}
void ClearContact(Contacts* Con)
{
	free(Con->data);
	Con->data = NULL;
	Con->count = 0;
	Con->total = 0;
	printf("已清空通讯录\n");
}

💢柔性数组形式

🌊Contact.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<string.h>
enum op
{
	Exit,
	Add,
	Delete,
	Search,
	Modify,
	Order,
	Show,
	Clear,
};
typedef struct PeopleInfo
{
	char name[20];
	int age;
	char tele[12];
	char address[30];
}People;
typedef struct Contacts
{
	int count;//通讯中总个数
	int total;//通讯录中当前个数
	People data[];
}Contacts;
#define X 3   //每次开辟三个空间

extern Contacts* AddContact(Contacts* Con);
extern void DelContact(Contacts* Con);
extern void SerContact(Contacts* Con);
extern void DelContact(Contacts* Con);
extern void ModContact(Contacts* Con);
extern void OrderContact(Contacts* Con);
extern void ShowContact(Contacts* Con);
extern Contacts* ClearContact(Contacts* Con);
🌊main.c
#include"Contact.h"
void menu()
{
	printf("**************************************\n");
	printf("********0.Eixt     1.Add    **********\n");
	printf("********2.Delete   3.Search **********\n");
	printf("********4.Modify   5.Order  **********\n");
	printf("********6.Show     7.Clear  **********\n");
	printf("**************************************\n");
}


int main()
{
	//柔性数组
	Contacts* Con = (Contacts*)malloc(sizeof(Contacts) + sizeof(People) * X);
	Con->total = X;
	Con->count = 0;
	int x = 0;
	do
	{
		menu();
		scanf("%d", &x);
		printf("Main-->%p\n", Con);
		switch (x)
		{
		case Exit:
			printf("退出通讯录");
			break;
		case Add:
			Con = AddContact(Con);
			break;
		case Delete:
			DelContact(Con);
			break;
		case Search:
			SerContact(Con);
			break;
		case Modify:
			ModContact(Con);
			break;
		case Order:
			OrderContact(Con);
			break;
		case Show:
			ShowContact(Con);
			break;
		case Clear:
			Con = ClearContact(Con);
			break;
		default:
			printf("输入错误\n");
			break;
		}

	} while (x);
	free(Con);
	Con = NULL;
	return 0;
}
🌊Contact.c
#include"Contact.h"


Contacts* AddSpace(Contacts* Con)
{
	Contacts* p = (Contacts*)realloc(Con, sizeof(Contacts) + sizeof(People) * (Con->count + X));
	if (NULL == p)
	{
		perror("开辟失败AddSpace:");
		return 0;
	}
	printf("Space-->%p\n", p);
	printf("增容成功\n");
	p->total += X;
	memset(Con->data + Con->count, 0, sizeof(People) * X);
	return p;
}
Contacts* AddContact(Contacts* Con)
{
	//判断是否满
	if (Con->count == Con->total)
	{
		Con = AddSpace(Con);
		printf("Add-->%p\n", Con);

	}
	printf("%-20s\t%-3s\t%-12s\t%-30s\t\n", "姓名", "年龄", "电话", "地址");
	scanf("%s", Con->data[Con->count].name);
	scanf("%d", &(Con->data[Con->count].age));
	scanf("%s", Con->data[Con->count].tele);
	scanf("%s", Con->data[Con->count].address);
	Con->count++;
	printf("添加成功\n");
	return Con;
}
void DelContact(Contacts* Con)
{
	printf("请输入删除哪个联系人\n");
	if (0 == Con->count)
	{
		printf("通讯录--空\n");
		return;
	}
	int x = 0;
	int flag = 1;
	while (flag)
	{
		scanf("%d", &x);
		if (x > Con->count || x < 1)
			printf("输入错误,重新输入\n");
		else
			flag = 0;
	}
	//1 1 2 3 4
	//输入的x从1开始,数组下标从0开始
	for (int i = x - 1; i < Con->count - 1; i++)
	{
		Con->data[i] = Con->data[i + 1];
	}
	Con->count--;
	printf("删除成功\n");
}
void SerContact(Contacts* Con)
{
	if (0 == Con->count)
	{
		printf("通讯录--空\n");
		return;
	}
	printf("请输入要查找哪个联系人\n");
	int x = 0;
	int flag = 1;
	while (flag)
	{
		scanf("%d", &x);
		if (x > Con->count || x < 1)
			printf("输入错误,重新输入\n");
		else
			flag = 0;
	}
	printf("%-20s\t%-3s\t%-12s\t%-30s\t\n", "姓名", "年龄", "电话", "地址");
	printf("%-20s\t", Con->data[x - 1].name);
	printf("%-3d\t", (Con->data[x - 1].age));
	printf("%-12s\t", Con->data[x - 1].tele);
	printf("%-30s\t\n", Con->data[x - 1].address);

}
void ModContact(Contacts* Con)
{
	if (0 == Con->count)
	{
		printf("通讯录--空\n");
		return;
	}
	printf("请输入要修改的哪个联系人的信息\n");
	int x = 0;
	int flag = 1;
	while (flag)
	{
		scanf("%d", &x);
		if (x > Con->count || x < 1)
			printf("输入错误,重新输入\n");
		else
			flag = 0;
	}
	printf("请输入:\n");
	printf("%-20s\t%-3s\t%-12s\t%-30s\t\n", "姓名", "年龄", "电话", "地址");
	scanf("%s", Con->data[x - 1].name);
	scanf("%d", &(Con->data[x - 1].age));
	scanf("%s", Con->data[x - 1].tele);
	scanf("%s", Con->data[x - 1].address);
	printf("修改成功\n");
}
int age_cmp(const void* e1, const void* e2)
{
	return ((People*)e1)->age - ((People*)e2)->age;
}
int name_cmp(const void* e1, const void* e2)
{
	return strcmp(((People*)e1)->name, ((People*)e2)->name);
}
void OrderContact(Contacts* Con)
{
	if (0 == Con->count)
	{
		printf("通讯录--空\n");
		return;
	}
	printf("1.姓名排序,2.年龄排序\n");
	printf("请选择哪种排序\n");
	int x = 0;
	int flag = 1;
	while (flag)
	{
		scanf("%d", &x);
		switch (x)
		{
		case 1:
			qsort(Con->data, Con->count, sizeof(People), name_cmp);
			flag = 0;
			break;
		case 2:
			qsort(Con->data, Con->count, sizeof(People), age_cmp);
			flag = 0;
			break;
		default:
			printf("重新输入\n");
			break;
		}
	}
	printf("排序成功\n");
}
void ShowContact(Contacts* Con)
{
	int i = 0;
	if (0 == Con->count)
	{
		printf("通讯录--空\n");
		return;
	}
	printf("%-20s\t%-3s\t%-12s\t%-30s\t\n", "姓名", "年龄", "电话", "地址");
	for (i = 0; i < Con->count; i++)
	{
		printf("%-20s\t", Con->data[i].name);
		printf("%-3d\t", (Con->data[i].age));
		printf("%-12s\t", Con->data[i].tele);
		printf("%-30s\t\n", Con->data[i].address);
	}
}
Contacts* ClearContact(Contacts* Con)
{
	free(Con);
	Con = (Contacts*)malloc(sizeof(Contacts) + sizeof(People) * X);
	printf("After:Clear-->%p\n", Con);
	Con->total = X;
	Con->count = 0;
	return Con;
	printf("已清空通讯录\n");
}

🍑文件操作

文件操作也不是太复杂,当退出通讯录的时候就把内存中的数据存放到文件中就可以了。当运行程序的时候把文件中的数据加载到内存中就完成了这个文件操作。
这里就以单链表的操作为例

加载数据进内存

要注意的需要用尾插法和AddContact联系起来,同时每加载一个都要更新lenth所以head必须传过来

//加载进内存
void LoadingData(Contact* head,People** end)
{
	FILE* f = fopen("Contact.txt", "r");
	if (NULL == f)
	{
		perror("Loading::");
		exit(0);
	}

	People* p = AollocPeopleInfo();
	while (fscanf(f, "%s %d %s %s", p->name, &p->age, p->tele, p->address)!= EOF)
	{
		(*end)->next = p;
		*end = p;
		head->lenth++;
	}
	gotoxy(32, 7);
	printf("数据加载完成");
	Sleep(30);
	gotoxy(32, 7);
	printf("             ");
}

主函数中的修改

在这里插入图片描述
数据加载进文件中

从第一节点开始,一开始可以判断一下第一个节点是否为NULL,是的话就返回

//将数据存进文件中
void StoreData(Contact* head)
{
	FILE* f = fopen("Contact.txt", "w");
	if (NULL == f)
	{
		if (NULL == f)
		{
			perror("Store::");
			exit(0);
		}
	}
	People* p = head->next->next;//指向第一个节点。
	if (NULL == p)
	{
		return;
	}
	while (p)
	{
		fprintf(f, "%s %d %s %s", p->name, p->age, p->tele, p->address);
		p = p->next;
	}
	gotoxy(32, 8);
	printf("数据存储完成");
	Sleep(30);
	gotoxy(32, 8);
	printf("             ");
}

主函数中位置

在这里插入图片描述

这两个函数的声明要写在头文件中

在这里插入图片描述

写完的时候先把LoadingData(cHead,&pEnd);这个注释掉,先通过StoreData(cHead);生成文件。因为刚开始还没有文件,执行LoadingData(cHead,&pEnd);会报错,执行StoreData(cHead);没有文件会自动生成一个文件。当然你也可以在对应的路径下手动添加同名文件。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

呼呼,终于写完了,希望对你有所帮助🤗

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章           查看所有文章
加:2022-10-17 12:13:43  更:2022-10-17 12:18:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 5:59:54-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码