您的位置:首页 > 编程语言 > PHP开发

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

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

类的成员变量在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函数,从而更新此类的所有静态成员变量,其程序流程如图所示:



静态变量更新流程图

延伸阅读

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

PHP内核探索:从SAPI接口开始

PHP内核探索:一次请求的开始与结束

PHP内核探索:一次请求生命周期

PHP内核探索:单进程SAPI生命周期

PHP内核探索:多进程/线程的SAPI生命周期

PHP内核探索:Zend引擎

PHP内核探索:再次探讨SAPI

PHP内核探索:Apache模块介绍

PHP内核探索:通过mod_php5支持PHP

PHP内核探索:Apache运行与钩子函数

PHP内核探索:嵌入式PHP

PHP内核探索:PHP的FastCGI

PHP内核探索:如何执行PHP脚本

PHP内核探索:PHP脚本的执行细节

PHP内核探索:操作码OpCode

PHP内核探索:PHP里的opcode

PHP内核探索:解释器的执行过程

PHP内核探索:变量概述

PHP内核探索:变量存储与类型

PHP内核探索:PHP中的哈希表

PHP内核探索:理解Zend里的哈希表

PHP内核探索:PHP哈希算法设计

PHP内核探索:翻译一篇HashTables文章

PHP内核探索:哈希碰撞攻击是什么?

PHP内核探索:常量的实现

PHP内核探索:变量的存储

PHP内核探索:变量的类型

PHP内核探索:变量的值操作

PHP内核探索:变量的创建

PHP内核探索:预定义变量

PHP内核探索:变量的检索

PHP内核探索:变量的类型转换

PHP内核探索:弱类型变量的实现

PHP内核探索:静态变量的实现

PHP内核探索:变量类型提示

PHP内核探索:变量的生命周期

PHP内核探索:变量赋值与销毁

PHP内核探索:变量作用域

PHP内核探索:诡异的变量名

PHP内核探索:变量的value和type存储

PHP内核探索:全局变量Global

PHP内核探索:变量类型的转换

PHP内核探索:内存管理开篇

PHP内核探索:Zend内存管理器

PHP内核探索:PHP的内存管理

PHP内核探索:内存的申请与销毁

PHP内核探索:引用计数与写时复制

PHP内核探索:PHP5.3的垃圾回收机制

PHP内核探索:内存管理中的cache

PHP内核探索:写时复制COW机制

PHP内核探索:数组与链表

PHP内核探索:使用哈希表API

PHP内核探索:数组操作

PHP内核探索:数组源码分析

PHP内核探索:函数的分类

PHP内核探索:函数的内部结构

PHP内核探索:函数结构转换

PHP内核探索:定义函数的过程

PHP内核探索:函数的参数

PHP内核探索:zend_parse_parameters函数

PHP内核探索:函数返回值

PHP内核探索:形参return value

PHP内核探索:函数调用与执行

PHP内核探索:引用与函数执行

PHP内核探索:匿名函数及闭包

PHP内核探索:面向对象开篇

PHP内核探索:类的结构和实现

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

PHP内核探索:类的成员方法

PHP内核探索:类的原型zend_class_entry

PHP内核探索:类的定义

PHP内核探索:访问控制

PHP内核探索:继承,多态与抽象类

PHP内核探索:魔术函数与延迟绑定

PHP内核探索:保留类与特殊类

PHP内核探索:对象

PHP内核探索:创建对象实例

PHP内核探索:对象属性读写

PHP内核探索:命名空间

PHP内核探索:定义接口

PHP内核探索:继承与实现接口

PHP内核探索:资源resource类型

PHP内核探索:Zend虚拟机

PHP内核探索:虚拟机的词法解析

PHP内核探索:虚拟机的语法分析

PHP内核探索:中间代码opcode的执行

PHP内核探索:代码的加密与解密

PHP内核探索:zend_execute的具体执行过程

PHP内核探索:变量的引用与计数规则

PHP内核探索:新垃圾回收机制说明

本文地址:https://www.geek-share.com/detail/2518721606.html,欢迎访问原出处。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: