重生之我在异世界学编程之C语言小项目:通讯录-正文

时间:2024-12-22 07:58:54

一、通讯录系统的需求分析

在实现通讯录系统之前,我们需要明确其需求。一个基本的通讯录系统应具备以下功能:

添加联系人:允许用户输入新的联系人信息并将其添加到通讯录中。

删除联系人:根据用户的请求,从通讯录中删除指定的联系人。

查找联系人:提供按姓名、电话号码等方式查找联系人的功能。

更新联系人信息:允许用户修改已存在联系人的信息。

显示所有联系人:列出通讯录中的所有联系人信息。

此外,为了提高用户体验和系统的健壮性,我们还需要考虑以下几点:

数据校验:确保用户输入的数据合法有效,如电话号码应为数字且符合一定的格式要求。

异常处理:对于非法操作或错误输入,系统应给出明确的提示并尽可能恢复到一个稳定的状态。

性能优化:在数据量较大时,保持系统的响应速度和稳定性。

二、顺序表的选择与优势

在实现通讯录系统时,有多种数据结构可供选择,如链表、树、图等。然而,考虑到通讯录的特性和需求,顺序表具有以下几个显著的优势:

简单直观:顺序表基于数组实现,其访问和操作方式非常直观,易于理解和实现。

随机访问速度快:由于数组的内存是连续的,因此可以通过索引直接访问任意位置的元素,这使得查找和更新操作非常高效。

内存利用率高:在大多数情况下,顺序表能够充分利用分配的内存空间,减少内存碎片的产生。

易于扩展:虽然顺序表在初始时需要指定大小,但可以通过动态调整数组的大小来适应不断增长的联系人数量。

  • 当然,顺序表也存在一些局限性,如插入和删除操作可能需要移动大量元素,但在通讯录这种应用场景下,由于联系人信息的变动相对较少,这一缺点并不明显。

三、通讯录系统的实现思路

基于上述分析,我们可以使用顺序表来实现通讯录系统。以下是具体的实现思路:

1. 数据结构设计

首先,我们需要定义一个结构体来表示联系人信息。这个结构体可以包含以下字段:

typedef struct {
    char name[50];  // 姓名
    char phone[20]; // 电话号码
    // 可以根据需要添加其他字段,如地址、邮箱等
} Contact;

然后,我们定义一个顺序表来存储联系人信息:

#define MAX_CONTACTS 1000  // 最大联系人数量

typedef struct {
    Contact contacts[MAX_CONTACTS];  // 存储联系人信息的数组
    int size;                        // 当前联系人数量
} AddressBook;

2. 添加联系人

