PHP内核探索:类的成员变量

成员变量是定义在类里面
服务器君一共花费了212.608 ms进行了5次数据库查询,努力地为您提供了这个页面。
试试阅读模式?希望听取您的建议

在上一小节,我们介绍了类的结构和声明过程,从而,我们知道了类的存储结构,接口抽象类等类型的实现方式。 在本小节,我们将介绍类的成员变量和成员方法。首先,我们看一下,什么是成员变量,什么是成员方法。

类的成员变量在PHP中本质上是一个变量,只是这些变量都归属于某个类,并且给这些变量是有访问控制的。 类的成员变量也称为成员属性,它是现实世界实体属性的抽象,是可以用来描述对象状态的数据。

类的成员方法在PHP中本质上是一个函数,只是这个函数以类的方法存在,它可能是一个类方法也可能是一个实例方法, 并且在这些方法上都加上了类的访问控制。类的成员方法是现实世界实体行为的抽象,可以用来实现类的行为。

成员变量

前面介绍过变量,不过那些变量要么是定义在全局范围中,叫做全局变量,要么是定义在某个函数中, 叫做局部变量。 成员变量是定义在类里面,并和成员方法处于同一层次。如下一个简单的PHP代码示例,定义了一个类, 并且这个类有一个成员变量。

class Tipi {
    public $var;
}

类的结构在PHP内核中的存储方式我们已经在上一小节介绍过了。现在,我们要讨论类的成员变量的存储方式。 假如我们需要直接访问这个变量,整个访问过程是什么? 当然,以这个示例来说,访问这个成员变量是通过对象来访问,关于对象的相关知识我们将在后面的小节作详细的介绍。

当我们用VLD扩展查看以上代码生成的中间代码时,我们发现,并没有相关的中间代码输出。 这是因为成员变量在编译时已经注册到了类的结构中,那注册的过程是什么? 成员变量注册的位置在哪?

我们从上一小节知道,在编译时类的声明编译会调用zend_do_begin_class_declaration函数。 此函数用来初始化类的基本信息,其中包括类的成员变量。其调用顺序为: [zend_do_begin_class_declaration] --> [zend_initialize_class_data] --> [zend_hash_init_ex]

zend_hash_init_ex(&ce->default_properties, 0, NULL, zval_ptr_dtor_func, persistent_hashes, 0);

在声明类的时候初始化了类的成员变量所在的HashTable,之后如果有新的成员变量声明时,在编译时zend_do_declare_property。函数首先检查成员变量不允许的一些情况:

  • 接口中不允许使用成员变量
  • 成员变量不能拥有抽象属性
  • 不能声明成员变量为final
  • 不能重复声明属性

如果在上面的PHP代码中的类定义中,给成员变量前面添加final关键字:

class Tipi {
    public final $var;
}

运行程序将报错:Fatal error: Cannot declare property Tipi::$var final, the final modifier is allowed only for methods and classes in .. 这个错误由zend_do_declare_property函数抛出:

if (access_type & ZEND_ACC_FINAL) {
    zend_error(E_COMPILE_ERROR, "Cannot declare property %s::$%s final, the final modifier is allowed only for methods and classes",
               CG(active_class_entry)->name, var_name->u.constant.value.str.val);
}

在定义检查没有问题之后,函数会进行成员变量的初始化操作。

ALLOC_ZVAL(property);   //  分配内存
 
if (value) {    //  成员变量有初始化数据
    *property = value->u.constant;
} else {
    INIT_PZVAL(property);
    Z_TYPE_P(property) = IS_NULL;
}

在初始化过程中,程序会先分配内存,如果这个成员变量有初始化的数据,则将数据直接赋值给该属性, 否则初始化ZVAL,并将其类型设置为IS_NULL。在初始化过程完成后,程序通过调用 zend_declare_property_ex 函数将此成员变量添加到指定的类结构中。

以上为成员变量的初始化和注册成员变量的过程,常规的成员变量最后都会注册到类的 default_properties 字段。 在我们平时的工作中,可能会用不到上面所说的这些过程,但是我们可能会使用get_class_vars()函数来查看类的成员变量。 此函数返回由类的默认属性组成的关联数组,这个数组的元素以 varname => value 的形式存在。其实现核心代码如下:

if (zend_lookup_class(class_name, class_name_len, &pce TSRMLS_CC) == FAILURE) {
    RETURN_FALSE;
} else {
    array_init(return_value);
    zend_update_class_constants(*pce TSRMLS_CC);
    add_class_vars(*pce, &(*pce)->default_properties, return_value TSRMLS_CC);
    add_class_vars(*pce, CE_STATIC_MEMBERS(*pce), return_value TSRMLS_CC);
}

首先调用zend_lookup_class函数查找名为class_name的类,并将赋值给pce变量。 这个查找的过程最核心是一个HashTable的查找函数zend_hash_quick_find,它会查找EG(class_table)。 判断类是否存在,如果存在则直接返回。如果不存在,则需要判断是否可以自动加载,如果可以自动加载,则会加载类后再返回。 如果不能找到类,则返回FALSE。如果找到了类,则初始化返回的数组,更新类的静态成员变量,添加类的成员变量到返回的数组。 这里针对类的静态成员变量有一个更新的过程,关于这个过程我们在下面有关于静态成员变量中做相关介绍。

静态成员变量

类的静态成员变量是所有实例共用的,它归属于这个类,因此它也叫做类变量。 在PHP的类结构中,类本身的静态变量存放在类结构的 default_static_members 字段中。

与普通成员变量不同,类变量可以直接通过类名调用,这也体现其称作类变量的特别。一个PHP示例:

class Tipi {
    public static $var = 10;
}
 
Tipi::$var;

这是一个简单的类,它仅包括一个公有的静态变量$var。 通过VLD扩展查看其生成的中间代码:

function name:  (null)
number of ops:  6
compiled vars:  !0 = $var
line     # *  op                           fetch          ext  return  operands
--------------------------------------------------------------------------------
-
   2     0  >   EXT_STMT
         1      NOP
   6     2      EXT_STMT
         3      ZEND_FETCH_CLASS                                 :1      'Tipi'
         4      FETCH_R                      static member               'var'
         5    > RETURN                                                   1
 
branch: #  0; line:     2-    6; sop:     0; eop:     5
path #1: 0,
Class Tipi: [no user functions]

这段生成的中间代码仅与Tipi::$var;这段调用对应,它与前面的类定义没有多大关系。 根据前面的内容和VLD生成的内容,我们可以知道PHP代码:Tipi::$var; 生成的中间代码包括ZEND_FETCH_CLASS和FETCH_R。 这里只是一个静态变量的调用,但是它却生成了两个中间代码,什么原因呢? 很直白的解释:我们要调用一个类的静态变量,当然要先找到这个类,然后再获取这个类的变量。 从PHP源码来看,这是由于在编译时其调用了zend_do_fetch_static_member函数, 而在此函数中又调用了zend_do_fetch_class函数, 从而会生成ZEND_FETCH_CLASS中间代码。它所对应的执行函数为 ZEND_FETCH_CLASS_SPEC_CONST_HANDLER。 此函数会调用zend_fetch_class函数(Zend/zend_execute_API.c)。 而zend_fetch_class函数最终也会调用 zend_lookup_class_ex 函数查找类,这与前面的查找方式一样。

找到了类,接着应该就是查找类的静态成员变量,其最终调用的函数为:zend_std_get_static_property。 这里由于第二个参数的类型为 ZEND_FETCH_STATIC_MEMBER。这个函数最后是从 static_members 字段中查找对应的值返回。 而在查找前会和前面一样,执行zend_update_class_constants函数,从而更新此类的所有静态成员变量,其程序流程如图所示:

静态变量更新流程图

延伸阅读

此文章所在专题列表如下:

  1. PHP内核探索:从SAPI接口开始
  2. PHP内核探索:一次请求的开始与结束
  3. PHP内核探索:一次请求生命周期
  4. PHP内核探索:单进程SAPI生命周期
  5. PHP内核探索:多进程/线程的SAPI生命周期
  6. PHP内核探索:Zend引擎
  7. PHP内核探索:再次探讨SAPI
  8. PHP内核探索:Apache模块介绍
  9. PHP内核探索:通过mod_php5支持PHP
  10. PHP内核探索:Apache运行与钩子函数
  11. PHP内核探索:嵌入式PHP
  12. PHP内核探索:PHP的FastCGI
  13. PHP内核探索:如何执行PHP脚本
  14. PHP内核探索:PHP脚本的执行细节
  15. PHP内核探索:操作码OpCode
  16. PHP内核探索:PHP里的opcode
  17. PHP内核探索:解释器的执行过程
  18. PHP内核探索:变量概述
  19. PHP内核探索:变量存储与类型
  20. PHP内核探索:PHP中的哈希表
  21. PHP内核探索:理解Zend里的哈希表
  22. PHP内核探索:PHP哈希算法设计
  23. PHP内核探索:翻译一篇HashTables文章
  24. PHP内核探索:哈希碰撞攻击是什么?
  25. PHP内核探索:常量的实现
  26. PHP内核探索:变量的存储
  27. PHP内核探索:变量的类型
  28. PHP内核探索:变量的值操作
  29. PHP内核探索:变量的创建
  30. PHP内核探索:预定义变量
  31. PHP内核探索:变量的检索
  32. PHP内核探索:变量的类型转换
  33. PHP内核探索:弱类型变量的实现
  34. PHP内核探索:静态变量的实现
  35. PHP内核探索:变量类型提示
  36. PHP内核探索:变量的生命周期
  37. PHP内核探索:变量赋值与销毁
  38. PHP内核探索:变量作用域
  39. PHP内核探索:诡异的变量名
  40. PHP内核探索:变量的value和type存储
  41. PHP内核探索:全局变量Global
  42. PHP内核探索:变量类型的转换
  43. PHP内核探索:内存管理开篇
  44. PHP内核探索:Zend内存管理器
  45. PHP内核探索:PHP的内存管理
  46. PHP内核探索:内存的申请与销毁
  47. PHP内核探索:引用计数与写时复制
  48. PHP内核探索:PHP5.3的垃圾回收机制
  49. PHP内核探索:内存管理中的cache
  50. PHP内核探索:写时复制COW机制
  51. PHP内核探索:数组与链表
  52. PHP内核探索:使用哈希表API
  53. PHP内核探索:数组操作
  54. PHP内核探索:数组源码分析
  55. PHP内核探索:函数的分类
  56. PHP内核探索:函数的内部结构
  57. PHP内核探索:函数结构转换
  58. PHP内核探索:定义函数的过程
  59. PHP内核探索:函数的参数
  60. PHP内核探索:zend_parse_parameters函数
  61. PHP内核探索:函数返回值
  62. PHP内核探索:形参return value
  63. PHP内核探索:函数调用与执行
  64. PHP内核探索:引用与函数执行
  65. PHP内核探索:匿名函数及闭包
  66. PHP内核探索:面向对象开篇
  67. PHP内核探索:类的结构和实现
  68. PHP内核探索:类的成员变量
  69. PHP内核探索:类的成员方法
  70. PHP内核探索:类的原型zend_class_entry
  71. PHP内核探索:类的定义
  72. PHP内核探索:访问控制
  73. PHP内核探索:继承,多态与抽象类
  74. PHP内核探索:魔术函数与延迟绑定
  75. PHP内核探索:保留类与特殊类
  76. PHP内核探索:对象
  77. PHP内核探索:创建对象实例
  78. PHP内核探索:对象属性读写
  79. PHP内核探索:命名空间
  80. PHP内核探索:定义接口
  81. PHP内核探索:继承与实现接口
  82. PHP内核探索:资源resource类型
  83. PHP内核探索:Zend虚拟机
  84. PHP内核探索:虚拟机的词法解析
  85. PHP内核探索:虚拟机的语法分析
  86. PHP内核探索:中间代码opcode的执行
  87. PHP内核探索:代码的加密与解密
  88. PHP内核探索:zend_execute的具体执行过程
  89. PHP内核探索:变量的引用与计数规则
  90. PHP内核探索:新垃圾回收机制说明

本文地址:http://www.nowamagic.net/librarys/veda/detail/1499,欢迎访问原出处。

不打个分吗?

转载随意,但请带上本文地址:

http://www.nowamagic.net/librarys/veda/detail/1499

如果你认为这篇文章值得更多人阅读,欢迎使用下面的分享功能。
小提示:您可以按快捷键 Ctrl + D,或点此 加入收藏

阅读一百本计算机著作吧,少年

很多人觉得自己技术进步很慢,学习效率低,我觉得一个重要原因是看的书少了。多少是多呢?起码得看3、4、5、6米吧。给个具体的数量,那就100本书吧。很多人知识结构不好而且不系统,因为在特定领域有一个足够量的知识量+足够良好的知识结构,系统化以后就足以应对大量未曾遇到过的问题。

奉劝自学者:构建特定领域的知识结构体系的路径中再也没有比学习该专业的专业课程更好的了。如果我的知识结构体系足以囊括面试官的大部分甚至吞并他的知识结构体系的话,读到他言语中的一个词我们就已经知道他要表达什么,我们可以让他坐“上位”毕竟他是面试官,但是在知识结构体系以及心理上我们就居高临下。

所以,阅读一百本计算机著作吧,少年!

《链接器和加载器》 莱文(John R.Levine) (作者), 李勇 (译者)

《链接器和加载器》讲述构建程序的关键工具——链接器和加载器,内容包括链接和加载、体系结构、目标文件、存储分配、符号管理、库、重定位、加载和覆盖、共享库、动态链接和加载、动态链接的共享库,以及着眼于成熟的现代链接器所做的一些变化;并介绍一个持续的实践项目,即使用Perl语言开发一个可用的小链接器。《链接器和加载器》适合高校计算机相关专业的学生、实习程序员、语言设计者和开发人员阅读参考。

更多计算机宝库...