当前位置: 首页 > news >正文

静态通讯录,适合初学者的手把手一条龙讲解

数据结构的顺序表和链表是一个比较困难的点,初见会让我们觉得有点困难,正巧C语言中有一个类似于顺序表和链表的小程序——通讯录。我们今天就来讲一讲通讯录的实现,也有利于之后顺序表和链表的学习。

目录

0.通讯录的初始化 

1.菜单的创建

2.主函数的创建

3.定义联系人内容(结构体)

4.定义通讯录内容(结构体)

5.避免代码冗余而定义的查找函数

6.删除联系人信息

7.修改通讯录信息

8.显示某一个人的信息

9.整个通讯录的打印

10.将通讯录按名字排序

11.全部代码

我们需要用C语言模拟一个通讯录可以用来存储100个人的信息

每个人的信息包括:

姓名、电话、性别、住址、年龄

功能包括:

  1. 新增联系人
  2. 查找联系人
  3. 删除联系人
  4. 修改联系人
  5. 查看所有联系人
  6. 以名字排序所有联系人 

0.通讯录的初始化 

尽管我们这是一个基础的通讯录,它并不具备保存功能,但是我们应该让它拥有一个初始化功能。

void InitContact(Contact* pc)
{pc->sz = 0;memset(pc->data, 0, sizeof(pc->data));
}

1.菜单的创建

首先要有一个一目了然的菜单。

要实现这么多的功能,我们首先想到了switch语句,用case表示不同的情况。

不过考虑到case 1,case 2这样的写法不容易读懂,代码的可维护性也不高。

我们在这里定义一个枚举类型。

enum Option
{EXIT,ADD,DEL,SEARCH,MODIFY,SHOW,SORT
};

 如图所示,枚举类型在我们没有额外操作的情况下,里面的元素是从零开始

这样一来,我们就可以从case 1这样的写法变成 case ADD,一目了然,一看就能看懂。

我们也需要打印选项,另外定义一个menu函数:

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");
}

2.主函数的创建

当要实现某一个函数功能时,我们直接在case语句后调用此函数便可。

int main()
{int input = 0;do{menu();printf("请选择:>");scanf("%d", &input);switch(input){case ADD:break;case DEL:break;case SEARCH:break;case MODIFY:break;case SHOW:break;case SORT:break;case EXIT:printf("退出");break;default:break;}} while (input);return 0;
}

缺少的东西我们待会再补上。

3.定义联系人内容(结构体)

一个人的信息包括很多方面,要录入的话还是用结构体比较方便。

我们定义姓名,年龄,性别,电话和地址五个变量,并将其重命名为PeoInfo。

typedef struct PeoInfo
{char name[MAX_NAME];int age;char sex[MAX_SEX];char tele[MAX_TELE];char addr[MAX_ADDR];
}PeoInfo;

4.定义通讯录内容(结构体)

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

 我们要在什么位置插入一个元素呢?

我们知道,下标是从0开始的。

当我们插入一个数据时,我们实际上是插入在我们所定义的sz的位置。

举个例子。

当sz为5时,我们已经插入了下标为0,1,2,3,4的五个元素。

这时如果要插入新元素的话,就是在下标为5的地方插入,也就是插入到sz的位置。

但是我们并没有在sz的位置分配空间,所以我们在输入之后,需要将sz加一来分配空间。

void AddContact(Contact* pc)
{if (pc->sz == 100){printf("通讯录已满\n");return;}printf("请输入名字:>");scanf("%s", pc->data[pc->sz].name);printf("请输入年龄:>");scanf("%d", &pc->data[pc->sz].age);printf("请输入性别:>");scanf("%s", pc->data[pc->sz].sex);printf("请输入电话:>");scanf("%s", pc->data[pc->sz].tele);printf("请输入地址:>");scanf("%s", pc->data[pc->sz].addr);pc->sz++;printf("添加成功\n");
}

需要注意的是,当空间已满时,要返回,这时就不要往里面硬加了。

5.避免代码冗余而定义的查找函数

在接下来的修改或者删除函数里面,我们将会需要查找所要删除或者修改的人的名字这样的一个功能,为了避免冗余,我们不是在用到时再写一遍,而是定义一个函数,在用到它的时候直接调用,这就方便了许多。

int FindByName(Contact* pc, char name[])
{int i = 0;for (i = 0; i < pc->sz; i++){if (strcmp(pc->data[i].name, name) == 0){return i;break;}}return -1;
}

当查找到要查找到名字时,返回这个位置的下标,没有找到则返回-1。

6.删除联系人信息

要删除一个人的信息,首先需要输入这个人的名字并查找它的位置,这时上面的代码就派上用场了。

我们先调用函数找到这个人的信息,然后调用它的返回值,也就是目标联系人的下标,并用这个下标来修改。

修改的方式是经典的后一个覆盖前一个,不过由于下标是元素个数再减一,为了避免溢出,我们需要是将sz减一。

void DelContact(Contact* pc)
{char name[MAX_NAME];if (pc->sz == 0){printf("通讯录已满\n");return;}printf("输入要删除的人的名字:>");scanf("%s", name);int pos = FindByName(pc, name);if (pos == -1){printf("要删除的人不存在\n");return;}for (int i = pos; i < pc->sz - 1; i++){pc->data[i] = pc->data[i + 1];}pc->sz--;printf("删除成功\n");}

同样的,在通讯录元素已满时,返回。

7.修改通讯录信息

首先查找,当查找到时,我们只需利用它的下标,来将此位置的信息进行重新录入即可。

void ModifyContact(Contact* pc)
{printf("输入要修改的人的名字:>");char name[12] = { 0 };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");
}

8.显示某一个人的信息

我们调用上面的查找函数 ,找到就用返回值下标打印出来就行。

int SearchContact(Contact* pc)
{char name[12] = { 0 };printf("请输入要查找的人的名字:>");scanf("%s", name);int pos = FindByName(pc, name);if (pos == -1){printf("此人信息不存在\n");} printf("此人信息如下:\n");printf("%-10s %-4s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");printf("%-10s %-4d %-5s %-12s %-30s\n", pc->data[pos].name, pc->data[pos].age, pc->data[pos].sex, pc->data[pos].tele, pc->data[pos].addr);return pos;
}

9.整个通讯录的打印

我们调用sz,来将每一个人的信息打印出来。

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

10.将通讯录按名字排序

qsort函数可以很好的实现这个问题。

1.首元素地址base
我们要排序一组数据,首先我们需要找到这组数据在哪,因此我们直接将首元素的地址传给qsort函数来确定从哪开始排序。

2.元素个数num
我们知道了从哪开始,也要知道在哪结束才能确定一组需要排序的数据,但是我们不方便直接将结尾元素的地址传入函数,因此我们将需要排序的元素的个数传给qsort函数来确定一组数据。

3.元素大小size
我们知道qsort函数能排序任意数据类型的一组数据,因此我们用void*类型的指针来接收元素,但是我们知道void*类型的指针不能进行加减操作,也就无法移动,那么在函数内部我们究竟用什么类型的指针来操作变量呢?我们可以将void*类型的指针强制类型转换成char*类型的指针后来操作元素,因为char*类型的指针移动的单位字节长度是1个字节,我们只需要再知道我们需要操作的数据是几个字节就可以操作指针从一个元素移动到下一个元素,因此我们需要将元素大小传入qsort函数。

4.自定义比较函数compare

我们需要告诉qsort函数我们希望数据按照怎么的方式进行比较,比如对于几个字符串,我们可以比较字符串的大小。代码如下,我们想要传入什么类型的参数,就要将其强制类型转换成什么类型,并且返回一个int值。

int cmp_by_name(const void* e1, const void* e2)
{return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}

然后就是qsort函数的调用了:

void SortContact(Contact* pc)
{qsort(pc->data, pc->sz, sizeof(PeoInfo), cmp_by_name);
}

11.全部代码

下面是全部的代码,定义两个源文件和一个头文件。

//contact.h部分
#pragma once
#define MAX 100
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 12
#define MAX_ADDR 30
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
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 sz;
}Contact;
void InitContact(Contact* pc);
void AddContact(Contact* pc);
void DelContact(Contact* pc);
void SortContact(Contact* pc);
int SearchContact(Contact* pc);
void ModifyContact(Contact* pc); 
void ShowContact(Contact* pc);
int FindByName(Contact* pc, char name[]);
//contact.c
#include"contact.h"
void InitContact(Contact* pc)
{pc->sz = 0;memset(pc->data, 0, sizeof(pc->data));
}
void AddContact(Contact* pc)
{if (pc->sz == 100){printf("通讯录已满\n");return;}printf("请输入名字:>");scanf("%s", pc->data[pc->sz].name);printf("请输入年龄:>");scanf("%d", &pc->data[pc->sz].age);printf("请输入性别:>");scanf("%s", pc->data[pc->sz].sex);printf("请输入电话:>");scanf("%s", pc->data[pc->sz].tele);printf("请输入地址:>");scanf("%s", pc->data[pc->sz].addr);pc->sz++;printf("添加成功\n");
}
void DelContact(Contact* pc)
{char name[MAX_NAME];if (pc->sz == 0){printf("通讯录已满\n");return;}printf("输入要删除的人的名字:>");scanf("%s", name);int pos = FindByName(pc, name);if (pos == -1){printf("要删除的人不存在\n");return;}for (int i = pos; i < pc->sz - 1; i++){pc->data[i] = pc->data[i + 1];}pc->sz--;printf("删除成功\n");}
int FindByName(Contact* pc, char name[])
{int i = 0;for (i = 0; i < pc->sz; i++){if (strcmp(pc->data[i].name, name) == 0){return i;break;}}return -1;
}
int cmp_by_name(const void* e1, const void* e2)
{return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}
int SearchContact(Contact* pc)
{char name[12] = { 0 };printf("请输入要查找的人的名字:>");scanf("%s", name);int pos = FindByName(pc, name);if (pos == -1){printf("此人信息不存在\n");} printf("此人信息如下:\n");printf("%-10s %-4s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");printf("%-10s %-4d %-5s %-12s %-30s\n", pc->data[pos].name, pc->data[pos].age, pc->data[pos].sex, pc->data[pos].tele, pc->data[pos].addr);return pos;
}
void ShowContact(Contact* pc)
{int i = 0;printf("%-10s %-4s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");for (i = 0; i < pc->sz; i++){printf("%-10s %-4d %-5s %-12s %-30s\n",  pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);}
}
void ModifyContact(Contact* pc)
{printf("输入要修改的人的名字:>");char name[12] = { 0 };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");
}
void SortContact(Contact* pc)
{qsort(pc->data, pc->sz, sizeof(PeoInfo), cmp_by_name);
}
//test.c
#include"contact.h"
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");
}
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: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:printf("退出");break;default:printf("输入错误\n");break;}} while (input);return 0;
}

这篇博客旨在总结我自己阶段性的学习,要是能帮助到大家,那可真是三生有幸!😀如果觉得我写的不错的话还请点个赞和关注哦~我会持续输出编程的知识的!🌞🌞🌞  

相关文章:

静态通讯录,适合初学者的手把手一条龙讲解

数据结构的顺序表和链表是一个比较困难的点&#xff0c;初见会让我们觉得有点困难&#xff0c;正巧C语言中有一个类似于顺序表和链表的小程序——通讯录。我们今天就来讲一讲通讯录的实现&#xff0c;也有利于之后顺序表和链表的学习。 目录 0.通讯录的初始化 1.菜单的创建…...

【你不知道的 CSS】你写的 CSS 太过冗余,以至于我对它下手了

:is() 你是否曾经写过下方这样冗余的CSS选择器: .active a, .active button, .active label {color: steelblue; }其实上面这段代码可以这样写&#xff1a; .active :is(a, button, label) {color: steelblue; }看~是不是简洁了很多&#xff01; 是的&#xff0c;你可以使用…...

Lesson 8.1 决策树的核心思想与建模流程

文章目录一、借助逻辑回归构建决策树1. 决策树实例2. 决策树知识补充2.1 决策树简单构建2.2 决策树的分类过程2.3 决策树模型本质2.4 决策树的树生长过程2.5 树模型的基本结构二、决策树的分类与流派1. ID3(Iterative Dichotomiser 3) 、C4.5、C5.0 决策树2. CART 决策树3. CHA…...

