我们在php中用到的变量,在底层的C语言代码里是一个结构体,由四个成员组成
typedef struct _zval_struct {
zvalue_value value; /* 变量的值,也是一个结构体 */
zend_uint refcount__gc; /* 变量的引用计数 typedef unsigned int zend_uint */
zend_uchar type; /* 变量的类型 typedef unsigned char zend_uchar */
zend_uchar is_ref__gc; /* 是否引用 typedef unsigned char zend_uchar*/
} zval;
typedef union _zvalue_value {
long lval; /* 长整型,存储整数,bool,资源类型 */
double dval; /* 浮点型,存储小数 */
struct {
char *val;
int len; /* */
} str; /* 字符串,val是字符串指针,len是字符串长度 */
HashTable *ht; /* hashtable 即PHP数组 */
zend_object_value obj; /* php对象 */
} zvalue_value;

php变量的类型,即zval的type成员,一共有8种

 
类型   zvalue_value中存储的成员 说明
IS_NULL 不存储值 NULL
IS_LONG lval 整型
IS_DOUBLE dval 浮点型
IS_BOOL lval 布尔
IS_RESOURCE lval 资源
IS_STRING str 字符串
IS_ARRAY ht 数组
IS_OBJECT obj 对象

这些类型都是宏定义,在Zend/zend.h中可以查到

 #define IS_NULL     0
#define IS_LONG 1
#define IS_DOUBLE 2
#define IS_BOOL 3
#define IS_ARRAY 4
#define IS_OBJECT 5
#define IS_STRING 6
#define IS_RESOURCE 7

通常我们不会直接使用php变量的成员,例如zval->type或zvalue_value->lval,为了代码的兼容性,zend给我们提供了很多的API方便我们操作变量

宏定义原型 获取变量   描述
zend_uchar Z_TYPE(zval zv) type 返回变量类型
long Z_LVAL(zval zv) value.lval  返回zvalue_value的lval成员
zend_bool Z_BVAL(zval zv) value.lval  返回zvalue_value的lval成员,并且转换成zend_bool类型
double Z_DVAL(zval zv) value.dval  
long Z_RESVAL(zval zv) value.lval 返回zvalue_value的lval成员,此时的type是IS_RESOURCE
char* Z_STRVAL(zval zv) value.str.val 返回字符串的值
int Z_STRLEN(zval zv) value.str.len 返回字符串的长度
HashTable* Z_ARRVAL(zval zv) value.ht 返回hashtable即数组
zend_object_value Z_OBJVAL(zval zv) value.obj returns object value
uint Z_OBJ_HANDLE(zval zv) value.obj.handle returns the object handle for object value
zend_object_handlers* Z_OBJ_HT_P(zval zv) value.obj.handlers returns the handler table for object value
zend_class_entry* Z_OBJCE(zval zv) value.obj returns the class entry for object value
HashTable* Z_OBJPROP(zval zv) value.obj returns the properties of object value
HashTable* Z_OBJPROP(zval zv) value.obj returns the properties of object value
HashTable* Z_OBJDEBUG(zval zv) value.obj if an object has the get_debug_info handler set, it is called, else Z_OBJPROP is called

以上这些API其实就是宏定义,上面列出的每个宏同时都有另外两种类似的定义,以Z_TYPE为例

#define Z_TYPE(zval)        (zval).type         //参数是zval
#define Z_TYPE_P(zval_p)    Z_TYPE(*zval_p)     //参数是zval的指针
#define Z_TYPE_PP(zval_pp)  Z_TYPE(**zval_pp)   //参数是zval的指针的指针
以上这些宏定义,在Zend/zend_operators.h中可以查到
 #define Z_LVAL(zval)            (zval).value.lval
#define Z_BVAL(zval) ((zend_bool)(zval).value.lval)
#define Z_DVAL(zval) (zval).value.dval
#define Z_STRVAL(zval) (zval).value.str.val
#define Z_STRLEN(zval) (zval).value.str.len
#define Z_ARRVAL(zval) (zval).value.ht
#define Z_AST(zval) (zval).value.ast
#define Z_OBJVAL(zval) (zval).value.obj
#define Z_OBJ_HANDLE(zval) Z_OBJVAL(zval).handle
#define Z_OBJ_HT(zval) Z_OBJVAL(zval).handlers
#define Z_OBJCE(zval) zend_get_class_entry(&(zval) TSRMLS_CC)
#define Z_OBJPROP(zval) Z_OBJ_HT((zval))->get_properties(&(zval) TSRMLS_CC)
#define Z_OBJ_HANDLER(zval, hf) Z_OBJ_HT((zval))->hf
#define Z_RESVAL(zval) (zval).value.lval
#define Z_OBJDEBUG(zval,is_tmp) (Z_OBJ_HANDLER((zval),get_debug_info)?Z_OBJ_HANDLER((zval),get_debug_info)(&(zval),&is_tmp TSRMLS_CC):(is_tmp=0,Z_OBJ_HANDLER((zval),get_properties)?Z_OBJPROP(zval):NULL)) #define Z_LVAL_P(zval_p) Z_LVAL(*zval_p)
#define Z_BVAL_P(zval_p) Z_BVAL(*zval_p)
#define Z_DVAL_P(zval_p) Z_DVAL(*zval_p)
#define Z_STRVAL_P(zval_p) Z_STRVAL(*zval_p)
#define Z_STRLEN_P(zval_p) Z_STRLEN(*zval_p)
#define Z_ARRVAL_P(zval_p) Z_ARRVAL(*zval_p)
#define Z_AST_P(zval_p) Z_AST(*zval_p)
#define Z_OBJPROP_P(zval_p) Z_OBJPROP(*zval_p)
#define Z_OBJCE_P(zval_p) Z_OBJCE(*zval_p)
#define Z_RESVAL_P(zval_p) Z_RESVAL(*zval_p)
#define Z_OBJVAL_P(zval_p) Z_OBJVAL(*zval_p)
#define Z_OBJ_HANDLE_P(zval_p) Z_OBJ_HANDLE(*zval_p)
#define Z_OBJ_HT_P(zval_p) Z_OBJ_HT(*zval_p)
#define Z_OBJ_HANDLER_P(zval_p, h) Z_OBJ_HANDLER(*zval_p, h)
#define Z_OBJDEBUG_P(zval_p,is_tmp) Z_OBJDEBUG(*zval_p,is_tmp) #define Z_LVAL_PP(zval_pp) Z_LVAL(**zval_pp)
#define Z_BVAL_PP(zval_pp) Z_BVAL(**zval_pp)
#define Z_DVAL_PP(zval_pp) Z_DVAL(**zval_pp)
#define Z_STRVAL_PP(zval_pp) Z_STRVAL(**zval_pp)
#define Z_STRLEN_PP(zval_pp) Z_STRLEN(**zval_pp)
#define Z_ARRVAL_PP(zval_pp) Z_ARRVAL(**zval_pp)
#define Z_AST_PP(zval_p) Z_AST(**zval_p)
#define Z_OBJPROP_PP(zval_pp) Z_OBJPROP(**zval_pp)
#define Z_OBJCE_PP(zval_pp) Z_OBJCE(**zval_pp)
#define Z_RESVAL_PP(zval_pp) Z_RESVAL(**zval_pp)
#define Z_OBJVAL_PP(zval_pp) Z_OBJVAL(**zval_pp)
#define Z_OBJ_HANDLE_PP(zval_p) Z_OBJ_HANDLE(**zval_p)
#define Z_OBJ_HT_PP(zval_p) Z_OBJ_HT(**zval_p)
#define Z_OBJ_HANDLER_PP(zval_p, h) Z_OBJ_HANDLER(**zval_p, h)
#define Z_OBJDEBUG_PP(zval_pp,is_tmp) Z_OBJDEBUG(**zval_pp,is_tmp)

如果你是初始变量赋值,或者需要同时改变变量的类型,你可以直接使用以下这些宏定义函数

