在这之前,已经尝试用数组和动态数组的方法,实现通讯录。但用数组存储数据有着各种不便,于是经过一段时间学习后,使用动态单链表完成了新的通讯录。
设计目标:
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中了。
问题二
如何读取文件,还没有解决,留待以后补充。