文章目录
- 前言
- 一、案例介绍/笔者需求
- 二、B报表(被SUBMIT的程序)
- 三、A报表(用SUBMIT的程序)
- `a.`SUBMIT B程序
- `b.`AND RETURN 详解
- `c.`CL_SALV_BS_RUNTIME_INFO 捕获ALV数据的方法
- `d.`捕获ALV 表头 布局 等 信息的方法
- `e.`结束捕获模式 (ALV不能显示的原因)
- `f.`使用 WITH 给被SUBMIT的程序选择屏幕设值
- `g.`使用 内存 传递接收数据
- 四、其他扩展语法
- `a.`将被调用程序ALV数据写入内存 EXPORTING LIST TO MEMORY
- `b.`使用被调用程序的选择屏幕 VIA SELECTION-SCREEN
- `c.`给被调用程序指定变式 USING SELECTION-SET '<变式名>'
- `d.`设定执行后台作业 SUBMIT ... VIA JOB
- END、总结
前言
本文目前1w字+ 会持续更新,先 点赞 关注 收藏
制作不易 谢谢🤞
这篇文章给大家介绍一下SAP ABAP SUBMIT关键字的详细用法,SUBMIT这个东西我感觉还是非常非常实用的,在我们日常开发中可以巧妙的解决许多问题,我感觉它就是为了数据的二次加工而生的,反正是一个非常强大实用的东西,例如我们需要给一些标准报表加字段的话不要去Copy一个标准程序再修修改改,而应该使用SUBMIT调用执行标准程序后去获取程序显示ALV报表的内表,拿到这个内表之后再对数据进行加字段或者一切其他操作。这只是SUBMIT的一种用法,我们还可以灵活的使用它做一些其他事情,例如CRM删除过期邮件的动作可能要手动并且有序的执行几个标准程序,而我们就可以开发一个程序把需要的选择界面变量设计好然后使用SUBMIT逐步调用程序传递选择屏幕的值。这样就减少了用户的操作步骤。下面的案例我们将由易到难循序渐进的学会使用SUBMIT。
一、案例介绍/笔者需求
笔者在最近工作中有一个需求是关于财务一个报表的调整,大家都知道财务报表挺难的涉及大量的计算。这个报表设计之初本来是只能显示一个客户的数据,而最后业务又要调整成显示多个客户的数据,虽然需求看着简单但是用推倒重来 这个词描述毫不夸张。因为原来的计算逻辑是非常复杂的我是不想动的。所以我就想的是把这个程序Copy一份然后再LOOP SUBMIT 这个程序这样就能获取到多个客户的数据了然后再做调整显示。
下面的案例中我们将有A、B两个FUNALV报表我会尽量简化代码和报表,好让大家Copy Code即用。A报表是我们的主程序会使用SUBMIT来调用B程序,首先我们会把B程序设计好等着被A调用。
二、B报表(被SUBMIT的程序)
直接创建一个程序把下面这段代码复制进去 激活 运行 先让B报表ok。
REPORT zglyntest04."B报表
**********************设置显示的内表数据******************
TYPES: BEGIN OF ty_student,
name TYPE c LENGTH 40,"姓名
class TYPE c LENGTH 10,"班级
age TYPE i ,"年龄
sex TYPE c LENGTH 1 ,"性别
origin TYPE c LENGTH 40,"籍贯
END OF ty_student.
DATA: gt_students TYPE TABLE OF ty_student.
gt_students = VALUE #(
( name = '美国队长' class = '一班' age = '99' sex = '男' origin = '芝加哥')
( name = '钢铁侠' class = '二班' age = '54' sex = '男' origin = '华盛顿')
( name = '黑寡妇' class = '一班' age = '36' sex = '女' origin = '旧金山')
( name = '蜘蛛侠' class = '六班' age = '20' origin = '皇后区')
( name = '小贱贱' class = '三班' age = '37' origin = '柏林' )
).
**********************设置ALV表头*************************
DATA: gt_fieldcat TYPE slis_t_fieldcat_alv.
DATA: gs_fieldcat TYPE slis_fieldcat_alv.
DEFINE appendfield.
CLEAR gs_fieldcat.
gs_fieldcat-fieldname = &1.
gs_fieldcat-seltext_l = &2.
APPEND gs_fieldcat TO gt_fieldcat.
END-OF-DEFINITION.
appendfield 'NAME ' '姓名'.
appendfield 'CLASS ' '班级'.
appendfield 'AGE ' '年龄'.
appendfield 'SEX ' '性别'.
appendfield 'ORIGIN' '籍贯'.
**********************调用显示ALV***************************
CALL FUNCTION 'REUSE_ALV_GRID_DISPLAY'
EXPORTING
it_fieldcat = gt_fieldcat
i_callback_program = sy-cprog
TABLES
t_outtab = gt_students.
三、A报表(用SUBMIT的程序)
我会从最简单最简单的调用方式逐步根据需求介绍SUBMIT的大多数常用扩展语法,如果是有使用基础只是想过来Copy相关代码的还请转到文章目录点击跳转到对应位置,我都会把重要的关键字语法在目录显示,好让大家查找方便。
a.
SUBMIT B程序
这一步很简单也很重要,大家要先对这个 SUBMIT 有个理解认识它有什么作用。
1.直接SUBMIT
我们又创建了一个程序A,代码非常简单就直接SUBMIT 了B报表,现在我们执行这个A报表会发现它直接显示的是B报表的内容,但是请注意我还有一句打印的代码,我们会发现这个代码居然没执行 。。。。,这个发现会引出我们SUBMIT和SAP很重要的一个概念 ,你们先打断点在 SUBMIT处然后再执行程序并逐步F5调试代码,观察它整个的运行逻辑是什么。
2.断点观察
执行卡在SUBMIT处之后按F5会 发现居然进入了B报表,这也就是为什么能显示B报表的内容了。但是A程序没有执行打印的代码,这样说明A程序执行完SUBMIT语句之后它就结束执行了 A程序已经完成它的使命了。但是我们并不像让它这样结束我想让它继续执行SUBMIT之后的代码该如何呢??请看下面章节
b.
AND RETURN 详解
这里我们会介绍SUBMIT扩展语法AND RETURN的作用和它的相关概念。
1.执行SUBMIT之后的代码
SUBMIT有一个扩展语法叫做 AND RETURN,加上之后我们会发现A程序SUBMIT完显示了B报表,然后当我们点击返回之后 等于B报表代码执行完了使命结束了,神奇的是有了AND RETURN关键字之后A程序执行了SUBMIT之后的代码打印了相关结果。为什么会这样呢?下面我会详细解释相关概念。
2.通俗易懂的理解
没有AND RETURN: 如果没有AND RETURN关键字,程序A会调用程序B并立即终止。程序B执行完毕后,控制权不会返回到程序A。
有AND RETURN: 当使用AND RETURN关键字时,程序A会调用程序B并等待其执行完毕。程序B执行完毕后,控制权返回到程序A,程序A继续执行SUBMIT语句之后的代码。
2.SAP LUW 的概念
AND RETURN 可以引出SAP中的一个很重要的概念叫SAP LUW,让我们先理解一下什么是LUW,首先LUW是一个单独的概念,和SAP LUW还是有区别的。
1、LUW: LUW一般指的是数据库LUW,它的作用是确保一组数据库操作要么全部成功,要么全部失败。一组数据库操作是指从一个COMMIT WORK语句开始,到下一个COMMIT WORK或ROLLBACK WORK语句结束。 LUW有几个特性1、 原子性:LUW中的所有操作要么全部执行成功,要么全部回滚,不会出现部分成功、部分失败的情况。2、 一致性:LUW确保数据在事务开始和结束时的一致性。3、 隔离性:在LUW执行期间,其他事务不能看到其中的中间状态。4、 持久性:一旦LUW成功完成,所有操作的结果将永久保存到数据库中。
2、SAP LUW: SAP LUW是由SAP系统管理的一组逻辑上相关的工作单元,可能包含多个数据库LUW。SAP LUW的管理通过SAP的事务管理器和对话控制器实现。SAP LUW有这几个特点1、 跨多个数据库LUW:SAP LUW可以包含多个数据库LUW,跨越多个事务。每个数据库LUW在其自身的事务中操作。2、 一致性检查:SAP LUW确保整个工作单元的逻辑一致性。例如,一个业务操作可能涉及多个数据库表的更新,SAP LUW确保这些操作在逻辑上保持一致。3、 事务管理:SAP LUW通过SAP事务管理器进行管理,事务的启动、提交和回滚由系统自动处理。
3、区别: 数据库LUW 在单个事务中处理数据库操作,例如在一张表中插入记录。如果操作失败,回滚整个事务。SAP LUW 在更复杂的业务场景中处理多个相关操作,例如更新多个表,调用多个程序,并确保所有操作的一致性。
3.AND RETURN 对 LUW 的影响
如果我们A、B两个报表都有数据库操作相关操作,也就是说A报表会有自己的LUW B报表也有自己的LUW,当我们A报表使用SUBMIT … AND RETURN 调用B报表的时候 A报表可以在自己的LUW中保持住,当执行到B报表的时候B报表也是会有自己的LUW的它两不会冲突影响,当B报表执行完之后返回A报表此时A报表仍然在它自己的LUW中。因此,在调用程序和被调用程序之间,事务处理是相互独立的,确保数据一致性。
c.
CL_SALV_BS_RUNTIME_INFO 捕获ALV数据的方法
上面的操作我们只是调用了B报表并没有拿到B报表ALV显示的内表 ,这一步我们会通过类cl_salv_bs_runtime_info的静态方法捕获到显示的ALV的数据。这个类有许多方法以及方法中的参数我只介绍常用的,具体还有哪些大家可以自己SE24去看。
1.捕获前的准备工作
我们在捕获前必须先 启动 ALV 捕获模式,不然你是无法捕获到ALV相关信息的。
cl_salv_bs_runtime_info=>set(
EXPORTING display = abap_false "是否在前台显示ALV 如果设置为abap_false则此GUI窗口后续执行ALV都不会再显示包括使用SUBMIT的主程序
metadata = abap_true "是否捕获ALV表头 布局信息等 如果设置为abap_false则获取相关数据时会报错
data = abap_true )."是否捕获ALV显示的数据内表 如果设置为abap_false则获取相关数据时会报错
2.捕获ALV显示的内表
捕获显示alv的内表有两个静态方法分别是 get_data 和 get_data_ref 。前者接收方法回参的时候数据必须是一个确定的内表结构类型 后者可以接收任意结构类型的内表 它的参数参考的是data通用类型,它是动态的所以即使我们不知道显示的alv内表类型也可以用它来获取接收。
1、 get_data 一般用这个就够了
2、 get_data_ref 这个是动态的所以读取数据的时候会比较麻烦。
下面的获取方式是分配到相匹配的结构中
下面是根据字段名动态获取
REPORT zglyntest05."A报表
*TYPES: BEGIN OF ty_student,
* name TYPE c LENGTH 40,"姓名
* class TYPE c LENGTH 10,"班级
* age TYPE i ,"年龄
* sex TYPE c LENGTH 1 ,"性别
* origin TYPE c LENGTH 40,"籍贯
*END OF ty_student.
FIELD-SYMBOLS <gt_data> TYPE ANY TABLE.
*FIELD-SYMBOLS <gs_data> TYPE any.
DATA gt_data TYPE REF TO data.
cl_salv_bs_runtime_info=>set(
EXPORTING display = abap_false "是否在前台显示ALV
metadata = abap_false "是否捕获ALV表头 布局信息等 如果设置为abap_false则获取数据时会报错
data = abap_true )."是否捕获ALV显示的数据内表 如果设置为abap_false则获取数据时会报错
SUBMIT zglyntest04 AND RETURN.
cl_salv_bs_runtime_info=>get_data_ref( IMPORTING r_data = gt_data )."由于gt_data不是内表所以我们不能循环或者直接读取
ASSIGN gt_data->* TO <gt_data>.
LOOP AT <gt_data> ASSIGNING FIELD-SYMBOL(<gs_data>).
ASSIGN COMPONENT 'name' OF STRUCTURE <gs_data> TO FIELD-SYMBOL(<fv_name>).
WRITE / <fv_name>.
ENDLOOP.
d.
捕获ALV 表头 布局 等 信息的方法
前提是捕获模式的 metadata = abap_true,不然会报错上面获取alv内表的也一样得把捕获模式的 data = abap_true。
1.get_metadata
能拿到表头信息就能拿到内表中所有字段了,我们就可以动态获取数据了。
TYPES:
BEGIN OF s_type_metadata,
is_hierseq TYPE abap_bool,
tabname TYPE string,
tabname_line TYPE string,
s_keyinfo TYPE kkblo_keyinfo,
s_layout TYPE lvc_s_layo,
t_fcat TYPE lvc_t_fcat,
t_filter TYPE lvc_t_filt,
t_sort TYPE lvc_t_sort,
END OF s_type_metadata .
DATA value TYPE s_type_metadata .
cl_salv_bs_runtime_info=>set(
EXPORTING display = abap_false "是否在前台显示ALV
metadata = abap_true "是否捕获ALV表头 布局信息等 如果设置为abap_false则获取数据时会报错
data = abap_true )."是否捕获ALV显示的数据内表 如果设置为abap_false则获取数据时会报错
SUBMIT zglyntest04 AND RETURN.
cl_salv_bs_runtime_info=>get_metadata( RECEIVING value = value ).
e.
结束捕获模式 (ALV不能显示的原因)
我们好多人在使用SUBMIT配合捕获ALV信息类的时候会出现主程序ALV不能显示的情况,这里介绍为什么会有这个问题以及如何解决这个问题。
1.调整A程序
我们把拿到的数据进行alv显示,表头做的跟我们B报表稍微有点区别,当我们激活之后执行程序发现没有任何效果ALV不能正常显示,这是因为我们在设置捕获模式的时候把display这个参数给了abap_true这样设置以后 此session下(此GUI窗口)后续调用的ALV都会在黑暗模式下运行,也就是不在前台显示了,所以我们A报表自己的ALV也不能显示了,如果你一直没有关闭捕获模式那么此session下ALV始终不能显示,但如果新开一个session运行ALV报表就能正常显示了,可见它这个类控制的是ABAP内存而并非SAP内存,不然所有session都不能正常显示了。 2.停止捕获模式
我们只需要在获取完B报表的ALV数据之后调用静态方法 clear_all 停止 ALV 捕获模式 A报表的ALV就能正常调用了。
REPORT zglyntest05."A报表
TYPES: BEGIN OF ty_student,
name TYPE c LENGTH 40,"姓名
class TYPE c LENGTH 10,"班级
age TYPE i ,"年龄
sex TYPE c LENGTH 1 ,"性别
origin TYPE c LENGTH 40,"籍贯
END OF ty_student.
DATA gt_data TYPE TABLE OF ty_student.
cl_salv_bs_runtime_info=>set(
EXPORTING display = abap_false
metadata = abap_false
data = abap_true ).
SUBMIT zglyntest04 AND RETURN.
cl_salv_bs_runtime_info=>get_data( IMPORTING t_data = gt_data ).
cl_salv_bs_runtime_info=>clear_all( )."停止ALV捕获模式
**********************设置ALV表头*************************
DATA: gt_fieldcat TYPE slis_t_fieldcat_alv.
DATA: gs_fieldcat TYPE slis_fieldcat_alv.
DEFINE appendfield.
CLEAR gs_fieldcat.
gs_fieldcat-fieldname = &1.
gs_fieldcat-seltext_l = &2.
APPEND gs_fieldcat TO gt_fieldcat.
END-OF-DEFINITION.
appendfield 'NAME ' '姓名'.
appendfield 'CLASS ' '班级'.
**********************调用显示ALV***************************
CALL FUNCTION 'REUSE_ALV_GRID_DISPLAY'
EXPORTING
it_fieldcat = gt_fieldcat
i_callback_program = sy-cprog
TABLES
t_outtab = gt_data.
f.
使用 WITH 给被SUBMIT的程序选择屏幕设值
这个也是很实用的,我们可以在程序中制造变量传递给被调用程序对应的选择屏幕输入框,PARAMETERS 的话就直接传对应类型变量就行了,如果是 SELECT-OPTIONS 那么我们就制造 RANGE 内表就行了。但是一般情况也是获取A报表的选择屏幕变量直接传递,看自己需求吧反正很灵活。
1.调整B程序
既然要给选择屏幕传递值我们B报表就首先得有选择屏幕。我们加两简单的输入框。因为我不是从数据库取数所以就用delete删除过滤数据即可。
TABLES crmchkcsn.
PARAMETERS s_name TYPE char10.
SELECT-OPTIONS s_class FOR crmchkcsn-platform.
IF s_name IS NOT INITIAL.
DELETE gt_students WHERE name <> s_name .
ENDIF.
IF s_class IS NOT INITIAL.
DELETE gt_students WHERE class NOT IN s_class.
ENDIF.
2.选择屏幕效果测试
可以看到我们选择屏幕是有效果的接下来我们使用A报表SUBMIT给B报表的选择屏幕传值并获取ALV的数据。
2.WITH 造数据传值
需要注意的是如果 WITH 的名称 在B程序的选择屏幕不存在的话也不会报错,所以你如果发现WITH 没起效的话就看看两边的 名称能不能对应上。我在写案例的时候就不小心写错了导致数据没有被过滤,我们还可以把B程序的 选择屏幕输入框设置为 NO-DISPLAY 不显示, 使用 WITH 依然是能传递的。
DATA s_class TYPE RANGE OF char2.
APPEND VALUE #( sign = 'I' option = 'BT' low = '02' high = '03' ) TO s_class.
SUBMIT zglyntest04
WITH s_name = ''
WITH s_class IN s_class
AND RETURN.
3.WITH 使用A程序选择屏幕数据传值
直接把 B 的选择屏幕Copy过来就行了。运行效果省略
g.
使用 内存 传递接收数据
上面我们是通过捕获ALV的类获取到了B程序报表的数据,但是这种方法是只能获取ALV相关的数据,其实是比较有局限性的,我们还可以通过内存的方式更灵活的在不同程序之间传递数据,内存其实分为以下两种。
4大区别 | ABAP内存 | SAP内存 |
---|---|---|
设置读取 | export to memory 、 import from memory | set parameter 、 get parameter |
共享范围 | 只能在同一个session的不同程序之间共享数据 | 可以在不同session不同程序之间共享数据 |
作用范围 | 只在一个session时间内存放 | 在整个终端session时间内有效 |
应用场景 | 用于程序模块之间传递数据 | 一般用于屏幕默认值输入 |
session可以理解为 打开了几个GUI窗口就是有几个session,对于ABAP内存来说如果运行程序的这个GUI窗口关闭了那么程序中的内存也就没了,但是SAP内存一旦被设置那么只要还有GUI窗口存在这个内存就还存在。
我们下面这个案例将会依靠ABAP内存把A程序中的变量导出到ABAP内存 然后在B程序通过内存获取到这个变量 并操作这个变量 然后再次导出到内存,最后A程序再从内存中获取这个变量。
1.将A程序数据导出到ABAP内存
将数据导出到ABAP内存使用语法 EXPORT <变量名> TO MEMORY ID ‘<ID标识符>’. 其中ID标识符是可选的但是最好还是写上,后面也会解释为什么要有ID标识符。
2.在B程序通过内存获取A程序导出到内存的变量
需要注意的是,B程序拿到的gt_data类型是由A程序决定的,所以给它赋值的时候类型要能对应上。当我们从内存中拿到变量之后这个变量其实跟内存中的那个变量就没关系了,你可以理解为等于把内存中的变量Copy了一份到B程序了,如果我们A程序想拿到在B程序修改后的变量那么还需要B程序把修改后的变量再设置回内存中也就是用EXPORT。
3.把修改后的值设置回内存中
会覆盖内存中的gt_data变量
4.A程序从内存中获取修改后的变量
5.运行效果
运行后先显示了B报表然后点击 返回 执行 A程序SUBMIT之后的代码再显示了A报表,可以看到我们在A程序并没有做任何获取或者赋值的操作然后A报表现在也是有值的。怎么样?点个赞赞吧
6.ID标识符
ID标识符最好还是写上,我们可以想象一下如果多个程序都往内存中写入了变量名称为gt_data数据。那么gt_data这个数据在内存中肯定会被不停的覆盖导致我们相关程序获取不到想要的数据。但是如果变量名和ID标识符都相同重复了的话那么还是会覆盖的所以我们尽量制定命名规范,确保变量名和ID标识符的唯一性。实在不行还可以使用动态生成的ID标识符,确保每次运行程序时ID都是唯一的。例如,可以在ID中包含时间戳或UUID。
7.注意事项
1、 我上面演示的是两个程序操作内存变量的一整套流程 A导出 B接收 B导出 A接收,但其实际开发中并非必须这样,我这样演示一整套操作是为了展现内存的灵活性,我们也可以在A程序把变量设置好值然后再B程序接收直接使用就行,我B程序也不想再把这个数据传回给A了。反正就灵活使用就行了,其实SMARTFORMS打印就是一个很好的例子报表用内存把数据传递后SMARTFORMS直接用就行了。
2、 如果不想显示B报表的画面可以使用我们上面介绍过的类设置ALV为黑暗模式,数据处理完之后记得结束捕获模式不然A程序的ALV也出不来了。
四、其他扩展语法
SUBMIT的扩展语法是蛮多的,上面介绍的是一些实用常用的,还有一些不常用或者完全可以被淘汰的。
a.
将被调用程序ALV数据写入内存 EXPORTING LIST TO MEMORY
EXPORTING LIST TO MEMORY 的作用在我看来还是捕获被调用程序的 ALV 数据,它可以完全可以被捕获ALV的类替代,感觉这玩意完全可以被淘汰了,但是我们这里还是介绍一下,万一遇到了也不至于看不懂。
EXPORTING LIST TO MEMORY 它的原理作用以及使用方法是跟ABAP内存有关的,它会把被调用的程序ALV显示的数据写入到内存中,然后如果要获取内存中它写入的数据 得用函数读取,一共有四个函数,一个是用来读取内存中的数据,其余三个都是转化数据,因为读到的数据都是二进制数据。
1.写入内存并读取
需要注意的是 B程序并不需要做任何操作只要被A程序SUBMIT之后ALV数据就会被自动写入到内存中。然后我们用函数读取或者转化的时候把变量类型参考对就行了,list_tab长什么样我也右侧附有图片。函数名:LIST_FROM_MEMORY(读取数据)
2.WRITE_LIST显示数据
WRITE_LIST函数可以将读取到的数据以表格的形式展现出来。
3.DISPLAY_LIST弹框形式显示数据
DISPLAY_LIST函数可以将读取到的数据以弹框表格的形式展现出来。
4.LIST_TO_ASCI将数转为ASCII
DISPLAY_LIST函数可以将读取到的数据转换为ASCII表示,感觉没啥卵用。
b.
使用被调用程序的选择屏幕 VIA SELECTION-SCREEN
如果加上这个扩展语法那么被调用的程序的选择屏幕也会显示出来,如果你用 A程序SUBMIT调用B程序加上这个语法 那么程序运行会把B程序的选择屏幕也显示出来。但是A程序如果有选择屏幕的话不会冲突影响的依然会正常显示A的选择屏幕。
c.
给被调用程序指定变式 USING SELECTION-SET ‘<变式名>’
如果变式不存在会返回I类型的消息弹框,所以如果使用变式最好还是用函数 RS_VARIANT_CONTENTS 获取一下变式,这个函数使用也简单就传入报表名和变式名 就能返回变式的详情信息,记得把异常打开。
SUBMIT zglyntest04"B报表
USING SELECTION-SET 'TEST'"使用变式
AND RETURN.
"获取变式
DATA valutab TYPE TABLE OF rsparams.
CALL FUNCTION 'RS_VARIANT_CONTENTS'
EXPORTING
report = 'ZGLYNTEST04'
variant = 'TEST1'
* MOVE_OR_WRITE = 'W'
* NO_IMPORT = ' '
* EXECUTE_DIRECT = ' '
* IMPORTING
* SP =
TABLES
* L_PARAMS =
* L_PARAMS_NONV =
* L_SELOP =
* L_SELOP_NONV =
valutab = valutab
* VALUTABL =
* OBJECTS =
* FREE_SELECTIONS_DESC =
* FREE_SELECTIONS_VALUE =
* FREE_SELECTIONS_OBJ =
EXCEPTIONS
VARIANT_NON_EXISTENT = 1 "变式不存在会报异常1
VARIANT_OBSOLETE = 2
OTHERS = 3.
IF sy-subrc <> 0.
* Implement suitable error handling here
ENDIF.
d.
设定执行后台作业 SUBMIT … VIA JOB
案例还在研究制作中哈,过段时间来填坑,谢谢。
END、总结
以上就是今天要讲的内容,本文仅仅简单介绍了 SAP ABAP SUBMIT的用法,感觉笔者讲的好对自己有帮助的还麻烦点个免费的赞赞制作不易谢谢谢谢!!!如果有说错或者不好的地方还望大家提出来见谅。感觉笔者写的好的别忘了关注点赞加评论哦,也欢迎大家一起来讨论。谢谢!