目录
通讯录是我们非常熟悉的信息存储软件,能够实现增加、删除、查找、修改联系人的操作。那么它背后的运行逻辑是什么呢?接下来我们来使用c语言一探究竟。
- 联系人具有姓名,年龄,性别,电话,住址等信息,这些信息只与某一个人对应,所以应该对它们进行绑定,使不同的联系人具有一套完整的这些数据。而结构体可以非常好地实现这个功能。因此,创建一个PeoInfo(联系人信息)结构体
- 而联系人的数量是非常多的,因此可以考虑用数组来存储这些结构体。由于这篇文章只讲静态版本通讯录,因此下面的实战将采用静态数组来实现。
- 在实现增删查改这些功能的过程中,我们需要一个变量来记录数组中存储的元素个数,否则在访问时就必须访问所有的元素,对于访问大量数据来说效率是非常低的。一个数组和它的大小对应,因此可以再创建一个Contact(通讯录)结构体将这两个变量绑定。
实现一个通讯录,需要创造一个菜单,使得用户能够实现对应的功能。
用户需要的操作主要有:
- 1.新增联系人,
- 2.删除联系人,
- 3.查找联系人,
- 4.修改联系人,
- 5.展示通讯录,
- 6.对联系人进行排序,
- 7.退出通讯录。
每个数字对应一个函数,即可实现用户需求。
这里,由于操作较多,我们可以使用枚举来将不同的操作的英文名称与数字联系起来,这样代码的可读性就会高很多。
通过选择数字来进入对应的功能,很容易让我们想到switch语句。每个case语句下调用相应模块的函数即可。
如何实现循环选择和退出,可以使用do while循环,先让用户输入,再判断是否退出。
可以使用memset函数将联系人所有数据初始化为0。
采用赋值方式将数组大小初始化为0。
因为Contact这个类型的结构体包含了几乎所有需要进行操作的变量(内部的数组存储信息,一个整型变量判断数组大小),所以不论那个模块的操作,只需要调用Contact这个类型的结构体就行。
在传参时,我们使用传址调用,原因如下:
- 有些函数只需要对形参进行操作即可,但有些函数内部操作是需要记忆的。
- 结构体中数据量大,如果采用传值调用,需要再创建一个相同的临时变量,大大浪费空间。
有些模块,如删除、查找、修改,都需要对指定的数据进行操作,需要查找到指定数据,实现方法类似,可以用一个函数实现。
如果找到,返回数据对应的下标。如果找不到,可以返回-1。
当用户选择新增联系人选项时,就进入相应的函数。用一个指针接收结构体的地址,实现对结构体的操作。
由于我们实现的是静态通讯录,无法扩容,因此当通讯录数据已满时,需要提醒用户并退出函数。
新增联系人需要用户主动输入相应数据,再用结构体中的相应变量接收。
添加成功时,不要忘记增加Contact结构体中记录数组大小的变量的值。
当用户输入要删除的联系人时,首先使用查找函数寻找相应的数据,用一个变量接收查找函数的返回值。
如果找不到,提示用户并退出函数。
如果能找到,利用返回的下标确定数据位置,并删除。
删除可以通过将后面的数据前移并覆盖来实现。可以自己使用循环,也可以直接使用memmove函数。
删除成功时,不要忘记减少Contact结构体中记录数组大小的变量的值。
当用户输入要查找的联系人时,首先使用查找函数寻找相应的数据,用一个变量接收查找函数的返回值。
如果找不到,提示用户并退出函数。
如果能找到,利用返回的下标确定数据位置,并打印。
打印时为了使结果更美观,可以使用类似”%-5s“这种对齐方式实现整齐的界面。
打印前可以先打印一组标题,表示下面的数据类型。
查找联系人时不会改变原数据,为了防止数据被修改,使用const对参数加以修饰。
当用户输入要查找的联系人时,首先使用查找函数寻找相应的数据,用一个变量接收查找函数的返回值。
如果找不到,提示用户并退出函数。
如果能找到,用户需要主动输入相应数据,用结构体中的相应变量接收。
当用户选择展示通讯录时,通讯录中的数据将被打印在屏幕上。
可以使用for循环,通过数组大小确定循环次数,依次将各组数据打印出来。
打印时为了使结果更美观,可以使用类似”%-5s“这种对齐方式实现整齐的界面。
打印通讯录时不会改变原数据,为了防止数据被修改,使用const对参数加以修饰。
打印前可以先打印一组标题,表示下面的数据类型。
当用户选择排序时,排序函数自动对所有的数据进行排序。
对结构体排序,可以使用qsort函数。
可以利用作为qsort参数的比较函数实现对不同内容的排序(按名字排序,按年龄排序等)。
这篇文章只按名字排序作为范例。
以上就是我们实现通讯录的全部思路。现在让我们具体实现一下吧!
首先创建三个文件,分别是contact.c,test.c,contact.h
- contact.c:实现不同模块的函数定义。
- test.c:通过调用函数测试不同的模块。
- contact.h:实现函数的声明和包含头文件
注意其他两个文件要包含contact.h文件才能使用其中的定义。
对结构体、函数、以及相关的常量在contact.c文件中进行定义。
contact.c
#pragma once
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MAX 1000
#define MAX_NAME 15
#define MAX_SEX 5
#define MAX_TELE 12
#define MAX_ADDR 5
typedef struct PeoInfo
{
char name[MAX_NAME];
int age;
char sex[MAX_SEX];
char tele[MAX_TELE];
char addr[MAX_ADDR];
}PeoInfo;
typedef struct Contact
{
PeoInfo Data[MAX];
int size;
}Contact;
void InitContact(Contact* pc);
void ContactAdd(Contact* pc);
void ShowContact(const Contact* pc);
void ContactDel(Contact* pc);
void ContactSearch(const Contact* pc);
void ContactModify(Contact* pc);
void ContactSort(Contact* pc);
为了使用和修改方便,对数组个数进行宏定义,对结构体进行重命名。
在contact.c中对函数进行定义。
contact.c
#include "contact.h"
//初始化通讯录
void InitContact(Contact* pc)
{
memset(pc, 0, sizeof(pc->Data));
pc->size = 0;
}
//添加联系人
void ContactAdd(Contact* pc)
{
if (pc->size == MAX)
{
printf("通讯录已满,无法添加联系人\n");
return;
}
printf("请输入名字>:");
scanf("%s", pc->Data[pc->size].name);
printf("请输入年龄>:");
scanf("%d", &pc->Data[pc->size].age);
printf("请输入性别>:");
scanf("%s", pc->Data[pc->size].sex);
printf("请输入电话>:");
scanf("%s", pc->Data[pc->size].tele);
printf("请输入地址>:");
scanf("%s", pc->Data[pc->size].addr);
pc->size++;
printf("添加成功\n");
}
//按名字查找,找到了返回下标,找不到返回-1
//这个函数是用来辅助search和show的,所以不应该被其他文件使用
static int FindByName(const Contact* pc, char name[MAX_NAME])
{
int i = 0;
for (i = 0; i < pc->size; i++)
{ //如果能找到
if (0 == strcmp(pc->Data[i].name, name))
{
return i;
}
}
//找不到
return -1;
}
//展示联系人
//不应该改变原数据
void ShowContact(const Contact* pc)
{
int i = 0;
printf("%-15s %-5s %-5s %-11s %-5s", "姓名", "年龄", "性别", "电话", "地址\n");
for (i = 0; i < pc->size; i++)
{
printf("%-15s %-5d %-5s %-11s %-5s\n", pc->Data[i].name,
pc->Data[i].age,
pc->Data[i].sex,
pc->Data[i].tele,
pc->Data[i].addr);
}
}
//删除指定联系人
void ContactDel(Contact* pc)
{
//1.找到要删除的数据下标
char name[MAX_NAME];
printf("请输入要删除的名字:>");
scanf("%s", name);
//如果找不到,提示后直接返回
int pos = FindByName(pc,name);//按名字查找,找到了返回下标,找不到返回-1
if (pos == -1)
{
printf("找不到指定联系人\n");
return;
}
//2.找到了就删除
memmove(pc->Data + pos, pc->Data + pos + 1, (pc->size - 1 - pos)*sizeof(pc->Data[0]));
pc->size--;
printf("删除成功\n");
}
//查找联系人
//不应该改变原数据
void ContactSearch(const Contact * pc)
{
char name[MAX_NAME];
printf("请输入要查找的人的名字>:");
scanf("%s", name);
int pos = FindByName(pc, name);//按名字查找,找到了返回下标,找不到返回-1
if (pos == -1)
{
printf("找不到要查找的人\n");
return;
}
printf("%-15s %-3s %-5s %-11s %-5s", "姓名", "年龄", "性别", "电话", "地址\n");
printf("%-15s %-3d %-5s %-11s %-5s\n", pc->Data[pos].name,
pc->Data[pos].age,
pc->Data[pos].sex,
pc->Data[pos].tele,
pc->Data[pos].addr);
}
//修改联系人
void ContactModify(Contact* pc)
{
//1.查找
char name[MAX_NAME];
printf("请输入要修改的人的名字>:");
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("找不到要修改的联系人\n");
return;
}
//修改
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_by_name(void* e1, void* e2)
{
return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}
void ContactSort(Contact* pc)
{
qsort(pc->Data, pc->size, sizeof(PeoInfo), cmp_by_name);
printf("排序成功\n");
}
不同的模块都在这里啦,注意查找函数是FindByName函数为了辅助这些函数实现的,所以用static取消外部链接属性,防止与外部函数冲突或被外部文件使用。
将用户选择和调用函数写进test.c文件中。
test.c
#include "contact.h"
void menu()
{
printf("***************************\n");
printf("***** 1.add *****\n");
printf("***** 2.del *****\n");
printf("***** 3.search *****\n");
printf("***** 4.modify *****\n");
printf("***** 5.show *****\n");
printf("***** 6.sort *****\n");
printf("***** 0.exit *****\n");
printf("***************************\n");
}
//受用枚举类型,提高可读性,比define更方便,
//define把字母替换成相应的数字,
//但是enum直接字母和数字完全相同
enum Option
{
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
SHOW,
SORT
};
int main()
{
int input = 0;
Contact con;//定义通讯录
//初始化通讯录
InitContact(&con);
do
{
menu();
printf("请选择>:");
scanf("%d", &input);
switch (input)
{
case ADD:
ContactAdd(&con);
break;
case DEL:
ContactDel(&con);
break;
case SEARCH:
ContactSearch(&con);
break;
case MODIFY:
ContactModify(&con);
break;
case SHOW:
ShowContact(&con);
break;
case SORT:
ContactSort(&con);
break;
case EXIT:
printf("退出通讯录\n");
break;
default:
printf("选择非法,请重新选择\n");
break;
}
} while (input);
return 0;
}
实现结果比较多,所以就不向大家展示啦。你可以在自己的编译器上运行,这段代码在运行时已经非常详细的引导用户使用啦。
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚
几个月前,我读了一篇关于rubygem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:
导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵
?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------
嗨~大家好,这里是可莉!今天给大家带来的是7个C语言的经典基础代码~那一起往下看下去把【程序一】打印100到200之间的素数#includeintmain(){ inti; for(i=100;i 【程序二】输出乘法口诀表#includeintmain(){inti;for(i=1;i 【程序三】判断1000年---2000年之间的闰年#includeintmain(){intyear;for(year=1000;year 【程序四】给定两个整形变量的值,将两个值的内容进行交换。这里提供两种方法来进行交换,第一种为创建临时变量来进行交换,第二种是不创建临时变量而直接进行交换。1.创建临时变量来
我认为我的问题最好用一个例子来描述。假设我有一个名为“Thing”的简单模型,它有一些简单数据类型的属性。像...Thing-foo:string-goo:string-bar:int这并不难。数据库表将包含具有这三个属性的三列,我可以使用@thing.foo或@thing.bar之类的东西访问它们。但我要解决的问题是当“foo”或“goo”不再包含在简单数据类型中时会发生什么?假设foo和goo代表相同类型的对象。也就是说,它们都是“Whazit”的实例,只是数据不同。所以现在事情可能看起来像这样......Thing-bar:int但是现在有一个新的模型叫做“Whazit”,看起来
我有一个要在我的Rails3项目中使用的数组扩展方法。它应该住在哪里?我有一个应用程序/类,我最初把它放在(array_extensions.rb)中,在我的config/application.rb中我加载路径:config.autoload_paths+=%W(#{Rails.root}/应用程序/类)。但是,当我转到railsconsole时,未加载扩展。是否有一个预定义的位置可以放置我的Rails3扩展方法?或者,一种预先定义的方式来添加它们?我知道Rails有自己的数组扩展方法。我应该将我的添加到active_support/core_ext/array/conversion
参见下面的示例,我想最好使用第二种方法,但第一种也可以。哪种方法最好,使用另一种的后果是什么?classTestdefstartp"started"endtest=Test.newtest.startendclassTest2defstartp"started"endendtest2=Test2.newtest2.start 最佳答案 我肯定会说第二种变体更有意义。第一个不会导致错误,但对象实例化完全过时且毫无意义。外部变量在类的范围内不可见:var="string"classAvar=A.newendputsvar#=>strin
关闭。这个问题是off-topic.它目前不接受答案。想改进这个问题吗?Updatethequestion所以它是on-topic用于堆栈溢出。关闭11年前。Improvethisquestion我不经常使用ruby-通常它加起来相当于每两个月或更长时间编写一次脚本。我的大部分编程都是使用C++进行的,这与ruby有很大不同。由于我与ruby之间的差距如此之大,我总是忘记语言的基本方面(比如解析文本文件和其他简单的东西)。我想每天练习一些基本的东西,我想知道是否有一些我可以订阅的网站,并且会向我发送当天的Ruby问题或类似的东西。有人知道这样的站点/Internet服务吗?