#define ZVAL_RESOURCE(z, l) do {    \//资源类型,值为l
zval *__z = (z); \
Z_LVAL_P(__z) = l; \
Z_TYPE_P(__z) = IS_RESOURCE;\
} while () #define ZVAL_BOOL(z, b) do { \//布尔类型
zval *__z = (z); \
Z_LVAL_P(__z) = ((b) != ); \
Z_TYPE_P(__z) = IS_BOOL; \
} while () #define ZVAL_NULL(z) { \//NULL
Z_TYPE_P(z) = IS_NULL; \
} #define ZVAL_LONG(z, l) { \//整型
zval *__z = (z); \
Z_LVAL_P(__z) = l; \
Z_TYPE_P(__z) = IS_LONG; \
} #define ZVAL_DOUBLE(z, d) { \//浮点型
zval *__z = (z); \
Z_DVAL_P(__z) = d; \
Z_TYPE_P(__z) = IS_DOUBLE; \
} #define ZVAL_STRING(z, s, duplicate) do { \//字符串,duplicate表示是否赋值一份字符串再赋值
const char *__s=(s); \
zval *__z = (z); \
Z_STRLEN_P(__z) = strlen(__s); \
if (UNEXPECTED(Z_STRLEN_P(__z) < )) { \
zend_error(E_ERROR, "String size overflow"); \
} \
Z_STRVAL_P(__z) = (duplicate?estrndup(__s, Z_STRLEN_P(__z)):(char*)__s);\
Z_TYPE_P(__z) = IS_STRING; \
} while () #define ZVAL_STRINGL(z, s, l, duplicate) do { \//字符串,l表示字符串的长度
const char *__s=(s); int __l=l; \
zval *__z = (z); \
Z_STRLEN_P(__z) = __l; \
Z_STRVAL_P(__z) = (duplicate?estrndup(__s, __l):(char*)__s);\
Z_TYPE_P(__z) = IS_STRING; \
} while () #define ZVAL_EMPTY_STRING(z) do { \//设置空字符串
zval *__z = (z); \
Z_STRLEN_P(__z) = ; \
Z_STRVAL_P(__z) = STR_EMPTY_ALLOC();\
Z_TYPE_P(__z) = IS_STRING; \
} while () #define ZVAL_ZVAL(z, zv, copy, dtor) do { \//赋值一个zval变量,copy表示是否硬拷贝zval中的数据,dtor表示是否对zv尝试unset
zval *__z = (z); \
zval *__zv = (zv); \
ZVAL_COPY_VALUE(__z, __zv); \
if (copy) { \
zval_copy_ctor(__z); \
} \
if (dtor) { \
if (!copy) { \
ZVAL_NULL(__zv); \
} \
zval_ptr_dtor(&__zv); \
} \
} while () #define ZVAL_FALSE(z) ZVAL_BOOL(z, 0)//布尔值TRUE
#define ZVAL_TRUE(z) ZVAL_BOOL(z, 1)//布尔值FALSE

接着我们来看一下变量的创建,复制和销毁的相关内容和操作的API