添加联系人时,我们首先检查通讯录是否已满。如果未满,则将新联系人信息添加到数组的末尾,并更新当前联系人数量。

 
void addContact(AddressBook *ab, const char *name, const char *phone) {
    if (ab->size >= MAX_CONTACTS) {
        printf("通讯录已满,无法添加新联系人!
");
        return;
    }
    strcpy(ab->contacts[ab->size].name, name);
    strcpy(ab->contacts[ab->size].phone, phone);
    ab->size++;
}

3. 删除联系人

删除联系人时,我们需要找到要删除的联系人在数组中的位置,并将其后的所有联系人向前移动一位以覆盖被删除的联系人。最后,更新当前联系人数量。

void deleteContactByName(AddressBook *ab, const char *name) {
   int i;
   for (i = 0; i < ab->size; i++) {
       if (strcmp(ab->contacts[i].name, name) == 0) {
           break;
       }
   }
   if (i >= ab->size) {
       printf("未找到名为 %s 的联系人!
", name);
       return;
   }
   for (; i < ab->size - 1; i++) {
       ab->contacts[i] = ab->contacts[i + 1];
   }
   ab->size--;
}

同样地,我们也可以按电话号码删除联系人,只需将查找条件改为电话号码即可。


4. 查找联系人

查找联系人时,我们遍历整个数组,比较每个联系人的姓名或电话号码是否与要查找的值匹配。如果找到匹配的联系人,则返回其位置;否则返回-1表示未找到。

 
int findContactByName(AddressBook *ab, const char *name) {
    for (int i = 0; i < ab->size; i++) {
        if (strcmp(ab->contacts[i].name, name) == 0) {
            return i;
        }
    }
    return -1;
}

int findContactByPhone(AddressBook *ab, const char *phone) {
    for (int i = 0; i < ab->size; i++) {
        if (strcmp(ab->contacts[i].phone, phone) == 0) {
            return i;
        }
    }
    return -1;
}

5. 更新联系人信息

更新联系人信息时,我们首先找到要更新的联系人在数组中的位置,然后修改其信息。

 
void updateContact(AddressBook *ab, const char *name, const char *newPhone) {
    int index = findContactByName(ab, name);
    if (index == -1) {
        printf("未找到名为 %s 的联系人!
", name);
        return;
    }
    strcpy(ab->contacts[index].phone, newPhone);
}
 

6. 显示所有联系人

显示所有联系人时,我们遍历整个数组,并打印出每个联系人的信息。

 
void displayContacts(AddressBook *ab) {
    for (int i = 0; i < ab->size; i++) {
        printf("姓名: %s, 电话号码: %s
", ab->contacts[i].name, ab->contacts[i].phone);
    }
}

四、实现理由的深入剖析

通过上述实现思路,我们可以看到使用顺序表来实现通讯录系统是合理且高效的。以下是对这一选择的深入剖析:

满足基本需求:顺序表能够方便地存储和管理联系人信息,支持添加、删除、查找和更新等操作,完全满足通讯录系统的基本需求。

高效的数据访问:由于顺序表是基于数组的,因此我们可以通过索引直接访问任意位置的元素。这使得在查找和更新联系人信息时,我们能够快速定位到目标元素,从而提高系统的响应速度。

简单的实现和维护:顺序表的结构相对简单,易于理解和实现。同时,由于其内存是连续的,因此在维护和管理上也比链表等其他数据结构更为方便。

良好的扩展性:虽然顺序表在初始时需要指定大小,但我们可以通过动态调整数组的大小来适应不断增长的联系人数量。这使得通讯录系统在数据量较大时仍然能够保持良好的性能和稳定性。

较低的资源消耗:相比链表等其他数据结构,顺序表在内存利用上具有更高的效率。由于数组的内存是连续的,因此可以减少内存碎片的产生,从而降低资源消耗。


五、源码

注意:这是小编自己写的,可能有不足之处,仅供参考!!!

(1)contact.h

#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>

#define _CRT_SECURE_NO_WARNINGS 1
#define NAME_MAX 20
#define SEX_MAX 6
#define SITE_MAX 20
#define TELE_MAX  12
//#define MAX 100
#define DEFAULT_SZ 3
#define ADD_SZ 2

//一个人的信息
typedef struct person {
	char name[NAME_MAX];    //名字
	int age;                //年龄
	char sex[SEX_MAX];      //性别
	char site[SITE_MAX];    //地址
	char tele[TELE_MAX];    //电话号码
}per;

//静态版本
通讯录的信息
//typedef struct contact {
//	int sz;                 //通讯录中联系人的数量
//	per arr[MAX];           //通讯录的联系人的信息
//}con;

//动态版本
//通讯录的信息
typedef struct contact {
	int sz;                 //通讯录中联系人的数量
	per* arr;               //通讯录的联系人的信息
	int capcity;            //通讯录的最大容量
}con;

//初始化通讯录
void initcontact(con* pc);

//显示通讯录信息
void Showcontact(const con* pc);

//查找联系人是否存在
int Findbyname(const con* pc, const char* name);

//增加联系人信息
void Addcontact(con* pc);

//删除联系人信息
void Delcontact(con* pc);

//查找联系人信息
void Searchcontact(const con* pc);

//修改联系人信息
void Modifycontact(con* pc);

//排序通讯录信息
void Sortcontact(con* pc);  

//按照名字对联系人进行排序
int compare_contact_by_Name(const void* e1, const void* e2);

//按照年龄对联系人进行排序
int compare_contact_by_Age(void* e1, const void* e2);

//按照性别对联系人进行排序
int compare_contact_by_Sex(const void* e1, const void* e2);

// 按照地址对联系人进行排序
int compare_contact_by_Site(const void* e1, const void* e2);

// 按照电话号码对联系人进行排序
int compare_contact_by_Tele(const void* e1, const void* e2);
 
//销毁通讯录
void Destory(con* pc);

//保存通讯录信息至文本
void Savecontact(con* pc);

//加载文本信息至通讯录
void loadcontact(con* pc);

(2)contact.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"contact.h"
enum sort {
	Exit,
	Name,  
	Age,
	Sex,    
	Site, 
	Tele,
};
//静态版本
//初始化通讯录

//void initcontact(con* pc) {
//	assert(pc);
//	pc->sz = 0;
//	memset(pc->arr, 0, sizeof(pc->arr));      //memset()函数可以把一个给定数组的地址(第一个参数)的元素按照字节(字节数为第三个参数)的方式设置成一个数(第二个参数)
//}

//动态版本
//初始化通讯录

void initcontact(con* pc) {
	assert(pc);
	pc->sz = 0;
	per* ptr = (per*)calloc(DEFAULT_SZ , sizeof(per));       //calloc函数可以开辟一个指定元素个数(第一个参数)和单个元素大小(第二个参数)的空间
	if (ptr != NULL) {
		pc->arr = ptr;
	    pc->capcity = DEFAULT_SZ;
	}
	else {
		perror("initcontact");
	}
	//加载信息到通讯录
	loadcontact(pc);
}

//显示通讯录

void Showcontact(const con* pc) {
	assert(pc);
	printf("%-20s%-4s%-5s%-20s%-12s\n", "姓名","年龄", "性别", "地址","电话号码");
	int i = 0;
	for (; i < pc->sz; i++) {
		printf("%-20s%-4d%-5s%-20s%-12s\n", pc->arr[i].name, (pc->arr[i].age), pc->arr[i].sex, pc->arr[i].site, pc->arr[i].tele);
	}
}

//静态版本
增加联系人的信息
//
//void Addcontact(con* pc) {
//	assert(pc);
//	//先判断通讯录有没有满
//	if ((pc->sz) == 100) {
//		printf("通讯录已满,无法再存放联系人信息\n");
//		return;
//	}
//	//
//		printf("请输入您要输入的联系人姓名:>");
//		scanf("%s", pc->arr[pc->sz].name);
//		printf("请输入您要输入的联系人年龄:>");
//		scanf("%d", &(pc->arr[pc->sz].age));
//		printf("请输入您要输入的联系人性别:>");
//		scanf("%s", pc->arr[pc->sz].sex);
//		printf("请输入您要输入的联系人的地址:>");
//		scanf("%s", pc->arr[pc->sz].site);
//		printf("请输入您要输入的联系人的电话号码:>");
//		scanf("%s", pc->arr[pc->sz].tele);
//		(pc->sz)++;
//		printf("增加联系人成功\n");
//		return;
//}

//增加容量
void check_capcity(con* pc) {
	assert(pc);
	if (pc->sz == pc->capcity) {
		printf("开始增容\n");
		per* ptr =(per*) realloc(pc->arr, (pc->capcity + ADD_SZ) * sizeof(per));
		if (ptr != NULL){
			pc->arr = ptr;
			pc->capcity += ADD_SZ;
			printf("增容成功\n");
			return;
		}
		else {
			perror("check_capcity->realloc");
		}
	}
}
//动态版本
//增加联系人的信息

void Addcontact(con* pc) {
	assert(pc);
	//先判断通讯录有没有满,需不需要增容,要增容就增容
	check_capcity(pc);
	//
	printf("请输入您要输入的联系人姓名:>");
	scanf("%s", pc->arr[pc->sz].name);
	printf("请输入您要输入的联系人年龄:>");
	scanf("%d", &(pc->arr[pc->sz].age));
	printf("请输入您要输入的联系人性别:>");
	scanf("%s", pc->arr[pc->sz].sex);
	printf("请输入您要输入的联系人的地址:>");
	scanf("%s", pc->arr[pc->sz].site);
	printf("请输入您要输入的联系人的电话号码:>");
	scanf("%s", pc->arr[pc->sz].tele);
	(pc->sz)++;
	printf("增加联系人成功\n");
}

//查找联系人是否存在
Findbyname(const con* pc, const char* name) {
	assert(pc);
	int i = 0;
	for (i; i < pc->sz; i++) {
		if (strcmp(pc->arr[i].name, name) == 0) {
			return i;
		}
	}
	return -1;
}

//删除联系人
void Delcontact(con* pc){
	assert(pc);
	//先判断通讯录里还有没有联系人
	if (pc->sz == 0) {
		printf("通讯录里无联系人,无法删除\n");
		return;
	}

	//删除联系人
	//1.输入要查找的联系人
	char name[NAME_MAX] = { 0 };
	printf("请输入要删除的联系人的姓名:>");
	scanf("%s", name);

	//2.查找要删除的联系人
	int pos = Findbyname(pc, name);

	//3.删除联系人
	if (pos != -1) {
		for (int i = pos; i < pc->sz - 1; i++) {
			pc->arr[i] = pc->arr[i + 1];
		}
		printf("删除成功\n");
		return;
	}
	printf("您要删除的人不存在\n");
	return;
}

//查找联系人的信息
void Searchcontact(const con* pc) {
	assert(pc);
	//输入要查找的联系人
	printf("请输入要查找的联系人:>");
	char name[20];
	scanf("%s", name);
	//查找联系人是否存在
	int pos = Findbyname(pc, name);
	//打印联系人的信息
	if (pos != -1) {
		printf("%-20s%-4s%-5s%-20s%-12s\n", "姓名", "年龄", "性别", "地址", "电话号码");
		printf("%-20s%-4d%-5s%-20s%-12s\n", pc->arr[pos].name, pc->arr[pos].age, pc->arr[pos].sex, pc->arr[pos].site, pc->arr[pos].tele);
		return;
	}
	printf("您要修改的联系人不存在\n");
	return;
}

//修改联系人的信息
void Modifycontact(con* pc) {
	assert(pc);

	//输入要修改的联系人
	printf("请输入要修改的联系人:>");
	char name[20];
	scanf("%s", name);

	//查找联系人是否存在
	int pos = Findbyname(pc, name);

	//修改联系人的信息
	if (pos != -1) {
		printf("请输入您要修改后的信息:\n");
		printf("联系人姓名:>");
		scanf("%s", pc->arr[pc->sz].name);
		printf("联系人年龄:>");
		scanf("%d", &(pc->arr[pc->sz].age));
		printf("联系人性别:>");
		scanf("%s", pc->arr[pc->sz].sex);
		printf("联系人地址:>");
		scanf("%s", pc->arr[pc->sz].site);
		printf("联系人电话号码:>");
		scanf("%s", pc->arr[pc->sz].tele);
		printf("修改联系人成功\n");
		return;
	}
	printf("您要修改的联系人不存在\n");
	return;
}

//按照名字对联系人进行排序
int compare_contact_by_Name(const void* e1, const void* e2) {
	assert(e1 && e2);
	per* a = (per*)e1;
	per* b = (per*)e2;
	return a->name - b->name;
}

//按照年龄对联系人进行排序
int compare_contact_by_Age(const void* e1, const void* e2) {
	assert(e1 && e2);
	per* a = (per*)e1;
	per* b = (per*)e2;
	return a->age - b->age;
}

//按照性别对联系人进行排序
int compare_contact_by_Sex(const void* e1, const void* e2) {
	assert(e1 && e2);
	per* a = (per*)e1;
	per* b = (per*)e2;
	return a->sex - b->sex;
}

//按照地址对联系人进行排序
int compare_contact_by_Site(const void* e1, const void* e2) {
	assert(e1 && e2);
	per* a = (per*)e1;
	per* b = (per*)e2;
	return a->site - b->site;
}

//按照电话号码对联系人进行排序
int compare_contact_by_Tele(const void* e1, const void* e2) {
	assert(e1 && e2);
	per* a = (per*)e1;
	per* b = (per*)e2;
	return a->tele - b->tele;
}

//打印排序方式
void menu1(void) {
	printf("*****************************\n");
	printf("****1.Name    2.Age    ******\n");
	printf("****3.Sex     4.Site   ******\n");
	printf("****5.Tele    0.Exit   ******\n");
	printf("*****************************\n");
}

//排序通讯录信息
void Sortcontact(con* pc) {
	assert(pc);
	//选择排序方法
	int input = 0;
	do {
		menu1();
		printf("请输入您要选择的排序方法:>");
		scanf("%d", &input);
		size_t sz = sizeof(pc->arr) / sizeof(pc->arr[0]);
		size_t width = sizeof(pc->arr[0]);
		switch (input) {
		case Name:
			qsort(pc->arr, sz, width, compare_contact_by_Name);  //按照名字对联系人进行排序
			printf("排序成功\n");
			break;
		case Age:
			qsort(pc->arr, sz, width, compare_contact_by_Age);  //按照年龄对联系人进行排序
			printf("排序成功\n");
			break;
		case Sex:
			qsort(pc->arr, sz, width, compare_contact_by_Sex);  //按照性别对联系人进行排序
			printf("排序成功\n");
			break;
		case Site:
			qsort(pc->arr, sz, width, compare_contact_by_Site);  //按照地址对联系人进行排序
			printf("排序成功\n");
			break;
		case Tele:
			qsort(pc->arr, sz, width, compare_contact_by_Tele);  //按照电话号码对联系人进行排序
			printf("排序成功\n");
			break;
		case Exit:
			printf("退出\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (input);
	return ;
}

//销毁通讯录
void Destory(con* pc) {
	free(pc->arr);
	pc->arr = NULL;
	pc->capcity = 0;
	pc->sz = 0;
	pc = NULL;
}

//保存通讯录信息
void Savecontact(con* pc)
{
	assert(pc);
	//打开文件
	FILE* pf = fopen("contact.txt", "wb");
	if (NULL == pf) {
		perror("fopen");
		printf("打开文件失败\n");
	}
	//把内存信息写入文件
	else
	{
		int i = 0;
		for (i = 0; i < pc->sz; i++) {
			fwrite(pc->arr + i, 1, sizeof(per),pf);
		}
		printf("保存成功\n");
		//关闭文件
		fclose(pf);
		pf = NULL;
	}
}

//加载文件文信息
void loadcontact(con* pc) {
	assert(pc);
	//打开文件
	FILE* pf = fopen("contact.txt", "rb");
	if (NULL == pf) {
		perror("loadcontact");
		printf("打开文件失败\n");
	}
	else {
		//读取文件信息
		//创建一个临时结构体变量方便传值和判断
		per temp = { 0 };
		int i = 0;
		while (fread(&temp, 1, sizeof(per), pf) ){    //利用fread返回值判断文件里的信息是否被完全读取
			//增容
			check_capcity(pc);
			pc->arr[i] = temp;
			pc->sz++;
			i++;
		}
		printf("加载信息成功\n");
		fclose(pf);
		pf = NULL;
	}
}

(3)Test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"contact.h"
enum contact_function {
	Exit,
	Add = 1,
	Del = 2,
	Search,
	Modify,
	Show,
	Sort,
};
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");
}
int main() {
	//创建通讯录
	con c;

	//初始化通讯录
	initcontact(&c);

	//选择通讯录的功能
	int input = 0;
	do {
		menu();
		printf("请输入您要选择的通讯录功能:>");
		scanf("%d", &input);
		switch (input) {
		case Add:
			Addcontact(&c);    //增加联系人信息
			break;
		case Del:
			Delcontact(&c);    //删除联系人信息
			break;
		case Search: 
			Searchcontact(&c); //查找联系人信息
			break;
		case Modify: 
			Modifycontact(&c); //修改联系人的信息
			break;
		case Show:
			Showcontact(&c);   //展示通讯录信息
			break;
		case Sort: 
			Sortcontact(&c);   //排序通讯录信息
			break;
		case Exit:             //先保存信息至文本再销毁
			Savecontact(&c);
			Destory(&c);
			printf("退出通讯录\n");  
			break;
		default: 
			printf("输入错误,请重新输入:>\n"); 
			break;
		}
	} while (input);
	return 0;
}

六、进一步优化与改进

尽管使用顺序表已经能够实现一个功能完善的通讯录系统,但在实际应用中,我们还可以进一步对其进行优化和改进:

哈希表加速查找:为了进一步提高查找效率,我们可以引入哈希表来存储联系人信息。通过为每个联系人计算一个唯一的哈希值,并将其作为键存储在哈希表中,我们可以实现常数时间复杂度的查找操作。

并发控制:在多线程环境下,我们需要对通讯录系统进行并发控制以确保数据的一致性和完整性。可以使用互斥锁、读写锁等同步机制来实现这一点。

持久化存储:为了实现通讯录的持久化存储,我们可以将其保存到磁盘文件中。这样即使程序退出或崩溃,联系人信息也不会丢失。同时,还可以提供导入导出功能以便用户在不同设备之间迁移通讯录数据。

用户界面优化:为了提高用户体验,我们可以设计一个友好的用户界面来与用户进行交互。这可以通过图形界面库或命令行工具来实现。

安全性增强:为了保护用户的隐私和数据安全,我们可以对通讯录数据进行加密存储和传输。同时,还可以提供密码保护等功能以防止未经授权的访问和操作。

不要担心,小编会在以后的日子里带着宝子们一起实现这个优化的!!!


七、结论

  • 综上所述,使用顺序表来实现通讯录系统是一种合理且高效的选择。它能够满足通讯录系统的基本需求并提供良好的性能和可扩展性。在实际应用中,我们还可以根据具体需求和场景对其进行优化和改进以提高系统的实用性和用户体验。