【C语言】通讯录(静态版本+动态版本)思路解析+完整源代码

通讯录

由于代码比较长,为了增加可读性,分成了contact.h,contact.c,test.c,分别用来声明函数或者类型,实现函数功能,测试代码

contact.h

我们希望通讯录具有增加联系人,删除联系人,显示联系人,找查联系人,修改联系人,排序的功能,联系人的信息具有名字,年龄,性别,电话,地址的信息

由于每次对通讯录进行操作都要把data数组和存的联系人的个数sz传过去,那干脆把他们两个打包到一个结构体类型struct contact里面

枚举类型是为了增加test.c里面switch语句选项的可读性,默认第一个成员也就是EXIT就是0,那么我们就可以把switch语句里面的0换成EXIT了。

test.c

contact.c

首先把所有data数组的所有内容以及p->sz初始化成0

增加联系人的函数

一次性增加一个联系人的信息

数字表示打印多少位,负号表示左对齐,\t表示插入一个制表符,能够让每一行对齐

在查找联系人,删除联系人,修改联系人信息的函数中,我们都需要先找到某个联系人,为了避免写三份类似的代码,我们这里使用了一个函数。

查找联系人的函数

修改信息的函数

排序的函数

像这样静态版本的通讯录问题还是比较大的,首先就是我们不管存多少个联系人的信息,上来都创建了一个100个元素的数组,这样对内存的开销就比较大,如果我们存的信息少,就浪费了内存,如果存的太多,又要去修改最大容量,在我们学习了动态内存开辟之后,我们可以对通讯录进行如下修改:通讯录刚上来可以存放三个联系人的信息,当通讯录满了之后,自动扩充两个人的名额。于是我们就可以把data数组改成一个指针,为了尽可能少的改动原来的代码,我们把这个指针的名字也叫做data。这与原来数组名的含义都是地址,现在data是一个people*类型的指针,我们可以把使用malloc,realloc函数开辟的内存首地址存到里面去。

那么我们先对以前的contact类型进行以下修改

实际上需要修改的函数只有初始化通讯录的函数和增加联系人的函数。

首先使用malloc开辟一块能存放三个people类型变量的空间,并返回首地址存到data里面去。这里申请了空间由于后面要用,所以并没有及时释放掉,最后关闭通讯录的时候再释放即可。

然后是增加联系人的函数,在增加之前我们先要判断一下通讯录是不是已经满了,我们使用下面的函数来实现这个功能

由于realloc增容可能会失败,因此必须检查,在增容完毕之后要应该更改掉当前的最大容量capacity。

然后在添加联系人的函数中调用这个检查容量的函数

由于以上所有动态内存开辟的空间都没有被释放掉,因此我们在推出这个通讯录的时候应该及时释放掉,在test函数这里调用一个用来释放内存的函数

这个函数也是需要我们自己编写的

这样我们的通讯录就是动态版本的了。

源代码

test.c

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include"contact.h"
void menu() {
	printf("******1.add*****2.del***********\n");
	printf("******3.search**4.modify********\n");
	printf("******5.show****6.sort**********\n");
	printf("***********0.exit***************\n");
} 
void test() {
	int input = 0;
	//创建通讯录
	contact con = {0};
	do {
		menu();
		printf("请选择功能\n");
		scanf("%d", &input);
		switch(input) {
		case ADD: 
			addcontact(&con);
			break;
		case DEL:
			delcontact(&con);
			break;
		case SEARCH:
			searchcontact(&con);
			break;
		case MODIFY:
			modifycontact(&con);
			break;
		case SHOW:
			showcontact(&con);
			break;
		case SORT:
			sortcontact(&con);
			break;
		case EXIT:
			release(&con);
			printf("退出通讯录\n");
			break;
		default:
			printf("输入错误,请重新选择\n");
		}
	} while (input);
}
int main() {
	test();
	return 0;
}

