基于smach的状态机设计【1】

2024-08-29 03:28
文章标签 设计 状态机 smach

本文主要是介绍基于smach的状态机设计【1】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

smach是ros中的一种状态机的功能包,这里面我们将来深入认识一下这个状态机的运行和各种动作的设计过程。
安装samch的相关内容:

sudo apt install ros-noetic-executive-smach*

首先我们来看一下官方的例子
去git所有的源码来:

git clone https://github.com/lizhiwei0304/ROS_SMACH_TUTORIALS.git

或者可以一步步自己创建,首先创建功能包和相关的依赖:

catkin_create_pkg smach_usecase rospy roscpp std_msgs std_srvs smach smach_ros turtlesim

创建启动turtlesim的节点turtle_nodes.launch:

<launch><node pkg="turtlesim" name="turtlesim" type="turtlesim" /></launch>

对于一个基本的smach,认识一下结构:

#/usr/bin/env python
import rospy
import smach
from smach import StateMachinedef main():# 初始化节点rospy.init_node("smach_example")# 定义smach的最终结果sm_root = StateMachine(outcomes=[])with sm_root:# 按照顺序来添加状态pass# smach的执行程序,按照顺序执行所有内容outcome = sm_root.execute()rospy.spin()
if __main__ == "__main__":main()

要有完整的状态机,我们需要向这里面添加相关的内容来进行实现,由于smach中一般是通过class来定义状态机的,所以后面的状态都使用类来进行定义。
首先第一个服务是reset,由turtlesim节点直接提供的,需要的可以去看turtlesim的源码,看它的服务是怎么定义的。
turtlesim源码:https://github.com/ros/ros_tutorials.git
以及动作状态(turtle_actionlib)内容:
https://github.com/ros/common_tutorials.git
第二个服务也是turtlesim直接定义的,spawn来生成小乌龟。
这里之前没有怎么接触过服务和动作,所以也会对相对代码进行解析。
首先服务是分服务端和客户端的,服务端提供了这个服务以及这个服务的具体作用,客户端则会去请求(call)这个服务来实现某种功能,向topic一样,该发送发送,该接收的接收
启动turtlesim_node后的所有服务内容:

rosservice list

在这里插入图片描述
可以去ros index官网看一下这些服务的作用:
在这里插入图片描述
在这里插入图片描述

前面的是话题的内容,后面是服务和参数的内容,这也刚好对应了ros的三大通信模式.(动作不在里面是因为动作和上面的通信过程很多是重复的)
clear服务的服务类型是std_srvs.Empty
empty就完全是empty的,不需要传递服务的信息就能请求,当然也就不需要反馈。
第二个服务的服务类型是turtlesim.srv.Spawn
turtlesim/Spawn.srv如下:

float32 x
float32 y
float32 theta
string name # Optional.  A unique name will be created and returned if this is empty
---
string name

注意这只是总的服务的定义,客户端请求它时也就是需要一个请求的服务类型,包含的是前四个内容,最后一个则是反馈的内容,也就是执行的情况,实际生成的turtle的名字。
kill服务则是清除指定的turtle,服务类型是turtlesim/Kill,turtlesim/Kill.srv如下:

string name
---

其余服务类型都是同样就行分析。
那么现在疑问就来了,怎么去定义的这些服务,然后又怎么才能去请求这些服务呢?
不急,我们来看看turtlesim的源码来解答。

turtle_frame.h:

/** @Author: CYUN && cyun@tju.edu.cn* @Date: 2024-08-28 13:08:01* @LastEditors: CYUN && cyun@tju.enu.cn* @LastEditTime: 2024-08-28 19:43:19* @FilePath: /src/ros_tutorials/turtlesim/include/turtlesim/turtle_frame.h* @Description: * * Copyright (c) 2024 by Tianjin University, All Rights Reserved. */
#include <QFrame> // Qt框架头文件,用于创建窗口部件
#include <QImage> // 用于处理图像
#include <QPainter> // 用于绘制图像
#include <QPaintEvent> // 处理绘图事件
#include <QTimer> // 定时器,用于定期更新图像
#include <QVector> // Qt的向量容器类#ifndef Q_MOC_RUN // 宏定义,防止MOC错误,通常与Qt和Boost库相关
#include <ros/ros.h>
#include <std_srvs/Empty.h> // ROS的标准空服务
#include <turtlesim/Spawn.h> // ROS的turtlesim生成服务
#include <turtlesim/Kill.h> // ROS的turtlesim删除服务
#include <map> // C++标准库中的map容器,用于存储键值对#include <turtlesim/turtle.h> // Turtlesim的Turtle类
#endifnamespace turtlesim
{
class TurtleFrame : public QFrame
{Q_OBJECT // 使得该类能够使用Qt的信号和槽机制
public:TurtleFrame(QWidget* parent = 0, Qt::WindowFlags f = 0); // 构造函数~TurtleFrame(); // 析构函数// 生成海龟的方法std::string spawnTurtle(const std::string& name, float x, float y, float angle);std::string spawnTurtle(const std::string& name, float x, float y, float angle, size_t index);protected:void paintEvent(QPaintEvent* event); // 重载的函数,用于在窗口上绘制图像private slots:void onUpdate(); // 定时器触发的更新槽private:void updateTurtles(); // 更新所有海龟的状态void clear(); // 清除内容bool hasTurtle(const std::string& name); // 检查是否存在给定名字的海龟// ROS的服务回调函数bool clearCallback(std_srvs::Empty::Request&, std_srvs::Empty::Response&);bool resetCallback(std_srvs::Empty::Request&, std_srvs::Empty::Response&);bool spawnCallback(turtlesim::Spawn::Request&, turtlesim::Spawn::Response&);bool killCallback(turtlesim::Kill::Request&, turtlesim::Kill::Response&);// 新增的服务回调函数:bool spawnMeCallback(turtlesim::Spawn::Request&, turtlesim::Spawn::Response&);bool killMeCallback(turtlesim::Kill::Request&, turtlesim::Kill::Response&);ros::NodeHandle nh_; // ROS节点句柄ros::NodeHandle private_nh_; // 私有节点句柄QTimer* update_timer_; // 定时器指针QImage path_image_; // 存储绘制海龟轨迹的图像QPainter path_painter_; // 用于在path_image_上绘制路径uint64_t frame_count_; // 记录绘制帧数量ros::WallTime last_turtle_update_; // 记录上次更新海龟状态的时间// ROS服务服务器ros::ServiceServer clear_srv_; // 清除服务ros::ServiceServer reset_srv_; // 重置服务ros::ServiceServer spawn_srv_; // 生成服务ros::ServiceServer kill_srv_; // 删除服务// 新增服务:ros::ServiceServer spawn_me_srv; // 自定义生成服务ros::ServiceServer kill_me_srv; // 自定义删除服务typedef std::map<std::string, TurtlePtr> M_Turtle; // 用于存储所有海龟的mapM_Turtle turtles_; // 存储所有的海龟实例uint32_t id_counter_; // 生成的海龟计数器QVector<QImage> turtle_images_; // 存储海龟图像float meter_; // 每米的像素float width_in_meters_; // 窗口宽度,以米为单位float height_in_meters_; // 窗口高度,以米为单位
};
}

turtle_frame.cpp:

/** @Author: CYUN && cyun@tju.enu.cn* @Date: 2024-08-28 13:08:01* @LastEditors: CYUN && cyun@tju.enu.cn* @LastEditTime: 2024-08-28 19:46:46* @FilePath: /src/ros_tutorials/turtlesim/src/turtle_frame.cpp* @Description: * * Copyright (c) 2024 by Tianjin University, All Rights Reserved. */
// ctrl+win+i键使用快捷注释#include <turtlesim/turtle_frame.h>
#include <QPointF> // QPointF用于表示浮点数的2D点
#include <ros/package.h> // ros包工具
#include <cstdlib> // c++标准库,用于调用srand和rand
#include <ctime> // 时间相关函数#define DEFAULT_BG_R 0x45
#define DEFAULT_BG_G 0x56
#define DEFAULT_BG_B 0xffnamespace turtlesim
{
// 构造函数
TurtleFrame::TurtleFrame(QWidget* parent, Qt::WindowFlags f)
: QFrame(parent, f) // 调用基类QFrame的构造函数
, path_image_(500, 500, QImage::Format_ARGB32) // 初始化轨迹图像大小和格式
, path_painter_(&path_image_) // 将path_painter_指向path_image_
, frame_count_(0) // 初始化帧计数
, id_counter_(0) // 初始化海龟计数器
, private_nh_("~") // 初始化私有节点句柄
{setFixedSize(500, 500); // 设置窗口大小setWindowTitle("TurtleSim"); // 设置标题srand(time(NULL)); // 用当前时间初始化随机数生成器update_timer_ = new QTimer(this); // 创建定时器update_timer_->setInterval(16); // 定时器间隔,16msupdate_timer_->start(); // 启动定时器connect(update_timer_, SIGNAL(timeout()), this, SLOT(onUpdate())); // 将定时器超时信号连接到 onUpdate 槽// 设置背景颜色:if (!private_nh_.hasParam("background_r")){private_nh_.setParam("background_r", DEFAULT_BG_R);}if (!private_nh_.hasParam("background_g")){private_nh_.setParam("background_g", DEFAULT_BG_G);}if (!private_nh_.hasParam("background_b")){private_nh_.setParam("background_b", DEFAULT_BG_B);}QVector<QString> turtles;// 初始化海龟图像文件turtles.append("box-turtle.png");turtles.append("robot-turtle.png");turtles.append("sea-turtle.png");turtles.append("diamondback.png");turtles.append("electric.png");turtles.append("fuerte.png");turtles.append("groovy.png");turtles.append("hydro.svg");turtles.append("indigo.svg");turtles.append("jade.png");turtles.append("kinetic.png");turtles.append("lunar.png");turtles.append("melodic.png");turtles.append("noetic.png");// 获取ros包路径,加载图像文件:QString images_path = (ros::package::getPath("turtlesim") + "/images/").c_str();for (int i = 0; i < turtles.size(); ++i){QImage img;img.load(images_path + turtles[i]);turtle_images_.append(img); // 将图像添加到列表中}meter_ = turtle_images_[0].height(); // 每米的像素clear(); // 初始化图像clear_srv_ = nh_.advertiseService("clear", &TurtleFrame::clearCallback, this);reset_srv_ = nh_.advertiseService("reset", &TurtleFrame::resetCallback, this);spawn_srv_ = nh_.advertiseService("spawn", &TurtleFrame::spawnCallback, this);kill_srv_ = nh_.advertiseService("kill", &TurtleFrame::killCallback, this);// 新增服务:kill_me_srv = nh_.advertiseService("kill_me", &TurtleFrame::killMeCallback, this);spawn_me_srv = nh_.advertiseService("spawn_me", &TurtleFrame::spawnMeCallback, this);ROS_INFO("Starting turtlesim with node name %s", ros::this_node::getName().c_str()); // 输出节点名称width_in_meters_ = (width() - 1) / meter_;height_in_meters_ = (height() - 1) / meter_;// 生成第一只海龟spawnTurtle("", width_in_meters_ / 2.0, height_in_meters_ / 2.0, 0);// 可选,生成所有可用类型的海龟(当前禁用)if (false){for (int index = 0; index < turtles.size(); ++index){QString name = turtles[index];name = name.split(".").first();name.replace(QString("-"), QString(""));spawnTurtle(name.toStdString(), 1.0 + 1.5 * (index % 7), 1.0 + 1.5 * (index / 7), PI / 2.0, index);}}
}
// 析构函数
TurtleFrame::~TurtleFrame()
{delete update_timer_;
}// turtle的服务回调函数
bool TurtleFrame::spawnCallback(turtlesim::Spawn::Request& req, turtlesim::Spawn::Response& res)
{std::string name = spawnTurtle(req.name, req.x, req.y, req.theta);// 生成turtleif (name.empty()){ROS_ERROR("A turtle named [%s] already exists", req.name.c_str());return false;}res.name = name;// 返回海龟的名字return true;
}// 回调函数的操作内容都相同:
bool TurtleFrame::spawnMeCallback(turtlesim::Spawn::Request& req, turtlesim::Spawn::Response& res)
{std::string name = spawnTurtle(req.name, req.x, req.y, req.theta);// 生成turtleif (name.empty()){ROS_ERROR("A turtle named [%s] already exists", req.name.c_str());return false;}res.name = name;// 返回海龟的名字return true;
}
bool TurtleFrame::killMeCallback(turtlesim::Kill::Request& req, turtlesim::Kill::Response&)
{M_Turtle::iterator it = turtles_.find(req.name);if (it == turtles_.end()){ROS_ERROR("Tried to kill turtle [%s], which does not exist", req.name.c_str());return false;}turtles_.erase(it); // 删除海龟update(); //更新显示return true;
}bool TurtleFrame::killCallback(turtlesim::Kill::Request& req, turtlesim::Kill::Response&)
{M_Turtle::iterator it = turtles_.find(req.name);if (it == turtles_.end()){ROS_ERROR("Tried to kill turtle [%s], which does not exist", req.name.c_str());return false;}turtles_.erase(it); // 删除海龟update(); //更新显示return true;
}// 检查是否存在给定名字的海龟
bool TurtleFrame::hasTurtle(const std::string& name)
{return turtles_.find(name) != turtles_.end();
}// 生成海龟
std::string TurtleFrame::spawnTurtle(const std::string& name, float x, float y, float angle)
{return spawnTurtle(name, x, y, angle, rand() % turtle_images_.size());
}
// 生成海龟函数带索引
std::string TurtleFrame::spawnTurtle(const std::string& name, float x, float y, float angle, size_t index)
{std::string real_name = name;if (real_name.empty()){do{std::stringstream ss;ss << "turtle" << ++id_counter_;real_name = ss.str();} while (hasTurtle(real_name));}else{if (hasTurtle(real_name)){return "";}}// 创建海龟并加入到map中TurtlePtr t(new Turtle(ros::NodeHandle(real_name), turtle_images_[index], QPointF(x, height_in_meters_ - y), angle));turtles_[real_name] = t;update();ROS_INFO("Spawning turtle [%s] at x=[%f], y=[%f], theta=[%f]", real_name.c_str(), x, y, angle);return real_name;
}// 重载paintEvent函数用于绘制窗口内容
void TurtleFrame::paintEvent(QPaintEvent*)
{QPainter painter(this);painter.drawImage(QPoint(0, 0), path_image_); // 绘制轨迹图像M_Turtle::iterator it = turtles_.begin();M_Turtle::iterator end = turtles_.end();for (; it != end; ++it){it->second->paint(painter);}
}// 清除屏幕路径
void TurtleFrame::clear()
{int r = DEFAULT_BG_R;int g = DEFAULT_BG_G;int b = DEFAULT_BG_B;private_nh_.getParam("background_r", r);private_nh_.getParam("background_g", g);private_nh_.getParam("background_b", b);path_image_.fill(qRgb(r, g, b));update();
}void TurtleFrame::onUpdate()
{ros::spinOnce();updateTurtles();if (!ros::ok()){close();}
}// 更新所有海龟的状态
void TurtleFrame::updateTurtles()
{if (last_turtle_update_.isZero()){last_turtle_update_ = ros::WallTime::now();return;}bool modified = false;M_Turtle::iterator it = turtles_.begin();M_Turtle::iterator end = turtles_.end();for (; it != end; ++it){// 更新每只海龟位置和状态modified |= it->second->update(0.001 * update_timer_->interval(), path_painter_, path_image_, width_in_meters_, height_in_meters_);}if (modified){update();}++frame_count_; // 增加帧计数
}// 清除海龟轨迹的服务回调:
bool TurtleFrame::resetCallback(std_srvs::Empty::Request&, std_srvs::Empty::Response&)
{ROS_INFO("Resetting turtlesim.");turtles_.clear();id_counter_ = 0;spawnTurtle("", width_in_meters_ / 2.0, height_in_meters_ / 2.0, 0); // 生成新海龟clear();return true;
}bool TurtleFrame::clearCallback(std_srvs::Empty::Request&, std_srvs::Empty::Response&)
{ROS_INFO("Clearing turtlesim.");clear();return true;
}}

这两个就是关于turtle的服务端的内容了,如果和我一样没有系统的学习过c++的话,看起来还是比较费劲的,那么最好的方式就是按照自己的代码习惯来重写一遍并且附上相关的注释了,以上就是重写完后的。

注意由于这些服务以及随着turtlesim_node的启动自动启动了,那么我们可以重新去添加自己的服务并且实现相关的功能。比如我们添加一个/spawn_me的服务,然后自定的srv类型与之前相同.仿照他的内容添加了两个服务:
spawn_me和kill_me
在这里插入图片描述
用rqt去call它依然可以实现相关功能:
在这里插入图片描述
在这里插入图片描述
底下那个(0,0,0)就是我们call出来的,服务也就创建成功了。当然,这里面也包含自定义服务类型,和自定义msg类似,这里就不在赘述了。
这就是服务端的书写,至于客户端怎么去请求它,这里先不深入了,状态机定义过程中会有这个请求,到时候再研究,继续看我们动作状态怎么去请求到的。
action又比service更进一步,将整个动作行为进行了扩展,我们还是以turtle的shape动作为例来学习。
源码位于turtle_actionlib中,在这里插入图片描述
定义的action主要是Shape.action:

#goal definition
int32 edges
float32 radius
---
#result definition
float32 interior_angle
float32 apothem
---
#feedback

包含三个部分,goal,definition和feedback
这个动作是让小乌龟来绘制形状的,告诉边数,告诉半径就能给出相关的结果,然后没有需要反馈的内容。
动作是用来长时间的一个使用的一种服务,但是很特别的是他的实现和请求都是使用的topic的形式,并不是自成一体的通信方式。很明显,这其实也是一种特别的服务,所以动作不属于ros的三大通讯模式。

ros::this_node::getName()是用来获取当前节点名字的,我们把客户端写到一个cpp里面:

#include <ros/ros.h>
#include <turtlesim/Pose.h>
#include <actionlib/server/simple_action_server.h>
#include <cmath>
#include <math.h>
#include <angles/angles.h>#include <geometry_msgs/Twist.h>
#include <turtle_actionlib/ShapeAction.h> // 这是定义的action编译后的头文件// This class computes the command_velocities of the turtle to draw regular polygons 
class ShapeAction
{
public:ShapeAction(std::string name) : as_(nh_, name, false),action_name_(name){//注册的动作内容as_.registerGoalCallback(boost::bind(&ShapeAction::goalCB, this));as_.registerPreemptCallback(boost::bind(&ShapeAction::preemptCB, this));//订阅和发布内容,由于是action,所以一定是输出了内容的sub_ = nh_.subscribe("/turtle1/pose", 1, &ShapeAction::controlCB, this);pub_ = nh_.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel", 1);as_.start();}~ShapeAction(void){}// 分别执行了两个动作void goalCB(){// accept the new goalturtle_actionlib::ShapeGoal goal = *as_.acceptNewGoal();//save the goal as private variablesedges_ = goal.edges;radius_ = goal.radius;// reset helper variablesinterior_angle_ = ((edges_-2)*M_PI)/edges_;apothem_ = radius_*cos(M_PI/edges_);//compute the side length of the polygonside_len_ = apothem_ * 2* tan( M_PI/edges_);//store the result valuesresult_.apothem = apothem_;result_.interior_angle = interior_angle_;edge_progress_ =0;start_edge_ = true;}void preemptCB(){ROS_INFO("%s: Preempted", action_name_.c_str());// set the action state to preemptedas_.setPreempted();}void controlCB(const turtlesim::Pose::ConstPtr& msg){// make sure that the action hasn't been canceledif (!as_.isActive())return;if (edge_progress_ < edges_){// scalar values for drive the turtle faster and straighterdouble l_scale = 6.0;double a_scale = 6.0;double error_tol = 0.00001;if (start_edge_){start_x_ = msg->x;start_y_ = msg->y;start_theta_ = msg->theta;start_edge_ = false;}// compute the distance and theta error for the shapedis_error_ = side_len_ - fabs(sqrt((start_x_- msg->x)*(start_x_-msg->x) + (start_y_-msg->y)*(start_y_-msg->y)));theta_error_ = angles::normalize_angle_positive(M_PI - interior_angle_ - (msg->theta - start_theta_));if (dis_error_ > error_tol){command_.linear.x = l_scale*dis_error_;command_.angular.z = 0;}else if (dis_error_ < error_tol && fabs(theta_error_)> error_tol){ command_.linear.x = 0;command_.angular.z = a_scale*theta_error_;}else if (dis_error_ < error_tol && fabs(theta_error_)< error_tol){command_.linear.x = 0;command_.angular.z = 0;start_edge_ = true;edge_progress_++;}  else{command_.linear.x = l_scale*dis_error_;command_.angular.z = a_scale*theta_error_;} // publish the velocity commandpub_.publish(command_);} else{          ROS_INFO("%s: Succeeded", action_name_.c_str());// set the action state to succeededas_.setSucceeded(result_);}   }protected:ros::NodeHandle nh_;actionlib::SimpleActionServer<turtle_actionlib::ShapeAction> as_;std::string action_name_;double radius_, apothem_, interior_angle_, side_len_;double start_x_, start_y_, start_theta_;double dis_error_, theta_error_;int edges_ , edge_progress_;bool start_edge_;geometry_msgs::Twist command_;turtle_actionlib::ShapeResult result_;ros::Subscriber sub_;ros::Publisher pub_;
};int main(int argc, char** argv)
{ros::init(argc, argv, "turtle_shape");ShapeAction shape(ros::this_node::getName());ros::spin();return 0;
}

我们创建的这个动作是以topic来表现出来的:
在这里插入图片描述
这里就是turtle_shape的那两个话题。
然后怎么请求这个服务呢,也是通过topic来实现的:

rostopic pub /turtle_shape/goal turtle_actionlib/ShapeActionGoal '{goal: {edges: 4, radius: 1.5}}'

发布一下这些信息就能启动这个action了:
在这里插入图片描述
到这里我们对状态机内部的内容有了一个整体的了解了,接下来就是在状态机中去结合这些动作状态和服务状态完成一系列复杂的任务了。

这篇关于基于smach的状态机设计【1】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1116704

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

怎么让1台电脑共享给7人同时流畅设计

在当今的创意设计与数字内容生产领域,图形工作站以其强大的计算能力、专业的图形处理能力和稳定的系统性能,成为了众多设计师、动画师、视频编辑师等创意工作者的必备工具。 设计团队面临资源有限,比如只有一台高性能电脑时,如何高效地让七人同时流畅地进行设计工作,便成为了一个亟待解决的问题。 一、硬件升级与配置 1.高性能处理器(CPU):选择多核、高线程的处理器,例如Intel的至强系列或AMD的Ry

基于51单片机的自动转向修复系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 单片机

SprinBoot+Vue网络商城海鲜市场的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍:CSDN认证博客专家,CSDN平台Java领域优质创作者,全网30w+

单片机毕业设计基于单片机的智能门禁系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍程序代码部分参考 设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订

Spring的设计⽬标——《Spring技术内幕》

读《Spring技术内幕》第二版,计文柯著。 如果我们要简要地描述Spring的设计⽬标,可以这么说,Spring为开发者提供的是⼀个⼀站式的轻量级应⽤开发框架(平台)。 作为平台,Spring抽象了我们在 许多应⽤开发中遇到的共性问题;同时,作为⼀个轻量级的应⽤开发框架,Spring和传统的J2EE开发相⽐,有其⾃⾝的特点。 通过这些⾃⾝的特点,Spring充分体现了它的设计理念:在

开题报告中的研究方法设计:AI能帮你做什么?

AIPaperGPT,论文写作神器~ https://www.aipapergpt.com/ 大家都准备开题报告了吗?研究方法部分是不是已经让你头疼到抓狂? 别急,这可是大多数人都会遇到的难题!尤其是研究方法设计这一块,选定性还是定量,怎么搞才能符合老师的要求? 每次到这儿,头脑一片空白。 好消息是,现在AI工具火得一塌糊涂,比如ChatGPT,居然能帮你在研究方法这块儿上出点主意。是不

创业者该如何设计公司的股权架构

本文来自七八点联合IT橘子和车库咖啡的一系列关于设计公司股权结构的讲座。 主讲人何德文: 在公司发展的不同阶段,创业者都会面临公司股权架构设计问题: 1.合伙人合伙创业第一天,就会面临股权架构设计问题(合伙人股权设计); 2.公司早期要引入天使资金,会面临股权架构设计问题(天使融资); 3.公司有三五十号人,要激励中层管理与重要技术人员和公司长期走下去,会面临股权架构设计问题(员工股权激

分布式文件系统设计

分布式文件系统是分布式领域的一个基础应用,其中最著名的毫无疑问是 HDFS/GFS。如今该领域已经趋向于成熟,但了解它的设计要点和思想,对我们将来面临类似场景 / 问题时,具有借鉴意义。并且,分布式文件系统并非只有 HDFS/GFS 这一种形态,在它之外,还有其他形态各异、各有千秋的产品形态,对它们的了解,也对扩展我们的视野有所俾益。本文试图分析和思考,在分布式文件系统领域,我们要解决哪些问题、有

(入门篇)JavaScript 网页设计案例浅析-简单的交互式图片轮播

网页设计已经成为了每个前端开发者的必备技能,而 JavaScript 作为前端三大基础之一,更是为网页赋予了互动性和动态效果。本篇文章将通过一个简单的 JavaScript 案例,带你了解网页设计中的一些常见技巧和技术原理。今天就说一说一个常见的图片轮播效果。相信大家在各类电商网站、个人博客或者展示页面中,都看到过这种轮播图。它的核心功能是展示多张图片,并且用户可以通过点击按钮,左右切换图片。