【SPIN】PROMELA语言编程入门基础语法(SPIN学习系列--1)

在这里插入图片描述

PROMELA(Protocol Meta Language)是一种用于描述和验证并发系统的形式化建模语言,主要与SPIN(Simple Promela Interpreter)模型检查器配合使用。本教程将基于JSPIN(SPIN的Java图形化版本),围绕顺序编程(Sequential Programming)核心知识点展开,通过示例代码和操作演示,帮助你快速掌握PROMELA的基础语法与实践。


1.1 第一个PROMELA程序

PROMELA程序的核心是定义进程(process),通过proctype关键字声明。程序执行从init进程开始(类似C语言的main函数)。

示例1-1:基础结构与输出

/* 第一个PROMELA程序:输出简单信息 */
proctype HelloWorld() {printf("Hello, PROMELA!\n");  /* 打印输出 */
}init {run HelloWorld();  /* 启动HelloWorld进程 */
}

操作步骤(JSPIN工具):

  1. 打开JSPIN,新建文件并输入上述代码,保存为hello.pml
  2. 点击VerifyParse检查语法(无错误则显示Parsing completed)。
  3. 点击SimulateRun启动模拟,控制台将输出Hello, PROMELA!

示例1-2:变量赋值与输出

proctype VarDemo() {int x = 5;    /* 声明并初始化int变量 */byte y = 10;  /* 声明byte变量(0-255) */printf("x = %d, y = %d\n", x, y);  /* 格式化输出 */
}init {run VarDemo();
}

示例1-3:简单计算

proctype CalcDemo() {int a = 10, b = 3;int sum = a + b;int product = a * b;printf("Sum: %d, Product: %d\n", sum, product);
}init {run CalcDemo();
}

关键说明

  • proctype定义进程模板,init是程序入口。
  • printf支持%d(整数)、%s(字符串)、%c(字符)等格式化符号。

1.2 Random simulation(随机模拟)

PROMELA支持非确定性(Non-determinism),通过rand()函数或多分支选择(如if语句)实现随机行为。JSPIN的模拟功能可展示不同执行路径。

示例1-4:随机数生成

proctype RandDemo() {int r = rand() % 10;  /* 生成0-9的随机数 */printf("Random number: %d\n", r);
}init {run RandDemo();
}

操作步骤(观察随机结果):

在JSPIN中多次点击SimulateRun,每次输出的随机数可能不同(因rand()依赖伪随机种子)。

示例1-5:多分支随机选择

proctype BranchDemo() {if:: printf("执行分支1\n");  /* 分支1 */:: printf("执行分支2\n");  /* 分支2 */fi;  /* if结束 */
}init {run BranchDemo();
}

示例1-6:条件随机选择

proctype CondRandDemo() {int x = rand() % 2;  /* 0或1 */if:: x == 0 -> printf("x是0\n");  /* 条件分支1 */:: x == 1 -> printf("x是1\n");  /* 条件分支2 */fi;
}init {run CondRandDemo();
}

关键说明

  • if语句的分支用::分隔,JSPIN会随机选择一个可执行的分支(若多个分支条件满足)。
  • ->符号表示“仅当条件满足时执行该分支”(后续1.6节详细说明)。

1.3 Data types(数据类型)

PROMELA支持以下基础数据类型,需注意取值范围和使用场景:

1.3.1 基础类型对比

类型描述取值范围示例
bool布尔值true/falsebool flag = true;
byte无符号8位整数0 ~ 255byte age = 30;
int有符号32位整数(依赖SPIN配置)-2³¹ ~ 2³¹-1int score = -5;
mtype枚举类型用户定义的符号常量集合mtype { A, B, C };

示例1-7:bool类型使用

proctype BoolDemo() {bool isReady = true;if:: isReady -> printf("准备就绪\n");:: else -> printf("未就绪\n");  /* else分支(仅当isReady为false时执行) */fi;
}init {run BoolDemo();
}

示例1-8:byte与int的取值范围

proctype OverflowDemo() {byte b = 255;b = b + 1;  /* 溢出:255+1=0(模256) */int i = 2147483647;  /* int最大值 */i = i + 1;  /* 溢出:2147483648(依赖SPIN配置,可能报错) */printf("b=%d, i=%d\n", b, i);
}init {run OverflowDemo();
}

示例1-9:mtype枚举类型

mtype { SUCCESS, FAIL, PENDING };  /* 定义枚举类型 */proctype MtypeDemo() {mtype status = PENDING;if:: status == SUCCESS -> printf("成功\n");:: status == FAIL -> printf("失败\n");:: status == PENDING -> printf("等待中\n");fi;
}init {run MtypeDemo();
}

关键说明

  • byte溢出会自动取模(如255+1=0),int溢出可能导致未定义行为(SPIN默认不检查)。
  • mtype枚举用于提高代码可读性,避免“魔法数值”。

1.3.2 Type conversions(类型转换)

PROMELA支持隐式转换(小范围类型转大范围)和显式转换(需强制声明)。

示例1-10:隐式转换(byte→int)

proctype ImplicitConv() {byte b = 100;int i = b;  /* 隐式转换:byte→int */printf("b=%d, i=%d\n", b, i);  /* 输出:100, 100 */
}init {run ImplicitConv();
}

示例1-11:显式转换(int→byte)

proctype ExplicitConv() {int i = 300;byte b = (byte)i;  /* 显式转换:截断高位,300 → 300-256=44 */printf("i=%d, b=%d\n", i, b);  /* 输出:300, 44 */
}init {run ExplicitConv();
}

示例1-12:bool与int的转换

proctype BoolIntConv() {bool flag = true;int x = flag;  /* true→1,false→0 */bool y = (bool)0;  /* 0→false,非0→true */printf("x=%d, y=%s\n", x, y ? "true" : "false");  /* 输出:1, false */
}init {run BoolIntConv();
}

关键说明

  • 隐式转换仅允许从低精度到高精度(如byteint),反之需显式转换。
  • boolint时,true为1,false为0;intbool时,0为false,非0为true

1.4 Operators and expressions(操作符与表达式)

PROMELA支持算术、逻辑、比较等操作符,优先级与C语言类似(()可改变优先级)。

1.4.1 操作符分类

类型操作符示例说明
算术+, -, *, /, %a + b, c % d%为取模,结果符号与被除数一致
逻辑&&, `, !`
比较==, !=, <, >, <=, >=a != b, c <= d结果为bool类型

示例1-13:算术操作符

proctype ArithmeticOps() {int a = 10, b = 3;printf("a+b=%d, a-b=%d\n", a+b, a-b);        /* 13, 7 */printf("a*b=%d, a/b=%d\n", a*b, a/b);        /* 30, 3(整数除法取整) */printf("a%%b=%d\n", a%b);                    /* 1(10%3=1) */
}init {run ArithmeticOps();
}

示例1-14:逻辑与比较操作符

proctype LogicOps() {int x = 5, y = 10;bool cond1 = (x > 0) && (y < 20);  /* true */bool cond2 = (x == 5) || (y != 10); /* true */bool cond3 = !cond1;                /* false */printf("cond1=%s, cond2=%s, cond3=%s\n", cond1 ? "true" : "false", cond2 ? "true" : "false", cond3 ? "true" : "false");
}init {run LogicOps();
}

示例1-15:表达式优先级

proctype PrecedenceDemo() {int a = 2, b = 3, c = 4;int res1 = a + b * c;  /* 2 + (3*4)=14 */int res2 = (a + b) * c;/* (2+3)*4=20 */printf("res1=%d, res2=%d\n", res1, res2);
}init {run PrecedenceDemo();
}

关键说明

  • 整数除法/会截断小数部分(如10/3=3)。
  • 逻辑操作符&&||支持短路求值,避免无效计算。

1.4.2 Local variables(局部变量)

PROMELA中变量分为全局变量(声明在进程外)和局部变量(声明在进程或init内),局部变量作用域限于所在进程。

示例1-16:全局变量与局部变量对比

int global_var = 10;  /* 全局变量 */proctype LocalVarDemo() {int local_var = 20;  /* 局部变量 */printf("全局变量: %d, 局部变量: %d\n", global_var, local_var);
}init {run LocalVarDemo();/* 无法访问LocalVarDemo的local_var(作用域仅限该进程) */
}

示例1-17:局部变量的生命周期

