什么是例程,子例程,标签,函数,标签,过程,类方法,静态方法,实例方法,对象方法

文章目录

  • 什么是例程,子例程,标签,函数,标签,过程,类方法,静态方法,实例方法,对象方法
    • 编程语言
    • [例程 - `Routine`](#例程 - Routine)
    • [子例程 - `Subroutine`](#子例程 - Subroutine)
    • [函数 - `Function`](#函数 - Function)
    • [标签 - `Label`](#标签 - Label)
    • [过程 - `Procedure`](#过程 - Procedure)
    • [类方法 - `ClassMethod`](#类方法 - ClassMethod)
      • [`Not ProcedureBlock`关键字原理](#Not ProcedureBlock关键字原理)
    • 实例方法

什么是例程,子例程,标签,函数,标签,过程,类方法,静态方法,实例方法,对象方法

相信有很多小伙伴在刚接触ObjectScript编程语言(M语言)的时候,对一些"方法"的概念感到困惑,不同的人称呼这些"方法"各有不同。这些"方法"在功能上大体调用的都差不多,但是还有一些细微的区别。称呼"方法"的方式有很多,让人感到非常困惑,到底这些"方法"的区别是什么。这篇文章就带领大家搞清楚这些"方法"的概念与定义。

了解了这些"方法"的概念之后,就可以搞清楚关键字Not ProduceBlock的原理与使用。如果不清楚Not ProduceBlock作用,稍不注意就可能在使用方法过程当中引起一些严重的故障,例如死循环导致临时Global无限增长把磁盘撑爆,导致正常业务宕机。

编程语言

这里先明确一个概念,我们使用的编程语言在ISC的官方定义为ObjectScript对象脚本语言,实际上ObjectScript是底层MMumps)语言拓展而来。可类比JavaAndroidC#.Net

MUMPS语言,简称:M技术,全称:Massachusetts General Hospital Utility Multi-Programming System,麻省总医院多用途程序设计系统;算起来也是一种古老的语言了,与FORTRANCOBOL属于同时代的语言。因为这门语言最主要是用于医疗数据库方面,所以其应用并不像SQL ServerOracle等那么广泛。

例程 - Routine

例程是可调用的用户编写的代码块,它是ObjectScript程序,传统的编写M程序都是用的例程,因为在古早年代面向对象编程还没有出现。

一般保存的.mac文件为例程,mac文件名称为例程名称。int文件也称例程。包含ObjectScript代码的文件均可称之为例程。

注:例程指的文件,而不是某个具体具体例程中的方法。执行例程指的是执行.mac文件。

执行例程时,使用DO命令,如下所示:

java 复制代码
 do ^routinename
  • routinename - 为例程文件名称。

创建例程文件Routine.mac包含如下代码:

java 复制代码
	w "执行例程文件",!

编写如下方法执行例程:

java 复制代码
ClassMethod Routine()
{
	d ^Routine
}
java 复制代码
USER>d ##class(M.Method).Routine()
执行例程文件

注:例程只能去执行,不能获取例程返回值。

可能会见到如下执行方式:

java 复制代码
s var = $$^RoutineName

这种方式并不是真正执行了例程,而是执行了例程的第一个方法。

创建例程文件Routine1.mac包含如下代码:

注:首行代码必须为子例程标签方法名称,如果为其他调用时会发生<PARAMETER>+1^Routine错误。

java 复制代码
Routine()
	ret "这是例程的第一个子例程方法!"
Routine1()
	ret "这是例程的第二个子例程方法!"

注:如果直接用变量接收 .mac 文件名称返回值,默认调用 .mac 文件第一个子例程方法,并且该方法不能为标签或直接用例程返回变量。**

java 复制代码
ClassMethod Routine1()
{
	s var = $$^Routine1
	w var,!
}
java 复制代码
USER>d ##class(M.Method).Routine1()
这是例程的第一个子例程方法!

子例程 - Subroutine

子例程是例程内的命名代码块。通常,子例程以 LABEL 开头,可以接受参数,以 QUIT 语句结束,虽然以QUIT 命令结束,但是注意是不返回值的。

通常我们常说的例程方法实际上指的就是子例程。

要调用子例程,请使用以下语法:

java 复制代码
d Subroutine^Routine
  • Subroutine - 子例程名称
  • Routine - 例程名称

子例程的形式为:

java 复制代码
Label(arg) scopekeyword // 作用域关键字
//code 代码
quit // 注意,QUIT没有参
  • Label - 标签名称, 例程方法名称。
  • arg - 参数,是可选的以逗号分隔的参数列表。如果没有参数,则括号是可选的。
  • scopekeyword - 作用域关键字,可选的作用域关键字是 Public(子例程的默认值)或 Private
  • code - 执行代码。
  • quit - 退出命令,但是没有返回值。

Routine.mac中添加SubRoutine无参数子例程方法:

java 复制代码
SubRoutine()
	w "这是例程的第一个子例程方法!"
	q 

编写调用方法如下:

注:如果子例程没有参数可省略括号。

java 复制代码
ClassMethod Routine2()
{
	d SubRoutine^Routine
}
java 复制代码
USER>d ##class(M.Method).Routine2()
这是例程子例程方法!

Routine.mac中添加SubRoutineParams有参数子例程方法:

java 复制代码
SubRoutineParams(str)
	w "这是例程子例程方法!" _ "参数:"_str,!
	q 

编写调用方法如下:

java 复制代码
ClassMethod Routine3()
{
	d SubRoutine^Routine("yx")
}
java 复制代码
ClassMethod Routine3()
{
	d SubRoutineParams^Routine("yx")
}

注:因为子例程没有返回值,所以调用子例程时一般只去执行方法,一般使用do命令或job命令。

函数 - Function

函数是例程中的命名代码块。通常,函数以 LABEL 开头,可以接受参数,以 QUIT 语句结束,并且有返回值。这里与子例程不同。

也可以返回值。要调用函数,有以下有效的语法形式:

java 复制代码
Label(args) scopekeyword
    zcode
    QUIT optionalreturnvalue
  • Label - 标签名称, 例程方法名称。
  • arg - 参数,是可选的以逗号分隔的参数列表。如果没有参数,则括号是可选的。
  • scopekeyword - 作用域关键字,可选的作用域关键字是 Public(子例程的默认值)或 Private
  • code - 执行代码。
  • quit - 退出命令,有返回值。

要调用函数,请使用以下语法:

java 复制代码
d Function^RoutineName(params) /* 忽略返回值 */
s x = $$Function^RoutineName(params)
  • Function - 函数名称
  • RoutineName - 例程名称
  • params - 参数
  • $$ - 如果需要获取返回值必须包含$$语法。

Routine.mac中添加Fuction无参数方法,FuctionParams有参数子例程方法:

java 复制代码
Fuction()
	w "这是例程函数方法!",!
	q "这是例程函数方法!"
	
FuctionParams(str)
	w "这是例程函数方法!"_ "参数:"_str,!
	q "这是例程函数方法!"_ "参数:"_str

忽略返回值调用方式:

java 复制代码
ClassMethod Routine4()
{
	d Fuction^Routine
	d FuctionParams^Routine("yx")
}
java 复制代码
USER>d ##class(M.Method).Routine4()
这是例程函数方法!
这是例程函数方法!参数:yx

获取返回值调用方式:

java 复制代码
ClassMethod Routine5()
{
	s ret = $$Fuction^Routine
	w ret,!
	
	s ret = $$FuctionParams^Routine("yx")
	w ret,!
}
java 复制代码
USER>d ##class(M.Method).Routine5()
这是例程函数方法!
这是例程函数方法!
这是例程函数方法!参数:yx
这是例程函数方法!参数:yx

注:在子例程与函数中默认的方法作用域是Public,如果想改为私有作用域可设置关键字Private

Routine.mac中添加FuctionPrivate私有函数方法:

java 复制代码
FuctionPrivate() Private
	w "这是私有方法不允许调用",!
	q "这是私有方法不允许调用"

调用该方法:

java 复制代码
ClassMethod Routine6()
{
	d FuctionPrivate^Routine
}

可观察到:调用私有子例程或函数方法时提示<NOLINE>错误。

注:当方法添加关键字 PRIVATE 时,该例程以外的程序调用不到。会报错 <NOLINE> 错误。只能在当前 mac 中使用。

java 复制代码
USER>d ##class(M.Method).Routine6()
 
 d FuctionPrivate^Routine }
 ^
<NOLINE>zRoutine6+1^M.Method.1
  • 在函数与子例程中,声明的局部变量默认是共有的,也就是说全局变量。这种情况很容易发生变量覆盖产生死循环。
java 复制代码
FuctionPublicVar() 
	s x = 5 
	s y = 10
	w "FuctionPublicVar中 x + y:" _ (x + y),!
	d FuctionPublicVar1()
	q x + y
FuctionPublicVar1() 
	w "FuctionPublicVar1中 x + y:" _ (x + y),!
	q x + y
java 复制代码
ClassMethod Routine12()
{
	d FuctionPublicVar^Routine
}
java 复制代码
USER>d ##class(M.Method).Routine12()
FuctionPublicVar中 x + y:15
FuctionPublicVar1中 x + y:15

为了将局部变量作用域当前方法内部需要在函数或子例程方法第一行声明new命令开启新的堆栈来初始化变量。

标签 - Label

标签是例程内的命名代码块。不接受参数,无返回值。

要调用标签,请使用以下语法:

java 复制代码
d Label^Routine 

Routine.mac中添加Lable标签方法:

java 复制代码
Lable
	w "这是一个标签方法",!

调用该方法:

java 复制代码
ClassMethod Routine7()
{
	d Lable^Routine
}
java 复制代码
USER>d ##class(M.Method).Routine7()
这是一个标签方法

注:即使给标签加上了返回值,实际上也获取不到。

Routine.mac中添加LableValue有返回值的标签方法:

java 复制代码
LableValue
	w "有返回值的标签方法",!
	q "有返回值的标签方法"

调用该方法:

java 复制代码
ClassMethod Routine8()
{
	s ret = $$LableValue^Routine
	w ret,!
}

由于标签没有括号,所以提示参数错误,不能获取返回值。

java 复制代码
USER>d ##class(M.Method).Routine8()
 
LableValue
          ^
<PARAMETER>LableValue^Routine

常见的使用标签的方式:

  • 在类方法里抽取重复的部分。
  • Query里收取数据放入临时Global中。

示例如下:

代码中output就是标签的用法。没有参数没有回值。一般调用方式为 d label实际上相当于goto命令的使用。

java 复制代码
ClassMethod QueryPersonByAgeExecute(ByRef qHandle As %Binary, pAge As %String = "", count As %Integer = "10") As %Status
{
	s pid = $i(^CacheTemp) // 注释1
	s qHandle = $lb(0, pid, 0) // 注释2
	s index = 1 // 注释3
	
	/* 业务逻辑代码 注释4 */ 
	s id = ""
	for {
		s id = $o(^M.T.PersonD(id))
		q:(id = "")
		q:(id > count)
		s data = ^M.T.PersonD(id)
		s i = 1
		s name = $lg(data, $i(i))
		s age = $lg(data, $i(i))
		continue:(age < pAge)
		s no = $lg(data, $i(i))
		d output
	}	
	/* 业务逻辑代码 */

	q $$$OK
	
output
	s ^CacheTemp(pid, index) = $lb(id, age, name, no) // 注释6 
	s index = index + 1 // 注释7
}

另外还一点需要注意,定义的标签如果没有退出命令例如 quit , retrun命令 是可以顺序执行之后的标签,直到遇到退出命令。

Routine.mac中修改Lable标签方法:

java 复制代码
Lable
	w "这是一个标签Lable方法",!
Lable1
	w "这是一个标签Lable1方法",!
Lable2
	w "这是一个标签Lable2方法",!
Lable3
	w "这是一个标签Lable3方法",!
LableValue
	w "有返回值的标签方法",!
	q "有返回值的标签方法"

再次调用方法:

java 复制代码
USER>d ##class(M.Method).Routine7()
这是一个标签Lable方法
这是一个标签Lable1方法
这是一个标签Lable2方法
这是一个标签Lable3方法
有返回值的标签方法

过程 - Procedure

Procedures - 在网上翻译为: [计算机]过程; (为解决一个特殊问题而专门设计的)文字程序。也有的叫程序,这里我们称呼为过程更为严谨。

Procedures 过程是一种特殊的ObjectScript方法。过程是在例程中功能最为强大的自定义代码块,也是最为推荐的形式。

Procedure 过程语法形式:

java 复制代码
label([param[=default]][,...]) [[pubvar[,...]]] [access] {
	code
}
  • label - 过程名称。

  • param - 变量。

  • default - 参数的可选默认值。

  • pubvar - 公共变量。

  • access - 可选关键字,用于声明过程是公共的还是私有的。有两个可用值: publicprivate

  • code - 用大括号括起来的代码。

Procedure过程也称为过程块ProcedureBlock

注:ProcedureBlock是创建类时默认的关键字,也就是意味着类方法实际就是ProcedureBlock

要调用过程,请使用以下语法:

java 复制代码
d Procedure^RoutineName(params) /* 忽略返回值 */
s x = $$Procedure^RoutineName(params)
  • 默认情况下,过程是私有的,这意味着只能从同一例程中的其他地方调用。如果外部调用则会提示<NOLINE>错误。

Routine.mac中添加Procedure过程方法:

java 复制代码
Procedure(x, y) {
	w "x + y = ", x + y,!
	q x + y
}
java 复制代码
ClassMethod Routine10()
{
	s ret = $$Procedure^Routine(2, 8)
	w ret,!
}
java 复制代码
USER>d ##class(M.Method).Routine10()
 
 s ret = $$Procedure^Routine(2, 8)
 ^
<NOLINE>zRoutine10+1^M.Method.1
  • 如果过程需要外部调用,可以创建公共的过程,在过程名称后使用 Public 关键字。可以从其他例程调用公共过程。
java 复制代码
Procedure(x, y) Public {
	w "x + y = ", x + y,!
	q x + y
}
java 复制代码
USER>d ##class(M.Method).Routine10()
x + y = 10
10
  • 过程的另一特点是声明公共变量。公共变量相当于全局变量,环境变量,可用于所有过程。即此过程调用的过程以及调用此过程的过程都可以使用全局变量。要定义公共变量,请将它们列在过程名称及其参数后面的方括号中。

Routine.mac中添加ProceduresPublicVarProceduresPublicVar1过程方法并声明公共变量ab,局部变量c,并调用ProceduresPublicVar1过程方法:

java 复制代码
ProceduresPublicVar(e, f) [a, b] Public{
	s a = 10
	s b = 20
	s c = 30
	w "e + f = ",e + f,!
	d ProceduresPublicVar1(9,9)
}
ProceduresPublicVar1(g, h)[a, b] Public{
	w "a:", $d(a),!
	w "b:", $d(b),!
	w "c:", $d(c),!
	w g + h,!
	w a + b,!
}

调用方法观察到ProceduresPublicVar1中公共变量ab$d判断是存在的,局部变量c不存在。

java 复制代码
ClassMethod Routine11()
{
	d ProceduresPublicVar^Routine(5, 10)
}
java 复制代码
USER>d ##class(M.Method).Routine11()
e + f = 15
a:1
b:1
c:0
18
30

注意:过程比以前在子例程,函数中提供的编码更先进。过程参数在过程内的作用域中自动是局部的。不需要 NEW命令来确保它们不会覆盖其他值,因为它们是过程专用的,此外,公共变量的显式声明允许引用应用程序中的全局变量。

过程与其他方法区别

  • 子例程Subroutine默认是公共的,不能返回值。
  • 函数Function默认是公共的,局部变量默认是共有变量,会覆盖同名外部变量,并且必须具有返回值。
  • 例程RoutineObjectScript程序。可以包括一个或多个过程、子例程和函数,以及三者的任意组合。
  • 建议使用过程Procedure,因为可以简化控制变量范围作用域。但是,在现有代码中,可能还会看到函数和子例程,需要能够识别并区分。在新的编码中不应该在编写函数与子例程。

下表总结了例程、子例程、函数和过程之间的差异:

Routine Subroutine Function Procedure
可以接受参数 no yes yes yes
可以返回值 no no yes yes
可以在例程之外调用(默认情况下) yes yes yes no
其中定义的变量在代码执行完成后可用 yes yes yes 取决于变量的性质

注:在日常用法中,通常称呼"子例程subroutine"可以表示过程procedure、函数function或子例程subroutine

类方法 - ClassMethod

类方法是我们在日常开发中最常用的方法类型,它是可以直接调用的方法,在其他语言当中称为静态方法。

在类中定义类方法,使用如下格式:

java 复制代码
ClassMethod MethodName(Arguments) as Classname [ Keywords]
{
//method implementation
}
  • MethodName - 方法的名称,方法名最长 180 个字符。

  • Arguments - 参数以逗号分割。

  • Classname - 可选的类名,表示此方法返回的值的类型。如果方法不返回值,则省略 As Classname 部分。

  • Keywords - 代表关键字。

要调用类方法的格式为:

java 复制代码
##class(Package.Class).Method(Args)
  • Package.Class - 完全限定类名。

  • Method - 方法名。

  • Args - 方法参数。

  • ##class - 调用前缀,不区分大小写。

如果调用的类在同一级包下,或使用 IMPORT 导入包。可以直接使用类名。

java 复制代码
##class(Class).Method(Args)

在同一个类中的方法互相调用或调用父类继承的方法,使用以下表达式:

java 复制代码
..Method(Args)

定义一个类方法:

java 复制代码
ClassMethod ClassMethod(x As %Integer, y As %Integer) As %Integer
{
		q x + y
}

调用该类方法:

java 复制代码
USER>w ##class(M.Method).ClassMethod(5,10)
15

Not ProcedureBlock关键字原理

此时我们查看类方法对应的int文件:

可以发现在cls定义的类方法实际上就是生成的int例程的过程Procedure。两者一一对应。也是为什么类方法默认的局部变量的作用域都是在自己的方法内部。

在定义一个类方法声明Not ProcedureBlock关键字:

java 复制代码
ClassMethod ClassMethod1(x As %Integer, y As %Integer) As %Integer [ ProcedureBlock = 0 ]
{
		q x + y
}

此时我们查看类方法对应的int文件:

可以发现在cls定义的类方法加上Not ProcedureBlock关键字后,生成的int文件中的该方法不是Procedure过程了,变成了函数Function或子例程Subroutine。前面小节讲过函数或子例程中局部变量为全局变量。也就是为什么在Not ProcedureBlock的类方法中需要加new命令原因了。

实例方法

实例方法实际上就对象方法,顾名思义调用该方法需要对象,有了对象我们就可以面向对象编程了。理解面向对象对于编程十分重要。

在类中定义实例方法,使用如下格式:

java 复制代码
Method MethodName(arguments) as Classname [ Keywords]
{
//method implementation
}

注:实例方法只与对象类相关、因为没有对象的实例就不能执行实例方法,所以实例方法只有在对象类中定义时才有用。

要调用实例方法的格式为:

java 复制代码
do obj.MethodName()
set value = obj.MethodName(args)
  • obj - 声明的对象,使用%New()来构造对象,相当于构造方法。
  • MethodName - 方法名称
  • args - 方法参数

创建一个简单的实例方法并调用:

java 复制代码
Method Method(x As %Integer, y As %Integer) As %Integer
{
	q x + y
}
java 复制代码
USER>w ##class(M.Method).%New().Method(5,10)
15

这里需要注意一点对象实际上也是可以调用类方法的。

java 复制代码
USER>w ##class(M.Method).%New().ClassMethod(5,10)
15

创造价值,分享学习,一起成长,相伴前行,欢迎大家提出意见,共同交流。

相关推荐
乐悠小码6 分钟前
数据结构------队列(Java语言描述)
java·开发语言·数据结构·链表·队列
史努比.7 分钟前
Pod控制器
java·开发语言
敲敲敲-敲代码16 分钟前
游戏设计:推箱子【easyx图形界面/c语言】
c语言·开发语言·游戏
ROC_bird..25 分钟前
STL - vector的使用和模拟实现
开发语言·c++
MavenTalk31 分钟前
Move开发语言在区块链的开发与应用
开发语言·python·rust·区块链·solidity·move
XiaoLeisj1 小时前
【JavaEE初阶 — 多线程】生产消费模型 & 阻塞队列
java·开发语言·java-ee
2401_840192271 小时前
python基础大杂烩
linux·开发语言·python
@东辰1 小时前
【golang-技巧】- 定时任务 - cron
开发语言·golang·cron
机器人天才一号1 小时前
C#从入门到放弃
开发语言·c#
Mr_Xuhhh3 小时前
递归搜索与回溯算法
c语言·开发语言·c++·算法·github