contact.c

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include"contact.h"
//初始化通讯录的函数
void initcontact(contact* p) {
	p->data = (people*)malloc(DEFAULT_NUM *sizeof(people));//刚上来容量是3
	if (p->data == NULL) {
		perror("initcontact");
		return;
	}
	p->sz = 0;
	p->capacity = DEFAULT_NUM;
}
int check_capacity(contact*p) {
	if (p->capacity == DEFAULT_NUM) {
		//扩大容量
		people* ptr=(people*)realloc(p->data, (DEFAULT_NUM + INC) * sizeof(people));
		//一个变量名为ptr的people*类型指针,用来接收调整之后的空间首地址
		if (ptr == NULL) {
			perror(check_capacity);
			return 0;
		}
		else {
			p->data = ptr;//这个ptr可能是原来的data,也可能不是,增容成功之后赋给data
			p->capacity += INC;
			return 1;
		}
	}
	return 1;//压根不需要增容,也返回1
}
//添加联系人的函数,动态版本
void addcontact(contact* p) {
	int ret = check_capacity(p);
	if (0 == ret) {
		return;
	}
	printf("请输入名字\n");
	scanf("%s", p->data[p->sz].name);
	printf("请输入年龄\n");
	scanf("%d", &(p->data[p->sz].age));
	printf("请输入性别\n");
	scanf("%s", p->data[p->sz].sex);
	printf("请输入电话号码\n");
	scanf("%s", p->data[p->sz].tele);
	printf("请输入地址\n");
	scanf("%s", p->data[p->sz].address);
	p->sz++;
	printf("增加联系人成功\n");
}
//显示联系人的函数
void showcontact(const contact* p) {
	int i = 0;
	for (i = 0; i < p->sz; i++) {
		//打印标题
		printf("%-10s\t,%-4s\t,%-5s\t,%-12s\t,%-30s\n", "名字", "年龄", "性别", "电话", "地址");
		printf("%-10s\t,%-4d\t,%-5s\t,%-12s\t,%-30s\n",
			p->data[i].name,
			p->data[i].age,
			p->data[i].sex,
			p->data[i].tele,
			p->data[i].address
		);
	}
}
//通过名字找查联系人的函数
// 由于在delcontact,searchcontact,modifycontact函数中都需要找查
// 为了避免写三份类似的代码,我们使用函数来实现找查联系人的功能
int find_by_name(contact* p, char name[]) {
	//找查联系人
	int i = 0;
	for (i = 0; i < p->sz; i++) {
		if (strcmp(p->data[i].name, name) == 0) {
			return i;//找到了,返回下标
		}
	}
	return -1;//没有找到,返回-1
}
//删除联系人的函数(暂时不考虑两个人的名字相同的情况)
void delcontact(contact* p) {
	char name[20] = { 0 };
	printf("请输入要删除的人名字\n");
	scanf("%s", name);
	int i = 0;
	int del = 0;//记录要删除的人信息在data数组中的下标
	int flag = 0;
	if (p->sz == 0) {
		printf("通讯录为空,无法删除\n");
	}
	//找查联系人
	del = find_by_name(p, name);
	//删除联系人
	if (del == -1) {
		printf("要删除的人不存在\n");
		return;
	}
	for (i = del; i < p->sz - 1; i++) {
		p->data[i] = p->data[i + 1];//循环用后面的元素覆盖前面的元素
	}
	p->sz--;
	//如果要删除最后一个联系人,也就是下标为sz-1的那个人的信息
	//由于刚上来del就是sz-1,不会进入循环,也就不会覆盖掉最后一个元素
	//但是sz--了,就访问不到最后一个元素了,效果上就好像删除了最后一个联系人
	printf("删除成功\n");
}
//查找某个联系人的函数
void searchcontact(contact* p) {
	char name[20] = { 0 };
	printf("请输入要查找的人的名字\n");
	scanf("%s", name);
	int pos = find_by_name(p, name);//返回的就是要找的这个人的信息在data数组中的下标 
	if (pos == -1) {
		printf("查无此人\n");
		return;
	}
	else {
		//打印这一个人的信息
		printf("%-10s\t,%-4s\t,%-5s\t,%-12s\t,%-30s\n", "名字", "年龄", "性别", "电话", "地址");
		printf("%-10s\t,%-4d\t,%-5s\t,%-12s\t,%-30s\n",
			p->data[pos].name,
			p->data[pos].age,
			p->data[pos].sex,
			p->data[pos].tele,
			p->data[pos].address
		);
	}
}
//修改信息的函数
void modifycontact(contact* p) {
	char name[20] = { 0 };
	printf("请输入要修改信息的人的名字\n");
	scanf("%s", name);
	int pos = find_by_name(p, name);
	if (pos == -1) {
		printf("查无此人\n");
	}
	else {
		printf("请输入名字\n");
		scanf("%s", p->data[pos].name);
		printf("请输入年龄\n");
		scanf("%d", &(p->data[pos].age));
		printf("请输入性别\n");
		scanf("%s", p->data[pos].sex);
		printf("请输入电话号码\n");
		scanf("%s", p->data[pos].tele);
		printf("请输入地址\n");
		scanf("%s", p->data[pos].address);
		printf("修改联系人信息成功\n");
	}
}
//qsort需要的比较大小的函数
int cmp_by_name(void* str1,void* str2) {
	return strcmp(((people*)str1)->name, ((people*)str2)->name);
}
//根据名字对信息排序的函数
void sortcontact(contact* p) {
	qsort(p->data, p->sz, sizeof(p->data[0]), cmp_by_name);
	printf("排序成功\n");
}
void release(contact* p) {
	free(p->data);
	p->data = NULL;//注意是data指向的空间被释放掉,不是p指向的空间被释放掉
	p->capacity = 0;
	p->sz = 0;
}

