面向对象深入总结

面向对象
一、类:


1. 属性注意点:
1. 关于属性值,可以声明属性并赋值,也可以声明属性先不赋值。如果不赋值则属性的初始值是  NULL。
2. 关于PHP中的类,属性必须是一个“直接的值”。就是必须是一个8种类型中的任意一个类型的  值。
  不能是表达式(1-2)的值或者函数的返回值(time()),如下:
  class Human{
    public $age =time(); //错误
    public $age =1+2;  //错误
  }


2. 方法注意点:
1. 类方法不能重复定义。但是类中的方法可以理解为是包在类范围内的函数,和全局的函数不是   一回事,因此这两种可以重名。
2. 不能定义系统关键字的方法


3. 构造函数:
class Human{
public $name=null;
public $sex;
public function __construct($name,$sex){
$this->name=$name;
$this->sex=$sex;
}
}
$a = new Human(‘zhangsan’,’男’);


作用:构造函数用来初始化对象,利用构造函数,我们可以操作对象,传值等。
作用时机:构造函数就是在对象产生的时候,自动执行,就像婴儿出生时候的啼哭声。




4. 析构函数:
class Human{
public $name=null;
public $sex;
public function __destruct(){
echo “最后的遗言”;
}
}


作用:在脚本调用完毕以后,需要做的事情,放在析构函数里。
作用时机:析构函数就是在对象销毁的时候,自动执行,就像老人的临时遗言。
publci __destruct(){}当一个对象销毁的时候被调用。但实际程序中,却不需要专门销毁一个对象
因为PHP是脚本语言,在代码执行到最后一行的时候,所有申请的内存都要释放掉,自然对象的那段内存也要释放,而这个时候析构函数被自动执行。php在刷新页面的时候,申请所有内存,执 行完毕以后收回所有内存。所以php想内存泄露,也不是件容易的事情。




$a=new Human();
$b=new Human();
$c=new Human();




例子1:
class Human{
    public function __construct(){
        echo "我诞生了<br/>";
    }
    public function __destruct(){
        echo "终究没有逆袭<br/>";
    }
}
$a = new Human();
$b = new Human();
$c = new Human();
$d = new Human();
unset($a);
$b = false;
$c = NULL;
echo "<hr/>";
问题:为什么最后一个’终究没有逆袭’出现在灰线下面?
答案:最后一次销毁是在php的页面执行完毕了,也就是echo "<hr/>";执行完了,系统才销毁 对象进行回收,此时的$d才销毁,这个时候才调用$d对象中的 echo "终究没有逆袭<br/>";


例子2:
class Human{
    public function __destruct(){
        echo "死了<br/>";
    }
}
$a = new Human();
$b = $c = $d = $a;
unset($a);
echo "<hr/>";
问题:输出结果是什么?为什么?
答案:输出横线和横下下面 "死了",因为当unset($a)后,由于还有$b,$c,$d依旧指向这个对   象,所以这个对象并没有因为$a的销毁而销毁,直到页面运行完毕以后才由系统自动销毁。


例子3:
class Human{
    public function __destruct(){
        echo "死了<br/>";
    }
}
$a = $b = $c =new Human();
unset($a);echo "a<br/>";
unset($b);echo "b<br/>";
unset($c);echo "c<br/>";
echo "<hr/>";
问题:输出为什么?为什么?
答案:输出如下图:
  
注销变量$a后输出a,注销$b后输出b, 但是注销$c后,对象没有其他变量指向它了所以跟着 $c的注销也销毁了,而这个时候调用了析构函数,输出了”死了” ,接着输出c和横线。


5. $this:
class Human{
    public $name='lis';
    public function who(){
        echo $this->name;
    }
public function test(){
echo $name;
}
}
$a = new Human();
$a->who();
$a->test();
原理:当一个对象($a)调用其方法(who())时,该方法执行之前,先完成一个绑定。就是$this绑  定到调用该方法的那个对象上。


注意:$a->test();这个时候,会报错说未定义的方法。因为在方法体内想访问调用者的属性,必须  用$this,如果不加$this则理解为调用方法内部的一个局部变量。


6. new :
1.  new 对象时:
1. 申请内存,生成对象(属性和方法的集合)
2. 如果有构造函数,则执行
3. 返回该对象的地址


2.  构造函数:
1. 对象销毁时执行。
2. 什么叫销毁对象?
答:$a = new Human();
$a 并不是对象,它只是一个变量名,指到对象。
unset($a) 未必销毁了对象。如果还有其他变量同时指到对象的时候
比如:$b=$c=$a=new Human();。只有所有指向对象的变量全部销毁才销毁。
7. static  静态属性和方法:


概念:在属性和方法之前加static修饰,这种称为静态属性/静态方法。


原理引导:


我们声明类的时候,内存中有一个类的内存空间,存放着所有属性和方法。当我们实例化对象的时候,每实例化一次,都会在内存中重新开辟一个内存空间。比如一个类如下:
class Human{
public name;
public age weight;
public weight;
static public head=1;
}
如果实例化8个对象,就相当于在内存中分别有了8个name,age ,weight,head=1.但是如果这个类恰好比如的是人,而人都有一个共同特点,就是都有一个头,head=1,如果我们把这个共同的属性拿出来的话,就不用定义8次浪费空间了。这个时候,我们就可以不把它定义为普通的属性,而定义为静态属性。这个时候这个静态属性就存在于类当中了,无论是否实例化它都存在于类中,并且通过类名::静态属性调用,比如:Human::head.


从内存角度看:
static属性存放在类的区域中,普通属性存放在对象中,由此推到出2点:
1. 类声明完毕该属性就存在,对于它的访问不依赖于对象。
2. 因为类在内存中只有一个,因此静态属性只有1个。
3. 当时当静态属性被某个对象调用过程中改变了值的话,那么它就真的在类中改变了值。


和普通方法属性的区别:
普通方法需要对象去调用,也就需要绑定$this,即普通方法必须要有对象去调用。
静态方法不属于哪个对象,不需要绑定$this,只需要类名就可以调用。


如上分析:
1. 其实非静态方法是不能由类名静态调用的,但是由于php中的面向对象检测不严格,  只要该方法没有$this,就会转化为静态方法来调用,但是PHP5.3的strict模式下,或  者PHP5.4默认模式下,都已经对类名::非静态方法的调用做错误提示:
  Error_reporting(E_ALL|E_STRICT);
  


2.  类  --》 访问 --静态方法 :可以
   类  --》 访问 --普通方法 :不可以
   
   对象 --》 访问 --静态方法: 可以
   对象 --》 访问 --普通方法: 可以


8. self 和 parent :


1. 概念:self :代表本类,自身(不要理解为本类的对象)
         parent: 代表父类


2. 使用场景:在引入自身的静态属性和静态方法,以及父类的属性和方法的时候,可以用到。


3.  用法:
self::$staticProperty  调用静态属性  //有$符号
self::staticMothed  调用静态方法
parent::Mothed;   调用父类的所有方法
--------
$this->Property  //没有$符号
Human::HEAD;(const HEAD = 1;) //常量




4.  class Human{
static public $head=1;
public function say(){
echo Human::$head; 改为 :echo self::$head;
}
    }
当我们这个类名需要改的时候,其他涉及到类名的地方都必须跟着改,不是很方便,如果使用类名的次数很庞大的时候,我们的灵活性就极度下降了,这个时候如果在类内使用类名的放换成self的话,讲解决这个问题。


class Stu extends Human{
static public $head = 2;
public function say(){
echo self::$head; //子类继承父类的$head后重写,所以sel::$head调用的子类的2.
echo parent::$head; == Human::$head; //但是我依旧想调用父类的$head,这样就ok了。
}
}
$ming = new Stu(); -
$ming->say(); 


5. 用$this 还是用 parent?
class a{
public function a1(){
echo ‘this is class function a1()’;
}
}
class b extends a{
public function b1(){
$this->a1();
}
public function b2(){
parent::a1();
}
}
$b = new b();
$b->b1();
$b->b2();


解析:
从效果看:显示效果是一样的。
从速度看:理论上parent::稍微快一点点,因为它直接锁定的就是父类中的方法,$this它  是先从子类中搜索,如果没有才从继承过来的父类中找。
从面向对象:继承过来的就是自己的,$this更符合面向对象的思想。


答案:用$this,除非你明确要调用父类中某个方法,而这个方法子类已经重写,和父类不同了。


反例演示:
class a{}  class b extends a{}  class c extends b{} ......
class f extends e{如果要调用a类中的方法,总不能parent::parent::parent::.....吧??}


9.  final :
final,最终的。
这个关键字在php中,可以修饰类和方法名。 但是不能修饰属性。
修饰类的话,  此类不能被继承。
修饰方法的话,此方法可以被继承,但是子类中不能覆盖重写父类的该方法。 
 
10. 访问控制修饰符:


1. 作用:来说明属性、方法的权限特点,只能写在属性和方法前面。


2. 三类: private 私有的  、protected 受保护的  、public 公共的


3. 访问位置:


private的属性,只能在类定义的花括号{}内才能访问。
public 的属性,在任意位置都可以访问。


4. 权限控制的bug:


class Human(){
    private $money = 1000;
    public function getMoney($people){
        return $people->money;
    }
    public function setMoney($people){
        return $people-=500
;
    }
}
$zhangsan = new Human();
$lisi = new Human();
//echo $lisi->money; 是肯定不行的,因为它是私有的
//让李四去打探张三的钱
$lisi->getMoney($zhangsan);
//让张三去改变李四的钱
$lisi->setMoney($zhangsan);


1. 奇怪之处在于:
1. zhangsan的钱应该由zhangsan来调用getMoney和setMoney才能影响。
2.但是在符合调用原则的情况下:
$lisi有权利调用getMoney方法,而getMoney方法又来类的{}内,有权利读取私有属性money,如果调用私有属性的话需要$this或者该类的对象,这个时候我们已经对$zhangsan=new Human();进行了实例化,把$zhangsan当做参数传了进去, 也就顺利调用到了money全局属性。这样就实现了类外调用类内的私有属性。


2.  李四读取和改变张三的钱,这如果从生活的角度来看是不合理的。因为钱私有,是指    每个对象的钱针对每个对象私有:
即:张三的钱,由张三->shouwMoney才能引用。李四不应该有权引用或者说李四想引用也只能是引用自己的money才对。但是上面的代码李四却引用和改变了张三的钱。这是为什么呢?


答案:PHP在实现上,并不是以对象为单位来控制权限的。而是以类为单位来控制权  限的。权限的界限是类{}内和类外。


注意;所以说只要执行的过程环境只要在类内,我们就有办法取得类私有属性或方法 的使用权。但是为什么PHP的权限控制不设为对象呢,以为类只设定一次,这 样简化了程序的定义过程,但我们无法去预测对象会有多少。


3.  但是无论怎么样,张三可以操作李四的钱终究不合理。其实我们这样的用法本身就不 具有现实的意义,我们应该尽量的来调用对象的方法,而不应该直接把对象当成参数 传到另一个对象的方法中使用。就像,小偷是可以去伸到别人的口袋里去偷钱的,即 使钱是我们自己私有的,但是并不是说可以就是对的。


二、面向对象:


面向对象的三大特征:封装、继承、多态。
1. 封装:


class Human(){
    public $money = 1000;
}
$lisi = new Human();
echo $lisi->money;  //这个时候李四有1000块钱。


//我们变一下
$lisi->money=500;
echo $lisi->money;  //这个时候却变成了500块钱。


李四的钱,别人可以随便访问,也可以随便改变,在现实生活中是不合理的。所以有些东西是可以 让大家共用的比如名字,但是有些的东西必须保护起来自己用,比如钱。但是你不能让你的钱保护 起来别人就不能问或者用不了,但是可以经过你的同意后,经过处理再让外部调用。
class Human(){


    public function showMoney(){
        return $this->money * 0.8;
    }
}
$lisi =new Human;


总结封装概念:
对于一个对象,对于外界开放一个接口,调用接口时内部进行的操作不需要外界知道。隐藏了内部的一些实现细节,这就是对方法的封装。
比如:你看电视的时候,只需要按一下电视开关就可以实现看电视的动作,却不需要知道人家电视机内部是怎么工作的。


2. 继承:
概念:是指以一个类为父类,另一个类作为其子类,子类在继承父类的属性和方法的基础上,进一  步增添或修改
class Human{
    private $wife = '小甜甜';
    public function cry(){
        echo '1111';
    }
}
class Stu extends Human{
    public function subtell(){
        echo $this->wife;
    }
}
$lisi = new stu();
$lisi->subtell();//出错如下:
Undefined property:Stu::$wife in D....
问:父类不是有wife属性吗,为什么没有继承过来?
答:私有的属性,可以理解为不能继承。(其实可以继承过来,只不过无法访问)




  1. private :其实可以继承,只是无权修改。就像牌位,可以继承但不能动它。print_r($lisi); 打印   已经继承了含有私有属性父类的子类的时候,可以看出,私有属性已经被继承过来
        


第一:$lisi = new Stu() 创建一个STu对象,由于类Stu继承了Human,所以$lisi    这个对象里保存了两个$wife属性,一个来自父类Human,一个来自Stu。


第二:$lisi->sshow()调用时,无论是sshow方法里的$this还是pshow方法里的$this  都指向的是$lisi这个对象。为什么呢?parent::pshow()就相当于$this->pshow()。


第三: $lisi->sshow(),由于parent::pshow()的执行层面在Human中,所以这里 的wife指向的是“小甜甜”。sshow方法里的$this->wife执行层面是在Stu 这个指向的是Human层中的wife。


问题1:如果只把Stu类中的wife=’凤姐’,什么效果?
答案1:因为sshow方法里的$this->wife;不可能去父类的层面找wife属性,所以   报属性未定的错误。






问题2: 如果只把Human类中的wife=’小甜甜’去掉,什么效果?
答案2: 如果把父类中的wife=“小甜甜”去掉以后,当parent::pshow()->然后 到父类中的pshow(){echo $this->wife}时,$this->wife时,没有属性wife了, 而且执行层面在父类,它没有权限调用在子类Human中的private属性的 $wife=”凤姐”。但是如果去掉Human类中的wife=’小甜甜’的同时把Stu中的 $wife=”凤姐”设置为public的话,就不会报错,而是打印出两个“凤姐”。




2. public protected :都可以继承,并拥有访问和修改的权限。
       比如:家产已经继承,爱卖就卖,爱改就改。


class Human{
    protected $wine="1斤";
    public function play(){
        echo "谈理想<br/>";
    }
}
class Stu extends Human{
    public function play(){
        echo "玩网游<br/>";
    }
    public function momo(){
        echo "美女,认识一下<br/>";
    }
    public function show(){
        echo $this->wine,"<br/>";
    }
}
$lisi = new Stu();
$lisi->show();//父类有的,继承过来
$lisi->play(); //父类有的,继承过来可以修改和覆盖
$lisi->momo();//父类没有的,可以增加


3. 继承时的权限变化:
class Human{
    public function play(){
        echo "谈理想<br/>";
    }
}
class Stu extends Human{
    private function play(){
        echo "玩网游<br/>";
    }
}
$lisi = new Stu();
$lisi->play(); 


Fatal error: Access level to Stu::play() must be public (as in class Human) in


子类的cry比父类的cry方法权限要严格,这不行。
继承自父类的属性和方法在覆盖或者重写的时候一定要比父类的宽松或者相同。


4. 构造函数的继承:
构造方法也是可以继承的,而且继承的原则和普通方法一样。进而,如果子类也声明构造函数,则父类的构造函数被覆盖!如果父类的构造函数被覆盖了,自然只执行子类中的新的构造函数。


问题:如果一个数据库类,或者model类,我们肯定是要继承过去再使用,不能直接      操作model类,而model类的构造函数做了很多的初始化工作。我们如果重  写构造函数以后,导致父类的初始化工作被覆盖而不能完成。怎么办?
答案:子类继承父类以后,为了保留父类的构造函数中的业务逻辑,可以先在子类的  构造函数中调用parent::__construct();这样就会先执行父类中的业务逻辑,然  后再执行子类中拓展的逻辑。


class MyDB extends mysql{
public funtion __construct(){
parent::__construct(); //如果子类继承时,有构造函数,保险一点,
//然后再写自己的业务逻辑。
$this->ini();
}
public function ini(){
如果有更多的业务逻辑需要初始化,则把这个方法在构造函数中调用,
就起到了相当于构造函数的目的。
}
}


5. public,private,protected区别:




public   :  类外可以调用,  可以被子类继承
protected:  类外不可以调用,可以被子类继承
private  :  类外不可以调用, 不可以被子类继承。
(private其实可以被继承,但是系统有标记,标记为父类层面的私有属  性,所以子类无权修改或者调用)


3. 多态:
概念:多态其实就是只抽象的声明父类,具体的工作由子类对象继承父类后具体来完成。这样不同  的子类对象完成不同特点的工作了。


注意:因为如果按PHP本身特点,是不检测类型,本身就可以说是多态,甚至是变态的。PHP5.3  以后,引入了对于对象类型的参数进行检测。只检测对象所属的类。其实这样做对于php  来说是限制了其灵活性,达到了java中多态的效果。
实例:
Glass参数必须是$g要传进来这个对象的父类名字,对应的所有子类实例化后才可以都通过。比如new Pig()的父类不是Glass,这个时候就不会通过类型验证。如果不Glass位置的参数不是父类的话,只是一个子类的话,比如是BlueGlass的话,$g就只能是类BlueGlass类实例化以后的对象。注意:public function ons(Glass $g)中间是空格,不是逗号。


class Light{
    public function ons(Glass $g){
        $g->display();
    }
}
class Glass{
    public function display(){}
}
class RedGlass extends Glass{
    public function display(){
        echo "red";
    }
}
class BlueGlass extends Glass{
    public function display(){
        echo "blue";
    }
}
class Pig{
    public function display(){
        echo "八戒,八戒!";
    }
}


$light = new Light();//造手电筒
$red = new RedGlass();//造红玻璃
$blue = new BlueGlass();//造蓝玻璃


$light->ons($red);
$light->ons($blue);


//$light->ons(new Pig());按照上面的参数,这个是通不过类型检验的。


三、单例模式:


1. 单例模式引入和完成:


第一步:一个普通的类可以new来实例化,这显然不是单例。
class single{  }
$single = new single();


第二部:防止类外实例化new。看来new是罪恶之源,干脆不让new了。
class single{
protected function __construct(){}
}
我们把构造方法protected/private的话,外部就不能new了。
但引出一个问题:不能new的话,那得不到对象这也不是单例呀,是“0例模式”了?


第三部:实例化对象在类内进行。不能在外部实例的原因是__construnct()实例化的时候会被自动的 外部调用,但是如果protected的话,外部是不能调用的,但是我们内部调用的话就不存 在上述问题了。
class single{
protected function __construct(){}
public function getInstance(){
return new self();
}
}


第四部:类内实例化后,静态调用静态方法。通过内部的static方法来调用,没有对象的话它依旧 可以发回作用,因为它不依赖对象。
class single{
public $hash; //随机码,用它来检测,两次调用单例后,是不是用的统一随机码
protected function __construct(){
$this->hash = mt_rand(1,9999);
}
static public function getInstance(){
return new self();
}
}
$s1 = single::getInstance();
$s2 = single::getInstance();
问题:两个对象什么时候相等?可以print一下来看看?
答案:只有指向同一个对象地址的时候,才能相等。


但是,你每single::getInstance()一次,内部又实例化一次,还是无法得到$s1=$s2,让所有 的对象指向同一个对象。


第五步:通过内部的static方法实例化,并且把实例化保存在类内部的静态属性上。判断实例有没    有存在,如果不在则创建,存在则直接返回.单例基本完成!
class single{
public $hash; //随机码,用它来检测,两次调用单例后,是不是用的统一随机码
static protected $ins = null;
protected function __construct(){ 
$this->hash = mt_rand(1,9999);
}
static public function getInstance(){
//instance实例,of谁的,专门判断某个对象是不是某个类的实例用的。
if(self::$ins instanceof self){   //对象obj  instanceof  类名
return self::$ins;
}
self::$ins = new self();
return $ins;
}
}
$s1 = single::getInstance();
$s2 = single::getInstance();
这个时候就完成了$s1 = $s2;


第六步:继承上面的单例模式:
class test extends single{
public function __construct(){
parent::__construct();
}
}
$t1 = new test();
$t2 = new test();
问题:我们辛苦些的单例,继承一下就不灵了。
答案:出现这个情况的主要原因,是因为子类继承了父类以后,重写了父类的构造方法后  让子类再次可以new了,所以我们不让子类重写父类的构造方法,可以解决这个  问题。final关键字。


第七部:防止子类重写父类的构造函数。我们可以在用final修饰构造函数:
class single{
static protected $ins = null;
final protected function __construct(){ 
}
static public function getInstance(){
//instance实例,of谁的,专门判断某个对象是不是某个类的实例用的。
if(self::$ins instanceof self){   //对象obj  instanceof  类名
return self::$ins;
}
self::$ins = new self();
return $ins;
}
}
class t extends s{
这个类里是不能重写父类的构造函数的
}
$t1 = t::getInstance();
$t2 = t::getInstance();


第八部:防止类外克隆。如果clone的话,不仅可以克隆出多个实例,而且原来单例模式中的静态 属性中存着的地址(static protected $ins)也跟着克隆,出现多个对象,这个效果就相当于类 外多次实例了。如何解决呢?


$t3 = clone t2; 


解决:在类中加入魔术方法__clone(){} ,并且赋给它受保护的约束,这个时候如果在克隆对象的时候,这个方法将自动在类外调用,因为它受保护,所以无法在类外调用而报错,这样就可以防止它在clone了。


最终单例模式如下:


class single{
static protected $ins = null;
final protected function __construct(){}
static public function getInstance(){
if(self::$ins instanceof self){   
return self::$ins;
}
self::$ins = new self();
return $ins;
}
private function __clone(){}
}
class t extends s{
这个类里是不能重写父类的构造函数的。其他的业务逻辑随便哦。
}
$t1 = t::getInstance();
$t2 = t::getInstance();
instance实例,of谁的,专门判断某个对象是不是某个类的实例用的。对象obj  instanceof 类名。


四、魔术方法:


1. 概念:
魔术方法是指在某些情况下,会自动调用的方法。PHP面向对象中,提供了这几个魔术方法,他们 的特点都是以双下划线__开头的。


2. 作用:这些魔术方法在自己写框架和比较底层的时候比较有用。


3. 演示:
1. __get :当我们调用一个权限上不允许调用的属性时,__get方法会自动调用,并传参,参数值 是被调用的那个属性。比如:$lily调用age的时候,age属性时私有的在外部无权调用, 这个时候就把age当做参数传给了__get($p),这个时候__get就会去自动调用$age.


 class Human{
private $age = 29;
public function __get($p){
echo ‘你想访问我的’.$p.’属性’;
}
  }
  $lily = new Human();
  echo $lily->age .


2. __set : 当我们无权操作属性赋值时,或者操作不存在的属性赋值时,__set自动调用。且自动   传入两个参数,一个属性,一个属性值。
class Stu{
private $age = 23;
}
$human = new Stu();
$human->age = 28;   
$human->age =28 // ----无权--- __set(‘age’,28);


