PHP面向对象

2019-09-25补充 使用clone关键字对对象进行克隆之后,会创建新的对象。如果定义了__clone方法,,则此魔术方法会被调用,此魔术方法不能直接被调用!

关于类

  1. 类是抽象的。类就是一些属性和方法的集合。
  2. 关于属性:
    可以先声明属性并赋值,也可以声明属性先不赋值;
    属性的值必须是一个直接的值,不能是表达式(例如1+2),也不能是函数的返回值(例如time());
    但是经测试,属性值为表达式时是可以的,不能为函数返回值。
  3. 关于方法:类中的方法可以和可以与外部函数(比如全局函数)重名,例如:
class Test{
    public $money=1-1;
    public function time(){
        echo time();
    }
}
$test=new test();
var_dump($test->money);
echo '<br>';
$test->time();
  1. new 对象时发生了什么?
    申请内存,生成对象;
    如果有构造函数,则执行;
    返回该对象地址。

构造函数与析构函数

  1. __construct()用来初始化对象:每当new一个对象时,就会自动对新new出来的对象发挥作用。
    new ClassName($args)时,$args会原样传给构造函数。
  2. __destruct()在对象被显式销毁或者对象的所有引用都被删除时(比如unset()对象、把对象赋值为其他、脚本执行完毕)执行。
class Human
{
    public $name;
    public $job;

    public function __construct($name, $job)
    {
        $this->name = $name;
        $this->job = $job;
    }

    public function __destruct()
    {
        echo 'die<br>';
    }
}

$a = new Human('苏大强', '坑儿子');
$b = new Human('苏明玉', '女强人');
$c = new Human('苏明成', '啃老');
echo $a->name;
echo '<br>';
echo $b->name;
echo '<br>';
echo $c->name;
echo '<br>';
/*
输出:
苏大强
苏明玉
苏明成
die
die
die
*/
回收机制

对象默认是采用引用传值,所以:

class Human
{
    public $name;
    public $job;

    public function __construct($name, $job)
    {
        $this->name = $name;
        $this->job = $job;
    }

    public function __destruct()
    {
        echo 'die<br>';
    }
}

$a = new Human('苏大强', '坑儿子');
$b=$c=$a;
unset($a);
var_dump($b);//object(Human)#1 (2) { ["name"]=> string(9) "苏大强" ["job"]=> string(9) "坑儿子" }
echo '<hr>';
/*解释:$a,$b,$c指向内存中同一个地址,$a销毁后,因为还有$a,$b指向该内存,所以该内存并未被回收;最后一行代码(echo '<hr>')运行结束后,所有内存都被回收,所以会触发该对象的析构函数,会在页面的分割线下echo 一个die*/
$this绑定

$this 会绑定到调用他的对象上。
类的方法内,存、取对象的属性必须用$this。

封装

特点:

  1. 封装:就是把一些重要的属性封装在类内,然后通过一个开放的接口来操作。
  2. 对于一个对象,对外界开放一个接口,调用接口时,内部进行的操作外界不知道,隐藏了内部的一些实现细节。

封装MySQL类:

封装MySQL类
header("content-type:text/html;charset=gbk");

class sql
{
    private $host;
    private $user;
    private $pwd;
    private $dbname;
    public $conn;//保存连接资源
    public $char;

    public function __construct()
    {
        $this->host = 'localhost';
        $this->user = 'root';
        $this->pwd = 'root';
        $this->dbname = 'test';
        $this->char = 'gbk';
        //连接
        $this->connect($this->host, $this->user, $this->pwd);
        //切换库
        $this->switchDb($this->dbname);
        //设置字符集
        $this->setChar($this->char);
    }

    //连接数据库服务器
    public function connect($host, $user, $pwd)
    {
        $this->conn = @mysqli_connect($host, $user, $pwd);
        if (!$this->conn) {
            die('Connect Error(' . mysqli_connect_errno() . ')' . mysqli_connect_error());
        }
    }

    //设置字符集
    public function setChar($char)
    {
        mysqli_set_charset($this->conn, $char);
    }

    //查询语句
    public function query($sql)
    {
        return mysqli_query($this->conn, $sql);
    }

    //切换数据库
    public function switchDb($db)
    {
        $this->dbname = $db;
        mysqli_select_db($this->conn, $db);
    }

    //查询一行
    public function getRow($sql)
    {
        $re = $this->query($sql);
        return mysqli_fetch_row($re);
    }

    //查询多行
    public function getRows($sql)
    {
        $list = [];
        $re = $this->query($sql);
        while ($row = mysqli_fetch_assoc($re)) {
            $list[] = $row;
        }
        return $list;

    }

    //查询单个值
    public function getOne($sql)
    {
        $re = $this->query($sql);
        return mysqli_fetch_row($re);
    }

    //关闭连接
    public function close()
    {
        return mysqli_close($this->conn);
    }
}

$mysql = new sql();
echo '<br>getRow: ';
var_dump($mysql->getRow('select * from  m where mid=1'));
echo '<br>getRows: ';
var_dump($mysql->getRows('select * from m'));
echo '<br>getOne: ';
var_dump($mysql->getOne("select 'name' from girl where hid='B'"));
echo '<br>close: ';
var_dump($mysql->close());
权限修饰符

作用:用来说明属性/方法的权限特点
写在属性/方法的前面
共有3个:
public定义的属性/方法在任意位置都可以访问
private只能被其定义所在的类访问。
protected 可以被其自身以及其子类和父类访问

继承

语法:

子类 extends 父类{
}

注意:

  1. 子类只能继承一个父类。
  2. 子类可以添加属性/方法。
  3. public protected属性/方法可以继承,并拥有访问、修改的权限,并且继承后权限只能越来越宽松/不变;
  4. private的属性/方法可以继承,但是有个标记,标记从属父类而来,但是无法在子类里直接访问和修改。
利用trait实现变相多继承

优先级:优先顺序是来自当前类的成员覆盖了 trait 的方法,而 trait 则覆盖了被继承的方法。
冲突的解决 :为了解决多个 trait 在同一个类中的命名冲突,需要使用 insteadof 操作符来明确指定使用冲突方法中的哪一个。
别名:以上方式仅允许排除掉其它方法,as 操作符可以为某个方法引入别名。

<?php
trait A {
    public function smallTalk() {
        echo 'a';
    }
    public function bigTalk() {
        echo 'A';
    }
}

trait B {
    public function smallTalk() {
        echo 'b';
    }
    public function bigTalk() {
        echo 'B';
    }
}

class Talker {
    use A, B {
        B::smallTalk insteadof A;
        A::bigTalk insteadof B;
    }
}

class Aliased_Talker {
    use A, B {
        B::smallTalk insteadof A;
        A::bigTalk insteadof B;
        B::bigTalk as talk;
    }
}
?> 

修改方法的访问控制 : 使用 as 语法还可以用来调整方法的访问控制。

<?php
trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

// 修改 sayHello 的访问控制
class MyClass1 {
    use HelloWorld { sayHello as protected; }
}

// 给方法一个改变了访问控制的别名
// 原版 sayHello 的访问控制则没有发生变化
class MyClass2 {
    use HelloWorld { sayHello as private myPrivateHello; }
}
?> 
private属性的继承

一个小例子:

class Hu
{
    private $a = '哥哥';

    public function a()
    {
        echo $this->a;
    }
}

class stu extends Hu{
    private $a='姐姐';
    public function cry(){
        parent::a();
        echo '<br>';
        echo $this->a;
    }
}
$lili=new stu();
echo '<br>';
$lili->cry();
/*哥哥
姐姐*/
public 和 protected属性和方法的继承

public的属性/方法在类外能调用;
protected的属性/方法在类外不能调用。

构造函数的继承
  1. 可以继承
  2. 如果子类有有构造函数,则在子类构造函数里要调用parent::__construct,以此来保证调用父类构造函数。
    例如,父类为数据库操作类或者是model类,其构造函数会做很多初始化工作,如果子类直接重写构造函数,则有可能导致初始化工作完不成。

多态

多态就是只抽象的声明父类,具体的工作由子类对象来完成。
实现多态的思想步骤:

  1. php是弱类型动态语言,传参时,参数没有强制的类型,任何类型变量都可以当做参数传。

  2. php5后,引入了对于对象类型的参数检测(即:检测对象所属的类)。限制了其灵活性(以下截图来自php7.2参考手册–函数的参数)。
    在这里插入图片描述

  3. 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。

小例子:

class Light
{
    public function lighter(Glass $d)
    {
        $d->display();
    }
}

class Glass
{

}

class RedLight extends Glass
{
    public function display()
    {
        echo 'red<br>';
    }
}

class GreenLight extends Glass
{
    public function display()
    {
        echo 'green<br>';
    }
}

class pig
{
    public function display()
    {
        echo 'pig<br>';
    }

}

$red = new RedLight;
$green = new GreenLight();
$pig = new Pig();
$light = new Light();

$light->lighter($red);
$light->lighter($green);
$light->lighter($pig);
/*
输出:
red
green
Fatal error: Uncaught TypeError: Argument 1 passed to Light::lighter() must be an instance of Glass, instance of pig given, called in D:\phpStudy\PHPTutorial\WWW\01.php on line 385 and defined in D:\phpStudy\PHPTutorial\WWW\01.php:342 Stack trace: #0 D:\phpStudy\PHPTutorial\WWW\01.php(385): Light->lighter(Object(pig)) #1 {main} thrown in D:\phpStudy\PHPTutorial\WWW\01.php on line 342
*/

静态属性与静态方法

  1. 在属性/方法前,加static关键字修饰。
  2. 静态属性:
  • 存放在类空间内;
  • 访问:类名::$ 属性名(类声明完毕,该属性就已存在,不需要依赖对象访问)(静态属性不可以由对象通过 -> 操作符来访问 。 );
  • 在内存中只有一个,为所有对象所共享,一个对象改变了静态属性,其他对象也受影响。例子:
class Human
{
    static $head = 1;

    public function getHead()
    {
        echo Human::$head;
    }

    public function changeHead()
    {
        Human::$head=2;
    }
}

$a = new Human();
$b = new Human();
$a->getHead();
$a->changeHead();
$b->getHead();
//12
  1. 静态方法:
  • 在内存中只有一个;
  • 与普通方法的区别:
**普通方法静态方法
相同点存放于类空间里,在内存中只有1份存放于类空间,在内存中只有1份
不同点需要用对象调用,所以需要绑定$this不属于哪个对象,所以不需要绑定$this
调用方法$this->方法名类名::方法名

注意:

  • 静态属性不能通过一个类已实例化的对象来访问(但静态方法可以)。
  • 为了兼容 PHP 4,如果没有指定访问控制,属性和方法默认为public。
调用方法方式静态方法非静态方法
通过类调用报E_STRICT级别错误
通过对象调用

self和parent

self:本类:用来调用自身方法/静态属性
parent:父类:用来调用父类方法/静态属性

单例模式

主要思路:

  1. 声明一个protected/private构造函数,防止外部实例化
  2. 内部开放一个public静态方法,负责实例化
  3. 类内有一个静态属性用来存放对象。
/*
 * final修饰的类不可以被继承。
 * 这样可以避免继承single类后,重写构造函数,从而可以通过子类实例化多个对象的情况。
 */
final class Single
{
    static $instance;
    public $name;

    /*
     * 具有构造函数的类会在每次创建新对象时先调用此方法;
     * 而被protected修饰的方法无法在类外调用,在类外调用会产生一个fetal error并终止脚本.
     * 这样可以避免实例化多个对象。
     */
    protected function __construct()
    {
    }

    static function getInstance()
    {
        return self::$instance ?: self::$instance = new self();
    }
}

关于final关键字

可以修饰类/方法,不能修饰属性!
final修饰后的类不能被继承!
final修饰后的方法能被继承,无法被重写

魔术方法

PHP 将所有以 __(两个下划线)开头的类方法保留为魔术方法。所以在定义类方法时,除了上述魔术方法,建议不要以 __ 为前缀。
后面将使用"不可访问属性(inaccessible properties)"和"不可访问方法(inaccessible methods)"来称呼这些未定义或不可见的类属性或方法。

  • __clone():当对象被克隆时,将会自动调用该方法。
  • __call():在对象中调用一个不可访问方法时,__call() 会被调用。注意这个方法传的参数,第一个参数为调用的方法名,第二个为方法参数组成的数组。
  • __callstatic():在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。可见性未设置为 public 或未声明为 static 的时候会产生一个警告。
  • __get():读取不可访问属性的值时自动调用,并且自动传一个参数,参数值为此属性名称。
  • __set():在给不可访问属性赋值时自动调用,阻止添加该属性,并且自动传2个参数,第一个参数是该属性名称,第二个参数是要赋的值。
  • __isset():当对不可访问方法调用isset()/empty()时。
    总结:
    empty():当变量存在,并且是一个非空非零的值时返回 FALSE 否则返回 TRUE.
    以下的东西被认为是空的:
    •"" (空字符串)
    •0 (作为整数的0)
    •0.0 (作为浮点数的0)
    •“0” (作为字符串的0)
    •NULL
    •FALSE
    •array() (一个空数组)
    •$var; (一个声明了,但是没有值的变量)
    isset() 如果变量存在,并且值不是 NULL 则返回 TRUE,否则返回 FALSE。
    两个方法虽然有些相似,但是一个判断变量存在时返回TRUE,一个返回FALSE,这点不一样。
    发现: 当使用empty()时,如果类内有__get()方法和__isset()方法时,则会先调用__isset()方法,然后调用__get()方法。
    __isset()方法里return的值会直接影响到方法外isset()的判断。例子如下:
class T
{
    public $name = 'kk';
    protected $age = 18;
    private $money = 4000;
    public function __isset($name)
    {
        // TODO: Implement __isset() method.
        echo "嘻嘻,{$name}不可判断是否存在<br>";
        return 0;
    }
    public function __get($name)
    {
        // TODO: Implement __get() method.
        echo "{$name}不可访问或不存在<br>";
    }

}

$a = new T;
var_dump(isset($a->money));

__isset()方法return值0101
调用方法isset()isset()empty()empty()
var_dump()输出嘻嘻,money不可判断是否存在;bool(false);嘻嘻,money不可判断是否存在;bool(true);嘻嘻,money不可判断是否存在;bool(true);嘻嘻,money不可判断是否存在;money不可访问或不存在;bool(true)(布尔返回值取决于__get()方法return的值!
  • __unset():当对不可访问属性调用unset()时。

重写和重载

重写

指子类重写了父类的方法。

重载

函数重载指一个标识符被用作多个函数名,且能够通过函数的参数个数或参数类型将这些同名的函数区分开来,调用不发生混淆。即当调用的时候,虽然方法名字相同,但根据参数的不同可以自动调用相应的函数。
但是php中不允许存在多个同名的方法,要达到重载的效果,要采用一点小技巧。

  1. 燕十八老师给出的,用func_get_args()方法:
class T
{
    public function test()
    {
        $arg = func_get_args();
        $count = count($arg);
        if ($count == 1) {
            echo 3.14 * $arg[0] * $arg[0], '<br>';
        } elseif ($count == 2) {
            echo $arg[0] * $arg[1], '<br>';;
        } else {
            echo '??', '<br>';;
        }
    }
 }
$a = new T;
$a->test(2);
$a->test(1, 2);
$a->test();
  1. 网上给出的,用魔术方法实现:
public function __call($method,$p)
{
  if($method=="display"){
    if(is_object($p[0])){
      $this->displayObject($p[0]);
    }else if(is_array($p[0])){
      $this->displayArray($p[0]);
    }else{
      $this->displayScalar($p[0]);
    }
  }
}
//下面是对上面定义的调用
$ov=new overload;
$ov->display(array(1,2,3));
$ov->display('cat');

类常量、魔术常量与延期绑定

类常量

相当于值无法修改的静态属性。
定义:const 在定义和使用常量的时候不需要使用 $ 符号。
调用:与静态属性调用方法一样,但有一点:不需要加$。
存放:在内存中只有一份(这点与静态属性相同)。

魔术常量
__FILE__文件的完整路径和文件名
__DIR__文件所在的目录
__CLASS__ 类的名称 
延期绑定

该功能从语言内部角度考虑被命名为"后期静态绑定"。"后期绑定"的意思是说,static:: 不再被解析为定义当前方法所在的类,而是在实际运行时计算的。

  1. self:: 的限制
    使用 self:: 或者__CLASS__ 对当前类的静态引用,取决于定义当前方法所在的类:
class A {
    public static function who() {
        echo __CLASS__;
    }
    public static function test() {
        self::who();
    }
}

class B extends A {
    public static function who() {
        echo __CLASS__;
    }
}

B::test();//A

  1. 后期静态绑定的用法
class A {
    public static function who() {
        echo __CLASS__;
    }
    public static function test() {
        static::who(); // 后期静态绑定从这里开始
    }
}

class B extends A {
    public static function who() {
        echo __CLASS__;
    }
}

B::test();//B

抽象类

声明:用abstract声明。
特点:

  1. 不能被实例化;
  2. 任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象的;(但是抽象类中可以没有抽象方法)
  3. 被定义为抽象的方法只是声明了其调用方式(参数),不能定义其具体的功能实现。
  4. 2019-09-16补充: 非抽象方法可以有函数体,而且可以不被继承。
  5. 继承一个抽象类的时候,子类必须定义父类中的所有抽象方法;
  6. 此外方法的调用方式必须匹配,即类型和所需参数数量必须一致。例如,子类定义了一个可选参数,而父类抽象方法的声明里没有,则两者的声明并无冲突。
    小例子:
abstract class AbstractClass
{
    // 我们的抽象方法仅需要定义需要的参数
    abstract protected function prefixName($name);

}

class ConcreteClass extends AbstractClass
{

    // 我们的子类可以定义父类签名中不存在的可选参数
    public function prefixName($name, $separator = ".") {
        if ($name == "Pacman") {
            $prefix = "Mr";
        } elseif ($name == "Pacwoman") {
            $prefix = "Mrs";
        } else {
            $prefix = "";
        }
        return "{$prefix}{$separator} {$name}";
    }
}

$class = new ConcreteClass;
echo $class->prefixName("Pacman"), "\n";
echo $class->prefixName("Pacwoman"), "\n";

接口

定义
  1. 通过 interface 关键字来定义的;
  2. 但其中定义所有的方法都是空的;
  3. 接口中定义的所有方法都必须是public;
  4. 接口是一堆方法说明,不能有属性,可以有常量;
  5. 接口常量和类常量的使用方法完全相同,但是与类常量有一点不同,就是不能被子类或子接口所覆盖。
  6. 接口可以被继承。
interface animal
{

    public function eat();
}

interface animal2 extends animal
{
    public function smile();
}
实现
  1. 要实现一个接口,使用 implements 操作符;
  2. 类中必须实现接口中定义的所有方法,否则会报一个致命错误;
  3. 类可以实现多个接口,用逗号来分隔多个接口的名称;
  4. 实现多个接口时,接口中的方法不能有重名。

类的自动加载

spl_autoload_register() — 注册给定的函数作为 __autoload 的实现

异常处理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值