介绍
在 SAP 中,与"静态"(static)编程相比,ABAP 程序可以包括动态(dynamic)和静态部分。
动态编程是一个非常大的话题。它可以简单地定义为一种其行为/影响仅在执行
时才明显的技术。使用 ABAP 进行动态编程有许多方面。其中包括通用编程 ,如动态源代码生成。此外,使用字符串变量动态指定开放式 SQL 语句(子句)的部分内容,以及根据运行时才知道的类型创建数据对象,都属于动态编程的范畴。使用动态编程时,我们有时可能还需要在运行时确定数据对象的数据类型。这就是所谓的运行时类型标识(Runtime Type Identification
,RTTI)。
这一点有点类似编译型编程语言(C、Java)和解释型编程语言(Python)。前者通过定义数据类型和代码中的静态属性来提供所有属性,而后者的属性、名称、类型不是在编译时确定的,而是在运行时确定的。
ABAP 语言提供了许多新功能/特点来用于使程序动态化或避免对数值进行硬编码。我们将从字段符号(Field Symbols)和对引用的了解开始。
例如,我们将创建一个程序,输入表格名称并打印其内容。在了解动态编程之前,我们可以回这样写:
ABAP
REPORT zprint_test.
PARAMETERS: p_table(20) OBLIGATORY . "表名
SELECT *
FROM (p_table)
INTO TABLE @DATA(some_itab).
PERFORM frm_show_data USING some_itab.
字段符号和数据引用相关知识介绍
字段符号(Field symbols)和数据引用(data references)是动态编程的重要组合。数据引用是存储在引用变量中的数据对象的地址。字段符号是占位符,或这些数据对象的符号表示。
字段符号
字段符号的概念:
- 是几乎任何数据对象或现有数据对象的一部分的符号名称。
- 可以在程序运行时分配实际内存区域(使用
ASSIGN
)。请注意,如果字段符号之前确实已分配,则只能使用字段符号。 - 可用作操作数位置上数据对象的占位符:假设程序中有一个数据对象。此外,还可以使用字段符号来分配此数据对象的内存区域。访问字段符号就像访问命名数据对象或对象本身的一部分一样。
- 不要在程序的数据区域中保留物理空间,例如数据对象。相反,它们充当特定数据对象或对象的一部分所在的内存区域的动态标识符。
- 可以使用泛型数据类型或完整数据类型进行类型化。
- 使用语句
FIELD-SYMBOLS
或声明运算符FIELD-SYMBOL
进行声明。他们的名字必须包含在尖括号(<>
)之间。
语法:
- 用于声明完整的类型
ABAP
FIELD-SYMBOLS: <fs_i> TYPE i,
<fs_fli> TYPE zdemo_abap_fli,
<fs_tab_type> TYPE LINE OF some_table_type,
<fs_like> LIKE some_data_object.
- 用于声明通用类型
ABAP
FIELD-SYMBOLS <fs_c> TYPE c. "Text field with a generic length
FIELD-SYMBOLS <fs_cseq> TYPE csequence. "Text-like (c, string)
FIELD-SYMBOLS <fs_data> TYPE data. "Any data type
FIELD-SYMBOLS <fs_any_table> TYPE any table. "Internal table with any table type
- 分配数据对象
ASSIGN
语句将数据对象的内存区域分配给字段符号。分配内存区域后,即可处理内容。
xml
FIELD-SYMBOLS: <fs_banks> TYPE banks,
<fs_swift> TYPE swift,
<fs_banka> TYPE banka,
<fs_excel> LIKE gs_excel,
<fs_icon> TYPE char10,
<fs_type> TYPE char01,
<fs_msg> TYPE char128,
<fs_bankl> TYPE bankk,
<fs_regio> TYPE regio,
<fs_bnklz> TYPE bankk.
DATA: lv_count TYPE i.
LOOP AT <fs_out_tab>[] ASSIGNING <fs_out>.
UNASSIGN: <fs_excel>, <fs_icon>, <fs_type>, <fs_msg>, <fs_bankl>, <fs_regio>, <fs_bnklz>.
ASSIGN COMPONENT 'BANKS' OF STRUCTURE <fs_out> TO <fs_banks>.
ASSIGN COMPONENT 'SWIFT' OF STRUCTURE <fs_out> TO <fs_swift>.
ASSIGN COMPONENT 'BANKA' OF STRUCTURE <fs_out> TO <fs_banka>.
ASSIGN COMPONENT 'ICON' OF STRUCTURE <fs_out> TO <fs_icon>.
ASSIGN COMPONENT 'TYPE' OF STRUCTURE <fs_out> TO <fs_type>.
ASSIGN COMPONENT 'MSG' OF STRUCTURE <fs_out> TO <fs_msg>.
ASSIGN COMPONENT 'BANKL' OF STRUCTURE <fs_out> TO <fs_bankl>.
ASSIGN COMPONENT 'PROVZ' OF STRUCTURE <fs_out> TO <fs_regio>.
ASSIGN COMPONENT 'BNKLZ' OF STRUCTURE <fs_out> TO <fs_bnklz>.
ENDLOOP.
如果使用未赋值的字段符号,则会出现异常。在使用前,可以用下面的逻辑表达式检查赋值情况。如果字段符号已赋值,则语句为真。
vbnet
IF <fs> IS ASSIGNED.
...
ENDIF.
DATA(check) = COND #( WHEN <fs> IS ASSIGNED THEN `assigned` ELSE `not assigned` ).
- 取消分配
UNASSIGN
使用语句 UNASSIGN
,可以显式删除字段符号的赋值。CLEAR
语句仅初始化值。
ABAP
UNASSIGN <fs>.
数据引用
数据引用概念:
- 是包含引用的数据对象。
- 是"不透明的",即无法直接访问所包含的引用。若要访问内容,必须先取消引用这些变量。
- 是深层数据对象,如字符串和内部表。
- 键入附加的语句
REF TO
,后跟静态类型。请注意此上下文中的动态类型:此类变量的动态类型是它实际指向的数据类型。这个概念在分配的上下文中特别重要。 - 可以使用完整类型或泛型类型进行类型化。但是,只有
data
可以用作泛型类型。
声明数据引用变量
- 显示声明:使用静态类型的数据引用变量声明示例。静态类型可以是完整的,也可以是通用的(但只能使用数据)请注意,它们尚未指向数据对象。在此阶段初始引用变量包含空引用。
ABAP
DATA: ref_a TYPE REF TO i, "Complete data type
ref_b TYPE REF TO some_dbtab, "Complete data type
ref_c LIKE REF TO some_data_object,
ref_d TYPE REF TO data, "Generic data type
ref_e LIKE ref_a. "Referring to an existing data reference variable
- 内联声明 :使用引用运算符
REF
来进行内联声明
ABAP
DATA: dy_table TYPE REF TO data,
dy_line TYPE REF TO data.
DATA ref1 TYPE REF TO i.
使用字段符合和数据应用打印动态数据库表内容
声明用于输入要访问其数据的表格名称。此外,我们还要输入要显示的行数和列数(字段)。 1.
ABAP
PARAMETERS: p_tab(30) DEFAULT 'MARA',
row TYPE i,
columns TYPE i.
- 为内部表、表行和表字段声明字段符号。此外,还定义了内部表和结构的数据引用变量:
ABAP
DATA: tab_ref TYPE REF TO data,
struct_ref TYPE REF TO data.
FIELD-SYMBOLS: <my_struct> TYPE any,
<my_field> TYPE any,
<my_itab> TYPE ANY TABLE.
- 我们放置了一个小的检查
check
语句,以确保只有当用户输入的列值等于一个或多个时,程序才会运行。
css
CHECK columns GE 1.
- 接下来,我们使用创建数据语句创建数据对象。然后,我们取消引用并将它们分别赋值给占位符(字段符号)。如果用户输入的表名有误,就会产生
cx_sy_create_data_error
异常,因此我们需要在编码中使用catch
语句捕获该异常。
ABAP
TRY.
CREATE DATA tab_ref TYPE SORTED TABLE OF (p_tab)
WITH NON-UNIQUE DEFAULT KEY.
ASSIGN tab_ref->* TO <my_itab>.
CREATE DATA struct_ref TYPE (p_tab).
ASSIGN struct_ref->* TO <my_struct>.
CATCH cx_sy_create_data_error.
WRITE :/ 'The Table Name does not exist!'.
ENDTRY.
- 然后,编写
SELECT
语句以获取数据。参数中的名称作为表名,并读入字段符号<my_itab>
所指向的内部表中。行数也根据用户输入指定。
SQL
SELECT * FROM (p_tab) INTO TABLE <my_itab> UP TO row ROWS.
- 然后使用
describe_by_data
语句读取已创建的内部表行结构(由my_struc
指向)的描述。我们声明了类cl_abap_structdescr
的对象引用descr
。然后,调用cl_abap_typedescr
类的静态方法describe_by_data
,并返回给descr
变量。操作符 (?=
) 用于向下传递描述对象cl_abap_typedescr
的引用。
ini
DATA: descr TYPE REF TO cl_abap_structdescr.
descr ?= cl_abap_typedescr=>describe_by_data( <my_struct> ).
- 由于数据库表中的每一列(字段)在
descr
对象的组件中都表示为一行,因此要对其运行一个循环。使用from 1 to columns
只读取用户输入的列数。 - 还将创建一个位置
positions
内部表,用于保存屏幕上显示的每一列的位置(起始位置)。每个字段的长度用于查找下一个字段的位置。还包括打印表列标题的代码。
ABAP
DATA: BEGIN OF positions OCCURS 0,
position TYPE i,
END OF positions.
DATA: end_position TYPE i.
DATA: wa_key TYPE LINE OF abap_compdescr_tab.
positions-position = 1.
LOOP AT descr->components INTO wa_key FROM 1 TO columns.
WRITE AT positions-position '|'.
IF wa_key-length LT 10.
wa_key-length = 10.
ENDIF.
WRITE: wa_key-name.
APPEND positions.
positions-position = wa_key-length + positions-position.
ENDLOOP.
end_position = positions-position + 1.
WRITE AT end_position '|'.
- 然后,写入读取表格内容的主要部分。然后在字段符号
my_itab
指向的数据内部表上运行循环。每一行还运行一个do
循环,对每个字段值使用赋值语句。读取表的位置值,以获得相应字段列的正确位置。
ABAP
LOOP AT <my_itab> INTO <my_struct>.
NEW-LINE.
DO columns TIMES.
READ TABLE positions INDEX sy-index.
ASSIGN COMPONENT sy-index OF STRUCTURE <my_struct> TO <my_field>.
IF sy-subrc = 0.
WRITE AT positions-position '|'.
WRITE: <my_field>.
ELSE.
EXIT.
ENDIF.
WRITE: AT end_position '|'.
ENDDO.
ENDLOOP.
NEW-LINE.
ULINE AT 1(end_position).
测试运行
在数据浏览器程序中,语句读取用户输入的任何表格名称以及输入的行数和列数,然后显示表格中的相关数据。让我们看看程序代码是如何工作的。
参数语句向用户显示选择屏幕,并输入表名、表行和表列。
cl_abap_typedescr
类的静态方法 describe_by_data
提供了与传递的结构相关的描述对象。
拓宽 cast
方法用于将返回的对象存储到 cl_abap_structdescr
类型的 descr
变量中(因为 cl_abap_typedescr
类是 cl_abap_structdescr
的超类,而 cl_abap_typedescr
是一个抽象类)。
descr
对象包含一个组件内部表组件。然后在组件表上执行循环,以打印作为列标题的表字段名称。字段的长度也在考虑之列。例如,如果输入表格 MARA
,则各种表格字段的长度和名称将存在于 DESCR->COMPONENTS
中。位置表也会在此循环中填入,以存储每个字段的适当位置。这将在以后使用,以便在相应的列标题(字段名)下打印正确的数据。
最后,运行主循环。循环在内部表上执行,该表包含相关数据库表的所有行。在 do
循环中,处理表行的组件(字段)并将其分配给字段符号 <my_field>
。do
循环的运行次数等于用户要求的表字段数。然后输出字段的值。处理完所有要求的字段后,退出 do
循环。在 do
循环中,将读取之前填写的位置表,以获得特定单元格的正确位置。
通过位置表和 uline
语句,您可以为输出设置方框形状。
参考链接: