构造函数和析构函数
构造函数:
PHP5可以在类中使用__construct()定义一个构造函数,具有构造函数的类,会在每次对象创建的时候调用该函数,因此常用来在对象创建的时候进行一些初始化工作。
- 构造函数可以接受参数,能够在创建对象时赋值给对象属性
- 构造函数可以调用类方法或其他函数
- 构造函数可以调用其他类的构造函数
- PHP中的子类的构造函数不会主动调用父类的构造函数,要显示的使用parent::__construct()调用:
- 也可以使用类名直接调用构造函数
private $name;
public function __construct($name){
$this->setName($name);
parent::__construct($name);//调用父类构造函数
Animal::__construct($name); // 调用Animal构造函数
}
析构函数
析构函数指的是当某个对象的所有引用被删除,或者对象被显式的销毁时会执行的函数。
- 析构函数是在销毁对象时自动调用,不能显示的调用
- 析构函数不能带参数
- 要执行父类的析构函数,必须在子类的析构函数中显示调用parent::__destruct()
以下几种情况可能会调用析构函数:页面加载完毕、unset()、变量引用指向别的对象或值时
<?php
class test{
function __destruct(){
echo "当对象销毁时会调用!!!";
}
}
$a = $b = $c = new test();
unset($b);
?>
私有属性的设置获取
实际应用中,经常会把类的属性设置为私有(private),那么需要对属性进行访问时,就会变得麻烦。虽然可以将对属性的访问写成一个方法来实现,但 PHP 提供了一些特殊方法来方便此类操作。
_set() 方法用于设置私有属性值;在类里面使用了 __set() 方法后,当使用 $p1->name = "张三"; 这样的方式去设置对象私有属性的值时,就会自动调用 __set() 方法来设置私有属性的值。
function __set($property_name, $value){
$this->$property_name = $value;
}
_get() 方法用于获取私有属性值
function __get($property_name, $value){
return isset($this->$property_name) ? $this->$property_name : null;
}
_isset() 方法用于检测私有属性是否被设置;如果对象里面成员是公有的,可以直接使用 isset() 函数。如果是私有的成员属性,那就需要在类里面加上一个 __isset() 方法:
private function __isset($property_name){
return isset($this->$property_name);
}
_unset() 方法用于删除私有属性;同 isset() 函数一样,unset() 函数只能删除对象的公有成员属性,当要删除对象内部的私有成员属性时,需要使用__unset() 方法:
private function __unset($property_name){
unset($this->$property_name);
}
__toString()方法
__toString() 是快速获取对象的字符串信息的魔术方法,它在直接输出对象引用时自动调用。
__toString() 的作用:
当我们调试程序时,需要知道是否得出正确的数据,比如打印一个对象时,看看这个对象都有哪些属性,其值是什么。如果定义了toString方法,在打印对象体时,就会自动调用该方法,格式化输出这个对象所包含的数据。
<?php
class Person{
private $name = "";
function __construct($name = ""){ // 定义构造函数
$this->name = $name;
}
public function say(){ // 定义公共方法
echo "hello,".$this->name."!";
}
function __toString(){ // 定义获取字符串的魔术方法
return "__toString:".$this->name.'!';
}
}
$blog = new Person('blog');
$blog->say(); // 输出:hello,blog!
echo $blog; // 输出:__toString:blog!
?>
如果没有定义__toString() 方法,则执行echo语句时,会报错。
Catchable fatal error: Object of class Person could not be converted to string in /usercode/file.php on line 16
__clone()方法
clone关键字
用于克隆一个完全一样的对象,克隆之后两个对象互不干扰。
适用场景:在编码过程中,有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。
// 使用clone关键字之前的复制效果实例
//
// 定义一个类,类中定义一个公共成员变量
class TestClass{
public $value;
}
$obj = new TestClass(); // 实例化TestClass类的对象
$obj->value = 'qq'; // 给成员变量赋值
var_dump($obj); // object(TestClass)#228 (1) { ["value"]=> string(2) "qq" }
$obj2 = $obj; // 将$obj赋值给$obj2变量,等同于复制一个$obj
$obj2->value = 'zz'; // 更改$value的值
var_dump($obj); // object(TestClass)#228 (1) { ["value"]=> string(2) "zz" }
var_dump($obj2); // object(TestClass)#228 (1) { ["value"]=> string(2) "zz" }
从以上的例子可以看出来当实例化对象赋值给其他变量时,改变实例化对象和改变赋值对象,另一方都会随之改变。这是因为普象的赋值,仅仅是把新对象指向对象存储的地址(即 $obj给了$obj2它的地址,就和引用一样,$obj2指向了$obj,所以导致两者所执行的操作将会收到影响)。
// 使用clone关键字之后的复制效果实例
//
// 定义一个类,类中定义一个公共成员变量
class TestClass
{
public $value;
}
$obj = new TestClass(); // 实例化TestClass类的对象
$obj->value = 'qq'; // 给成员变量赋值
var_dump($obj); // object(TestClass)#228 (1) { ["value"]=> string(2) "qq" }
$obj2 = clone $obj; // 使用clone关键字克隆$obj
$obj2->value = 'zz'; // 更改$value的值
var_dump($obj); // object(TestClass)#228 (1) { ["value"]=> string(2) "qq" }
var_dump($obj2); // object(TestClass)#228 (1) { ["value"]=> string(2) "zz" }
可以看出,使用clone关键字之后,更改$obj2的成员变量值并不会对$obj的成员变量产生影响,克隆之后的两个对象$obj和$obj2是互不影响,互相独立的。
__clone方法
用于重写原本的属性和方法;__clone() 方法只会在对象被克隆的时候自动调用。
__clone()方法对一个对象实例进行的浅复制,对象内的基本数值类型进行的是传值复制,而对象内的对象型成员变量,如果不重写__clone方法,显式的clone这个对象成员变量的话,这个成员变量就是传引用复制,而不是生成一个新的对象。
// 定义age类
class Age{
public $balance;
public function __construct($balance){
$this->balance = $balance;
}
}
//
// 定义第二个类,类中包括两个公共成员以及构造函数、克隆方法
class IndexService{
public $age; // 对象属性成员变量
public $name; // 非对象属性成员变量
public function __construct($name, Age $age){
$this->name = $name;
$this->age = $age;
}
// 使用clone的时候触发
public function __clone()
{
$this->name = 'name:'.$this->name;
$this->age = clone $this->age; // 这个对象会被强制复制,这样就不会指向原来的对象。
}
}
// 运行
$obj = new IndexService('peter',new Age(20));
$obj2 = clone $obj;
$obj2->age->balance = 10;
var_dump($obj);
var_dump($obj2); // age对象中的balance值更改为10,name值也被更改。
注意: __clone()方法中的clone操作只能用于对象,如果用于非对象属性,将会报错:# 致命错误: __clone method called on non-object。
例子中的age就是一个对象。
以上例子中,$age对象被强制复制,不会指向原来的对象,所以修改其中的值之后,并不会对原来对象有影响;如果不加上$this->age = clone $this->age;这行代码,运行结果则变成这样:两个对象的age对象的balance值都改成了10。
__call()方法
__call() 方法用于监视错误的方法调用。该方法在调用的方法不存在时会自动调用,程序会继续执行下去。
该方法有两个参数,第一个参数会自动接收不存在的方法名,第二个参数则以数组的方式接收不存在方法的多个参数。
<?php
class Test
{
function __call($function_name, $args)
{
echo "你所调用的函数:$function_name(参数:<br />";
var_dump($args);
echo ")不存在!";
}
}
// 调用不存在的方法
$p1=new Test();
$p1->test(2,"test");
//你所调用的函数:test(参数:array(2) {[0]=> int(2) [1]=>string(4) "test"})不存在!
类的自动加载
php实现类文件自动载入有两种办法:
- 魔术方法:__autoload();
- SPL标准库提供的spl_autoload_register();
__autoload()
__autoload()是php中的一个魔术方法,在代码中当调用不存在的类的时候会自动调用。
在开发过程中可以使用include或者require函数来引入需要调用的类文件,但是如果需要调用的类很多的时候,对应的include或者require也会很多,会造成代码冗余,而且每次执行都要加载这些文件,会白白占用内存,其次当删除了某个类文件时,也要对应的修改include或者require语句。
使用__autoload()魔术方法来代替include或者require语句,则可以避免大量的代码冗余,也可以提高执行效率。
在 Test.php文件中我们调用 类Test1 和 类Test2,由于我们没有显式的引用类文件,那么系统就会自动调用 __autoload() 方法。
//Test.php文件
function __autoload($class){
if(file_exists($class.".php")){
require_once($class.".php");
}else{
die("文件不存在!");
}
}
Test1::test();
Test2::test();
__autoload()的缺点:
- 一个文件中只允许有一个__autoload(),当引入的项目中有其他__autoload()时,则会发生冲突。
- 当项目中的类根据不同的用处放在不同的文件夹中,此时要分别调用对应的类,会十分困难。
spl_autoload_register()
该函数可以注册任意数量的自动加载器,当使用未被定义的类和接口时自动去加载。
//Test.php文件
function my_autoload1($class){
if(file_exists("classes/".$class.".php")){
require_once("classes/".$class.".php");
}else{
die("文件不存在!");
}
}
function my_autoload2($class){
if(file_exists("core/".$class.".php")){
require_once("core/".$class.".php");
}else{
die("文件不存在!");
}
}
//将加载函数注册到PHP中
spl_autoload_register("my_autoload1");
spl_autoload_register("my_autoload2");
Test1::test();
Test2::test();
鸣谢:看云chunyu