67-类的结构和实现

原创 2016年04月27日 13:24:05

67-类的结构和实现

面向对象编程中我们的编程都是围绕类和对象进行的。那在PHP内部类是怎么实现的呢? 它的内存布局以及存储是怎么样的呢?继承、封装和多态又是怎么实现的呢?

类的结构

首先我们看看类是什么。类是用户定义的一种抽象数据类型,它是现实世界中某些具有共性事物的抽象。 有时我们也可以理解其为对象的类别。类也可以看作是一种复合型的结构,其需要存储多元化的数据, 如属性、方法、以及自身的一些性质等。

类和函数类似,PHP内置及PHP扩展均可以实现自己的内部类,也可以由用户使用PHP代码进行定义。 当然我们在编写代码时通常是自己定义。

使用上,我们使用class关键字进行定义,后面接类名,类名可以是任何非PHP保留字的名字。 在类名后面紧跟着一对花括号,里面是类的实体,包括类所具有的属性,这些属性是对象的状态的抽象, 其表现为PHP中支持的数据类型,也可以包括对象本身,通常我们称其为成员变量。 除了类的属性, 类的实体中也包括类所具有的操作,这些操作是对象的行为的抽象,其表现为用操作名和实现该操作的方法, 通常我们称其为成员方法或成员函数。看类示例的代码:

class ParentClass {
}

interface Ifce {
        public function iMethod();
}

final class Tipi extends ParentClass implements Ifce {
        public static $sa = 'aaa';
        const CA = 'bbb';

        public function __constrct() {
        }

        public function iMethod() {
        }

        private function _access() {
        }

        public static function access() {
        }
}

这里定义了一个父类ParentClass,一个接口Ifce,一个子类Tipi。子类继承父类ParentClass, 实现接口Ifce,并且有一个静态变量$sa,一个类常量 CA,一个公用方法,一个私有方法和一个公用静态方法。 这些结构在Zend引擎内部是如何实现的?类的方法、成员变量是如何存储的?访问控制,静态成员是如何标记的?

首先,我们看看类的内部存储结构:

struct _zend_class_entry {
    char type;     // 类型:ZEND_INTERNAL_CLASS / ZEND_USER_CLASS
    char *name;// 类名称
    zend_uint name_length;                  // 即sizeof(name) - 1
    struct _zend_class_entry *parent; // 继承的父类
    int refcount;  // 引用数
    zend_bool constants_updated;

    zend_uint ce_flags; // ZEND_ACC_IMPLICIT_ABSTRACT_CLASS: 类存在abstract方法
    // ZEND_ACC_EXPLICIT_ABSTRACT_CLASS: 在类名称前加了abstract关键字
    // ZEND_ACC_FINAL_CLASS
    // ZEND_ACC_INTERFACE
    HashTable function_table;      // 方法
    HashTable default_properties;          // 默认属性
    HashTable properties_info;     // 属性信息
    HashTable default_static_members;// 类本身所具有的静态变量
    HashTable *static_members; // type == ZEND_USER_CLASS时,取&default_static_members;
    // type == ZEND_INTERAL_CLASS时,设为NULL
    HashTable constants_table;     // 常量
    struct _zend_function_entry *builtin_functions;// 方法定义入口


    union _zend_function *constructor;
    union _zend_function *destructor;
    union _zend_function *clone;


    /* 魔术方法 */
    union _zend_function *__get;
    union _zend_function *__set;
    union _zend_function *__unset;
    union _zend_function *__isset;
    union _zend_function *__call;
    union _zend_function *__tostring;
    union _zend_function *serialize_func;
    union _zend_function *unserialize_func;
    zend_class_iterator_funcs iterator_funcs;// 迭代

    /* 类句柄 */
    zend_object_value (*create_object)(zend_class_entry *class_type TSRMLS_DC);
    zend_object_iterator *(*get_iterator)(zend_class_entry *ce, zval *object,
        intby_ref TSRMLS_DC);

    /* 类声明的接口 */
    int(*interface_gets_implemented)(zend_class_entry *iface,
            zend_class_entry *class_type TSRMLS_DC);


    /* 序列化回调函数指针 */
    int(*serialize)(zval *object, unsignedchar**buffer, zend_uint *buf_len,
             zend_serialize_data *data TSRMLS_DC);
    int(*unserialize)(zval **object, zend_class_entry *ce, constunsignedchar*buf,
            zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC);


    zend_class_entry **interfaces;  //  类实现的接口
    zend_uint num_interfaces;   //  类实现的接口数


    char *filename; //  类的存放文件地址 绝对地址
    zend_uint line_start;   //  类定义的开始行
    zend_uint line_end; //  类定义的结束行
    char *doc_comment;
    zend_uint doc_comment_len;


    struct _zend_module_entry *module; // 类所在的模块入口:EG(current_module)
};

取上面这个结构的部分字段,我们分析文章最开始的那段PHP代码在内核中的表现。 如下表所示:

字段名字段说明ParentClass类Ifce接口Tipi类
name类名ParentClass IfceTipi
type类别222
parent父类ParentClass类
refcount引用计数112
ce_flags类的类型0144524352
function_table函数列表function_name=iMethod | type=2 | fn_flags=258function_name=__construct | type=2 | fn_flags=8448
function_name=iMethod | type=2 | fn_flags=65800
function_name=_access | type=2 | fn_flags=66560
function_name=access | type=2 | fn_flags=257
interfaces接口列表Ifce接口接口数为1
filename存放文件地址/tipi.php/tipi.php/ipi.php
line_start类开始行数151822
line_end类结束行数162038

类的结构中,type有两种类型,数字标记为1和2。分别为一下宏的定义,也就是说用户定义的类和模块或者内置的类也是保存在这个结构里的:

#define ZEND_INTERNAL_CLASS         1
#define ZEND_USER_CLASS             2

对于父类和接口,都是保存在struct _zend_class_entry结构体中。这表示接口也是以类的形式存储, 而实现是一样的,并且在继承等操作时有与类操作的不同的处理。常规的成员方法存放在函数结构体的哈希表中, 而魔术方法则单独保存。 如在类定义中的 union _zend_function *constructor; 定义就是类的构造魔术方法, 它是以函数的形式存在于类结构中,并且与常规的方法分隔开来了。在初始化时,这些魔术方法都会被设置为NULL。

类的实现

类的定义是以class关键字开始,在Zend/zend_language_scanner.l文件中,找到class对应的token为T_CLASS。 根据此token,在Zend/zend_language_parser.y文件中,找到编译时调用的函数:

unticked_class_declaration_statement:
        class_entry_type T_STRING extends_from
            { zend_do_begin_class_declaration(&$1, &$2, &$3 TSRMLS_CC); }
            implements_list
            '{'
                class_statement_list
            '}' { zend_do_end_class_declaration(&$1, &$2 TSRMLS_CC); }
    |   interface_entry T_STRING
            { zend_do_begin_class_declaration(&$1, &$2, NULL TSRMLS_CC); } interface_extends_list
            '{'
                class_statement_list
            '}' { zend_do_end_class_declaration(&$1, &$2 TSRMLS_CC); }
;


class_entry_type:
        T_CLASS         { $$.u.opline_num = CG(zend_lineno); $$.u.EA.type = 0; }
    |   T_ABSTRACT T_CLASS { $$.u.opline_num = CG(zend_lineno); $$.u.EA.type = ZEND_ACC_EXPLICIT_ABSTRACT_CLASS; }
    |   T_FINAL T_CLASS { $$.u.opline_num = CG(zend_lineno); $$.u.EA.type = ZEND_ACC_FINAL_CLASS; }
;

上面的class_entry_type语法说明在语法分析阶段将类分为三种类型:常规类(T_CLASS), 抽象类(T_ABSTRACT T_CLASS)和final类(T_FINAL T_CLASS )。 他们分别对应的类型在内核中为:

  • 常规类(T_CLASS) 对应的type=0
  • 抽象类(T_ABSTRACT T_CLASS) 对应type=ZEND_ACC_EXPLICIT_ABSTRACT_CLASS
  • final类(T_FINAL T_CLASS) 对应type=ZEND_ACC_FINAL_CLASS

除了上面的三种类型外,类还包含有另外两种类型没有加abstract关键字的抽象类和接口:

  • 没有加abstract关键字的抽象类,它对应的type=ZEND_ACC_IMPLICIT_ABSTRACT_CLASS。 由于在class前面没有abstract关键字,在语法分析时并没有分析出来这是一个抽象类,但是由于类中拥有抽象方法, 在函数注册时判断成员函数是抽象方法或继承类中的成员方法是抽象方法时,会将这个类设置为此种抽象类类型。
  • 接口,其type=ZEND_ACC_INTERFACE。接口类型的区分是在interface关键字解析时设置,见interface_entry:对应的语法说明。

这五种类型在Zend/zend_complie.h文件中定义如下:

#define ZEND_ACC_IMPLICIT_ABSTRACT_CLASS    0x10
#define ZEND_ACC_EXPLICIT_ABSTRACT_CLASS    0x20
#define ZEND_ACC_FINAL_CLASS                0x40
#define ZEND_ACC_INTERFACE                  0x80

常规类为0,在这里没有定义,并且在程序也是直接赋值为0。

语法解析完后就可以知道一个类是抽象类还是final类,普通的类,又或者接口。 定义类时调用了zend_do_begin_class_declaration和zend_do_end_class_declaration函数, 从这两个函数传入的参数,zend_do_begin_class_declaration函数用来处理类名,类的类别和父类, zend_do_end_class_declaration函数用来处理接口和类的中间代码 这两个函数在Zend/zend_complie.c文件中可以找到其实现。

在zend_do_begin_class_declaration中,首先会对传入的类名作一个转化,统一成小写,这也是为什么类名不区分大小的原因,如下代码:

<?php
class TIPI {
}

class tipi {

}
?>

运行时程序报错: Fatal error: Cannot redeclare class tipi。 这个错误会在运行生成中间的代码时触发。 此错误的判断过程在后面中间代码生成时说明。而关于类的名称的判断则是通过 T_STRING token, 在语法解析时做的判断, 但是这只能识别出类名是一个字符串。假如类名为一些关键字, 如下代码:

class self {
}

运行, 程序会显示: Fatal error: Cannot use ‘self’ as class name as it is reserved in…

以上的错误程序判断定义在 zend_do_begin_class_declaration 函数。 与self关键字一样, 还有parent, static两个关键字的判断在同一个地方。 当这个函数执行完后,我们会得到类声明生成的中间代码为:ZEND_DECLARE_CLASS 。 当然,如果我们是声明内部类的话,则生成的中间代码为: ZEND_DECLARE_INHERITED_CLASS。

根据生成的中间代码,我们在Zend/zend_vm_execute.h文件中找到其对应的执行函数 ZEND_DECLARE_CLASS_SPEC_HANDLER。 这个函数通过调用 do_bind_class 函数将此类加入到 EG(class_table) 。 在添加到列表的同时,也判断该类是否存在,如果存在,则添加失败,报我们之前提到的类重复声明错误,只是这个判断在编译开启时是不会生效的。

类相关的各个结构均保存在struct _zend_class_entry 结构体中。这些具体的类别在语法分析过程中进行区分。 识别出类的类别,类的类名等,并将识别出来的结果存放到类的结构中。

eclipse-查看继承层次图/继承实现层次图

阅读代码时,如果想要看某个类继承了哪些类、实现了哪些接口、哪些类继承了这个类,恰巧这个类的继承实现结构又比较复杂,那么如果对开发工具不是很熟练,这个需求是比较难以实现的。eclipse中的type h...
  • panweiwei1994
  • panweiwei1994
  • 2017年08月05日 16:46
  • 2130

IOS开发语言Swift入门连载---类和结构体

IOS开发语言Swift入门连载—类和结构体类和结构体是人们构建代码所用的一种通用且灵活的构造体。为了在类和结构体中实现各种功能,我们必须要严格按照常量、变量以及函数所规定的语法规则来定义属性和添加方...
  • wangzi11322
  • wangzi11322
  • 2015年04月25日 09:41
  • 1107

C++中结构体与类的区别

学习了C++的面向对象,最常见的和写的就是类结构体,下面主要介绍一下结构体和类的区别。 首先类是C++中面向对象独有的,但是C和C++中都有结构体,下面我们来看一下C和C++中结构体的区别。这里主要...
  • nopoppy
  • nopoppy
  • 2016年10月25日 22:37
  • 1309

类和结构的区别

区别如下: 1.存储类型:结构是值类型,存储在
  • yikeshu19900128
  • yikeshu19900128
  • 2014年10月23日 17:02
  • 1773

Java反射机制(二):通过反射取得类的结构

一、 通过反射取得类所实现的全部接口
  • zuiwuyuan
  • zuiwuyuan
  • 2014年09月28日 22:42
  • 591

结构体和类的区别

结构体和类的区别:     在做一个项目时,使用了较多的结构体,并且存在一些结构体的嵌套,即某结构体成员集合包含另一个结构体等,总是出现一些奇怪的错误,才终于下决心好好分析一下到底类和结构体有啥不同...
  • u013341034
  • u013341034
  • 2016年03月14日 11:34
  • 2592

自定义php通用树形结构类

  • zhangfei8625
  • zhangfei8625
  • 2015年04月09日 18:20
  • 1321

Class类文件的结构

JAVA实现平台无关性的基础是虚拟机和字节码存储格式,使用Java编译器可以把Java代码编译为存储字节码的Class文件,使用JRuby等其他语言的编译器一样可以把程序代码编译成Class文件,虚拟...
  • linzhuowei0775
  • linzhuowei0775
  • 2015年11月01日 10:56
  • 771

深入理解JVM06--类文件结构--Class类文件结构及实例

本文是基于周志明的《深入理解Java虚拟机》 Class类文件结构   Class文件     1).是一组以8字节为基础单位的二进制流,     2).各个数据项目严格按照顺序紧凑排列在clas...
  • oChangWen
  • oChangWen
  • 2016年05月19日 22:39
  • 2330

线性表链式存储结构的C++模板类程序源代码

线性表链式存储结构就是链表,刚开始定义该结构的时候,一直困惑于链表的成员变量是否只是一个节点头指针,觉得为什么不能将节点结构体的数据和next节点指针直接作为链表的成员变量呢?现在回过头来想想,是混淆...
  • Alex123980
  • Alex123980
  • 2016年05月31日 19:58
  • 1054
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:67-类的结构和实现
举报原因:
原因补充:

(最多只允许输入30个字)