【C语言】通讯录,用动态链表实现

在这之前,已经尝试用数组和动态数组的方法,实现通讯录。但用数组存储数据有着各种不便,于是经过一段时间学习后,使用动态单链表完成了新的通讯录。

设计目标:

1能存放1000人信息。
2每个人信息包括名字、年龄、性别、电话和地址。
3能增加信息
4能查找指定人信息
5能删除指定人信息
6能修改指定人信息
7通讯录能够根据名字排序
8能将信息读取和保存在文件中
9使用动态开辟空间的方法
10使用单向链表存储空间
11使用多文件编程

头文件:


#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#define maxName 20
#define maxSex 10
#define maxPhoneNumber 15
#define maxAddress 10
#define len sizeof(perIn)
//库函数的头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include<assert.h>
//一个联系人内包含的信息
typedef struct personalInfomation
{
	char name[maxName];
	int age;
	char sex[maxSex];
	char phoneNumber[maxPhoneNumber];
	char address[maxAddress];
	struct personalInfomation* next;
}perIn;



//初始化通讯录
perIn* initializeList();
//添加联系人,并存储在链表中
void add(perIn** head);
//输入姓名,查找链表中联系人全部信息
void search(perIn* head);
打印函数,打印通讯录链表的内容
void print(perIn*head);
输入姓名,删除指定联系人
void dele(perIn** head);
//输入姓名,修改联系人的其他信息
void mod(perIn*head);
给存储在链表中的联系人排序
void sort(perIn** head);
保存联系人
void saveFile(perIn *head);

函数源文件

#include "contact.h"
perIn* initializeList()
{
	perIn*  head = (perIn*)malloc(len);//开辟头结点空间,head头指针指向头结点地址
	//若堆区内存开辟失败,将会报错
	if (head == NULL)
	{
		perror("initialize");
		return;
	}
	//初始化头结点
	memset(head->name, 0, sizeof(perIn));
	head->next = NULL;
	return head;
}





//添加联系人,并存储在数组中
void add(perIn** head)//二阶指针,head值会被改变
{
	perIn* value, * newvalue,*temp,*dex;
	temp = *head;//临时变量,防止head改变




	value = newvalue = (perIn*)malloc(len);//开辟空间
	int index = 0;//计数器
	//若开辟空间失败,将会报错
	if (newvalue&&value==NULL)
	{
		perror("value");
		return ;
	}


	if ((*head)->next != NULL)//链表中已经有了数据
	{
		while (index<2)
		{
			dex = temp;
			temp = temp->next;//顺着指针域不断寻找下一个结点
			if (dex->next==NULL)//寻找到尾结点
			{
				
				dex->next = newvalue;//尾结点指针域指向新空间
				
				index = 2;
				
			}

		}

	}

	
	printf("请输入姓名,输入0退出>:");
	scanf("%s",newvalue->name);
	printf("请输入年龄>:");
	scanf("%d", &newvalue->age);
	printf("请输入性别>:");
	scanf("%s", newvalue->sex);
	printf("请输入电话号码>:");
	scanf("%s", newvalue->phoneNumber);
	printf("请输入地址>:");
	scanf("%s", newvalue->address);
	printf("通讯录添加成功\n");
	while (1)
	{
		index++;
		if (index == 1)
		{
			(*head)->next = newvalue;//头结点指针域指向新空间
		}
		else
		{
			value->next = newvalue;
		}
		value = newvalue;
		newvalue = (perIn*)malloc(len);//开辟空间
		//若开辟空间失败,将会报错
		if (newvalue == NULL)
		{
			perror("newvalue");
			return 0;
		}
		//输入姓名,若输入0,将会退出循环,0不会计入链表数据中
		printf("请输入姓名输入0退出>:");
		scanf("%s", newvalue->name);
		if (strcmp(newvalue->name, "0") == 0)
		{
			goto loop;//跳出
		}
		printf("请输入年龄>:");
		scanf("%d", &newvalue->age);
		printf("请输入性别>:");
		scanf("%s", newvalue->sex);
		printf("请输入电话号码>:");
		scanf("%s", newvalue->phoneNumber);
		printf("请输入地址>:");
		scanf("%s", newvalue->address);
		printf("通讯录添加成功\n");

	}
	loop://跳出位置
	value->next = NULL;//尾指针的指针域指向空指针
}

