06 php面向对象高级02

面向对象三大特性:封装、继承、多态

1.封装

1.1 概述

封装是指将数据和对数据的操作捆绑到一起,形成对外界的隐蔽,同时对外提供可以操作的接口

  • 数据:要操作的数据,一般是类成员属性:属性、静态属性、类常量,这些都是在类的内部定义的用来保存数据的
  • 数据的操作:即数据加工过程,就是方法,内部定义业务逻辑对数据进行加工处理
  • 捆绑到一起:使用类结构{}将属性、类常量和方法存放到一起,成为一个整体
  • 对外提供可操作的接口:即提供可以供外部访问的类成员(通常是方法)
<?php
// 封装
class Saler
{
  //捆绑内容到一起,形成对外界隐蔽的整体
  const ALLOW = true;
  private static $count = 0;
  private $money = 0; //数据:属性和类常量保存

  public function getMoney()
  {
    //数据操作
    return $this->money;
  }
}

// 外部
$s = new Saler();
$s->getMoney(); // 外部只能访问类中公有的方法,具体实现不可见

2. 继承extends

2.1 概述

继承性是子类自动共享父类的数据结构和方法的机制,这是类之间的一种关系。在定义和实现一个类的时候,可以在一个已经存在的类的基础上来进行,把这个已经存在的类所定义的内容作为自己的内容,并加入若干新的内容。

继承的本质是子类通过继承,可以直接使用父类已经存在的数据和方法,即对象的继承

子类:要继承其他类的类,也叫派生类
父类:被继承的类,也称之为 基类

  1. 继承extends是面向对象思想中实现代码重复利用的重要特性
  2. 继承是指子类可以直接访问父类中已经存在的成员
  3. 继承可以节省代码工作,同时允许子类中进行扩展,即在子类中增加必要的父类不存在的功能
// 语法格式:
class Child extends Parent {
   // 代码部分
}

实例中: 子类Man继承了 Human父类,同时又扩展了自己的功能

<?php
// 父类
class Human
{
  public function eat()
  {
    echo '吃饭';
  }
}
// 子类继承父类
class Man extends Human
{
  public $name; // 定义子类自己的属性
  // 扩展自己的方法
  public function song()
  {
    echo '唱歌';
  }
}      // 子类为空类:没有类成员

// 实例化子类对象
$m = new Man();
$m->eat(); // 可以直接访问父类中的方法
$m->song(); // 也可以访问自己对象中的方法

2.2 有限继承

有限继承,指子类在继承父类的成员的时候,并非继承所有内容,而是继承并使用父类部分内容

  • 继承的内容:子类继承父类所有的公有成员受保护成员私有属性,不能继承父类的私有方法
  • 受保护(protected)成员是专用于继承的,可以在父类或子类内部访问,但是不可以在子类的外部访问
  • 私有成员的访问,只能在所属类中设定公有或受保护的方法中访问
  • 静态成员也遵循继承原则:子类可以访问父类的静态成员
<?php
//父类
class Human
{
  const CALL = '人';
  public $name = 'human'; // public子类和父类都可以访问
  protected $age = '100'; // protected在子类的内部可以访问,外部不可以访问
  private $money = '100'; // private只能在父类的内部访问

  public function showName()
  {
    echo $this->name;
  }
  // 提供接口供子类访问:此时通常是受保护的方法,肯定不允许外部直接访问的
  protected function showAge()
  {
    echo $this->age;
  }

  private function showMoney()
  {
    echo $this->money;
  }
}


// 子类
class Man extends Human
{
  public function  showManAge()
  {
    // 在子类的内部,可以访问父类的 受保护的方法
    $this->showAge();
    echo $this->age; // 在子类的外部不可访问,在子类的内部可以访问
    echo $this->money; // 虽然属性被继承了,但是在子类的外部和内部都不可以访问,因为属性是私有的
  }
}
//实例化子类
$m = new Man();
echo $m->name; // 可以访问公有属性,输出name:human
echo $m->age; // 在子类的外部不可访问,在子类的内部可以访问
echo $m->money; // 在子类的外部和内部都不可以访问,因为属性是私有的


// 子类自己的方法
$m->showManAge();
// 继承父类的公有方法
$m->showName();
$m->showAge(); // 受保护的:不可以在外部访问
$m->showMoney(); // 私有方法,不可以被继承

构造方法和析构方法也可以被子类继承,但是需要注意子类对象实例化时对应的父类构造方法的参数

<?php
//父类
class Human
{
  private $money;
  public function __construct($money)
  {
    $this->money = $money;
  }

  public function __destruct()
  {
    echo 'die';
  }
}

// 子类继承
class Man extends Human {}

// 子类实例化:自身是空类,没有指定构造方法
//$m = new Man(); //错误:缺少参数,因为会自动调用父类构造方法
$m = new Man(100); //正确

2.3 重写override

重写,即子类中定义了与父类重名的成员

  • 子类可以重写父类的任意类成员(属性和方法)

  • 父类被重写的属性:子类可以直接覆盖父类的公有和受保护的属性,私有属性不会被覆盖,会被继承但是不可访问

  • 父类被重写的方法:方法同时存在,但是子类会优先调用自己的方法

  • 通常重写是用来重写父类的方法,用于扩展或者更改某些业务逻辑

  • 不管是公有和是受保护的属性,一旦重写,父类的就会不存在,而私有属性不会被覆盖而丢失

<?php
// 父类
class Human
{
  public $name = 'Human';
  public function show()
  {
    echo __CLASS__, '<br />';
  }
}

// 子类继承
class Man extends Human
{
  // 定义同名属性
  public $name = 'Man';
  // 定义父类同名方法
  public function show()
  {
    echo __CLASS__, ' hello world<br />';
  }
}

// 实例化子类
$m = new Man();
$m->show();        // 输出子类自己的数据 Human hello world
var_dump($m);			// 只有子类属性$name

重写是指子类拥有特殊的情况,一般是需要在父类的基础上进行扩展,此时如果想要继续保证父类被重写的方法继续执行(默认永远只访问子类重写的新方法),需要在子类重写方法的时候使用parent关键字

parent不能访问父类的属性,可以访问静态属性、静态方法、类常量和普通方法

<?php
//父类
class Human
{
  protected function show()
  {
    echo 'Human父类';
  }
}

// 子类继承
class Man extends Human
{
  // 重写
  public function show()
  {
    // 强制调用父类被重写方法
    parent::show();

    //扩展业务逻辑
    echo 'Man子类';
  }
}

$m = new Man();
$m->show();        // 输出Human hello world

重写的要求:

  • 子类重写父类的方法,控制权不能高于父类,即子类可以比父类更开放
  • 子类重写父类方法的时候,必须保证与父类同名方法参数和数据类型都一致

2.4 parent关键字

主要是用于实现被重写后,子类依然可以访问父类成员-静态属性和方法、类常量、公有和受保护的方法

  • parent关键字是用于在被重写的方法中,访问父类被重写的成员的
  • 可以访问父类的静态属性和方法、类常量、普通方法(公有和受保护的)
  • parent不能访问父类的属性,可以访问静态属性、静态方法、类常量和普通方法
  • 与 :: 一起使用
parent::父类方法()
<?php
// 父类
class Human
{
  public $name1 = 'Human public'; // 属性被子类覆盖
  protected $name2 = 'Human protected'; // 属性被子类覆盖
  private $name3 = 'Human private'; // 属性被子类覆盖,但是不可以被访问
  public function show1()
  {
    echo '父类方法 public';
  }
  protected function show2()
  {
    echo '父类方法 protected';
  }
  private function show3()
  {
    echo '父类方法 private';
  }
}

// 子类继承
class Man extends Human
{
  // 定义父类同名方法
  public function show()
  {
    // 属性都不可以访问
    parent::$name1;
    parent::$name2;
    parent::$name3;

    // 调用父类 公有的方法
    echo parent::show1(); // 可以访问
    echo parent::show2(); // 可以访问
    echo parent::show3(); // 不可以访问
  }
}


$m = new Man();
$m->show();

让父类的构造函数优先执行:

<?php
// 父类
class DB
{
  protected $link;
  public function __construct()
  {
    $this->link = mysqli_connect('localhost', 'root', 'root', 'mydb', '3306') or die('数据库连接失败');
    echo '父类构造函数';
  }
}

// 子类继承
class Table extends DB
{
  // 重写父类的构造函数
  public function __construct()
  {
    // 要求执行父类的构造函数:父类构造函数先执行
    parent::__construct();

    echo '子类构造函数';
  }
}


$m = new Table();

2.5 继承的特点

PHP中继承只能单继承:即子类只有一个父类(有些语言支持多继承)

class Man{}
class Woman{}
class Ladyboy extends Man,Woman{}	//PHP中错误,不允许继承多个父类

PHP若想继承多个类,可以使用链式继承

class Man{}
class Woman extends Man{}
class Ladyboy extends Woman{}	//Ladyboy包含了Man和Woman类中所有可继承的成员

PHP中继承只有私有方法不能被继承
PHP允许子类继承父类的构造方法和析构方法

2.6 最终类final:不可被继承的类,只能实例化

最终类,使用final关键字修饰类名,表示此类不可以被继承

  • 最终类表示此类已经到头,不允许再作为其他类的父类被继承
  • 最终类只能实例化使用
  • final除了修饰类之外,还可以修饰方法,表示该方法不可以被重写
  • final不管修饰的类还是方法,都是为了保护结构不被恶意扩展或修改

最终类无法被继承

<?php
// 最终类-语法
final class Man{}

// 最终类无法被继承
class Man18 extends Man{}		// 致命错误:无法从final类继承

final关键字不止修饰类表示类不可被继承,还能修饰方法,表示方法不能被重写

//父类
class Human{
    public function show(){}		//普通方法
    public final function walk(){}	//最终方法
}
//子类
class Man extends Human{
    //重写
    public function show(){}		//没问题
    public function walk(){}		//致命错误:不能重写父类中的最终方法
}

2.7 抽象类Abstract:只能被继承,不能被实例化

抽象类,使用abstract关键字修饰的类,表示该类只能被继承,不能被实例化

  • 抽象类的目的是用来规范下属类的基本结构(通常必配抽象方法),本身不可被实例化

  • abstract还可以修饰方法,称之为抽象方法:抽象方法所在的类必须是抽象类,抽象方法不能有方法体

  • 有抽象方法的抽象类被继承时,子类要么自身是抽象类,要么实现所有抽象方法

  • 因为抽象类无法被实例化,因此私有成员在类中没有实质意义(还需要额外提供受保护或者公有方法来实现访问)

  1. 基本语法:使用abstract关键字修饰类
// 抽象类
abstract class Human{}
  1. 抽象类无法被实例化
//抽象类
abstract class Human{}
$h = new Human();				//致命错误,抽象类不能被实例化
  1. 抽象类只能被继承
//抽象类(父类)
abstract class Human{}

//子类
class Man extends Human{}		//正确
  1. abstract关键字还可以用来修饰方法(抽象方法),abstract修饰的方法不能有方法体而且有抽象方法的类必须声明为抽象类
// 抽象方法抽象类
abstract class Human{
    // 定义抽象方法:没有方法体
    abstract public function eat();
    public function show(){}			// 普通方法有方法体
}
  1. 抽象方法因为要被子类继承实现,所以不能使用private修饰(私有方法不会被继承)
//抽象类
abstract class Human{
    //抽象方法
    abstract private function eat();		//错误:抽象方法不能私有化
}
  1. 子类继承抽象类后,如果抽象类中有抽象方法,那么子类必须实现所有抽象方法
//抽象方法抽象类(父类)
abstract class Human{
    //定义抽象方法:没有方法体
    abstract public function eat();
    public function show(){}			//普通方法有方法体
}

//子类1:抽象类继承抽象类
abstract class Man extends Human{}		//正常继承
//子类2:子类实现父类所有抽象方法
class Boy extends Man{
    //实现从祖父类继承的eat抽象方法
    public function eat(){
        echo 'eat';
    }
}

3. trait代码复用

Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。trait可以使得单继承语言拜托为了复用而不得不继承的尴尬,让面向对象变得更加纯粹

  • trait不能被实例化,可以拥有所有类成员结构(类常量不行)

  • trait是用来实现代码复用的,为其他类提供公共代码(方法),其他类如果使用trait,用use关键字引入

  • 在类中use具体trait就相当于将trait内的所有代码在类中写了一遍

  • 一个类可以使用多个trait,但是要注意同名问题

  • 同名方法可以使用insteadof来实现替代:一个trait中的同名方法替代另外一个,类就访问替代的那个

  • 同名方法可以在被替代之后使用as制作方法别名:类就可以拥有两个方法

  1. trait内部可以拥有一个类能拥有成员属性(包含静态),成员方法(包含静态),但不能有类常量
trait Eat{
    public $time;
    protected $how;					//允许定义,但是实际不用
    private $info;
    
    public function showTime(){
        echo $this->time;
    }
    protected function showHow(){	//允许定义,但是实际不用
        echo $this->how;
    }
    
    const PI = 3.14;				//错误:trait中不能有常量
}
  1. trait是用来实现代码的复用的,不可以被实例化也不可以被继承(不是类)
trait Eat{}
new Eat();			//错误,没有该类
  1. trait是用来将公共代码提供给其他类使用的,而类要使用trait的前提是加载对应的trait
// 定义trait
trait Eat{
    public function show(){
        echo 'eat';
    }
}


//类中加载trait
class Human{
    //加载:使用use关键字
    use Eat;	//use就表示将trait Eat中的所有东西拿到了当前类Human中
}

//使用trait中的内容
$h = new Human();

//eat:Human类自己没有show方法,但是因为使用了trait Eat,所以可用
$h->show();					
  1. 一个类可以使用多个trait
trait t1{
    public function eat(){
        echo 'eat';
    }
}

trait t1{
    public function sleep(){
        echo 'sleep';
    }
}

class Human{
    // 使用多个trait
    use t1,t2;
    public function show(){
        $this->eat();
        $this->sleep();
    }
}


$h = new Human();
$h->show();						//eat sleep
  1. 如果同时引入的多个trait中有同名方法,那么会产生冲突:解决冲突的方法是使用insteadof代替处理以及对被替代方法使用别名
trait t1{
    public function eat(){
        echo 't1,eat';
    }
}
trait t2{
    public function eat(){
        echo 't2,eat';
    }
}


class Human{
    use t1,t2;	//错误:eat()方法冲突
}

//解决方案1:明确替代
class Person{
    use t1,t2{					//花括号
        t2::eat insteadof t1;	 //t2的eat代替t1的eat,明确指定使用t2里面的eat
    }
}


// 解决方案2:先替换后别名
class Animal{
    use t1,t2{
        t1::eat insteadof t2;	//明确使用t1中的eat方法
        t2::eat as eat2;		//t2中的eat方法改名叫eat2
    }
}
  1. 同名覆盖问题:如果类中有与引入的trait同名成员,会有不同处理方式
  • 属性:不允许重名,即类中不允许定义与trait中同名的成员属性(静态属性也一样)
  • **方法:类方法优先,类 覆盖 trait **
trait Eat{
    public $food = '米饭';
    public function show(){
        echo $this->food;
    }
}

class Human{
    use Eat;
    
    //定义同名属性
    //public $food = '面条';		//错误
    
    //定义同名方法,此处的方法会覆盖trait中的方法
    public function show(){
        echo 'show';
    }
}

$h = new Human();
$h->show();						//show:类覆盖掉trait
  1. 继承覆盖问题:如果类中在使用trait的同时,也是继承自父类,而trait中与父类中有同名方法,那么trait中将覆盖父类同名方法如果要访问父类方法,可以在trait同名方法中使用parent关键字访问父类同名方法
<?php
trait Eat
{
  public function eat()
  {
    echo 'Eat::eat';
  }
}
class Human
{
  public function eat()
  {
    echo 'Human::eat';
  }
}
//子类继承父类同时使用trait:trait方法优先 覆盖类方法
class Man extends Human
{
  use Eat;
}

$m = new Man();
$m->eat(); // 输出 Eat::eat
  1. trait自己不能访问,只是用来给其他类提供代码复用的,因此允许类在使用trait时更高里面方法的访问控制权:在as之后,使用目标访问修饰限定符