【算法】FIFO先来先淘汰算法分析和编码实战

背景 在设计一个系统的时候&#xff0c;由于数据库的读取速度远小于内存的读取速度 为加快读取速度&#xff0c;将一部分数据放到内存中称为缓存&#xff0c;但内存容量是有限的&#xff0c;当要缓存的数据超出容量&#xff0c;就需要删除部分数据 这时候需要设计一种淘汰机制…...

二分查找——我欲修仙(功法篇)

个人主页&#xff1a;【&#x1f60a;个人主页】 系列专栏&#xff1a;【❤️我欲修仙】 学习名言&#xff1a;临渊羡鱼,不如退而结网——《汉书董仲舒传》 系列文章目录 第一章 ❤️ 二分查找 文章目录系列文章目录前言&#x1f697;&#x1f697;&#x1f697;二分查找&…...

Python 多线程

文章目录一、简介1.1 多线程的特性1.2 GIL二、线程1.2 单线程1.3 多线程三、线程池3.1 pool.submit3.2 pool.map四、Lock&#xff08;线程锁&#xff09;4.1 无锁导致的线程资源异常4.2 有锁五、Event&#xff08;事件&#xff09;5.1 简介5.2 示例六、Queue&#xff08;队列&a…...

JVM笔记(九)选择合适的垃圾收集器

Epsilon收集器Epsilon收集器由RedHat公司在JEP 318中提出&#xff0c;在此提案里Epsilon被形容成一个无操作的收集器&#xff08;A No-Op Garbage Collector&#xff09;&#xff0c;而事实上只要Java虚拟机能够工作&#xff0c;垃圾收集器便不可能是真正“无操作”的。原因是“…...

二维图像处理到三维点云处理

一、Opencv和PCL 下面是opencv和pcl的特点、区别和联系的详细对比表格。 特点/区别/联系OpenCVPCL英文全称Open Source Computer Vision LibraryPoint Cloud Library语言C、Python、JavaC功能图像处理(图像处理和分析、特征提取和描述、图像识别和分类、目标检测和跟踪等)、计…...

leetcode 删除有序数组中的重复项

题目 给你一个 升序排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。 由于在某些语言中不能改变数组的长度&#xff0c;所以必须将结果放在数组nums的第一…...

JVM学习.03 类加载机制

1、前言从事Java开发工作的都知道&#xff0c;Java程序提交到JVM运行时&#xff0c;需要编译成Class文件&#xff0c;才能被JVM加载运行。那么这些Class文件进入到虚拟机后会发生什么&#xff1f;以及Class是如何被加载的&#xff1f;这些都是本文要讲解的部分。2、类加载时机所…...

Celery使用:优秀的python异步任务框架

目录Celery 简介介绍安装基本使用Flask使用Celery异步任务定时任务Celery使用Flask上下文进阶使用参考停止Worker后台运行Celery 简介 介绍 Celery 是一个简单、灵活且可靠的&#xff0c;处理大量消息的分布式系统&#xff0c;并且提供维护这样一个系统的必需工具。 它是一个…...

第十四届蓝桥杯三月真题刷题训练——第 19 天

第 1 题&#xff1a;灌溉_BFS板子题 题目描述 小蓝负责花园的灌溉工作。 花园可以看成一个 n 行 m 列的方格图形。中间有一部分位置上安装有出水管。 小蓝可以控制一个按钮同时打开所有的出水管&#xff0c;打开时&#xff0c;有出水管的位置可以被认为已经灌溉好。 每经过一分…...

类和对象 - 下

本文已收录至《C语言》专栏&#xff01; 作者&#xff1a;ARMCSKGT 目录 前言 正文 初始化列表 成员变量的定义与初始化 初始化列表的使用 变量定义顺序 explicit关键字 隐式类型转换 自定义类型隐式转换 explicit 限制转换 关于static static声明类成员 友元 友…...

【云原生】Linux基础IO(文件理解与操作)

✨个人主页&#xff1a; Yohifo &#x1f389;所属专栏&#xff1a; Linux学习之旅 &#x1f38a;每篇一句&#xff1a; 图片来源 &#x1f383;操作环境&#xff1a; CentOS 7.6 阿里云远程服务器 Great minds discuss ideas. Average minds discuss events. Small minds disc…...