3. __isset()方法:


当用isset()判断对象不可见不存在的属性时,__isset()被触发。


问题:如果用isset(属性)的话,还能相信属性真的存在吗?不行
答案:__isset(){return 1} 就可以骗过判断。


4. __unset()方法:
当用unset销毁对象不可见的属性时,会应发unset。


4框架中魔术方法的使用:


THINKPHP中的一段用户注册代码:


$userModel->username = $_POST['username'];
$userModel->email = $_POST['email'];
if($num = $userModel->table('user')->add()){
echo "注册成功";
}else{
echo "fail";
}


思考:


1. userModel就有username属性供你去赋值吗?
2.  如果$userModel->xxx属性,是保护的,而我的表又有一个字段敲好也叫xxx。那么我 自然是$user->xxx=$POST[‘xxx’];这不就出差了么。
3.  userModel有一些属性很正常,比如a,b,c,d...我在注册的时候,又动态设置了属性,f,g,h,i
    疑问:在拼接sql的时候,得把a,b,c,d忽略才行,又怎么忽略呢?


答案:
通过__set()方法,把属性的设置 --》 都放到一个数组里,下次处理时,专门处理这个数 组就可以了。这样就不会和其他属性相冲突的,tp就是这么做的 。如下:


class UserModel{
protected $emial = "user@163.com";
protected $data = array();


public function __set($k,$v){
//$this->$k=$v 并没有真正赋成自己的属性。
$this->data[$k]=$v;//而是放在了一个数组里。
}


public function add(){
$sql = 'insert into table (';
$sql .= implode(',',array_keys($this->data));
$sql .= ") vales ('";
$sql .= implode("','", array_values($this->data));
$sql .="')";
return $sql;
}
public function __get($p){
return isset($this->data[$p]?$this->data[$p]:null;)
}
public function __isset($p){
return isset($this->data[$p])
}
//在查看有没有这个数据的时候,也不会真的去本类中查看有没有这个属性,
public function __unset($p){
unset($this->data[$p]?$this->data[$p]:null)
}


}


$userModel = new UserModel();
$userModel->username = 'lisi';
$userModel->email= 'lisi@126.com';
$userModel->sex= 'man';
echo $userModel->add(); 
unset($userModel->email);//删除的是$data数组下的成员,而不是本类属性。


5. __call__callStatic:


1. __call :
class Human{}
$lisi = new Human();
$lisi->say();
// 报错:定义了一个未定义的方法
//Fatal error: Call to undefined method Human::say() in


class Human{
public function hello(){
echo "hello--","<br/>";
}
public function  __call($method,$arguments){
echo "有对象调用",$method,"方法<br/>";
echo "还打印了参数<br/>";
}
}
$lisi = new Human();
$lisi->hello();
$lisi->say(1,2,3);


总结:调用不可见(不存在或者无权限)的方法时,自动调用。
  $lisi->say(1,2,3);   -----》  没有say()方法 ---》  __call(‘say’,array(1,2,3))运行


2. __callStatic:


总结:调用不可见的静态方法时,自动调用。
  Human::cary(‘a’,’b’,’c’) ---》 没有cry方法--》Human::__callStatic(‘cry’,array(‘a’,’b’,’c’));


五、重写和重载:


1. 重写/覆盖:
概念:override,子类重写了父类的同名方法。
2. 重载:
概念:overload,在java,C++中,同一个类中存在多个同名方法,但是参数类型、个数不同,传入 不同的参数,就可以调用不同的方法。但是在PHP中,不允许存在同名的方法,因此不能够完成 Java,C++中的这种重载效果。格式如下:


class Stu extends Human{
public function say($a){
echo "切克闹",$a;
}
public function say($a,$b,$c){  //在PHP中,错误的用法
echo $a,$b,$c;
}
}


但是,PHP的灵活,能达到类似的效果:


class Calc{
public function area(){
$args = func_get_args();
if (count($args)==1) {
return 3.14*$args[0]*$args[0]; //求圆形面积
}else if (count($args==2)) {
return $args[0]*$args[1];//求四边形面积
}else{
return '未知图形';
}


}
//上面的这个方法,就代替了下面这种在java中的方法重载的方法,
      而在PHP中的错误用法。也达到了同样的效果。
public function area($a){
return 3.14*$a*$a;
}
public function area($a,$b){
return $a*$b;
}
}
$calc = new Calc();
echo $calc->area(10),'<br/>';
echo $calc->area(5,8);


六、类常量:


1. 普通常量: define定义的常量全局有效,无论是页面内,函数内,都可以访问。


 define(‘常量名’, ‘常量值’);


2. 类常量: 专门在类内发挥作用的常量。


const HEAD = 1;


Human::HEAD;


1. 作用域在类内,类似于静态属性,用const声明,前面不加修饰符。
2. 必须是一个定值,不可改变的静态属性。
3. 在定义和使用常量的时候不需要使用$符号。


3. 魔术常量:


1. 无法手动修改他的值,所以叫做常量
2. 但是他的值又是随着环境的变动改变的,所以叫做魔术常量。


__LINE__  :  返回当前的行号。在框架中,可以用来在debug时,记录错误的行号。
 


__FILE__  :  返回文件的完整路径和文件名。在框架开发或者网站初始化脚本中,用来计算网 站的根目录。如果用在被包含文件中,则返回被包含的文件名。自 PHP 4.0.2 起,__FILE__总是包含一个绝对路径(如果是符号连接,则是解析后的绝对路径), 而在此之前的版本有时会包含一个相对路径。 


__DIR__ : 
文件所在的目录。如果用在被包括文件中,则返回被包括的文件所在的目录。它 等价于 dirname(__FILE__)。除非是根目录,否则目录中名不包括末尾的斜杠。 (PHP 5.3.0中新增)


__FUNCTION__ : 
函数名称(PHP 4.3.0 新加)。自 PHP 5 起本常量返回该函数被定义时的名字(区 分大小写)。在 PHP 4 中该值总是小写字母的。 


__CLASS__ :
类的名称(PHP 4.3.0 新加)。自 PHP 5 起本常量返回该类被定义时的名字(区 分大小写)。在 PHP 4 中该值总是小写字母的。 






__METHOD__ :
类的方法名(PHP 5.0.0 新加)。返回该方法被定义时的名字(区分大小写)。 






__NAMESPACE__ :
当前命名空间的名称(大小写敏感)。这个常量是在编译时定义的(PHP 5.3.0 新 增) 


4. 后期绑定(延迟绑定):


class Human{
public static function whoami(){
echo '来自父类的whomi在执行<br/>';
}
public static function say(){
self::whoami();
}
public static function say2(){
static::whoami();
}
}
class Stu extends Human{
public static function whoami(){
echo '来自子类的whoami在执行<br/>';
}
}
Stu::say();
子类内没有say方法,找到了父类里的say方法,在这里的self指的是父类,因为你的执行环 境是父类。
Stu::say();
子类也没有say2方法,又找到了父类的say2方法,但是父类调用的是static::whoami,指调用 子类自己的whoami方法。如果子类没有whoami的话依旧会调用父类中的whoami。
七、抽象类:


1. 无法实例化,类前加abstract,此类就成了抽象类,无法实例化。
2. 抽象方法是不能有方法体的。
3. 有抽象方法,此类必须是抽象类,但是抽象类内未必都是或者都有抽象方法。但是即便全是具体方法,只要类是抽象的,依旧不能实例化。


abstract class Flydea{
public abstract function engine();
public abstract function blance();
}


八、接口:


1. 概念:使用接口(interface),你可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。 
我们可以通过interface来定义一个接口,就像定义一个标准的类一样,但其中定义所有的方法都是 空的。接口中定义的所有方法都必须是public,这是接口的特性。 


2. 注意:
1. 接口的方法默认是抽象的,之前不必在加abstract,而且方法必须是public公开的。
2. 接口是一堆方法的说明,不能有属性,但是常量可以用。
3. 要实现一个接口,可以使用implements操作符。类中必须实现接口中定义的所有方法,否则会  报一个fatal错误。 
4. 实现多个接口,可以用逗号来分隔多个接口的名称,接口中的方法不能有重名。
5. 接口也可以继承,使用extends操作符。如果有接口继承了父接口,那么父接口中的方法也    必须全部实现。
6. 接口可以继承多个接口,这个和类不同。
7. 接口就像零件,而用多种零件可以组合出一种新的东西。


3. 代码实例:


interface animal{
public function eat();
}
interface bird{
public function fly();
}
interface monkey extends animal{
public function run();
}
class Human implements monkey,bird{
public function eat(){
echo "eat";
}
public function run(){
echo "run";
}
public function fly(){
echo "fly";
}
}
$lisi = new Human();
$lisi->eat();


4. 使用场景:


比如一社交网站,关于用户的处理是核心应用。应用如下:
登录
退出
写信
招呼
更新心情
吃饭
捣乱
示爱
聊骚


这么多方法,都是用户的方法,自然可以写一个user类,全包装起来,但是,分析用户一次性使用不了这么多方法,会造成浪费,而且不能达到灵活使用的目的:


用户信息:(登录,退出,写信,看信,招呼,更新心情)
用户娱乐:(骂人,捣乱,示爱,聊骚)


开发网站的时候,分析出这么多类,这么多方法,我们都晕了。而且作为调用者,我不需要了解你 的用户信息类,用户娱乐类,我就可以知道如何调用者两个类。
因为:这两个类都要实现上述的接口,通过这个接口就可以规范开发。


interface UserBase{
public function login($u,$p);
public function logout();
}
interface UserMsg{
public function writeMsg();
public function readMsg();
}
interface UserFun{
public function spit($to);
public function showLove($to);
}


下面的这个类和接口的参数不一样,就马上报错,这样,接口强制统一了类的功能,不管你有几个 类,一个类中有几个方法。
class User implements UserBase{
public function login($u){
}
}


九、类的自动加载:


1. 类自动加载的优势和引入:


$lisi = new HumanModel();
$lisi -> t();
单纯这样做,程序是会报错的,必须把有HumanModel类的页面引用过来。
但是如果网站比较大,model类比较多的话,比如:
HumanModel
userModel
GoodsModel
CatModel
...
...
这么多的Model,我用谁就得include/require谁,而且不知道,之前是否已经把include/require引 进来某个类。遇到这种情况我们如何解决。这个时候我们可以用自动加载。


function __autoload($c){
require('./'.$c.'.php');
}
$ming = new Pig();


如果在调用某个不存在的类,在报错之前我们还有一次介入的机会__autoload函数会自动调用。把 类名自动传给__autoload函数,我们自然可以在__autoload加载需要的类。


被加载的类的php文件名必须和类名相同才行。


2. 函数内部的代码,只要函数调用之后,就和写在函数外部的代码是一模一样的:


function test(){
//函数内可以写任何合法的php代码,包括再声明一个函数或者类
class Bird{
public static function sing(){
echo '百灵鸟会唱歌';
}
}
}


Bird::sing(); //这个时候类未定义的。Class 'Bird' not found,是因为没有调用test函数,函数要调用才 可以执行,函数里的代码没有执行。如下就可以了:


test();
Bird::sing();


3. 自动加载只能用__autoload函数吗?用其他的有什么意义吗?


答案:不是的,其实也可以指定一个函数的。
function zdjz($c){
require('./'.$c.'.php');
}
$Human = new HumanModel();
这个时候,系统是无法识别zdjz这个函数的,也不会再调用HumanModel找不到的时候,自动调 用zdjz这个函数。


所以,要通知系统,让系统知道我自己写了一个自动加载的方法,当自动找不到HumanModel的函 数的时候,自动调用我这个方法。


而系统正好有一个函数可以做这件事情。spl_autoload_register('zdjz')这个函数,可以把zdjz函数注 册成为一个自动加载的函数类似__autoload。


spl_autoload_register('zdjz');
function zdjz($c){
requrie './'.$c.'.php';
}
$Human = new Human();
$human->t();


我能自己注册一个自动加载的函数,能否这次类的一个静态方法,当做自动加载函数?
TP里就是这么做的。


十、异常处理:


class msyql{
protected $conn = NULL;
public function __construct(){
$this->conn=mysql_connect('localhost','root','root');
}
}
$mysql = new mysql();//返回mysql对象,并且自动连接上了数据库


/*
疑问:我怎么判断连接成功了没有?
答案:可以打印对象$conn属性,来判断


那么这个时候就多了一个步骤:


1.new mysql
2.if($mysql->conn){}


问题:为什么不用or die('连接失败')?
答案:因为这个太暴力了,这样的话会让后面所有的脚本全部死掉不能继续运行。


思考:我们以前用函数的时候,都是返回一个值,用值来判断各种情况,比如返回true/false代码成功或者  失败。
现在我们用返回值还行不行?


*/
if($msyql instanceof mysql){
echo "成功";
}else{
echo "失败";
}
问题:通过这种方法可以吗?
答案:不行,因为无论数据库连接成功还是失败的话,返回的都是一个msyql的对象,所以这个是不合理的。


class msyql{
protected $conn = NULL;
public function __construct(){
$this->conn=mysql_connect('localhost','root','root');
if(!$this->conn){
//发送卫星报告,在PHP中卫星是规定的一种对象,那个类的对象:Exception类的对象。new Exception('错误原因','错误代码');
$e = new Exception('连接失败',9);
throw $e; //throw抛出异常
}
}
}
try{//测试,并捕捉错误信息
$mysql = new mysql();返回mysql对象,并且自动连接上了数据库
}catch(Exception $e){
echo "捕捉到错误信息:<br />";
echo  $e->getMessage(),'<br/>';
echo '错误代码',$e->getCode(),'<br/>';
echo '错误文件',$e->getFile(),'<br/>';
echo '错误行',$e->getLine(),'<br/>';
}
if ($mysql instanceof mysql) {
echo "对象创建成功,连接成功";
}else{
echo "对象创建失败,连接失败";
}



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值