trait Eat{
    private function show(){
        echo 'eat';
    }
}
class Human{
    use Eat{
        show as public eshow;				
        //注意:as是用来设定别名的,虽然没有同名show,但是系统认为show已经存在,所以必须别名
    }
}
$h = new Human();
$h->eshow();								//eat
  1. trait中可以使用抽象方法,用来规范使用类必须实现对应抽象方法:使用类要么为抽象类,要么就必须实现抽象方法
trait Eat{
    public function eat();		//抽象方法
}
abstract class Human{			
    use Eat;					//抽象类:可以不实现抽象方法
}

class Animal{
    use Eat;
    public function eat(){		//具体类:实现抽象方法
        echo 'Animal::eat';	
    }		
}

4. 接口interface:专门用来规范一些共性类必须实现的方法

  • 接口不是类,但是与类有类似的结构

  • 接口不能实例化,类可以** 实现(implements) **接口

  • 接口使用来规范项目体系,提供一些必须得行为规范的

// 定义接口
interface 接口名字{}

// 实现接口:implements 
class 类名 implements 接口名字{}
  • 使用接口(interface),可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容

  • 接口只能被类实现

  • 接口成员只有两种:接口常量和公有抽象方法(没有方法体:不需要abstract关键字)

  • 类实现接口的时候,必须实现接口中所有的抽象方法(或者抽象类实现)

  • 接口中的成员,不能被实现接口的类进行重写(接口常量)和权限更高(受保护或者私有化接口方法)

  • 接口可以继承接口,而且可以多继承

  • 接口是在更大型的项目中,为了保证底层的实现而设定的规范,通常是抽象类实现接口,增加必要成员,然后让实际业务类去继承抽象类

  1. 接口成员:接口中只能定义公有抽象方法和接口常量

接口成员只能有 : 接口常量(const)、公有的接口方法(普通方法和静态方法)
接口方法为抽象方法:没有方法体(不需要abstract关键字)
实现接口的类:

    • 可以访问接口常量:接口常量不能被重写
    • 需要实现所有的接口方法(除非类本身是抽象类)
    • 接口方法实现不允许增加控制权限(必须是public)
interface Human{
    //接口常量
    const NAME = '人';
    //接口抽象方法:不可以有方法体
    public function eat();
    public static function show();
    
    //错误示例
    public function go(){}				//错误:接口中的方法必须为抽象
    public $age;					   //错误:接口中不能有属性
    public static $count = 0;			//错误:接口中不能有静态属性(成员属性)
    protected function walk();			//错误:接口方法必须为公有抽象方法
}
  1. 接口成员方法必须被实现的子类实现或者类为抽象类,接口常量可以直接在实现类中访问
interface Human{
   // 接口常量
    const NAME = '人';
    // 接口抽象方法
    public function eat();  
}

// 实现接口
class Man implements Human{
    // 必须实现接口所有抽象方法
    public function eat(){
        echo self::NAME;				// 可以访问接口常量
    }
}

// 抽象类实现接口
abstract class Ladyboy implements Human{}		// 正常实现
  1. 实现接口的类成员,不允许重写接口中的常量,不允许增加接口方法的控制权限
//接上述代码
class Woman implements Human{
    // 重写接口常量
    const NAME = '女人';			  //错误:不允许重写接口常量
    
    // 强化接口方法控制
    private function eat(){}		//错误:接口方法不允许使用其他访问修饰限定符,必须使用public
}
  1. 接口可以继承接口:extends,而且接口可以多继承接口
interface Human{
    public function walk();
}

interface Animal{
    public function eat();
}
//单继承
interface Man extends Human{}
//多继承
interface Ape extends Human,Animal{}

5. php重载-同名:参数和类型不同

重载overload,指在一个类中可以出现多个同名方法,彼此之间的参数个数和类型不一样

  • 重载在强类型语言中,可以灵活的应对不同的类型(参数不一样)

  • PHP中不支持同名方法,而且也不区分数据类型(弱类型语言),所以PHP不支持传统重载

  • PHP中的重载:当某些不允许操作发生时,会自动调用的一种内部机制,即自动调用相关的魔术方法,来实现错误处理

    • 属性重载:属性不存在或权限不够访问的时候自动触发
    • 方法重载:方法不存在或权限不够访问的时候自动触发
  • 所有php重载都需要开发者事先设定好:实现魔术方法

  1. 魔术方法:指系统为类中预先设计好的,只需要开发者实现的方法,魔术方法以双下划线__开始。对象在某些特定情况下会自动调用的方法。构造方法、析构方法和克隆方法就是魔术方法
class Man{
    // 构造函数
    public function __construct(){}
}
new Man();			// 实例化后对象自动调用(触发时机:对象被实例化)
  1. PHP重载是指某些容错处理(也可以理解为为了某些特殊情况而自动调用),在访问没有权限或者不存在的属性或者方法的时候,会自动触发的魔术方法

  2. 属性重载:当PHP对象访问不存在的或者没有权限访问的属性的时候,自动触发的魔术方法让代码不出错(容错处理)

__get($key:读属性的时候触发
__set($key,$value):写属性的时候触发
__isset($key):外部调用isset()函数或者empty()函数时自动触发
__unset($key):外部调用unset()结构删除对象属性时自动触发
__toString():对象被当做普通变量输出或者连接时自动调用
class Man{
    private $age = 10;
    
    //读取重载
    public function __get($key){
        echo $key,__METHOD__,'<br/>';
    }
    
    //写重载
    public function __set($key,$value){
        echo $key . ' : ' . $value . '<br/>';
    }
    
    //查是否存在重载
    public function __isset($key){
        echo $key,__METHOD__,'<br/>'
    }
    
    //删除属性重载
    public function __unset($key){
        echo $key,__METHOD__,'<br/>';
    }
    
    //对象字符串化
    public function __toString(){
        echo __METHOD__,'<br/>';
    }
}
//实例化
$m = new Man();
$m->age;			//类外访问私有属性:原本不允许
$m->age = 100;		//设置
isset($m->age);		//判定
unset($m->age);		//删除
  1. 属性重载的目的:一方面为了不让程序运行出错,另一方面可以在类内部由我们自己控制内容的访问
class Man{
    private $age = 10;
    
    //读取重载
    public function __get($key){
        //echo $key,'<br/>';
        //定义一个允许访问列表:假设有很多私有属性
        $allow = array('age');
        //判定是否在列表内:在就允许访问,不在就返回NULL或者false
        if(in_array($key,$allow)){
            return $this->$key;				//可变属性:$key是外部访问的目标,最终为$this->age
        }
        
        //不允许访问
        return false;
    }
    
    //写重载(该方法没有返回值)
    public function __set($key,$value){
        //echo $key . ' : ' . $value . '<br/>';
        //与__get理论类似:允许的设置,不允许的不设置(什么都不做即可)        
    }
    
    //判定重载
    public function __isset($key){
        //给出内部判定结果
        return isset($this->$key);
    }
    
    //对象重载
    public function __toString(){
        //返回一个指定字符串(一般是当类有属性保存某些信息时,输出某个属性)
        return __METHOD__;	
    }
}
  1. 方法重载:当PHP对象访问不存在的方法或者不允许访问的方法时自动调用的方法(抑或是谋者特殊情况,如构造方法)
__call($function_name[,$args]):对象调用不可调用方法时触发
__callStatic($function_name[,$args]):类访问不可调用静态方法时触发
class Man{
    private function show(){
        echo __METHOD__,'<br/>';
    }
    private static function staticShow(){
        echo __METHOD__,'<br/>';
    }
    
    //普通方法重载
    public function __call($name){
        echo $name,__METHOD__,'<br/>';
    }
    
    //静态方法重载
    public static function __callStatic($name){
        echo $name,__METHOD__,'<br/>';
    }
}

//访问不可访问的方法
Man::staticShow();
$m = new Man();
$m->show();
  1. 方法重载的主要目的:不让外部访问出错。当然,如果必要时也可以进行内部访问
class Man{
    private function show(){
        echo __METHOD__,'<br/>';
    }
    private static function staticShow(){
        echo __METHOD__,'<br/>';
    }
    
    //方法重载
    public function __call($name,$arg){
        //允许访问列表
        $allow = array('show');
        
        //判定是否在列表中
        if(in_array($name,$allow)) return $this->$name($arg);
        //其他情况
        return false;
    }
    public static function __callStatic($name){
        //不允许访问
        return false;
    }
}
  1. 总结
  • PHP重载不是指同名方法,而是指对象或者类在访问一些不允许或者不存在的属性或者方法的时候自动调用的魔术方法

  • PHP重载分为属性重载和方法重载

  • PHP重载的目的是为了保护程序的正确运行而提供的一种容错机制

  • 并非所有类都需要实现这些重载,只是如果有类需要对外提供访问使用的时候才有必要采取

6. 对象遍历:迭代器和生成器

6.1 对象遍历 foreach

遍历对象,将对象中的所有属性(公有属性)以键值对的形式取出并进行访问

  • 对象是一种复合型的护具类型,对象中真正保存的内容是属性

  • 对象的属性本质也是一种键值对的关系:名字=值

  • 对象遍历就是利用foreach对对象中的属性进行取出解析:但是类外部只能遍历所有公有属性(主要是受访问修饰限定符的限定)

<?php
//定义类
class Man
{
  public $name = 'LiLei';
  public $height = 178;
  public $weight = 140;
  protected $age = 30; // 不输出
  private $money = 1000; // 不输出
}

//实例化
$m = new Man();
//遍历:只能遍历所有公有属性
foreach ($m as $k => $v) {
  echo $k . ' : ' . $v . '<br/>';    // $k为属性名,$v为属性值
  // 输出:name height weight,不能访问 age和money
}

6.2 Iterator迭代器

Iterator迭代器:PHP内置的一种能够修改foreach内部运行机制的接口

  • Iterrator迭代器内置了5个抽象方法,实现迭代器的类必须实现5个抽象方法

  • 实现了Iterator迭代器的类的对象,在进行foreach遍历时,不会按照foreach原来的机制处理,而是调用迭代器的5个方法

  • 迭代器的本质就是改变foreach内部的5个方式,实现开发者的自定义控制

foreach本身的执行:

  • 初始化目标:将对象指针指向第一个目标(执行一次)
  • 判定指针是否有效:判定当前指针指向的元素是否存在,存在下一步,不存在终止(N+1次执行)
  • 取出当前指针元素下标(属性名):将当前属性的名字取出来存储到变量(N次执行)
  • 取出当前指针元素值(属性值):将当前属性的值取出来存储到变量(N次执行)
  • 将指针指向下一个:将取出元素后的指针指向下一个属性(N次执行)

简单的迭代

<?php
class Person implements Iterator
{
  # 属性列表(索引数组)
  private $properties = ['name', 'age', 'gender', 'height', 'weight'];

  # 下标属性
  private $key = 0;

  # 实现5个方法:current  key next rewind valid 
  public function current()
  {
    // 取出当前数组元素值
    return $this->properties[$this->key];
  }

  public function key()
  {
    # 返回当前下标
    return $this->key;
  }

  public function next()
  {
    # 当前下标+1
    $this->key++;
  }
  public function rewind()
  {
    # 重置数组下标
    $this->key = 0;
  }

  public function valid()
  {
    # 判定下标对应元素是否存在
    return isset($this->properties[$this->key]);
  }
}

$p = new Person();
foreach ($p as $key => $val) {
  echo $key . ':' . $val . '</br>';
}

复杂的迭代器:

<?php
class Person implements Iterator
{
  private $info = [
    'name' => '',
    'age' => 0,
    'gender' => '',
    'height' => 0,
    'weight' => 0,
  ];

  # 实现5个方法:current  key next rewind valid 
  public function current()
  {
    // 取出当前元素当前的值
    return current($this->info);
  }

  public function key()
  {
    # 返回当前下标
    return key($this->info);
  }

  public function next()
  {
    # 当前下标+1
    next($this->info);
  }
  public function rewind()
  {
    # 重置数组下标
    reset($this->info);
  }

  public function valid()
  {
    # 判定下标对应元素是否存在
    return isset($this->info[key($this->info)]);
  }
}

$p = new Person();
foreach ($p as $key => $val) {
  echo $key . ':' . $val . '</br>';
}

6.3 Generator生成器

生成器:Generator,提供了一种更容易的方式来实现简单的对象迭代

  • 相比较定义类实现Iterrator接口的方式,性能开销和复杂性大大降低

  • 生成器是一个类Generator实现了Iterrator接口,并且实现了Iterrator方法(修改了内部逻辑)

  • 生成器是暂停循环执行逻辑,等到使用的时候,才触发循环再次执行

for($i=0;$i<10000;$i++){
  // 次循环只执行一次
  yield $i;
}
  • yield关键字代表暂停代码继续向下执行:直到yield代码被使用

  • 生成器进行对象遍历:使用yield后,函数机会返回一个Generator的对象,此时就可以对对象进行遍历

使用普通方式

<?php
function getArr()
{
  for ($i = 0; $i < 100000; $i++) {
    $arr[] = $i;
  }
  return $arr;
}

echo memory_get_usage() . '</br>'; // 取出当前php占用的内存

// 获取数组
$arr = getArr();
// 遍历数组
foreach ($arr as $val) {
  // echo  $val . '</br>';
}

echo memory_get_usage();

使用生成器:大大减少内存的消耗

<?php
function getArr()
{
  for ($i = 0; $i < 100000; $i++) {
    yield $i;
  }
}

echo memory_get_usage() . '</br>'; // 取出当前php占用的内存

// 获取数组
$arr = getArr();
// 遍历数组
foreach ($arr as $val) {
  // echo  $val . '</br>';
}

echo memory_get_usage();

获取数据库表中的所有记录,打印到表格

<?php
// 获取数据库表中的所有记录,打印到表格
$conn = mysqli_connect('localhost', 'root', 'root', 'mydb', '3306') or die('数据库连接失败');
mysqli_set_charset($conn, 'utf8') or die('字符集设置失败');

function query($conn, $sql)
{
  $res = mysqli_query($conn, $sql);
  while ($row = mysqli_fetch_assoc($res)) {
    // 这样设置就不需要保存大数据了,如果数据量较大,会产生很大的消耗
    yield $row;
  }
}

$t1 = memory_get_usage();
$list = query($conn, "select * from class");

echo '<table border=1>';
echo '<tr><th>ID</th><th>名称</th>';
foreach ($list as $val) {
  echo <<<EOT
     <tr> 
        <td>{$val['id']}</td>
        <td>{$val['name']}</td>
     </tr>
EOT;
}
echo '</tr>';
echo '</table>';
// var_dump($list);

$t2 = memory_get_usage();

// 耗时
echo '耗时:', $t2 - $t1;

  • 生成器是一种实现了Iterrator迭代器接口的类
  • 生成器的目的是利用yield关键字实现循环内部的暂停,而直到yield被使用循环才继续执行,从而节省通过循环产生一个大数组的过程,,最终实现内存优化
  • 在大型数据展示的时候(数据库数据操作、文件读取等),一般都建议使用生成器来实现内存解析

多态

多态性是指相同的操作或函数、过程可作用于多种类型的对象上,并获得不同的结果:一个对象有多个同名函数
多态,是指在发生类的继承的情况下,同时出现方法的重写(override),即子类拥有与父类同名的方法。然后在实例化对象的时候让父类对象指向子类对象(强制类型,PHP不支持),父类对象表现的子类对象的特点。

  1. 多态的发生必须是有继承关系,并且子类要重写父类方法
  2. 多态是指父类对象拥有子类形态,并且可以表现出子类的特性(调用子类方法)
  3. PHP是弱类型语言,不支持多态

多态需要强类型语言,php模拟强类型

<?php
// 父类Animal
class Animal
{
  public static function show(Animal $obj)
  {
    // Animal 强制对象为Animal类型
    $obj->display();
  }

  // 父类方法
  public function display()
  {
    echo 'Animal';
  }
}


// 子类Cat 继承Animal
class Cat extends Animal
{
  public function display()
  {
    echo 'Cat';
  }
}

// 子类Dog 继承Animal
class Dog extends Animal
{
  public function display()
  {
    echo 'Dog';
  }
}

// 调用Animal的静态方法
Animal::show(new Cat()); // 输出:Cat
Animal::show(new Dog()); // 输出:Dog
posted @ 2024-11-30 10:44  songxia777  阅读(4)  评论(0编辑  收藏  举报