ABAP program结构
以下两种Processing blocks 是由 ABAP runtime system在运行时自动调用的:
? Event blocks
? Dialog modules
以下两种Processing blocks 由在 ABAP programs中手动编程调用的:
? Subroutines
? Function modules
? Methods
上面这三种Processing blocks是从ABAP programs编程调用的,所以也叫procedures
Subroutines(Form)一般供内部(局部)使用,而Function modules是借外部(全局)使用
Macros
宏与INCLUDE相似,但只能在同一程序内部使用。
DEFINE<macro>.
<statements>
END-OF-DEFINITION.
可以使用占位符(&1, &2, ..., &9)来使用调用时传递进的参数。
必须在使用宏之前进行定义。
宏不能调用自己,但可以调用其他的宏。
<macro> [<p1><p2> ... <p9>].
DATA: result TYPE i,
n1 TYPE i VALUE 5,
n2 TYPE i VALUE 6.
DEFINE operation.
result = &1 &2 &3.
output &1 &2 &3 result.
END-OF-DEFINITION.
DEFINE output.
write: / 'The result of &1 &2 &3 is', &4.
END-OF-DEFINITION.
operation 4 + 3.
operation 2 ** 7.
operation n2 - n1.
Include
Include programs 是一种全局的R/3 Repository objects
Includeprograms可以包括其他的Include文件,但Includeprograms不能包括自己
ABAP程序中的局部与全局变量
Dialog Modules、AT SELECTION-SCREEN…事件块、GET事件块、以及methods(类中的方法)、 subroutines(FORM子过程)、function modules(Function函数)中声明的变量为局部的,即在这些块里声明的变量不能在其他块里使用,但这些局部变量可以覆盖同名的全局变量;除这些处理块外,其他块里声明的变量都属于全局的,效果与在程序最开头定义的变量效果是一样的,所以可以在其他处理块直接使用(但要注意的是,需遵守先定义后使用的原则,这种先后关系是从语句书写顺序来说的,与事件块的本身运行顺序没有关系);另外,局部变量声明时,不管在处理块的任何地方,其效果都是相当于处理块的全局变量,而不像其他语言如Java那样:局部变量的作用域可以存在于任何花括号{}之间(这就意味着局部变量在处理过程范围内是全局的),如下面的i,在ABAP语言中还是会累加输出,而不会永远是1(在Java语言中会是1):
FORM aa.
DO 10 TIMES.
DATA: i TYPE i VALUE 0.
i = i + 1.
WRITE: / i.
ENDDO.
ENDFORM.
下面的示例中,在END-OF-SELECTION中声明的变量,在START-OF-SELECTION中可以直接使用:
END-OF-SELECTION.
DATA: c VALUE 'A'.
START-OF-SELECTION.
WRITE:/ c.
A
但如果是这样,则编译不能通过(所以只与书写顺序有关,有块的运行顺序无关):
START-OF-SELECTION.
WRITE:/ c.
END-OF-SELECTION.
DATA: c VALUE 'A'.
在Form里,有时我们不想改变全局定义的变量,一般采用的方式是在Form里声明与全局定义相同的变量(这样会隐藏同名的全局变量)。实质上也可以使用LOCAL语句来使全局变量局部化(即拷贝一份),修改局部化的变量不会影响全局的变量:
TABLES sflight.
PERFORM tabtest1.
WRITE: / sflight-planetype, sflight-price.
PERFORM tabtest2.
WRITE: / sflight-planetype, sflight-price.
FORM tabtest1.
sflight-planetype = 'A310'.
sflight-price = '150.00'.
WRITE: / sflight-planetype, sflight-price.
ENDFORM. "TABTEST1
FORM tabtest2.
LOCAL sflight.
sflight-planetype = 'B747'.
sflight-price = '500.00'.
WRITE: / sflight-planetype, sflight-price.
ENDFORM. "TABTEST2
注:LOCAL语句只能用在FORM与ENDFORM语句之间。LOCAL语句阻止了局部变量对全局变量的隐藏
在Form中声明的类型TYPES与DATA变量,会隐藏全局同名类型与变量的定义,所以我们在在From定义变量时,一般以“L_”为前缀:
TYPES word(10) TYPE c.
DATA text TYPE word.
text = '1234567890'.
WRITE / text.
PERFORM datatest.
WRITE / text.
FORM datatest.
TYPES word(5) TYPE c.
DATA text TYPE word.
"使用的是局部定义的变量,隐藏了全局同名变量
text = 'ABCDEFGHJK'.
WRITE / text.
ENDFORM.
如果想在Form运行完后,Form中的数据还需要保留到下次继续使用,则可以在Form中使用定义STATICS来定义变量(注:虽然是STATICS属于一个全局定义,但只在定义的Form中可见):
PERFORM datatest1.
PERFORM datatest1.
SKIP.
PERFORM datatest2.
PERFORM datatest2.
FORM datatest1.
TYPES f_word(5) TYPE c.
DATA f_text TYPE f_word VALUE 'INIT'.
WRITE f_text.
f_text = '12345'.
WRITE f_text.
ENDFORM.
FORM datatest2.
TYPES f_word(5) TYPE c.
STATICS f_text TYPE f_word VALUE 'INIT'.
WRITE f_text.
f_text = 'ABCDE'.
WRITE f_text.
ENDFORM.
Local Copies of Global Fields(Form中,全局变量的局部化拷贝)
在Form中,可以为全局变量创建局部副本:
ASSIGN LOCAL COPY OF <f> TO <FS>.
在subroutine(Form)子过程中,系统会在local stack中对全局数据创建一份local拷贝,并使用local field symbol<FS>来指向它,这样我们就可能通过字段符号<FS>来使用拷贝的数据,而不会影响全局变量数据。
注:<FS>字符符号一定要是在Form中定义的局部变量,否则编译不通过。
针对所有变量,可以使用带LOCAL COPY OF选项的ASSIGN语句,但ASSIGN COMPONENT不能带此选项。
其他可用于Form中的ASSIGN LOCAL COPY OF语句:
ASSIGN LOCAL COPY OF INITIAL<f> TO <FS>.
该语句也会创建一份local拷贝,但不会将<f>的内容拷贝过来,只拷贝变量的类型结构,内存清0
ASSIGN LOCAL COPY OF INITIAL LINE OF <itab> TO <FS>
ASSIGN LOCAL COPY OF INITIAL LINE OF (<itab>) TO <FS>.
以上两句对都是针对全局内表<itab>的行进行局部化拷贝,且拷贝过来的局部内存内容清0
DATA text(5) VALUE 'Text1'.
PERFORM routine.
WRITE text.
FORM routine.
FIELD-SYMBOLS <fs>.
"此语句中的<fs>不会修改全局变量 text
ASSIGN LOCAL COPY OF text TO <fs>.
WRITE <fs>.
<fs> = 'Text2'.
WRITE <fs>.
"此语句中的<fs>会修改全局变量 text
ASSIGN text TO <fs>.
WRITE <fs>.
<fs> = 'Text3'.
ENDFORM.
FORM subroutine
FORM subr [TABLES t1 [{TYPE itab_type}|{LIKE itab}|{STRUCTURE struc}]
t2 […]]
[USING { VALUE(p1)|p1 } [ { TYPE generic_type }
| { LIKE <generic_fs>|generic_para }
| { TYPE {[LINE OF] complete_type}|{REF TO type} }
| { LIKE {[LINE OF] dobj} | {REF TO dobj} }
| STRUCTURE struc]
{ VALUE(p2)|p2 } […]]
[CHANGING{ VALUE(p1)|p1 } [ { TYPE generic_type }
| { LIKE <generic_fs>|generic_para }
| { TYPE {[LINE OF] complete_type} | {REF TO type} }
| { LIKE {[LINE OF] dobj} | {REF TO dobj} }
| STRUCTURE struc]
{ VALUE(p2)|p2 } […]]
[RAISING {exc1|RESUMABLE(exc1)} {exc2|RESUMABLE(exc2)} ...].
generic_type:为通用类型,通用类型请参考通用类型
complete_type:为完全限制类型
<generic_fs>:为字段符号变量类型,如下面的 fs 形式参数
generic_para:为另一个形式参数类型,如下面的 b 形式参数
DATA: d(10) VALUE'11'.
FIELD-SYMBOLS: <fs> LIKE d.
ASSIGN d TO <fs>.
PERFORM aa USING <fs> d d.
FORM aa USING fs like <fs>
a like d
b like a.
WRITE:fs,/ a , / b.
ENDFORM.
如果没有给形式参数指定类,则为ANY类型
如果TABLES与USING、CHANGING一起使用时,则一定要按照TABLES、USING、CHANGING顺序声明。
值传递中的VALUE关键字只是在FORM定义时出现,在调用时PERFORM语句中无需出现,也就是说,调用时值传递和引用传递不存在语法格式差别。
TABLES table_parameters
如果使用的是TABLES,TYPE与LIKE后面只能接标准内表类型或标准内表对象,如果以STRUCTURE,则使用行结构类型或结构对象。
注意:TABLES中的TYPE 与 LIKE 后面只能使用标准内表,如果要使用排序内表或者哈希内表,则只能使用USING与CHANGING方式来代替。
TABLES定义的形式参数是自带一个表头与使用默认Key的内表。当把一个带表头的实参传递给Form时,表头也会传递过去,如果实际参数不带表头或者只传递了表体(使用了[]时),系统会自动为内表参数变量创建一个Form局部空的表头
DATA: tab TYPE STANDARD TABLE OF i WITH HEADER LINE WITH NON-UNIQUE DEFAULT KEY.
DATA: line LIKE LINE OF tab.
tab = 2."只是给表头赋值
PERFORM fr_form TABLES tab."注,这里的tab后面不能带[],否则表头会传递不过去
LOOP AT tab INTO line.
WRITE:/ line.
ENDLOOP.
FORM fr_form TABLES itab LIKE tab[]."这里传递类似+中的引用参数,即别名传递,即形参与实参是同一内存变量
"实参数的表头也会传递过来
WRITE: / itab.
"使用TABLES 定义的形式参数默认就会带有表头
itab = 1.
APPEND itab.
ENDFORM.
2
1
不管是以TABLES还是以USING(CHANGE)非值方式传递时,都是以类似于C++中的引用参数传递,即别名传递,即形参与实参是同一内存变量,不会再在被调用的Form中再将实参拷贝一份
TABLES是一种过时的内表参数定义法,可以使用USING 与 CHANGING代替:
DATA: tab TYPE HASHED TABLE OF i WITH HEADER LINE WITH UNIQUE DEFAULT KEY.
DATA: line LIKE LINE OF tab,
tab2 like tab[].
tab = 1.
INSERT tab INTO TABLE tab.
PERFORM fr_form USING tab[].
LOOP AT tab INTO line.
WRITE:/ line.
ENDLOOP.
"使用USING定义的形式内表itab不会自带表头
FORM fr_form USING itab LIKE tab[]."也是别名传递
line = 2.
INSERT line INTO TABLE itab.
"如果不注掉下面语句,则上面不会输出结果
* itab = tab2. "重新给字段符号变量内存赋值,实质上操作的还是主调函数内表内存
ENDFORM.
1
2
"如果使用值传递,则不会修改主调程序中的实参
FORM fr_form USING VALUE(itab) LIKE tab[]."拷贝传递
itab = tab2. "不会修改主调程序中的实参
ENDFORM.
USING /CHANGING parameters
在ABAP中,如果FORM中是以非值方式(即没有VALUE选项)定义形式参数,USING和CHANGING则都是字段符号的方式(即别名)进行传递,即形参实际上是主调函数传递过来的实参的一个别名(至于传递的内容是地址还是直接为变量内容,则要看定义形式参数时所指定的参数类型是地址类型——ref to,还是非ref to),在被调的Form中操作字段符号就是直接对主调函数实参的内存进行操作,所以ABAP的子过程中以非值方式定义的形参就是C++的引用类型参数传递;另外 TABLES 传递参数的方式也是以字段符号传递;如果FORM的参数定义成Value传值方式,则在传递的过程时会对实参进行拷贝后再传递,至于传递的是地址还是直接为变量内容,则同样要看形参是否类型为 REF TO 类型。
注意:上表中的引用传递即ABAP中的字段符号(别名)传递,而不是说传递的实参内容为真正的传址,至于传递的是地址还是直接为变量内容,则同样要看形参类型是否定义为了 REF TO 类型
通过引用传递时,USING与CHANGING完全一样(此时CHANGING不会在等Form执行完后再去修改实参内容,而是只要修改语句执行,即修改了实参);但CHANGING为值传递方式时,需要在Form执行完后,才去真正修改实参变量的内容,所以CHANGING传值与传引用其结果都是一样:修改了实参内容,只是修改的时机不太一样
CHANGING值传递又称为值和结果传递:
FORM … CHANGING ... VALUE(P1) ... VALUE(P2)
该传递中“值(VALUE)”指的是在子程序调用“期间”,是值传递方式,即在FORM中是不能直接对主调程序中的实参进行修改,但在子程序正常结束时(ENDFORM, RETURN, CHECK, or EXIT),将把对形式参数的更改“结果”“复制”给实参,因而主程序中的实参数据对象值仍然会被修改(这种方式实际上是一种双向数据拷贝,数据传递和返回都需要拷贝)。但是一旦子程序非正常中断(If the subroutine is ended by a errormessage or an exception),参数变量仍然保持原值。
DATA: op1 TYPE i,
op2 TYPE i,
res TYPE i.
op1 = 3.
op2 = 4.
PERFORM multip USING op1 op2 CHANGING res.
WRITE: / 'After subroutine:',/ 'RES=' UNDER 'RES=', res.
FORM multip USING value(o1) value(o2)
CHANGING VALUE(r).
r = o1 * o2.
WRITE: / 'Inside subroutine:', / 'R=', r, 'RES=', res.
ENDFORM.
CHANGING VALUE要到Form执行结束后才改变外面的值
FORM multip USING value(o1) value(o2)
CHANGING r.
r = o1 * o2.
WRITE: / 'Inside subroutine:', / 'R=', r, 'RES=', res.
ENDFORM.
RAISING exc
RAISING {exc1|RESUMABLE(exc1)} {exc2|RESUMABLE(exc2)} ...
这里的exc1、exc2…可以是CX_STATIC_CHECK 、CX_DYNAMIC_CHECK或者是其子类,如果Form中出现了运行时错误,但Form签名又没有使用RAISING向上抛,则程序会直接挂掉,所以最好是向上抛
FORM subform RAISING cx_static_check cx_dynamic_check.
...
ENDFORM.
STRUCTURE struc
DATA: BEGIN OF line,
col1,
col2,
END OF line.
DATA text(2) VALUE 'XY'.
PERFORM demo USING text.
FORM demo USING p STRUCTURE line.
WRITE: p-col1, p-col2.
ENDFORM.
传递的实参将会以line结构变量数据类型视图来操作。注:实参的长度不能小数形参定义的长度。
FORM引用传递研究
DATA : i TYPE i VALUE 100.
WRITE: / 'frm_ref===='.
PERFORM frm_ref USING i .
WRITE: / i."200
WRITE: / 'frm_val===='.
i = 100.
PERFORM frm_val USING i .
WRITE: / i."100
WRITE: / 'frm_ref2===='.
"不能将下面的变量定义到frm_ref2过程中,如果这样,下面的dref指针在调用frm_ref2 后,指向的是Form中局部变量内存,为不安全发布,运行会抛异常,因为From结束后,它所拥有的所有变量内存空间会释放掉
DATA: i_frm_ref2 TYPE i VALUE 400.
i = 100.
DATA: dref TYPE REF TO i .
get REFERENCE OF i INTO dref.
PERFORM frm_ref2 USING dref ."传递的内容为地址,但还是属于引用传递
WRITE: / i."4000
field-SYMBOLS : <fs> TYPE i .
ASSIGN dref->* to <fs>."由于frm_ref2过程中已修改了dref的指向,现指向了i_frm_ref2 变量的内存空间
WRITE: / <fs>."400
WRITE: / 'frm_val2===='.
i = 100.
DATA: dref2 TYPE REF TO i .
get REFERENCE OF i INTO dref2.
PERFORM frm_val2 USING dref2 .
WRITE: / i."4000
ASSIGN dref2->* to <fs>.
WRITE: / <fs>."4000
FORM frm_ref USING p_i TYPE i ."C++中的引用参数传递:p_i为实参i的别名
WRITE: / p_i."100
p_i = 200."p_i为参数i的别名,所以可以直接修改实参
ENDFORM.
FORM frm_val USING value(p_i)."传值:p_i为实参i的拷贝
WRITE: / p_i."100
p_i = 300."由于是传值,所以不会修改主调程序中的实参的值
ENDFORM.
FORM frm_ref2 USINGp_i TYPE REF TO i ."p_i为实参dref的别名,类似C++中的引用参数传递(传递的内容为地址,并且属于引用传递——别名)
field-SYMBOLS : <fs> TYPE i .
"现在<fs>就是实参所指向的内存内容的别名,代表实参所指向的实际内容
ASSIGN p_i->* to <fs>.
WRITE: / <fs>."100
<fs> = 4000."直接修改实参所指向的实际内存
DATA: dref TYPE REF TO i .
get REFERENCE OF i_frm_ref2 INTO dref.
"由于USING为C++的引用参数,所以这里修改的直接是实参所存储的
"地址内容,这里的p_i为传进来的dref的别名,是同一个变量,所以
"实参的指向也发生了改变(这与Java中传递引用是不一样的,Java中
"传递引用时为地址的拷贝,即Java中永远也只有传值,但C/C++/ABAP中可以传递真正引用——别名)
p_i = dref."此处会修改实参的指向
ENDFORM.
FORM frm_val2 USING VALUE(p_i) TYPE REF TO i ."p_i为实参dref2的拷贝,类似Java中的引用传递(虽然传递的内容为地址,但传递的方式属于地址拷贝——值传递)
field-SYMBOLS : <fs> TYPE i .
"现在<fs>就是实参所指向的内存内容的别名,代表实参所指向的实际内容
ASSIGN p_i->* to <fs>.
WRITE: / <fs>."100
<fs> = 4000."但这里还是可以直接修改实参所指向的实际内容
DATA: dref TYPE REF TO i .
get REFERENCE OF i_frm_ref2 INTO dref.
"这里与过程 frm_ref2 不一样,该过程 frm_val2 参数的传递方式与
"java中的引用传递是原理是一样的:传递的是地址拷贝,所以下面不会修改主调程序中实参dref2的指向,它所改变的只是拷贝过来的Form中局部形式参数的指向
p_i = dref.
ENDFORM.
调用PERFORM
PERFORM <subr>(<prog>) [USING ... <pi>... ][CHANGING... <pi>... ] [IF FOUND].
prog只能是静态指定,其外面的括号表示的是一种外部Form调用,而不是动态变量的意思,值为程序名字符串字面常量
IF FOUND选项表示如果不存在指定的subr,则不会执行,也不会报错
当从外面调用时,如果prog程序未加载,则会将它加载到当前的internal session
如果动态指定Form名与程序名,则这样使用:
PERFORM (<fsubr>)[IN PROGRAM (<fprog>)][USING ... <pi>... ]
[CHANGING... <pi>... ]
[IF FOUND].
这里的括号表示动态指定名称,如果去掉,则与上面语法相当
PERFORM <idx> OF <subr1><subr2>.... <subrn>.
该语句只能调用内部的Form,并且不能带有参数。该语句会根据指定的位置idx来调用后面指定的Form,比如,idx = 2 时,则会调用subr2
DATA: a1 TYPE p DECIMALS 3,
a2 TYPE i,
a3 TYPE d,
a4 TYPE spfli-carrid,
a5 TYPE c.
"下面三种调用效果是一样的
PERFORM subr USING a1 a2 a3 a4 a5.
PERFORM subr CHANGING a1 a2 a3 a4 a5.
PERFORM subr USING a1 a2 a3 CHANGING a4 a5.
FORM subr USING
value(f1) TYPE p
value(f2) TYPE i
f3 LIKE a3
CHANGING
value(f4) TYPE spfli-carrid
f5.
ENDFORM.
Function Modules
Function Groups
功能组是function modules的容器。不能直接运行function group。
当你调用一个function module时,系统加将整个function group加载到主调程序所在的internal session中(如果还未加载时)。
当使用Function Builder创建函数组时,系统会自动创建main program与相应的include 程序
上图中,
l <fgrp>为Function Group的名称,SAPL<fgrp>为主程序名,它将Function Group里的所有Include文件包括进来(所以如果在Function Module里使用到了某个Include文件里定义的变量或Form时,就不需要再在Function Module里Include这里文件了)。它除了INCLUDE语句之外,没有其他语句了。
l L<fgrp>TOP,里面有FUNCTION-POOL语句(FUNCTION-POOL语句类似于REPORT或者PROGRAM语句),以及整个函数都可以使用的全局数据定义。
l L<fgrp>UXX,也只有INCLUDE语句,它所包括的Include文件为相应具体Function Module所对应Include文件名:L<fgrp>U01, L<fgrp>U02, ...这些Include文件实际上包含了所对应的Function Module代码(即双击它们进去就是对应的Function,而显示的不是真正Include文件代码)。
l L<fgrp>U01,和L<fgrp>U02中的01、02编号对应L<fgrp>UXX中的“XX”,代表其创建先后的序号,例如L<fgrp>U01和L<fgrp>U02是头两个被创建的函数,在函数组中创建出的函数代码就放在相应的L<fgrp>UXX(这里的XX代表某个数字,而不是字面上的XX)Include头文件中了。
l L<fgrg>FXX,用来存一些Form子过程,并且可以被所有的Function Modules所使用(不是针对某个Function Module的,但一般在设计时会针对每个Function Module设计这样单独的Include文件,这是一个好习惯),并且在使用时不需要在Function Module中使用INCLUDE语句包含它们(因为这些文件在主程序SAPL<fgrp>里就已经被Include进来了——从这里也可以看出这些Include文件中定义的Form是所有Function所能共享的)。另外, L<fgrg>FXX中的F是指Form的意思,这是一种名称约束而已,在创建时我们可以随便指定,一般还有IXX(表示些类Include文件包括的是一些PAI事件中调用的Module,有时干脆直接使用L<fgrg>PAI或者L<fgrg>PAIXX),OXX(表示些类Include文件包括的是一些PBO事件中调用的Module,有时干脆直接使用L<fgrg>PBO或者L<fgrg>PBOXX)。
如果Form只被单独函数所使用,可以将这些Form直接定义在Function Module里的ENDFUNCTION语句后面
当一个Function Module被调用的时候,该Function Module所在的整个Function Group都被装入内存,然后该Function Module得到执行,该Function Group一直保留在内存中,直到调用Function Module的程序关闭。这样当该Function Group中的另外一个Function Module被调用的时候,就不用重新装载整个Function Group到内存,而可以直接访问Function Group中的数据对象、屏幕、或子程序等,并且当被调入内存的Function Group中的某一个全局变量被其中一个Function Module更改时,则同一个Function Group中的其他Function Module可能同进受到该全局变量的状态的影响。
函数实参的修改
l import 用于定义输入参数。此情况下如果使用传值,在定义参数时钩选Pass Value 选项后,函数头注释会发现参数类型为VALUE;如果传引用,即未钩选Pass Value 选项,函数头注释会发现参数类型为REFERENCE。在函数里,引用类型且为Import类型的实参是不能修改的,如果试着修改外面传递进来的实参,则编译出错(值传递类型的输入参数可以修改,但修改后,主程序中的实参根本没有被修改)。Import类型的参数除了可选的外,在调用时必须输入
l export 用于定义输出参数。在函数中可以直接修改实参的值。也分传值与传址。
l changing 用于定义输入输出参数。通过此类型的参数,可以修改实参的值。另外,也分传值与传址。
l Tables 用于定义内表参数,与过程中的TABLES定义的参数完全一样(在传递过程中只是传字段符号,即传址),请参考过程的内表参数。
l Exception 用于定义可能出现的异常。
过程与函数中的参数如果以非值方式传递,则表示以ABAP中的字段符号进行传递,即C++的引用类型参数(其实在ABAP中所有参数传递只有传值与传字段符号(别名,即所谓的传址)两种方式,至于传递的内容到底地址还是非地址,则要看过程与函数的参数在定义时是否是 TYPE REF TO 或 LIKE REF TO 方式)。
CALL FUNCTION <module>
[EXPORTING f1 = a1.... fn = an]
[IMPORTING f1 = a1.... fn = an]
[CHANGING f1 = a1.... fn = an]
[TABLES f1 = a1.... fn = an]
[EXCEPTIONSe1= r1.... en= rn [ERROR_MESSAGE= rE][OTHERS = ro]].
上面fn都是形参,an都是实参。
EXCEPTIONS:可以处理功能模块程序代码中发生的异常。如果在调用时出现ei异常,系统停止执行功能模块,并且不将任何参数值从功能模块传递给主调程序。如果在EXCEPTION选项中指定了ei异常,主调程序会将ri分配给SY-SUBRC,ri必须是数字常量。如果在异常列表中指定了ERROR_MESSAGE,你将会影响Function Module中的message处理(MESSAGE ... RAISING语句只能在function modules中使用),在Call Function异常列表中使用系统隐式异常ERROR_MESSAGE来迫使系统忽略掉Function Module中的MESSAGE ... RAISING语句中的RAISING选项(Call Function异常列表中在只有ERROR_MESSAGE异常的情况下),将MESSAGE ... RAISING语句当作普通的MESSAGE语句来处理,并且消息会在主调程序中被ERROR_MESSAGE异常所捕获,具体捕获过程与MESSAGE的类型相关:
l 类型为S, I, and W的消息将会直接被忽略,也不会显示消息
l 如果是E and A类型的消息,将会被ERROR_MESSAGE异常所捕获,并且将SY-SUBRC设置成<rE>
关于ERROR_MESSAGE的详细使用,请参考《User Dialogs.docx》文档中的《CALL FUNCTION[error_message]》章节。
OTHERS类型的异常能捕获到所有除EXCEPTIONS选项后面的异常列表
因为功能模块可以被任何程序所调用,在功能模块中的参数只能参照内置基本类型、通用类型以及数据字典类型进行定义的类型,而不能是程序中自定义类型。
function fuc_ref .
*"-------------------------------------------------------------------
*"*"Local Interface:
*" IMPORTING
*" REFERENCE(I_I1) TYPE I REFERENCE(别名)为参数的默认传递类型
*" VALUE(I_I2) TYPE I定义时勾选了Pass Value选项才会是 VALUE类型
*" REFERENCE(I_I3) TYPE REF TO I
*" VALUE(I_I4) TYPE REF TO I
*" EXPORTING
*" REFERENCE(E_I1) TYPE I
*" VALUE(E_I2) TYPE I
*" REFERENCE(E_I3) TYPE REF TO I
*" VALUE(E_I4) TYPE REF TO I
*" TABLES
*" T_1 TYPE ZJZJ_ITAB
*" CHANGING
*" REFERENCE(C_I1) TYPE I
*" VALUE(C_I2) TYPE I
*" REFERENCE(C_I3) TYPE REF TO I
*" VALUE(C_I4) TYPE REF TO I
*"-------------------------------------------------------------------
write: / i_i1."1
"由于i_i1为输入类型参数,实参不能被修改。这里i_i1是
"以C++中的引用(别名)参数方式传递参数,所以如果修改了i_i1就
"会修改实际参数,所以函数中不能修改REFERENCE 的 IMPORTING类型的参数,如果去掉下面注释则编译出错
"i_i1 = 10.
write: / i_i2."2
"虽然i_i2是IMPORTING类型的参数,但不是REFERENCE类型,所以可以修改,编译能通过
"但不会修改外面实参的值,只会影响该函数局部
i_i2 = 20.
field-symbols: <fs> type i .
assign i_i3->* to <fs>.
"由于i_i3存储的是地址,所以先要解引用再能使用
write: / <fs>.
"同上面,REFERENCE的IMPORTING类型的参数不能被修改:这里即不能修改实参的指向
"GET REFERENCE OF 30 INTO i_i3.
"虽然不可以修改实参的指向,但可以修改实参所指向的实际内容
<fs> = 30.
assign i_i4->* to <fs>.
"i_i4存储也的是地址,所以先要解引用再能使用
write: / <fs>.
"只会修改函数中的局部参数i_i4的指向,但并不会修改实参的指向,所以可以修改
get reference of 40 into i_i4.
"虽然不能修改实参的指向,但可以直接修改实参的所指向的实际内容
<fs> = 400.
WRITE: / c_i1."111
"c_i1为实参的别名,修改形参就等于修改实参内容
c_i1 = 1110.
WRITE: / c_i2."222
"c_i2为实参的副本,所以不会影响实参的内容,但是,
"由于是CHANGING类型的参数,且为值传递,在函数正常执行完后,还是
"会将该副本再次拷贝给实参,所以最终实参还是会被修改
c_i2 = 2220.
ENDFUNCTION.
调用程序:
DATA: i_i1 TYPE i VALUE 1,
i_i2 TYPE i VALUE 2,
i_i3 TYPE REF TO i ,
i_i4 TYPE REF TO i ,
c_i1 TYPE i VALUE 111,
c_i2 TYPE i VALUE 222,
c_i3 TYPE REF TO i ,
c_i4 TYPE REF TO i ,
t_1 TYPE zjzj_itab WITH HEADER LINE.
DATA: i_i3_ TYPE i VALUE 3.
GET REFERENCE OF i_i3_ INTO i_i3.
DATA: i_i4_ TYPE i VALUE 4.
GET REFERENCE OF i_i4_ INTO i_i4.
DATA: c_i3_ TYPE i VALUE 333.
GET REFERENCE OF c_i3_ INTO c_i3.
DATA: c_i4_ TYPE i VALUE 444.
GET REFERENCE OF c_i4_ INTO c_i4.
CALL FUNCTION 'FUC_REF'
EXPORTING
i_i1 = i_i1
i_i2 = i_i2
i_i3 = i_i3
i_i4 = i_i4
TABLES
t_1 = t_1
CHANGING
c_i1 = c_i1
c_i2 = c_i2
c_i3 = c_i3
c_i4 = c_i4.
WRITE: / i_i2."2
WRITE: / i_i3_."30
WRITE: / i_i4_."400
WRITE: / c_i1."1110
WRITE: / c_i2."2220