php 类和对象
⾯向对象是⼀种编程范式,它将对象作为程序的基本单元,将程序和数据封装起来, 以此来提⾼程序的重⽤性、灵活性和可扩展性。
⽬前很多语⾔都⽀持⾯向对象编程,既然对象对象是⼀种范式,其实这就和具体的编程语⾔没有直接关系, 只不过很多语⾔将这个范式作为语⾔的基本元素,使⽤C语⾔也能够进⾏⾯向对象编程。
⾯向对象的程序设计中包含:
1. 类。类是具体事物的抽象。通常类定义了事物的属性和所能完成的⼯作。有⼀点需要注意, 并不是所有的⾯向对象编程语⾔的类都具有class这个明确的实体。例如Javascript就不是基于类的。Javascript中的类(Function)也具有类定义的特性。这也印证了⾯向对象只是⼀种编程范式。
2. 对象。对象是类的实例。对象是具体的。
3. ⽅法。⽅法是类定义对象可以做的事情。
4. 继承性。继承是类的具体化,⼦类是⽐具备更多特性和⾏为的类。⾯向对象是对现实世界的⼀个抽象。 在很多时候的关系并不⼀定是继承关系。能在⼀定程序上实现代码的重⽤。
5. 封装性、抽象性。封装性能实现的复杂性隐藏,减少出错的可能。
从我们接触PHP开始,我们最先遇到的是函数:数组操作函数,字符串操作函数,⽂件操作函数等等。 这些函数是我们使⽤PHP的基础,也是PHP⾃出⽣就⽀持的⾯向过程编程。⾯向过程将⼀个个功能封装, 以⼀种模块化的思想解决问题。
⾯向对象听起来很美,但是现实中的编程语⾔中很少有纯粹的⾯向对象的语⾔, 处于性能或者程序员的开发习惯,通常的编程语⾔都同时⽀持两种变编程⽅式。
PHP就是如此,从PHP4起开始⽀持⾯向对象编程。但PHP4的⾯向对象⽀持不太完善。 从PHP5起,PHP引⼊了新的对象模型(Object Model),增加了许多新特性,包括访问控制、 抽象类和final类、类⽅法、魔术⽅法、接⼝、对象克隆和类型提⽰等。 并且在近期发布的PHP5.3版本中,针对⾯向对象编程增加了命名空间、延迟静态绑定(Late Static Binding) 以及增加了两个魔术⽅法__callStatic()和__invoke()。
PHP中对象是按引⽤传递的,即对象进⾏赋值和操作的时候是按引⽤(reference)传递的,⽽不是整个对象的拷贝。
这⼀章我们从⾯向对象讲起,会说到PHP中的类,包括类的定义和实现、接⼝、抽象类以及与类相关的访问控制、 对象和命名空间等。除此之外也会从其存储的内部结构,类的单继承的实现,接⼝的多继承, 以及魔法⽅法的实现等细微处着⼿分析类相关的⽅⽅⾯⾯。
⾸先我们来看第⼀⼩节--类的结构和实现。
第⼀节 类的结构和实现
⾯向对象编程中我们的编程都是围绕类和对象进⾏的。那在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代码在内核中的表现。 如表5.1所⽰:
字段名 | 字段说明 | ParentClass类 | Ifce接⼝ | Tipi类 |
name | 类名 | ParentClass | Ifce | Tipi |
type | 类别 | 2 | 2 | 2 |
parent | ⽗类 | 空 | 空 | ParentClass类 |
refcount | 引⽤计数 | 1 | 1 | 2 |
ce_flags | 类的类型 | 0 | 144 | 524352 |
function_table | 函数列表 | 空 | function_name=iMethod | type=2 | fn_flags=258 |
function_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 | /tipi.php |
line_start | 类开始⾏数 | 15 | 18 | 22 |
line_end | 类结束⾏数 | 16 | 20 | 38 |
类的结构中,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(&$, &$, &$ TSRMLS_CC); }
implements_list
'{'
class_statement_list
'}' { zend_do_end_class_declaration(&$, &$ TSRMLS_CC); }
| interface_entry T_STRING
{ zend_do_begin_class_declaration(&$, &$, NULL TSRMLS_CC); }
interface_extends_list
'{'
class_statement_list
'}' { zend_do_end_class_declaration(&$, &$ TSRMLS_CC); }
;
class_entry_type:
T_CLASS { $$.u.opline_num = CG(zend_lineno); $$.u.EA.type = ;
}
| 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 结构体中。这些具体的类别在语法分析过程中进⾏区分。 识别出类的类别,类的类名等,并将识别出来的结果存放到类的结构中。
下⼀节我们⼀起看看类所包含的成员变量和成员⽅法。
第⼆节 类的成员变量及⽅法
在上⼀⼩节,我们介绍了类的结构和声明过程,从⽽,我们知道了类的存储结构,接⼝抽象类等类型的实现⽅式。 在本⼩节,我们将介绍类的成员变量和成员⽅法。⾸先,我们看⼀下,什么是成员变量,什么是成员⽅法。
类的成员变量在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, , NULL, zval_ptr_dtor_func,persistent_hashes, );
因为类的成员变量是保存在HashTable中,所以,其数据的初始化使⽤zend_hash_init_ex函数来进⾏。
在声明类的时候初始化了类的成员变量所在的HashTable,之后如果有新的成员变量声明时,在编译时 zend_do_declare_property。函数⾸先检查成员变量不允许的⼀些情况:
- 接⼝中不允许使⽤成员变量
- 成员变量不能拥有抽象属性
- 不能声明成员变量为final
php 类和对象的更多相关文章
- Java编程里的类和对象
像我们搞计算机这块的,都知道这么一件事,当前的计算机编程语言主要分为两大块,一为面向过程,二为面向对象.Java就是一门纯面向对象的语言.学习了一个月左右的Java,在下对于Java当中的类和对象有了 ...
- Python - 类与对象的方法
类与对象的方法
- C++基础知识(5)---类和对象
终于把C++中的基础在前面的几篇博客中总结完了,可能还有一些语法还没有总结到,没关系,以后用到了再查资料就好.类是C++中的一个非常重要的概念,这是区别你使用的C++到底是面向过程还是面向对象的一个重 ...
- 简述JavaScript对象、数组对象与类数组对象
问题引出 在上图给出的文档中,用JavaScript获取那个a标签,要用什么办法呢?相信第一反应一定是使用document.getElementsByTagName('a')[0]来获取.同样的,在使 ...
- 前端学PHP之面向对象系列第一篇——类和对象
× 目录 [1]类 [2]成员属性[3]成员方法[4]对象[5]成员访问[6]this 前面的话 面向对象程序设计(OOP)是一种计算机编程架构.计算机程序由单个能够起到子程序作用的单元或对象组成,为 ...
- Objective-C Runtime 运行时之一:类与对象
Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理.这种动态语言的优势在于:我们写代码时更具灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一 ...
- [Java入门笔记] 面向对象编程基础(一):类和对象
什么是面向对象编程? 我们先来看看几个概念: 面向过程程序设计 面向过程,是根据事情发展的步骤,按进行的顺序过程划分,面向过程其实是最为实际的一种思考方式,可以说面向过程是一种基础的方法,它考虑的是实 ...
- 解析Java类和对象的初始化过程
类的初始化和对象初始化是 JVM 管理的类型生命周期中非常重要的两个环节,Google 了一遍网络,有关类装载机制的文章倒是不少,然而类初始化和对象初始化的文章并不多,特别是从字节码和 JVM 层次来 ...
- 02OC的类和对象
这章重点介绍OC的类以及对象,由于C语言是面向过程语言,而OC只是对于C语言多了一些面向对象的特性,所以OC相对于其他面向对象语言,例如C#.Java等没有那么多的语法特性,所以差别还是比较大的. 一 ...
- swift基础:第六部分:类与对象
http://reactnative.cn/docs/0.24/getting-started.html#content(react Native 开发文档) 互联网这个时代,你松懈一天,就会有很多很 ...
随机推荐
- ELK kibana查询与过滤
在kibana中,可通过搜索查询过滤事务或者在visualization界面点击元素过滤. 创建查询 在Discover界面的搜索栏输入要查询的字段.查询语法是基于Lucene的查询语法.允许布尔运算 ...
- iOS开发-多线程之GCD(Grand Central Dispatch)
Grand Central Dispatch(GCD)是一个强有力的方式取执行多线程任务,不管你在回调的时候是异步或者同步的,可以优化应用程序支持多核心处理器和其他的对称多处理系统的系统.开发使用的过 ...
- 火速提升Android仿真器的运行速度 ——仿真器Genymotion
一.问题概述 Android开发中会使用仿真器测试应用,但不管你使用Eclispe ADT还是Android Studio仿真器都是基于arm架构的,运行起来都很慢,光启动就要花费很多时间,都不知道它 ...
- scala 学习笔记一 列表List
1.介绍 Scala 列表类似于数组,它们所有元素的类型都相同,但是它们也有所不同:列表是不可变的,值一旦被定义了就不能改变,其次列表 具有递归的结构(也就是链接表结构)而数组不是.. 列表的元素类型 ...
- libFM 简介
原文:https://eachcloudcn.blob.core.chinacloudapi.cn/clips/XOI1W.htm libFM全称为Factorization Machine Libr ...
- IStat Menus 5.02 5.03 的注册码
1574-5977-7956-8062-0000 6015-5448-3282-4975-0000 9665-5955-6856-2071-0000 2447-9517-7939-5221-0000
- [Javascript] Funciton Expression
//This will load the code into the memory no matter //you call it or not function diffOfSquares(a,b) ...
- BZOJ 3172 Tjoi2013 单词 后缀数组
题目大意:给定一个n个单词的文章,求每一个单词在文章中的出现次数 文章长度<=10^6(不是单词长度<=10^6,不然读入直接超时) 首先将全部单词用空格连接成一个字符串.记录每一个单词的 ...
- Oracle 与 SqlServer 的区别浅析总结
我主要用过的数据库为Oracle10g和SqlServer2008,通过实际运用和查阅资料整理如下: 主题 Oracle 10g SQLServer 2008 存储过程格式 Create Or Rep ...
- python中的lambda知多少!
python允许使用lambda关键字创造匿名函数,匿名函数是因为不需要以标准的方式来声明,比如说,使用def语句.(除非赋值给一个局部变量,这样的对象也不会再任何的名字空间内创建名字)然而,作为函数 ...
- Java编程里的类和对象