proctype LifeCycleDemo() {int x = 0;x = x + 1;  /* x=1 */printf("x=%d\n", x);  /* 输出1 */
}  /* 进程结束,x被销毁 */init {run LifeCycleDemo();run LifeCycleDemo();  /* 再次启动进程,x重新初始化为0 */
}

示例1-18:同名变量覆盖

int x = 100;  /* 全局变量x */proctype ShadowDemo() {int x = 200;  /* 局部变量x覆盖全局变量 */printf("局部x: %d, 全局x: %d\n", x, ::x);  /* ::x访问全局变量 */
}init {run ShadowDemo();
}

关键说明

  • 全局变量可被所有进程访问,局部变量仅所在进程可见。
  • 使用::x显式访问被局部变量覆盖的全局变量(如示例1-18)。

1.4.3 Symbolic names(符号名)

通过#define定义常量,或typedef定义类型别名,提高代码可读性。

示例1-19:#define常量

#define MAX_USERS 10  /* 定义常量 */
#define PI 3.14       /* 注意:PROMELA不支持浮点数,此处仅为示例 */proctype DefineDemo() {int users = MAX_USERS;printf("最大用户数: %d\n", users);
}init {run DefineDemo();
}

示例1-20:typedef类型别名

typedef Score int;  /* 定义Score为int的别名 */proctype TypedefDemo() {Score math = 90;  /* 等价于int math=90 */printf("数学成绩: %d\n", math);
}init {run TypedefDemo();
}

示例1-21:符号名与枚举结合

#define SUCCESS 0
#define FAIL 1
mtype { STATUS_SUCCESS=SUCCESS, STATUS_FAIL=FAIL };  /* 枚举关联常量 */proctype SymbolDemo() {int result = SUCCESS;if:: result == STATUS_SUCCESS -> printf("操作成功\n");:: result == STATUS_FAIL -> printf("操作失败\n");fi;
}init {run SymbolDemo();
}

关键说明

  • #define是预编译指令,仅替换文本(需注意括号避免优先级问题,如#define ADD(a,b) (a+b))。
  • typedef用于创建类型别名,提高代码可维护性。

1.5 Control statements(控制语句)

PROMELA的控制语句用于管理代码执行顺序,包括顺序执行、分支(if/alt)、循环(do/for)等。

1.6 Selection statements(选择语句)

PROMELA提供两种选择语句:if(非阻塞选择)和alt(阻塞选择),易混淆点在于分支条件不满足时的行为。

1.6.1 if与alt的对比

语句行为示例
if若所有分支条件均不满足,报错(invalid end of ifif :: cond -> ... fi
alt若所有分支条件均不满足,阻塞(等待条件变化)alt :: cond -> ... od

示例1-22:if语句(非阻塞)

proctype IfDemo() {int x = 5;if:: x > 10 -> printf("x>10\n");  /* 条件不满足 */:: x < 3 -> printf("x<3\n");    /* 条件不满足 */fi;  /* 报错:所有分支条件均不满足 */
}init {run IfDemo();
}

示例1-23:alt语句(阻塞)

proctype AltDemo() {int x = 5;alt:: x > 10 -> printf("x>10\n");  /* 条件不满足,阻塞 */:: x < 3 -> printf("x<3\n");    /* 条件不满足,阻塞 */od;  /* 程序卡住,等待x的值变化 */
}init {run AltDemo();
}

示例1-24:带else的if语句

proctype IfElseDemo() {int x = 5;if:: x > 10 -> printf("x>10\n");:: else -> printf("x≤10\n");  /* else分支(所有其他情况) */fi;  /* 输出:x≤10 */
}init {run IfElseDemo();
}

关键说明

  • if必须至少有一个分支条件满足,否则SPIN会报错(error: invalid end of if)。
  • alt用于并发场景中等待条件满足(如进程间同步),单独使用可能导致死锁。

1.6.2 Conditional expressions(条件表达式)

条件表达式是if/alt语句的核心,支持复杂逻辑组合。

示例1-25:多条件组合

proctype CondExprDemo() {int a = 5, b = 10;if:: (a > 0) && (b < 20) -> printf("条件1满足\n");  /* true */:: (a == 5) || (b != 10) -> printf("条件2满足\n"); /* true */fi;  /* 随机选择一个满足的分支执行 */
}init {run CondExprDemo();
}

示例1-26:基于变量的条件

proctype VarCondDemo() {int flag = rand() % 2;  /* 0或1 */if:: flag == 0 -> printf("flag=0\n");:: flag == 1 -> printf("flag=1\n");fi;
}init {run VarCondDemo();
}

示例1-27:枚举类型条件

mtype { ON, OFF };proctype MtypeCondDemo() {mtype state = ON;if:: state == ON -> printf("设备开启\n");:: state == OFF -> printf("设备关闭\n");fi;
}init {run MtypeCondDemo();
}

1.7 Repetitive statements(重复语句)

PROMELA支持do(类似while)和for(计数循环)两种循环结构。

1.7.1 do循环(无限循环)

do循环重复执行分支,直到break或所有分支条件不满足(do无退出条件时会无限循环)。

示例1-28:简单do循环

proctype DoLoopDemo() {int count = 0;do:: count < 3 -> printf("计数: %d\n", count);count++;:: else -> break;  /* 退出循环 */od;
}init {run DoLoopDemo();  /* 输出0,1,2 */
}

示例1-29:多分支do循环

proctype MultiBranchDo() {int x = 0;do:: x < 2 -> printf("x=%d(分支1)\n", x);x++;:: x >= 2 -> printf("x=%d(分支2)\n", x);break;od;
}init {run MultiBranchDo();  /* 输出x=0(分支1),x=1(分支1),x=2(分支2) */
}

示例1-30:无退出条件的do循环(死锁)

proctype InfiniteDo() {do:: true ->  /* 永远满足 */printf("循环中...\n");od;  /* 无限循环,SPIN模拟时需手动终止 */
}init {run InfiniteDo();
}

1.7.2 Counting loops(计数循环)

for循环用于已知次数的迭代,语法为for (init; cond; update) { ... }

示例1-31:基本for循环

proctype ForLoopDemo() {int sum = 0;for (int i=1; i<=5; i++) {  /* i从1到5 */sum += i;}printf("1+2+3+4+5=%d\n", sum);  /* 输出15 */
}init {run ForLoopDemo();
}

示例1-32:嵌套for循环

proctype NestedForDemo() {for (int i=1; i<=2; i++) {for (int j=1; j<=3; j++) {printf("i=%d, j=%d\n", i, j);}}
}init {run NestedForDemo();  /* 输出i=1,j=1; i=1,j=2; ... i=2,j=3 */
}

示例1-33:for循环与break

proctype ForBreakDemo() {for (int i=1; i<=5; i++) {if (i == 3) {break;  /* 退出循环 */}printf("i=%d\n", i);  /* 输出i=1,2 */}
}init {run ForBreakDemo();
}

关键说明

  • do循环更灵活(支持多分支),for循环适合固定次数的迭代。
  • 避免在for循环中修改循环变量(可能导致不可预期的行为)。

1.8 Jump statements(跳转语句)

PROMELA支持break(退出循环)和goto(跳转到标签),需谨慎使用以避免代码混乱。

示例1-34:break退出循环

proctype BreakDemo() {int i = 0;do:: i < 5 -> i++;if (i == 3) {break;  /* 退出do循环 */}printf("i=%d\n", i);  /* 输出i=1,2 */od;
}init {run BreakDemo();
}

示例1-35:goto跳转到标签

proctype GotoDemo() {int x = 0;start:  /* 标签 */x++;if:: x < 3 -> printf("x=%d\n", x);  /* 输出x=1,2 */goto start;  /* 跳转回start标签 */:: else -> printf("结束\n");fi;
}init {run GotoDemo();
}

示例1-36:goto跨循环跳转

proctype GotoCrossLoop() {int i = 0, j = 0;outer:  /* 外层循环标签 */for (i=1; i<=2; i++) {for (j=1; j<=3; j++) {if (j == 2) {goto outer;  /* 跳转到外层循环 */}printf("i=%d, j=%d\n", i, j);  /* 输出i=1,j=1 */}}
}init {run GotoCrossLoop();
}

关键说明

  • break仅退出当前最内层循环,goto可跳转到任意标签(包括循环外)。
  • 过度使用goto会降低代码可读性,建议优先使用break或重构循环逻辑。

总结

本教程围绕PROMELA顺序编程的核心知识点,结合JSPIN工具演示了从基础程序结构到控制语句的完整流程。通过30+示例代码,你已掌握:

  • PROMELA的进程定义与init入口。
  • 随机模拟与非确定性行为。
  • 数据类型、操作符与表达式的使用。
  • 选择语句(if/alt)和循环语句(do/for)的区别。
  • 跳转语句(break/goto)的应用场景。

后续可结合SPIN的模型检查功能(如spin -a生成验证代码),进一步验证并发系统的正确性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/81080.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Automatic Recovery of the Atmospheric Light in Hazy Images论文阅读

Automatic Recovery of the Atmospheric Light in Hazy Images 1. 论文的研究目标与实际意义1.1 研究目标1.2 实际问题与产业意义2. 论文的创新方法、模型与公式2.1 方法框架2.1.1 方向估计(Orientation Estimation)2.1.2 幅值估计(Magnitude Estimation)2.2 与传统方法的对…

基于微信小程序的在线聊天功能实现:WebSocket通信实战

基于微信小程序的在线聊天功能实现&#xff1a;WebSocket通信实战 摘要 本文将详细介绍如何使用微信小程序结合WebSocket协议开发一个实时在线聊天功能。通过完整的代码示例和分步解析&#xff0c;涵盖界面布局、WebSocket连接管理、消息交互逻辑及服务端实现&#xff0c;适合…

速通:国际数字影像产业园园区服务体系

速通&#xff1a;国际数字影像产业园园区服务体系 国际数字影像产业园服务体系致力于构建全周期、多维度、高效率的产业赋能平台&#xff0c;旨在优化营商环境&#xff0c;激发企业活力&#xff0c;推动数字影像产业集群化、高端化发展。 一、基础运营与智慧管理服务 智慧化…

DeerFlow:字节新一代 DeepSearch 框架

项目地址&#xff1a;https://github.com/bytedance/deer-flow/ 【全新的 Multi-Agent 架构设计】独家设计的 Research Team 机制&#xff0c;支持多轮对话、多轮决策和多轮任务执行。与 LangChain 原版 Supervisor 相比&#xff0c;显著减少 Tokens 消耗和 API 调用次数&#…

MySQL 大表中添加索引的两种常见方式及其优缺点分析

引言 在数据库性能优化过程中&#xff0c;给大表添加索引是一项常见且重要的操作。由于大表数据量庞大&#xff0c;索引的创建过程往往涉及较高的系统开销和复杂的操作流程。本文将介绍两种在大表中添加索引的常见方法&#xff1a;直接添加索引和表复制方式&#xff0c;分别分…

Ubuntu系统挂载磁盘并配置开机自动挂载

今天买了个服务器然后挂载了一个500G的磁盘&#xff0c;但是登录进去后发看不到&#xff0c;就是下面这样的 只能看到100G的系统盘 rootecm-74de:/usr/local# df -h Filesystem Size Used Avail Use% Mounted on tmpfs 3.1G 1.1M 3.1G 1% /run /dev/vda2 …

Android开发-Application

在Android应用开发中&#xff0c;Application类扮演着非常重要的角色。它作为整个应用程序的全局单例实例存在&#xff0c;在应用启动时最先被创建&#xff0c;并且在整个应用生命周期内持续存在。通过自定义Application类&#xff0c;开发者可以执行全局初始化操作、管理全局状…

边缘计算平台

本文来源 &#xff1a; 腾讯元宝 边缘计算平台是一种在靠近数据源头的网络边缘侧部署的分布式计算架构&#xff0c;通过融合网络、计算、存储和应用核心能力&#xff0c;就近提供实时、低延迟的智能服务。以下是其核心要点&#xff1a; ​​1. 定义与特点​​ ​​定义​​&a…

Spring 框架 JDBC 模板技术详解

一、JDBC 模板技术概述 在传统 JDBC 开发中&#xff0c;开发人员需要手动处理数据库连接&#xff08;Connection&#xff09;、事务管理、语句执行&#xff08;Statement&#xff09;和结果集&#xff08;ResultSet&#xff09;等繁琐操作&#xff0c;不仅代码冗余度高&#x…

Axure难点解决分享:统计分析页面引入Echarts示例动态效果

亲爱的小伙伴,在您浏览之前,烦请关注一下,在此深表感谢! Axure产品经理精品视频课已登录CSDN可点击学习https://edu.csdn.net/course/detail/40420 课程主题:统计分析页面引入Echarts示例动态效果 主要内容:echart示例引入、大小调整、数据导入 应用场景:统计分析页面…

SpringBoot 数据校验与表单处理:从入门到精通(万字长文)

一、SpringBoot 数据验证基础 1.1 数据验证的重要性 在现代Web应用开发中&#xff0c;数据验证是保证系统安全性和数据完整性的第一道防线。没有经过验证的用户输入可能导致各种安全问题&#xff0c;如SQL注入、XSS攻击&#xff0c;或者简单的业务逻辑错误。 数据验证的主要…

Ubuntu 22.04(WSL2)使用 Docker 安装 Zipkin 和 Skywalking

Ubuntu 22.04&#xff08;WSL2&#xff09;使用 Docker 安装 Zipkin 和 Skywalking 分布式追踪工具在现代微服务架构中至关重要&#xff0c;它们帮助开发者监控请求在多个服务之间的流动&#xff0c;识别性能瓶颈和潜在错误。本文将指导您在 Ubuntu 22.04&#xff08;WSL2 环境…

python打卡day25@浙大疏锦行

知识点回顾&#xff1a; 1.异常处理机制 2.debug过程中的各类报错 3.try-except机制 4.try-except-else-finally机制 在即将进入深度学习专题学习前&#xff0c;我们最后差缺补漏&#xff0c;把一些常见且重要的知识点给他们补上&#xff0c;加深对代码和流程的理解。 作业&a…

鸿蒙OSUniApp 开发实时聊天页面的最佳实践与实现#三方框架 #Uniapp

使用 UniApp 开发实时聊天页面的最佳实践与实现 在移动应用开发领域&#xff0c;实时聊天功能已经成为许多应用不可或缺的组成部分。本文将深入探讨如何使用 UniApp 框架开发一个功能完善的实时聊天页面&#xff0c;从布局设计到核心逻辑实现&#xff0c;带领大家一步步打造专…

43、Server.UrlEncode、HttpUtility.UrlDecode的区别?

Server.UrlEncode 和 HttpUtility.UrlDecode 是 .NET 中用于处理 URL 编码/解码的两个不同方法&#xff0c;主要区别在于所属命名空间、使用场景和具体行为。以下是详细对比&#xff1a; 1. 所属类库与命名空间 Server.UrlEncode 属于 System.Web.HttpServerUtility 类。通常…

代码随想录 算法训练 Day1:数组

题目一&#xff1a; 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。 示例 1: 输入: nums [-1,0,3,5,9,12], target …

容器技术 20 年:颠覆、重构与重塑软件世界的力量

目录 容器技术发展史 虚拟化技术向容器技术转变 Docker的横空出世 容器编排技术与Kubernetes 微服务的出现与Istio 工业标准的容器运行时 容器技术与 DevOps 的深度融合​ 无服务架构推波助澜 展望未来发展方向 从 20 世纪硬件虚拟化的笨重&#xff0c;到操作系统虚拟…

集成钉钉消息推送功能

1. 概述 本文档详细描述了在若依框架基础上集成钉钉消息推送功能的开发步骤。该功能允许系统向指定钉钉用户发送文本和富文本消息通知。 2. 环境准备 2.1 钉钉开发者账号配置 登录钉钉开发者平台&#xff1a;https://open.dingtalk.com/创建/选择企业内部应用获取以下关键信…

【行为型之访问者模式】游戏开发实战——Unity灵活数据操作与跨系统交互的架构秘诀

文章目录 &#x1f9f3; 访问者模式&#xff08;Visitor Pattern&#xff09;深度解析一、模式本质与核心价值二、经典UML结构三、Unity实战代码&#xff08;游戏物品系统&#xff09;1. 定义元素与访问者接口2. 实现具体元素类3. 实现具体访问者4. 对象结构管理5. 客户端使用 …

SQL:MySQL函数:日期函数(Date Functions)

目录 时间是数据的一种类型 &#x1f9f0; MySQL 常用时间函数大全 &#x1f7e6; 1. 获取当前时间/日期 &#x1f7e6; 2. 日期运算&#xff08;加减&#xff09; &#x1f7e6; 3. 时间差计算 &#x1f7e6; 4. 格式化日期 &#x1f7e6; 5. 提取时间部分 &#x1f7…