宏定义 说明
ALLOC_ZVAL(zval* pzval) 为变量分配空间
ALLOC_INIT_ZVAL(zval* pzval) 为变量分配空间,并且初始化变量为NULL类型
MAKE_STD_ZVAL(zval* pzval) 为变量分配空间,并且设置refcount__gc = 1,is_ref__gc = 0
ZVAL_COPY_VALUE(zval* dst, zval* src) 复制变量,dst的value和type分别等于src的value和type
INIT_PZVAL_COPY(zval* dst, zval*dst) 先执行ZVAL_COPY_VALUE,然后设置dst的refcount__gc = 1,is_ref__gc = 0
SEPARATE_ZVAL(zval** ppzval) 如果*ppzval的refcount__gc > 1, 则创建一个新的变量*zvalnew,用INIT_PZVAL_COPY函数来复制原变量,最后把ppzval指向新的变量
SEPARATE_ZVAL_IF_NOT_REF(zval** ppzval) 如果*ppzval不是一个引用(is_ref_gc = 0), 则执行SEPARATE_ZVAL(zval** ppzval)
SEPARATE_ZVAL_TO_MAKE_IS_REF(zval** ppzval) 如果*ppzval不是一个引用(is_ref_gc = 0), 则执行SEPARATE_ZVAL(zval** ppzval)接着执行Z_SET_ISREF_PP(zval** ppzval)设置is_ref__gc = 0
COPY_PZVAL_TO_ZVAL(zval dst, zval** src) results in dst being a copy of src without affecting the refcount of src
MAKE_COPY_ZVAL(zval** src, zval* dst) performs INIT_PZVAL_COPY, then zval_copy_ctor on the new zval
void zval_copy_ctor(zval* pzval) 把pzval的数据复制一份出来。
void zval_ptr_dtor(zval* pzval) 把pzval->refcount__gc减1,如果refcount__gc此时等于0,则销毁pzval指向的变量回收空间。
FREE_ZVAL(zval* pzval) free(pzval)回收空间
//zend.h
#define INIT_PZVAL(z) \
(z)->refcount__gc = ; \
(z)->is_ref__gc = ; #define INIT_ZVAL(z) z = zval_used_for_init; #define ALLOC_INIT_ZVAL(zp) \
ALLOC_ZVAL(zp); \
INIT_ZVAL(*zp); #define MAKE_STD_ZVAL(zv) \
ALLOC_ZVAL(zv); \
INIT_PZVAL(zv); #define PZVAL_IS_REF(z) Z_ISREF_P(z) #define ZVAL_COPY_VALUE(z, v) \
do { \
(z)->value = (v)->value; \
Z_TYPE_P(z) = Z_TYPE_P(v); \
} while () #define INIT_PZVAL_COPY(z, v) \
do { \
ZVAL_COPY_VALUE(z, v); \
Z_SET_REFCOUNT_P(z, ); \
Z_UNSET_ISREF_P(z); \
} while () #define SEPARATE_ZVAL(ppzv) \
do { \
if (Z_REFCOUNT_PP((ppzv)) > ) { \
zval *new_zv; \
Z_DELREF_PP(ppzv); \
ALLOC_ZVAL(new_zv); \
INIT_PZVAL_COPY(new_zv, *(ppzv)); \
*(ppzv) = new_zv; \
zval_copy_ctor(new_zv); \
} \
} while () #define SEPARATE_ZVAL_IF_NOT_REF(ppzv) \
if (!PZVAL_IS_REF(*ppzv)) { \
SEPARATE_ZVAL(ppzv); \
} #define SEPARATE_ZVAL_TO_MAKE_IS_REF(ppzv) \
if (!PZVAL_IS_REF(*ppzv)) { \
SEPARATE_ZVAL(ppzv); \
Z_SET_ISREF_PP((ppzv)); \
} #define COPY_PZVAL_TO_ZVAL(zv, pzv) \
(zv) = *(pzv); \
if (Z_REFCOUNT_P(pzv)>) { \
zval_copy_ctor(&(zv)); \
Z_DELREF_P((pzv)); \
} else { \
FREE_ZVAL(pzv); \
} \
INIT_PZVAL(&(zv)); #define MAKE_COPY_ZVAL(ppzv, pzv) \
INIT_PZVAL_COPY(pzv, *(ppzv)); \
zval_copy_ctor((pzv)); #define REPLACE_ZVAL_VALUE(ppzv_dest, pzv_src, copy) { \
int is_ref, refcount; \
\
SEPARATE_ZVAL_IF_NOT_REF(ppzv_dest); \
is_ref = Z_ISREF_PP(ppzv_dest); \
refcount = Z_REFCOUNT_PP(ppzv_dest); \
zval_dtor(*ppzv_dest); \
ZVAL_COPY_VALUE(*ppzv_dest, pzv_src); \
if (copy) { \
zval_copy_ctor(*ppzv_dest); \
} \
Z_SET_ISREF_TO_PP(ppzv_dest, is_ref); \
Z_SET_REFCOUNT_PP(ppzv_dest, refcount); \
} #define SEPARATE_ARG_IF_REF(varptr) \
if (PZVAL_IS_REF(varptr)) { \
zval *original_var = varptr; \
ALLOC_ZVAL(varptr); \
INIT_PZVAL_COPY(varptr, original_var); \
zval_copy_ctor(varptr); \
} else { \
Z_ADDREF_P(varptr); \
}

除了获取类型和值之外,还有一些操作跟l变量的refcount__gc和is_ref__gc相关

宏定义原型   描述
zend_uint Z_REFCOUNT(zval zv) 返回refcount__gc的值
zend_uint Z_SET_REFCOUNT(zval zv) 设置zval变量的refcount__gc并返回
zend_uint Z_ADDREF(zval zv) ++zval->refcount__gc并返回
zend_uint Z_DELREF(zval zv) --zval->refcount__gc并返回
zend_bool Z_ISREF(zval zv) 返回zval->is_ref__gc
void Z_UNSET_ISREF(zval zv) set is_ref__gc to 0
void Z_SET_ISREF(zval zv) set is_ref__gc to 1
void Z_SET_ISREF_TO(zval zv, zend_uchar to) set is_ref__gc to to

这些宏定义也同样有*_P或*_PP的版本,可以在Zend/zend.h中查看

 #define Z_REFCOUNT_PP(ppz)      Z_REFCOUNT_P(*(ppz))
#define Z_SET_REFCOUNT_PP(ppz, rc) Z_SET_REFCOUNT_P(*(ppz), rc)
#define Z_ADDREF_PP(ppz) Z_ADDREF_P(*(ppz))
#define Z_DELREF_PP(ppz) Z_DELREF_P(*(ppz))
#define Z_ISREF_PP(ppz) Z_ISREF_P(*(ppz))
#define Z_SET_ISREF_PP(ppz) Z_SET_ISREF_P(*(ppz))
#define Z_UNSET_ISREF_PP(ppz) Z_UNSET_ISREF_P(*(ppz))
#define Z_SET_ISREF_TO_PP(ppz, isref) Z_SET_ISREF_TO_P(*(ppz), isref) #define Z_REFCOUNT_P(pz) zval_refcount_p(pz)
#define Z_SET_REFCOUNT_P(pz, rc) zval_set_refcount_p(pz, rc)
#define Z_ADDREF_P(pz) zval_addref_p(pz)
#define Z_DELREF_P(pz) zval_delref_p(pz)
#define Z_ISREF_P(pz) zval_isref_p(pz)
#define Z_SET_ISREF_P(pz) zval_set_isref_p(pz)
#define Z_UNSET_ISREF_P(pz) zval_unset_isref_p(pz)
#define Z_SET_ISREF_TO_P(pz, isref) zval_set_isref_to_p(pz, isref) #define Z_REFCOUNT(z) Z_REFCOUNT_P(&(z))
#define Z_SET_REFCOUNT(z, rc) Z_SET_REFCOUNT_P(&(z), rc)
#define Z_ADDREF(z) Z_ADDREF_P(&(z))
#define Z_DELREF(z) Z_DELREF_P(&(z))
#define Z_ISREF(z) Z_ISREF_P(&(z))
#define Z_SET_ISREF(z) Z_SET_ISREF_P(&(z))
#define Z_UNSET_ISREF(z) Z_UNSET_ISREF_P(&(z))
#define Z_SET_ISREF_TO(z, isref) Z_SET_ISREF_TO_P(&(z), isref)

我们会在扩展的开发过程中,频繁用到这些zend提供的API操作,你不需要把它记下来,随着开发的进行,你将会慢慢习惯这些API的使用。我们在开发的过程中,经常还需要转换变量的类型,可以使用下面这些函数。

//把zval *pzval的变量转换其它类型
void convert_to_long(zval* pzval)//整型
void convert_to_double(zval* pzval)//浮点
void convert_to_long_base(zval* pzval, int base)//整型,base表示进制,2,8,10,16等
void convert_to_null(zval* pzval)//NULL
void convert_to_boolean(zval* pzval)//布尔
void convert_to_array(zval* pzval)//数组
void convert_to_object(zval* pzval)//对象
void convert_object_to_type(zval* pzval, convert_func_t converter)//根据回调函数转换成对象
convert_func_t functions should have the prototype (void) (zval* pzval)//接上,回调函数的原型

php扩展开发-变量的更多相关文章

  1. Chrome扩展开发之二——Chrome扩展中脚本的运行机制和通信方式

    目录: 0.Chrome扩展开发(Gmail附件管理助手)系列之〇——概述 1.Chrome扩展开发之一——Chrome扩展的文件结构 2.Chrome扩展开发之二——Chrome扩展中脚本的运行机制 ...

  2. PHP扩展开发相关总结

    1.线程安全宏定义 在TSRM/TSRM.h文件中有如下定义 #define TSRMLS_FETCH() void ***tsrm_ls = (void ***) ts_resource_ex(0, ...

  3. Firefox扩展开发

    Firefox扩展开发 (插件开发) Extension开发 入门教程 5步走 五步走   首先需要知道什么是"Firefox插件".这里说的"插件"只是一个通 ...

  4. Chrome扩展开发之三——Chrome扩展中的数据本地存储和下载

    目录: 0.Chrome扩展开发(Gmail附件管理助手)系列之〇——概述 1.Chrome扩展开发之一——Chrome扩展的文件结构 2.Chrome扩展开发之二——Chrome扩展中脚本的运行机制 ...

  5. PHP扩展开发(1):入门

    有关PHP扩展开发的文章.博客已经很多了,比较经典的有: TIPI项目(http://www.php-internals.com/,强烈推荐) <Extending and Embedding ...

  6. PHP扩展开发-简单类扩展

    今天来学习简单类扩展开发 实现目标为如下php的类 <?php class classext(){ private $username; CONST URL="http://www.g ...

  7. 【干货】Chrome插件(扩展)开发全攻略(不点进来看看你肯定后悔)

    写在前面 我花了将近一个多月的时间断断续续写下这篇博文,并精心写下完整demo,写博客的辛苦大家懂的,所以转载务必保留出处.本文所有涉及到的大部分代码均在这个demo里面:https://github ...

  8. 【干货】Chrome插件(扩展)开发全攻略

    写在前面 我花了将近一个多月的时间断断续续写下这篇博文,并精心写下完整demo,写博客的辛苦大家懂的,所以转载务必保留出处.本文所有涉及到的大部分代码均在这个demo里面:https://github ...

  9. PHP扩展开发教程(总结)

    PHP是一种解释型的语言,对于用户而言,我们精心的控制内存意味着easier prototyping和更少的崩溃!当我们深入到内核之后,所有的安全防线都已经被越过,最终还是要依赖于真正有责任心的软件工 ...

随机推荐

  1. 【JavaEE】tomcat部署项目的几种方式 .

    一.静态部署1.直接将web项目文件件拷贝到webapps 目录中     Tomcat的Webapps目录是Tomcat默认的应用目录,当服务器启动时,会加载所有这个目录下的应用.所以可以将JSP程 ...

  2. mysql存储方式MyISAM 和 InnoDB的区别

    MyISAM 和 InnoDB 讲解: InnoDB和MyISAM是许多人在使用MySQL时最常用的两个表类型,这两个表类型各有优劣,视具体应用而定.基本的差别为:MyISAM类型不支持事务处理等高级 ...

  3. Elasticsearch在后台启动

    Elasticsearch在linux下使用命令sh elasticsearch start,按键ctrl+c的时候程序就会stop掉,如何将程序在后台启动呢? 需要使用:./elasticsearc ...

  4. ORACLE将查询的多条语句拼在一个字段下

    select listagg(字段名,'分隔符') within group (order by 某个字段)

  5. 【问题记录】mysql TIMEDIFF 和 TIMESTAMPDIFF的使用

    今天遇到一个需求,需要计算数据表中两个时间的差值,并取对应的秒数 一开始我是用 time_to_sec(timediff (time1,time2)) 但是这样会有一个问题,,,时间短的用这个计算没有 ...

  6. JSP中,EL表达式向session中取出一个attribute和JSP脚本访问session取出一个attribute,写法有何不同?(转自百度知道)

    EL表达式使用起来会更简洁,假如session中有一个属性A(attrA),那么EL和jsp脚本取值的方式如下: EL表达式:${ sessionScope.attrA } JSP脚本:<%=s ...

  7. 配置海康IPC或大华IPC通过路由器公网访问

    设备:路由器DLink-DIR-600M,海康IPC:DS-2CD864FWD-E 海康默认端口为8000,HTTP访问为80,RTSP访问端口为554. 配置分成两步,分别为配置IPC相关网络参数和 ...

  8. CSS样式表优化

    前几天公司要模仿一家客户的网站模板来为另一客户新建一个模板,说白了就是换个数据源,然后样式表再小修小改一下就行了.但通过浏览器控制台下载素材时,发现这个网站开发的挺专业的,单就样式表而言,代码工整,注 ...

  9. linux 命令——36 diff(转)

    diff命令是 linux上非常重要的工具,用于比较文件的内容,特别是比较两个版本不同的文件以找到改动的地方.diff在命令行中打印每一个行的改动.最新版本的diff还支持二进制文件.diff程序的输 ...

  10. 300行ABAP代码实现一个最简单的区块链原型

    不知从什么时候起,区块链在网上一下子就火了. 这里Jerry就不班门弄斧了,网上有太多的区块链介绍文章.我的这篇文章没有任何高大上的术语,就是300行ABAP代码,实现一个最简单的区块链原型. 我个人 ...