C语言实现通讯录

1、通讯录的功能及操作

通讯录中储存了一个人的姓名、年龄、性别、电话号码、居住地址等信息。
通讯录可以对一个人的信息进行添加、删除、查找、修改等操作。也可以对多人的信息按某条件(如年龄、姓名)进行排序操作。

2、实现通讯录的流程

  1. 利用结构体创建个人信息与通讯录
  2. 初始化通讯录
  3. 通讯录信息添加功能
  4. 通讯录信息打印功能
  5. 通讯录信息删除功能
  6. 通讯录信息搜寻功能
  7. 通讯录信息修改功能
  8. 通讯录信息排序功能

3、利用结构体创建个人信息与通讯录

#define MAX 1000
#define NAME_MAX 20
#define SEX_MAX 5
#define TELE_MAX 12
#define ADDR_MAX 30


//利用结构体创建一个人的信息
typedef struct PeoInfo
{
	char name[NAME_MAX];
	char sex[SEX_MAX];
	int age;
	char tele[TELE_MAX];
	char addr[ADDR_MAX];
}PeoInfo;   //结构体类型重命名


//利用结构体创建通讯录
typedef struct Contact
{
	PeoInfo data[MAX]; //结构体数组,可以存放1000个人的信息
	int sz; //记录通讯中已经保存的信息的人的个数
}Contact;  //结构体类型重命名
  1. 构建一个结构体 struct PeoInfo,里面包含人的姓名、年龄、性别、电话号码、居住地址等信息,并将 struct PeoInfo 用 typedef 重命名为 PeoInfo。
  2. PeoInfo这个结构体只包含了一个人的信息,通讯录不止有一个人,所以利用创建一个结构体数组 PeoInfo data[1000],说明这个通讯录可以存储一千个人的信息,没储存一个人的信息要记录下来,用变量 sz 表示存储了几个人的信息。
  3. 为了方便,将结构体数组 PeoInfo data[1000] 与变量 sz再封装成一个结构体,这个结构体为 struct Contact,表示这个通讯录的容量,并将 struct Contact 用 typedef 重命名为 Contact。
  4. 为了以后便于代码修改,我们将数组中的大小用 define 定义的常量表示。
  5. 上述代码都是对类型的声明,我们将声明放在一个 .h的文件中。
#define _CRT_SECURE_NO_WARNINGS 1
#include "contact.h"



void menu()
{
	printf("**********************************\n");
	printf("*****     1.add     2.del    *****\n");
	printf("*****     3.search  4.modify *****\n");
	printf("*****     5.sort    6.print  *****\n");
	printf("*****     0.exit             *****\n");
	printf("**********************************\n");
}
void test()
{
	int input = 0;
	//创建通讯录
	Contact con; //通讯录

	//初始化通讯录
	InitContact(&con);
	do
	{
		menu();
		printf("请选择:>");
		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 SORT:
			SortContact(&con);
			break;
		case PRINT:
			PrintContact(&con); 
			break;
		case EXIT:
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误\n");
		}

	} while (input);
}
int main()
{
	test();
	return 0;
}
  1. 在 test.c文件中创建结构体变量 con,代表一个通讯录。
  2. 通讯录中功能的实现用到的 switch分支语句,为了代码的维护,这里的case 1、case2……写的是case ADD、case DEL……,对于以后要对代码进行修改的程序员来说,可以非常直观的了解到这块区域的功能。
  3. 那么 ADD对应的1, DEL对应的2 等怎么解决?
    在头文件 .h中利用枚举常量,将ADD DEL SEARCH 等功能一一枚举出来,那么这些常量将会自动初始化为 0、1 、2……,如下
    enum Option
    {
    EXIT, //0
    ADD,
    DEL,
    SEARCH,
    MODIFY,
    SORT,
    PRINT
    };
    当然也可以自己初始化为其他值。枚举常量的具体内容参考这篇文章《c语言自定义类型:结构体、枚举、联合》
  4. 上述功能的实现全都单独封装成一个函数,这些函数写在 contact.c文件中。这些函数的参数是结构体变量 con,为了防止函数压栈过大,实参传的都是结构体变量的地址,所以在 contact.c文件中函数形参应该用结构体指针来接收。

4、初始化通讯录

void InitContact(Contact* pc)
{
	assert(pc); //断言,防止为空指针

	pc->sz = 0;
	memset(pc->data, 0, sizeof(pc->data));   //利用meneset将数组中的每个字节都初始化为0,
	                                         //数组中的每个元素也就初始化为0了,
	                                         //也可以利用循环将数组中的每个元素初始化为0。
}
  1. 首先使用断言,防止传过来的是空指针。
  2. 结构体指针访问指向的结构体变量的成员,用操作符 ->
  3. 这里用到了内存操作函数 memset( );,将数组中的所有字节全都设置为0了,所有字节为0,那么数组中所有的元素也就是0了。memset函数的用法请看这篇文章《字符串函数和内存操作函数》

5、通讯录信息添加功能

void AddContact(Contact* pc)
{
	assert(pc);

	if (pc->sz == MAX)
	{
		printf("通讯录已满,无法添加\n");
		return; //已满,直接返回
	}
	//录入信息
	printf("请输入名字:>");
	scanf("%s", pc->data[pc->sz].name);  //将信息放在数组下标为sz的位置上去。
	printf("请输入年龄:>");
	scanf("%d", &(pc->data[pc->sz].age)); //将信息放在数组下标为sz的位置上去。
	printf("请输入性别:>");
	scanf("%s", pc->data[pc->sz].sex);  //将信息放在数组下标为sz的位置上去。
	printf("请输入电话:>");
	scanf("%s", pc->data[pc->sz].tele);  //将信息放在数组下标为sz的位置上去。
	printf("请输入地址:>");
	scanf("%s", pc->data[pc->sz].addr);  //将信息放在数组下标为sz的位置上去。

	pc->sz++;  //一个人的信息录入完后,人的信息数加1
	printf("添加成功\n");
}
  1. MAX是之前 #define定义的1000,即结构体数组(通讯录)所能存储的最大人数,用 if语句判断一下,如果数组满了,直接 return 返回。
  2. 以 pc->data[pc->sz].name 分析,为什么这么写?
  1. data[pc->sz],表示将成员存储在数组下标为 sz的位置,为什么?
    答:当数组为空时(即还没有存储信息),此时 sz等于0,储存第一个成员时,是将成员放在数组下标为0的位置上;
    已经储存好一个成员后,此时sz+1=1,储存第二个成员时,是将成员放在数组下标为1的位置上。我们看到数组中每个成员与 sz的大小一一对应,所以成员存储在数组下标为 sz的位置上。
  2. 为什么是 .name,而不是 ->name?
    我们知道结构体变量访问结构体成员的时候,用的是点操作符(.)。pc->data[pc->sz],结构体指针变量 pc指向的成员 data[pc->sz] 是一个具体的结构体变量 PeoInfo,再由这个结构体变量去访问他的结构体成员,就是用点操作符了。
  1. pc->sz++;,表示录入一个人的信息后,要将增加的人的个数记录下来。

6、通讯录信息打印功能

void PrintContact(const Contact* pc)
{
	assert(pc);

	int i = 0;
	printf("%-20s %-5s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-20s %-5d %-5s %-12s %-30s\n", pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);
	}
}
  1. 打印格式 %20s 代表以字符串形式输出,输出宽度为20个字符,不够20个字符以空格补齐,这种形式是右对齐。
  2. 打印格式 %-20s 这种形式是左对齐。
  3. 在 for循环外面打印一行信息,能够直观的看清楚打印的每一列是什么信息(姓名、年龄……)。

7、通讯录信息删除功能

//查找函数
//找到了返回下标
//找不到返回-1
int FindByName(Contact* pc, char name[])
{
	assert(pc);

	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		if (0 == strcmp(pc->data[i].name, name))
		{
			return i;
		}
	}
	return -1; //遍历完了还没找到,说明找不到了
}



void DelContact(Contact* pc)
{
	assert(pc);

	if (pc->sz == 0)
	{
		printf("通讯录已空,无法删除");
		return;
	}

	//删除
	//1.找到要删除的人
	char name[NAME_MAX] = { 0 };
	printf("请输入要删除人的名字:>");
	scanf("%s", name);
	int pos = FindByName(pc, name); //查找指定的名字的函数
	if (pos == -1)
	{
		printf("要删除的人不存在\n");
		return;
	}

	//2.找到以后删除
	int j = 0;
	for (j = pos; j < pc->sz - 1; j++)
	{
		pc->data[j] = pc->data[j + 1]; //将后面的元素往要删除的位置依次覆盖
	}
	pc->sz--;
	printf("删除成功\n");
}
  1. 删除一个人首先要找到这个人,所以先封装一个找人函数。
  2. 利用 strcmp函数(字符串比较函数)与参数(要找的人的姓名)进行比较,找到了返回数组下标,找不到返回-1。strcmp函数的详细内容请看这篇文章《字符串函数和内存操作函数》
  3. 找到要删除的人的数组下标后,将此数组下标后面的数组成员往此下标处依次覆盖,即删除了。
  4. pc->sz–;,表示删除一个人的信息后,要将删除的人的个数记录下来。
  5. 因为 pc->data[j + 1],所以数组的循环条件是 j < pc->sz - 1;,如果是j < pc->sz;,数组会越界访问。
  6. 按照代码中循环条件的写法,如果删除的是数组最后一个成员,并不会进入循环,就算进入了,最后一个成员的后面也没有其他的成员来往前覆盖,那么最后一个数组是否就删除不了了?
    答:在删除最后一个成员时,就算没有进入循环,当执行完循环外的 pc->sz–;后,数组再也访问不到最后一个成员的位置处,也就相当于删除了。

8、通讯录信息搜寻功能

void SearchContact(const Contact* pc)
{
	assert(pc);

	char name[NAME_MAX] = { 0 };
	printf("请输入要查找人的名字:>"); 
	scanf("%s", name);
	int pos = FindByName(pc, name); //查找指定的名字的函数
	if (pos == -1)
	{
		printf("要查找的人不存在\n");
		return;
	}

	//将找到人的信息打印出来
	printf("%-20s %-5s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
	printf("%-20s %-5d %-5s %-12s %-30s\n", pc->data[pos].name, pc->data[pos].age, pc->data[pos].sex, pc->data[pos].tele, pc->data[pos].addr);
	
}
  1. 搜寻通讯录中某个人的信息,即找到后将其打印出来,所以再次调用查找函数 FindByName(pc, name);。
  2. 然后根据返回的数组下标将这个下标处的人信息进行打印。

9、通讯录信息修改功能

void ModifyContact(Contact* pc)
{
	assert(pc);

	//1.找到要修改的人
	char name[NAME_MAX] = { 0 };
	printf("请输入要修改人的名字:>");
	scanf("%s", name);
	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要修改的人不存在\n");
		return;
	}

	//2.找到后进行修改
	printf("请输入要修改的名字:>");
	scanf("%s", pc->data[pos].name); 
	printf("请输入要修改的年龄:>");
	scanf("%d", &(pc->data[pos].age)); 
	printf("请输入要修改的性别:>");
	scanf("%s", pc->data[pos].sex);  
	printf("请输入要修改的电话:>");
	scanf("%s", pc->data[pos].tele);  
	printf("请输入要修改的地址:>");
	scanf("%s", pc->data[pos].addr);  

	printf("修改完成\n");
}
  1. 同理修改某人的信息,要先找到这个人,再次调用FindByName(pc, name);。
  2. 根据返回的数组下标,对这个下标处的人的信息重新进行输入自己要修改的信息。

10、通讯录信息排序功能

int cmp_PeoInfo_by_name(const void* e1, const void* e2)
{
	return strcmp(((Contact*)e1)->data->name, ((Contact*)e2)->data->name);
}

int cmp_PeoInfo_by_age(const void* e1, const void* e2)
{
		return (((Contact*)e1)->data->age - ((Contact*)e2)->data->age);	
}

void SortContact(Contact* pc)
{
	assert(pc);

	int n = 0;
	printf("请选择排序方式(1、姓名 0、年龄):>");
	scanf("%d", &n);
	if (1 == n)
	{
		qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_PeoInfo_by_name);
		printf("排序成功\n");
	}
	else
	{
		qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_PeoInfo_by_age);
		printf("排序成功\n");
	}
	
}
  1. 这里我们选用了两种排序方式,一个是根据名字排序,一个是根据年龄排序。
  2. 排序函数用的是 qsort函数,使用qsort函数最重要的是会懂得构建函数的第四个参数所指向的比较函数,具体的排序由qsort自己的逻辑实现。
  3. 构建的比较函数的返回值如下:
    e1 > e2---->返回大于0的数
    e1 == e2---->返回0
    e1 < e2---->返回小于0的数
  4. 为了满足比较返回值的要求
    对于用姓名排序的方式,比较函数内部用strcmp函数(字符串比较函数)的返回值来作为比较函数的比较值;
    对于用年龄排序的方式,比较函数内部用两个参数相减的结果来作为返回值。
  5. 为什么两个比较函数内部分别是 ((Contact*)e1)->data->name与((Contact*)e1)->data->age的写法。以 ((Contact*)e1)->data->age 分析为什么这么写?
  1. 在这里我们先将两个参数 e1 与 e2 强制类型转换为结构体指针类型((Contact*)e1),((Contact*)e2);
    结构体指针通过->操作符找到他所指向的结构体中的成员 data,这里的data是数组名,表示首元素的地址,data所有的成员都是结构体 PeoInfo类型,首个元素的地址既是首个结构体 PeoInfo的地址。
  2. 所以在访问结构体 PeoInfo的成员 age 时,也是用 ->操作符。
  3. 与前面的pc->data[pos].age 或者pc->data[pc->sz].age 进行区别。
  1. qsort函数的详细内容请看这篇文章《c语言指针重难点》

11、通讯录完整代码

contact.h 头文件:包含了库函数的头文件、自定义函数的声明以及类型的声明。

#pragma once

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

//类型的声明

enum Option
{
	EXIT, //0
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SORT,
	PRINT
};

#define MAX 1000
#define NAME_MAX 20
#define SEX_MAX 5
#define TELE_MAX 12
#define ADDR_MAX 30


//利用结构体创建一个人的信息
typedef struct PeoInfo
{
	char name[NAME_MAX];
	char sex[SEX_MAX];
	int age;
	char tele[TELE_MAX];
	char addr[ADDR_MAX];
}PeoInfo;   //结构体类型重命名


//利用结构体创建通讯录
typedef struct Contact
{
	PeoInfo data[MAX]; //结构体数组,可以存放1000个人的信息
	int sz; //记录通讯中已经保存的信息的人的个数
}Contact;  //结构体类型重命名

//函数的声明

//初始化通讯录函数的声明
void InitContact(Contact* pc);  //实参传的是结构体变量地址,形参用结构体指针接收

//增加信息函数的声明
void AddContact(Contact* pc); 

//打印通讯录中存储的信息
void PrintContact(const Contact* pc);

//删除指定联系人
void DelContact(Contact* pc);

//搜寻指定联系人并打印
void SearchContact(const Contact* pc);

//修改指定联系人
void ModifyContact(Contact* pc);

//按年龄或者姓名进行排序
void SortContact(Contact* pc);

contact.c文件,负责函数实现,实现通讯录的主要功能。

#define _CRT_SECURE_NO_WARNINGS 1
#include "contact.h"


void InitContact(Contact* pc)
{
	assert(pc); //断言,防止为空指针

	pc->sz = 0;
	memset(pc->data, 0, sizeof(pc->data));   //利用meneset将数组中的每个字节都初始化为0,
	                                         //数组中的每个元素也就初始化为0了,
	                                         //也可以利用循环将数组中的每个元素初始化为0。
}



void AddContact(Contact* pc)
{
	assert(pc);

	if (pc->sz == MAX)
	{
		printf("通讯录已满,无法添加\n");
		return; //已满,直接返回
	}
	//录入信息
	printf("请输入名字:>");
	scanf("%s", pc->data[pc->sz].name);  //将信息放在数组下标为sz的位置上去。
	printf("请输入年龄:>");
	scanf("%d", &(pc->data[pc->sz].age)); //将信息放在数组下标为sz的位置上去。
	printf("请输入性别:>");
	scanf("%s", pc->data[pc->sz].sex);  //将信息放在数组下标为sz的位置上去。
	printf("请输入电话:>");
	scanf("%s", pc->data[pc->sz].tele);  //将信息放在数组下标为sz的位置上去。
	printf("请输入地址:>");
	scanf("%s", pc->data[pc->sz].addr);  //将信息放在数组下标为sz的位置上去。

	pc->sz++;  //一个人的信息录入完后,人的信息数加1
	printf("添加成功\n");
}



void PrintContact(const Contact* pc)
{
	assert(pc);

	int i = 0;
	printf("%-20s %-5s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-20s %-5d %-5s %-12s %-30s\n", pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);
	}
}



//查找函数
//找到了返回下标
//找不到返回-1
int FindByName(Contact* pc, char name[])
{
	assert(pc);

	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		if (0 == strcmp(pc->data[i].name, name))
		{
			return i;
		}
	}
	return -1; //遍历完了还没找到,说明找不到了
}



void DelContact(Contact* pc)
{
	assert(pc);

	if (pc->sz == 0)
	{
		printf("通讯录已空,无法删除");
		return;
	}

	//删除
	//1.找到要删除的人
	char name[NAME_MAX] = { 0 };
	printf("请输入要删除人的名字:>");
	scanf("%s", name);
	int pos = FindByName(pc, name); //查找指定的名字的函数
	if (pos == -1)
	{
		printf("要删除的人不存在\n");
		return;
	}

	//2.找到以后删除
	int j = 0;
	for (j = pos; j < pc->sz - 1; j++)
	{
		pc->data[j] = pc->data[j + 1]; //将后面的元素往要删除的位置依次覆盖
	}
	pc->sz--;
	printf("删除成功\n");
}



//找到并打印
void SearchContact(const Contact* pc)
{
	assert(pc);

	char name[NAME_MAX] = { 0 };
	printf("请输入要查找人的名字:>");
	scanf("%s", name);
	int pos = FindByName(pc, name); //查找指定的名字的函数
	if (pos == -1)
	{
		printf("要查找的人不存在\n");
		return;
	}

	//将找到人的信息打印出来
	printf("%-20s %-5s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
	printf("%-20s %-5d %-5s %-12s %-30s\n", pc->data[pos].name, pc->data[pos].age, pc->data[pos].sex, pc->data[pos].tele, pc->data[pos].addr);
	
}



void ModifyContact(Contact* pc)
{
	assert(pc);

	//1.找到要修改的人
	char name[NAME_MAX] = { 0 };
	printf("请输入要修改人的名字:>");
	scanf("%s", name);
	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要修改的人不存在\n");
		return;
	}

	//2.找到后进行修改
	printf("请输入要修改的名字:>");
	scanf("%s", pc->data[pos].name); 
	printf("请输入要修改的年龄:>");
	scanf("%d", &(pc->data[pos].age)); 
	printf("请输入要修改的性别:>");
	scanf("%s", pc->data[pos].sex);  
	printf("请输入要修改的电话:>");
	scanf("%s", pc->data[pos].tele);  
	printf("请输入要修改的地址:>");
	scanf("%s", pc->data[pos].addr);  

	printf("修改完成\n");
}



int cmp_PeoInfo_by_name(const void* e1, const void* e2)
{
	return strcmp(((Contact*)e1)->data->name, ((Contact*)e2)->data->name);
}

int cmp_PeoInfo_by_age(const void* e1, const void* e2)
{
		return (((Contact*)e1)->data->age - ((Contact*)e2)->data->age);	
}

void SortContact(Contact* pc)
{
	assert(pc);

	int n = 0;
	printf("请选择排序方式(1、姓名 0、年龄):>");
	scanf("%d", &n);
	if (1 == n)
	{
		qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_PeoInfo_by_name);
		printf("排序成功\n");
	}
	else
	{
		qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_PeoInfo_by_age);
		printf("排序成功\n");
	}
	
}

test.c文件,将主函数放在此文件中,负责函数的逻辑

#define _CRT_SECURE_NO_WARNINGS 1
#include "contact.h"



void menu()
{
	printf("**********************************\n");
	printf("*****     1.add     2.del    *****\n");
	printf("*****     3.search  4.modify *****\n");
	printf("*****     5.sort    6.print  *****\n");
	printf("*****     0.exit             *****\n");
	printf("**********************************\n");
}
void test()
{
	int input = 0;
	//创建通讯录
	Contact con; //通讯录

	//初始化通讯录
	InitContact(&con);
	do
	{
		menu();
		printf("请选择:>");
		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 SORT:
			SortContact(&con);
			break;
		case PRINT:
			PrintContact(&con); 
			break;
		case EXIT:
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误\n");
		}

	} while (input);
}
int main()
{
	test();
	return 0;
}
  1. 选择do while循环结构,程序一运行先打印游戏菜单,然后进行初始化。
  2. 选择通讯录的功能的操作,1 ~ 6不同的数字对应通讯录不同的功能。
  3. 将输入功能的 input来作为循环条件,input =0 ,循环结束;input = 除了1 ~ 6的其他数字,循环条件非 0 ,循环依然在继续,重新选择是否对通讯录进行其他功能的操作。

12、代码运行视频展示

通讯录代码运行演示

13、结语

内容创作不易😁😁
你的关注与支持就是我的创作动力🧡💛
动动你的发财手给个一键三连吧😉
非常感谢!🌹🌹🌹

  • 15
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 13
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值