//输入姓名,查找数组中联系人全部信息
void search(perIn* head)
{
	perIn* p = head;
	int index = 0;//计数器,判断是第几个
	char Fintname[maxName]={0};
	printf("请输入查询姓名>:");
	scanf("%s", Fintname);
	//遍历,找到后重新输入数据
	while (p->next!=NULL)
	{
		p=p->next;
		index++;
		if (strcmp(p->name, Fintname) == 0)
		{
			printf("找到了\n");
			printf("%d:姓名:%s\t年龄:%d\t:性别:%s\t电话号码:%s\t:地址:%s\n", index, p->name,
				p->age,
				p->sex,
				p->phoneNumber,
				p->address);
			return;
		}
	}
	printf("没找到\n");
 }
//打印函数,打印通讯录数组的内容
void print(perIn* head)
{
	perIn* p = head;
	int n = 0;
	//遍历,打印屏幕
	while (p->next!= NULL)
	{
		p = p->next;
		n++;
		printf("%d:姓名:%s\t年龄:%d\t:性别:%s\t电话号码:%s\t:地址:%s\n", n, p->name,
			p->age,
			p->sex,
			p->phoneNumber,
			p->address);

	}
}


//输入姓名,删除指定联系人
void dele(perIn** head)//二阶指针,head值会被改变
{
	int index = 0;//计数标志
	perIn * p = *head;//head不能变,故用p充当临时变量
	perIn* temp;//前一个节点
	if (p->next==NULL)
	{
		printf("通讯录为空,不需要删除\n");
	}
	char Fintname[maxName] = { 0 };
	printf("请输入要删除联系人姓名>:");
	scanf("%s", Fintname);
	while (p->next != NULL)
	{
		temp = p;
		p = p->next;
		
		index++;
		if (strcmp(p->name, Fintname) == 0)
		{
			if (index == 1)
			{
				(*head)->next = p->next;
				*head = p;//头指针指向头结点

			}
			else
			{
				temp->next = p->next;
			}
			free(p);//释放结点
			p = NULL;//将p置位空指针,防止非法访问
			printf("删除成功\n");
			return;

		}
	}
	printf("没找到\n");
}


//
//输入姓名,修改联系人的其他信息
void mod(perIn* head)
{
	perIn* p = head;
	int index = 0;
	char Fintname[maxName] = { 0 };
	printf("请输入修改姓名>:");
	scanf("%s", Fintname);
	
	while (p->next != NULL)
	{
		p = p->next;
		index++;
		if (strcmp(p->name, Fintname) == 0)
		{
			printf("找到了\n");
			printf("请修改姓名>:");
			scanf("%s", p->name);
			printf("请修改年龄>:");
			scanf("%d", &p->age);
			printf("请修改性别>:");
			scanf("%s", p->sex);
			printf("请修改电话号码>:");
			scanf("%s", p->phoneNumber);
			printf("请修改地址>:");
			scanf("%s", p->address);
			printf("联系人修改成功\n");
			return;
		}
	}
	printf("没找到\n");
}



//
//给存储在数组中的联系人排序
void sort(perIn** head)//二阶指针,head值会被改变