contact.h

cpp 复制代码
#pragma once
//用于各种函数或者类型的声明
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX 100 //通讯录能添加的最大人数
#define DEFAULT_NUM 3//刚上来的默认容量是3
#define INC 2//到达最大容量的时候一次性扩充的个数
typedef struct people {
	char name[20];
	int age;
	char sex[5];
	char tele[12];
	char address[30];
}people;//创建结构体类型并重命名为people
typedef struct contact {
	people* data;//一个名为data的指针变量
	int sz;//用来记录通讯录里面存了几个人的信息了
	int capacity;//记录当前最大容量
}contact;
//枚举类型,增加可读性,默认EXIT就是0
enum OPTION {
	EXIT,
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SHOW,
	SORT,
};
void initcontact(contact* p);//初始化通讯录的函数,把通讯录中内容初始化为0
void addcontact(contact* p);//增加联系人的函数
void showcontact(const contact* p);//显示所有联系人的函数,显示并不会修改p指向的内容,因此加了const
void delcontact(contact* p);//删除联系人的函数
void searchcontact(contact* p);//找查某个联系人的函数
void modifycontact(contact* p);//修改信息的函数
void sortcontact(contact* p);//根据名字对信息排序的函数
void release(contact* p);//使用完成之后释放动态申请的那些空间的函数
相关推荐
k09336 分钟前
sourceTree回滚版本到某次提交
开发语言·前端·javascript
神奇夜光杯13 分钟前
Python酷库之旅-第三方库Pandas(202)
开发语言·人工智能·python·excel·pandas·标准库及第三方库·学习与成长
Themberfue16 分钟前
Java多线程详解⑤(全程干货!!!)线程安全问题 || 锁 || synchronized
java·开发语言·线程·多线程·synchronized·
plmm烟酒僧17 分钟前
Windows下QT调用MinGW编译的OpenCV
开发语言·windows·qt·opencv
EricWang135827 分钟前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端
测试界的酸菜鱼29 分钟前
Python 大数据展示屏实例
大数据·开发语言·python
我是谁??29 分钟前
C/C++使用AddressSanitizer检测内存错误
c语言·c++
小码农<^_^>31 分钟前
优选算法精品课--滑动窗口算法(一)
算法
羊小猪~~33 分钟前
神经网络基础--什么是正向传播??什么是方向传播??
人工智能·pytorch·python·深度学习·神经网络·算法·机器学习
晨曦_子画38 分钟前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin