草庐IT

C语言通讯录应用程序:从设计到实现

syseptember 2023-04-09 原文

hello,这期给大家带来C语言实现静态通讯录,主要也是建立起创建大项目的思维,与往期这两篇博客有点类似
C语言实现三子棋
C语言实现扫雷

文章目录

🤓通讯录介绍

通讯录存放的是100个联系人的信息,这些信息包括

  • 姓名
  • 年龄
  • 性别
  • 电话
  • 地址

通讯录要实现的功能有

  • 添加联系人
  • 删除联系人
  • 查找联系人
  • 修改联系人的信息
  • 给通讯录排序
  • 打印通讯录
  • 销毁通讯录(清空所有联系人)

😶‍🌫️效果演示

在正式开始实现之前,我们来看一下最终的完结通讯录是怎么样的

🤠主题框架

大文件至少分3个文件

  • text.c->逻辑测试文件
  • comtact.c->函数定义文件
  • contact.h->函数声明,结构体定义等文件

头文件

知道了通讯录需要实现的功能后,我们就可以给出头文件

#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#define MAX 100
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 13
#define MAX_ADDRESS 20
typedef struct PerInfo
{
	char name[MAX_NAME];
	int age;
	char sex[MAX_SEX];
	char tele[MAX_TELE];
	char address[MAX_ADDRESS];
}PeoInfo;

typedef struct Contact
{
	PeoInfo data[MAX];
	int sz;
}Contact;

enum Opreation
{
	EXIT,
	ADD,
	DELETE,
	SEARCH,
	MODIFY,
	SHOW,
	SORT,
	DESTROY
};

void InitContact(Contact* con);
void AddContact(Contact* con);
//这里传地址节省开销
void ShowContact(const Contact* con);

void DeleteContact(Contact* con);

void SearchContact(const Contact* con);

void ModifyContact( Contact* con);

void SortContact(Contact* con);

void DestroyContact(Contact* con);

给出几点说明

  1. 通讯录是静态的,最大可以存放100个联系人的信息,所以我们需要定义变量sz表示当前有效元素的个数,所以通讯录类型应该是结构体,结构体成员是数组和有效元素个数
  2. 数组的每个元素都是联系人的信息,联系人的信息有姓名、年龄、性别、地址、电话等,所以数组元素也是一个结构体
  3. 将通讯录的功能设置为枚举变量~提高程序的可读性
  4. 数组元素定义为符号常量,方便后续统一同意更改
  5. 函数的参数都是结构体指针
    1. 这样参数传递时可以只开辟一个指针的大小
    2. 可以通过结构体指针改变结构体的内容

测试文件

#define _CRT_SECURE_NO_WARNINGS 1
#include "contact.h"

void menu()
{
	printf("***********Contact***********\n");
	printf("****1. add     2. delete ****\n");
	printf("****3. search  4. modify ****\n");
	printf("****5. show    6. sort   ****\n");
	printf("****7. destroy 0. exit   ****\n");
	printf("***********Contact***********\n");
}
int main()
{
	Contact con;
	InitContact(&con);
	int input = 0;
	do
	{
		menu();
		printf("请输入需要进行的操作:>");
		scanf("%d", &input);
		switch (input)
		{
		case ADD:
			AddContact(&con);

			break;
		case DELETE:
			DeleteContact(&con);
			break;
		case SEARCH:
			SearchContact(&con);
			break;
		case MODIFY:
			ModifyContact(&con);
			break;
		case SHOW:
			ShowContact(&con);
			break;
		case SORT:
			SortContact(&con);
			break;
		case DESTROY:
			DestroyContact(&con);
			break;
		case EXIT:
			break;
		default :
			break;
		}
	} while (input);
}

函数实现

初始化

由于我们申请的是一个Contact类型的变量,并没有初始化,所以我们需要将Contac变量的sz(有效联系人个数)成员初始化为0,以及整个通讯录初始化为0

//初始化
void InitContact(Contact* con)
{
	con->sz = 0;
	memset(con->data, 0, sizeof(con->data));
}

显示

我们是通过成员sz来记录该通讯录有效联系人的个数,所以打印通讯录时也只需要通过sz来判断打印结束条件(即只用打印sz个联系人的信息)

//显示
void ShowContact(const Contact* con)
{
	printf("%-10s%-10s%-10s%-20s%-20s\n", 
		"姓名", "年龄", "性别", "电话", "地址");
	for (int i = 0; i < con->sz; i++)
	{
		printf("%-10s%-10d%-10s%-20s%-20s\n",
	con->data[i].name, con->data[i].age, con->data[i].sex, con->data[i].tele, con->data[i].address);
	}
}

这里处于安全考虑,形参用const修饰

添加

添加联系人需要完成2步

  1. 输入需要添加联系人的信息(姓名、年龄、性别、电话、地址)
  2. 将通讯录的有效成员个数+1
//添加
void AddContact(Contact* con)
{
	if (con->sz == MAX)
	{
		printf("通讯录容量已经满了,无法添加\n");
		return;
	}
	printf("请输入你需要添加人的姓名:");
	scanf("%s", con->data[con->sz].name);
printf("请输出你需要添加人的年龄:");
	scanf("%d", &(con->data[con->sz].age));
	printf("请输入你需要添加人的性别:");
	scanf("%s", con->data[con->sz].sex);
	printf("请输入你需要添加人的电话:");
	scanf("%s", con->data[con->sz].tele);
	printf("请输入你需要添加人的地址:");
	scanf("%s", con->data[con->sz].address);
	con->sz++;
	printf("添加成功!\n通讯录还可以容纳%d人\n", MAX - con->sz);
}

添加的联系人存储在当前有效联系人的最后面

删除

删除需要两步

  1. 删除首先需要找到该联系人(由于后面查找功能也需要查找联系人,所以将此时的查找定义为一个函数)
  2. 用后面的联系人的信息覆盖以该联系人开始后面信息(即删除)
//查找联系人的下标
static int findContact(const Contact* con, const char* name)
{
	for (int i = 0; i < con->sz; i++)
	{
		if (strcmp(con->data[i].name, name) == 0)
		{
			return i;
		}
	}
	return -1;
}

//删除
void DeleteContact(Contact* con)
{
	if (con->sz == 0)
	{
		printf("当前通讯录没有联系人,无法删除\n");
		return;
	}
	char name[20];
	printf("请输出你需要删除联系人的姓名:");
	scanf("%s", name);
	//查找
	int pos = findContact(con, name);
	if (pos == -1)
	{
		printf("没有找到该联系人\n");
		return;
	}
	//删除
	memmove(&(con->data[pos]), &(con->data[pos]) + 1, 
		sizeof(con->data[0]) * (MAX - pos - 1));
	con->sz--;
	printf("删除成功!\n通讯录可容纳的人数%d\n", MAX - con->sz);
}

给出几点注意:

  • 删除可以根据用户输入的姓名找到指定的联系人进行删除(不考虑删除),也可以根据用户输入的电话进行删除
    这里实现根据用户输入的姓名进行删除
  • 删除可以将data数组后面的元素一个一个挪动到前面,我这里直接用memmove,大同小异,但是不能使用memcpy,因为内存有重复的
  • 删除后通讯录有效人数的数量-1

查找

  1. 找到该联系人的下标
  2. 打印该联系人的信息
//查找
void SearchContact(const Contact* con)
{
	printf("请输出你需要查找联系人的姓名:");
	char name[20];
	scanf("%s", name);
	int pos = findContact(con, name);
	if (pos == -1)
	{
		printf("找不到该联系人\n");
		return;
	}
	printf("查找结果如下:\n");
	printf("%-10s%-10s%-10s%-20s%-20s\n",
		"姓名", "年龄", "性别", "电话", "地址");
	printf("%-10s%-10d%-10s%-20s%-20s\n",
		con->data[pos].name, con->data[pos].age, con->data[pos].sex,
		con->data[pos].tele, con->data[pos].address);
}

修改

当联系人不小心信息输入错了之后我们需要对它进行修改
修改分两步

  1. 找到需要修改的联系人的下标
  2. 录入修改后的信息
//修改
void ModifyContact(Contact* con)
{
	printf("请输出需要修改联系人的名字:");
	char name[20];
	scanf("%s", name);
	int pos = findContact(con, name);
	if (pos == -1)
	{
		printf("找不到该联系人\n");
		return;
	}
	printf("请输入修改后的姓名:");
	scanf("%s", con->data[pos].name);
	printf("请输入修改后的年龄:");
	scanf("%d", &(con->data[pos].age));
	printf("请输入修改后的性别:");
	scanf("%s", con->data[pos].sex);
	printf("请输入修改后的电话:");
	scanf("%s", con->data[pos].tele);
	printf("请输入修改后的地址:");
	scanf("%s", con->data[pos].address);
}

排序

排序主要为2中方式

  • 按照名字从小到大排序
  • 按照年龄从小到大排序
//自定义名字比较函数
int CmpByName(const void* e1, const void* e2)
{
	return (strcmp(((PeoInfo*)e1)->name , ((PeoInfo*)e2)->name));
}
//自定义年龄比较函数
int CmpByAge(const void* e1, const void* e2)
{
	return ((PeoInfo*)e1)->age - ((PeoInfo*)e2)->age;
}
void SortContact(Contact* con)
{
	int input = 0;
	do
	{
		printf("请选择排序方式\n1. 姓名  2.年龄\n");
		scanf("%d", &input);
		if (input == 1)
		{
			qsort(con->data, con->sz, sizeof(con->data[0]), CmpByName);
			printf("排序成功\n");
			ShowContact(con);
		}
		else if (input == 2)
		{
			qsort(con->data, con->sz, sizeof(con->data[0]), CmpByAge);
			printf("排序成功\n");
			ShowContact(con);
		}
		else printf("选择错误\n");
	} while (input != 1 && input != 2);
}

由于待排序数组是结构体数组,所以直接调用库函数qsort
注意比较函数是需要我们自己定义的

清空通讯录

比较简单,将结构体数组所有元素赋值为0,通讯录数组有效联系人置为0即可

void DestroyContact(Contact* con)
{
	printf("你确定要清空通讯录吗?(YES/NO)\n");
	char selection[MAX] = { 0 };
	scanf("%s", selection);
	fflush(stdin);
	if (strcmp(selection, "YES") != 0) return;
	memset(con->data, 0, sizeof(con->data));
	con->sz = 0;
	printf("清空成功!\n");
}

😵不足之处

此通讯录总体来说实现比较简单,类似于静态顺序表
次通讯录如下几方面不足,可以改进

  • 通讯录的大小是固定的
  • 通讯的内存存储是连续的(数组在空间中是连续存放的)
  • 通讯录的存储是存储在内存中的,没有保存在文件中,程序结束后通讯录销毁

😋😋😋本章内容到这结束了,不足之处我们后续会逐渐改进在后续文章中随着更加系统的学习我们会逐渐优化😋😋😋

完整代码在静态通讯录

有关C语言通讯录应用程序:从设计到实现的更多相关文章

  1. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  2. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  3. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  4. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  5. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  6. ruby - 在 Ruby 中编写命令行实用程序 - 2

    我想用ruby​​编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序

  7. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  8. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  9. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  10. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

随机推荐