CentOS 7 安装 mysql 8.0 客户端

只想安装 mysql-client 8.0 &#xff0c; 结果发现直接 yum install mysql mysql-client 安装的版本是 mysql Ver 15.1 Distrib 5.5.68-MariaDB &#xff0c;这个版本太低&#xff0c;连接其他服务器上的 mysql 8.0 时总是失败&#xff0c;因为 mysql 8.0 加密方式改变了&#…...

Ubuntu下载、配置、安装和编译opencv

1 安装相关依赖安装opencv前&#xff0c;需要先准备好编译器、相关依赖sudo apt-get install gcc g cmake vim sudo apt-get install build-essential libgtk2.0-dev libavcodec-dev libavformat-dev libjpeg-dev libswscale-dev libtiff5-dev sudo apt-get install libgtk2.0-…...

第七讲 贪心

文章目录股票买卖 II货仓选址&#xff08;贪心:排序中位数&#xff09;糖果传递&#xff08;❗贪心&#xff1a;中位数&#xff09;雷达设备&#xff08;贪心排序&#xff09;付账问题&#xff08;平均值排序❓&#xff09;乘积最大&#xff08;排序/双指针&#xff09;后缀表达…...

数字藏品的未来及发展趋势

随着互联网的普及以及数字文化的日益发展&#xff0c;数字藏品作为一种全新的收藏方式正在逐步兴起。数字藏品可以是数字版权、数字艺术品、数字音乐以及数字视频等形式&#xff0c;这些藏品通过数字化技术保存下来&#xff0c;并在互联网上进行传播和交易。数字藏品的发展趋势…...

值得记忆的STL常用算法,分分钟摆脱容器调用的困境,以vector为例,其余容器写法类似

STL常用算法 概述&#xff1a; 算法主要是由头文件<algorithm> <functional> <numeric>组成 <algorithm>是所有STL头文件中最大的一个&#xff0c;范围涉及到比较、交换、查找、遍历操作、复制、修改等等 <nuneric>体积很小&#xff0c;只包括…...

java如何手动导jar包

今天用IDEA&#xff0c;需要导入一个Jar包&#xff0c;因为以前都是用eclipse的&#xff0c;所以对这个idea还不怎么上手&#xff0c;连打个Jar包都是谷歌了一下。 但是发现网上谷歌到的做法一般都是去File –> Project Structure中去设置&#xff0c;有没有如同eclipse一样…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…...

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...

【Linux】shell脚本忽略错误继续执行

在 shell 脚本中&#xff0c;可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行&#xff0c;可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令&#xff0c;并忽略错误 rm somefile…...

基于FPGA的PID算法学习———实现PID比例控制算法

基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容&#xff1a;参考网站&#xff1a; PID算法控制 PID即&#xff1a;Proportional&#xff08;比例&#xff09;、Integral&#xff08;积分&…...

通过Wrangler CLI在worker中创建数据库和表

官方使用文档&#xff1a;Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后&#xff0c;会在本地和远程创建数据库&#xff1a; npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库&#xff1a; 现在&#xff0c;您的Cloudfla…...

JAVA后端开发——多租户

数据隔离是多租户系统中的核心概念&#xff0c;确保一个租户&#xff08;在这个系统中可能是一个公司或一个独立的客户&#xff09;的数据对其他租户是不可见的。在 RuoYi 框架&#xff08;您当前项目所使用的基础框架&#xff09;中&#xff0c;这通常是通过在数据表中增加一个…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版

7种色调职场工作汇报PPT&#xff0c;橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版&#xff1a;职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...

使用Spring AI和MCP协议构建图片搜索服务

目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式&#xff08;本地调用&#xff09; SSE模式&#xff08;远程调用&#xff09; 4. 注册工具提…...

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要&#xff1a; 近期&#xff0c;在使用较新版本的OpenSSH客户端连接老旧SSH服务器时&#xff0c;会遇到 "no matching key exchange method found"​, "n…...

DingDing机器人群消息推送

文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人&#xff0c;点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置&#xff0c;详见说明文档 成功后&#xff0c;记录Webhook 2 API文档说明 点击设置说明 查看自…...