{
	int i, count = 0, num;//count记录链表结点的个数,num进行内层循环,
	perIn* p, * q, * tail;//创建三个指针,进行冒泡排序
	p = *head;
	while (p->next != NULL)//计算出结点的个数
	{
		count++;
		p = p->next;
	}
	for (i = 0; i < count - 1; i++)//外层循环,跟数组冒泡排序一样
	{
		num = count - i - 1;//记录内层循环需要的次数,跟数组冒泡排序一样,
		q = (*head)->next;//令q指向第一个结点
		p = q->next;//令p指向后一个结点
		tail = *head;//让tail始终指向q前一个结点,方便交换,也方便与进行下一步操作
		while (num--)//内层循环 次数跟数组冒泡排序一样
		{
			if (strcmp(q->name , p->name)>0)//如果该结点的值大于后一个结点,则交换
			{
				q->next = p->next;
				p->next = q;
				tail->next = p;
			}
			tail = tail->next;
			q = tail->next;
			p = q->next;
		}
	}
	
	printf("排序完成\n");
	
}
//
//
//
//保存联系人
void saveFile(perIn* head)
{
	perIn* p = head;
	int i = 0;
	FILE* fp;
	fp = fopen("address book.txt", "w+");
	while(p->next!=NULL)
	{
		p = p->next;
	
		fwrite(head,sizeof(head),1,fp);//保存为二进制数据,速度更快,空间更小
	}
	fclose(fp);
	fp =NULL;
	printf("保存成功\n");
}

主函数文件

#define _CRT_SECURE_NO_WARNINGS 1
//简单的通讯录:
//1能存放1000人信息。
//2每个人信息包括名字、年龄、性别、电话和地址。
//3能增加信息
//4能查找指定人信息
//5能删除指定人信息
//6能修改指定人信息
//7通讯录能够根据名字排序
//8能将信息读取和保存在文件中
#include "contact.h"

//菜单
void menu()
{
	printf("***********************************\n");
	printf("***     简单通讯录       **********\n");
	printf("********1.adding contacts**********\n");
	printf("********2.searching contacts*******\n");
	printf("********3.dele contacts************\n");
	printf("********4.modyfi contacts**********\n");
	printf("********5.cls**********************\n");
	printf("********6.sort contacts************\n");
	printf("********7.print contacts***********\n");
	printf("********8.save*********************\n");
	printf("********0.exit*********************\n");
} 

//枚举,定义选项
enum input
{
	exit1,
	addingContacts,
	searchingContacts,
	deleContacts,
	modyfiContacts,
	cls,
	sortcontacts,
	printcotacts,
	save
};

int main()
{
	//打印菜单
	menu();
	//初始化通讯录
	perIn*head=initializeList();//开辟空间,返回头指针

	int input = 0;//初始化输入值
	do
	{
		printf("请输入数字>:");
		scanf("%d", &input);
		switch (input)
		{
		case exit1:
			break;
		case addingContacts:
			//增加联系人
			add(&head);
			break;
			//输入姓名,查找链表中联系人全部信息
		case searchingContacts:
			search(head);
			break;
		case deleContacts:
			输入姓名,删除指定联系人
			dele(&head);
			break;
		case modyfiContacts:
			//输入姓名,修改指定联系人
			mod(head);
			break;
		case cls:
			system("cls");
			break;
		case sortcontacts:
			//给存储在链表中的联系人排序
			sort(&head);

			break;

		case printcotacts:
			//打印函数,打印通讯录链表的内容
			print(head);
			break;
		case save:
			//保存联系人
			saveFile(head);
			break;
		default:
			printf("非法字符,重新输入\n");
			break;
		}

	} while (input);//当input=0的时候,为假,退出循环,结束程序
	return 0;
}

遇到的问题

问题一

在学习链表和编程的过程中,最让我难以理解的,就是开辟新结点,并和前面结点连接的过程。

value->next = newvalue;
value = newvalue;

上面是我自己取名的变量,而在学习的书中,它是这样写的:

p2->next=p1;
p2=p1;

结果思考后,我逐渐产生了理解。

一:开始的状态,p1,p2是结构体指针变量,指针中存放的结构体首地址假设分别为0x10和0x80
在这里插入图片描述

二:p2->next=p1;
在这里插入图片描述p2的指针域,指向了p1的存放的地址0x80.

三:p2->p1
在这里插入图片描述

p2指向了p1存放的地址0x80,注意这改变的并不是指针的地址,而是指针存放的地址,是p2指针之中存放的值变成了p1存放的地址的拷贝。
原来p2的数据,并不是消失了,还存在于堆区空间中!

经过这两步,p2指向了p1的结点地址,这个时候就可以重新开辟结点,并将地址存入p1中了。

问题二

如何读取文件,还没有解决,留待以后补充。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值