阿里云新用户优惠

第十章 PHP面向对象

一、面向对象的介绍

1、类和对象的发展(从面向过程到面向对象)

  最初的计算机语言只有基本变量(类似基本数据类型),用来保存数据。但是数据多了怎么办?成千上万的数据怎么处理?于是有了数组的概念,通过数组将这同类型的数据进行组织。后来,数据不仅多且复杂了,而且对数据的操作(指的就是函数)也频繁了,但结构体只包含了数据,没有包含对数据操作的方法,于是类和对象产生了。也就是说,面向过程的数据和方法是分离的,而面向对象的数据和方法集成到了一起,便于扩展。
  对于描述复杂的事物,为了从宏观上把握、从整体上合理分析,需要使用面向对象的思路来分析整个系统。但是,具体到微观操作,可能需要面向过程的思路去处理。如开车(简单事物),可以用一系列线性过程来描述;但是造车(复杂事物),就无法使用简单的线性思维来解决了。这时可以使用面向对象的思想,将造车看作车壳、发动机、车轮等对象的组合,从宏观上把握造车过程,而不是拘泥于从哪个小零件开始造起。

  • 面向对象和面向过程的总结:
    • 都是解决问题的思维方式,都是代码组织的方式。
    • 解决简单问题可以使用面向过程。
    • 解决复杂问题:宏观上使用面向对象把握,微观处理上仍然是面向过程。
    • 面向对象的思考方式:遇到复杂问题,先从问题中找名词,然后确立这些名词哪些可以作为类,再根据问题需求确定类的属性和方法,以此来理清类之间的关系。

2、面向对象的基本特征

面向对象编程具有封装、继承、多态三大特性,它们迎合了编程中注重代码重用性、灵活性和可扩展性的需要,奠定了面向对象在编程中的地位。

  • 封装
    • 封装就是将一个类的使用和实现分开,只保留有限的接口(方法)与外部联系。对于用到该类的开发人员,只要知道这个类该如何使用即可,而不用去关心这个类是如何实现的。这样做可以让开发人员更好地把精力集中起来专注于别的事情,同时也避免了程序之间的相互依赖而带来的不便。
        例如,在使用计算机时,我们并不需要将计算机拆开了解它每个部件的具体用处,只需要按下电源键就能将计算机启动,这就体现了封装的好处。
  • 继承
    • 继承就是派生类(子类)自动继承一个或多个基类(父类)中的属性与方法,并可以重写或添加新的属性或方法。继承这个特性简化了对象和类的创建,增加了代码的重用性。
        例如,已经定义了 A 类,接下来准备定义 B 类,而 B 类中有很多属性和方法与 A 类相同,那么就可以用 B 类继承 A 类,这样就不用再在 B 类中定义 A 类中已有的属性和方法,从而可以在很大程度上提高程序的开发效率。 继承分为单继承和多继承,PHP 目前只支持单继承,也就是说一个子类有且只有一个父类。
  • 多态
    • 对象的状态是多变的。一个对象相对于同一个类的另一个对象来说,它们拥有的属性和方法虽然相同,但却可以有着不同的状态。另外,一个类可以派生出若干个子类,这些子类在保留了父对象的某些属性和方法的同时,也可以定义一些新的方法和属性,甚至于完全改写父类中的某些已有的方法。多态增强了软件的灵活性和重用性。

二、创建类、属性和方法

1、定义类

在 PHP 中,可以使用 class 关键字加类名的方式定义一个类,然后用大括号{ }将在类体中定义类的属性和方法包裹起来,类的语法格式如下: class 类名{}

2、成员属性

在类中直接声明的变量称为成员属性(也可以称为成员变量),可以在类中声明多个变量,即对象中可以有多个成员属性,每个变量都存储对象不同的属性信息。语法格式如下:
访问权限修饰符 属性名称 = 属性值;

成员属性的类型可以是 PHP 中的标量类型和复合类型,所以也可以是其他类实例化的对象,但在类中使用资源和空类型是没有意义的。

前面我们绍过,声明变量时不需要任何关键字修饰,但是在类中声明成员属性时,变量前面一定要使用一个关键字来修饰,例如 public、private,static 等,但这些关键字修饰的变量都具有一定的意义。如果不需要有特定意义的修饰,可以使用“var”关键字,一旦成员属性有其他的关键字修饰就需要去掉“var”。

  • 常用访问权限修饰符及其含义如下所示:
    • public:公共的,在类的内部、子类中或者类的外部都可以使用,不受限制;
    • protected:受保护的,在类的内部和子类中可以使用,但不能在类的外部使用;
    • private:私有的,只能在类的内部使用,在类的外部或子类中都无法使用。
<?php
class Preson{
    public $name;
    public $age = 19;
    private $gender = "男";
}

//外部调用
$p = new Preson();
echo $p->age;
echo $p->gender;
?>

3、成员方法

一个类里面,除了有成员属性外还有行为,这些行为,我们用一个名称代表,这个名称就是成员方法。比如人可以跑步,可以跳舞,可以唱歌,可以吃饭等等。和成员属性一样,成员方法和普通方法的区别在于,它是在类的内部定义的方法。例如:

<?php
class Preson{
    public $name;
    public $age;
    public $gender;

    //声明成员方法
    public function Run()
    {
        echo "人在塔在";
    }
}
?>

三、实例化对象与访问

1、对象实例化

上面,我们已经知道了在php中,怎么样创建一个类了,但是我们也知道,类是代表所多具有共同特征的事物的统称,而我们想要得到的是某个对象,也就是某一个具体事物。那么我们怎么得到呢?我们可以使用 new 运算符来实例化该类的对象:

<?php
class Site {
    /* 成员变量 */
    public $url, $title;
    /* 成员函数 */
    function setUrl($par){
        $this->url = $par;
    }
    function getUrl(){
        echo $this->url . PHP_EOL;
    }
    function setTitle($par){
        $this->title = $par;
    }
    function getTitle(){
        echo $this->title . PHP_EOL;
    }
}
$runoob = new Site();
$taobao = new Site();
$google = new Site();
?>

以上代码我们创建了三个对象,三个对象各自都是独立的,接下来我们来看看如何访问成员方法与成员变量。

2、对象的属性访问

<?php
// 调用成员函数,设置标题和URL
$runoob->setTitle( "php学习" );
$taobao->setTitle( "淘宝" );
$google->setTitle( "Google 搜索" );
$runoob->setUrl( 'xiyoudaodao.gitee.io' );
$taobao->setUrl( 'www.taobao.com' );
$google->setUrl( 'www.google.com' );

// 调用成员函数,获取标题和URL
$runoob->getTitle();
$taobao->getTitle();
$google->getTitle();

$runoob->getUrl();
$taobao->getUrl();
$google->getUrl();
?>

3、类的静态属性和方法

在类里面,有一种特殊的属性和方法,它就是用static修饰词的属性和方法,我们称它为静态属性和静态方法。静态属性、方法(包括静态与非静态)在内存中,只有一个位置(而非静态属性,有多少实例化对象,就有多少个属性)。

  • 静态属性不需要实例化即可调用。因为静态属性存放的位置是在类里,调用方法为"类名::属性名"
  • 静态方法不需要实例化即可调用。同上
  • 静态方法不能调用非静态属性。因为非静态属性需要实例化后,存放在对象里
  • 静态方法可以调用非静态方法,使用 self 关键词。php里,一个方法被self:: 后,它就自动转变为静态方法
<?php
class Preson{
    public $number = 1;
    public static $staticNumber = 1;
    public function addStatic()
    {
        self::$staticNumber++;
    }
    public function add()
    {
        $this->number++;
    }
}

echo "staticNumber = ".Preson::$staticNumber."<br>";

$a = new Preson();
$a->add();
echo "Number = ".$a->number."<br>";

$a->addStatic();
echo "staticNumber = ".Preson::$staticNumber."<br>";

$b = new Preson();
$b->add();
echo "Number = ".$a->number."<br>";

$b->addStatic();
echo "staticNumber = ".Preson::$staticNumber."<br>";
?>

4、构造函数

构造函数就是当对象被创建时,类中被自动调用的第一个函数,并且一个类中只能存在一个构造函数。和普通函数类似构造函数也可以带有参数,如果构造函数有参数的话,那么在实例化也需要传入对应的参数,例如new Students($name, $age)。

创建构造函数的语法格式如下: public function __construct(参数列表){ ... ... }

其中,参数列表是可选的,不需要时可以省略。

如果没有在代码中显式地声明构造函数,类中会默认存在一个没有参数列表并且内容为空的构造函数。如果显式地声明构造函数则类中的默认构造方法将不会存在。所以构造函数通常用来做一些准备工作,比如为某些参数赋值等。 有了构造函数,现在我们就不需要再调用 setTitle 和 setUrl 方法了,我们可以直接使用构造函数,看下面例子:

<?php
class Site {
    /* 成员变量 */
    public $url, $title;

    function __construct( $par1, $par2 ) {
        $this->url = $par1;
        $this->title = $par2;
    }
    /* 成员函数 */
    function setUrl($par){
        $this->url = $par;
    }
    function getUrl(){
        echo $this->url . PHP_EOL;
    }
    function setTitle($par){
        $this->title = $par;
    }
    function getTitle(){
        echo $this->title . PHP_EOL;
    }
}
$runoob = new Site('xiyoudaodao.gitee.io', 'php学习'); 
$taobao = new Site('www.taobao.com', '淘宝'); 
$google = new Site('www.google.com', 'Google 搜索'); 
// 调用成员函数,获取标题和URL 
$runoob->getTitle(); 
$taobao->getTitle(); 
$google->getTitle(); 
$runoob->getUrl(); 
$taobao->getUrl(); 
$google->getUrl();
?>

5、析构函数

析构函数的作用和构造函数正好相反,析构函数只有在对象被垃圾收集器收集前(即对象从内存中删除之前)才会被自动调用。析构函数允许我们在销毁一个对象之前执行一些特定的操作,例如关闭文件、释放结果集等。

在 PHP 中有一种垃圾回收机制,当对象不能被访问时就会自动启动垃圾回收机制,收回对象占用的内存空间。而析构函数正是在垃圾回收机制回收对象之前调用的。

析构函数的声明格式与构造函数相似,在类中声明析构函数的名称也是固定的,同样以两个下画线开头的方法名__destruct(),而且析构函数不能带有任何参数。在类中声明析构方法的格式如下:
public function __destruct(){ ... ... }

在 PHP 中析构函数并不是很常用,它属于类中可选的一部分,只有需要的时候才在类中声明。

<?php
    class Site{
        public $name, $url, $title;
        public function __construct(){
            echo '------这里是构造函数------<br>';
        }
        public function __destruct(){
            echo '------这里是析构函数------<br>';
        }
    }
    $object = new Site();
    echo 'php学习<br>';
    echo '脚本运行结束之前会调用对象的析构函数<br>';
?>

6、继承

面向对象编程(OOP)的一大好处就是,可以使用一个类继承另一个已有的类,被继承的类称为父类或基类,而继承这个父类的类称为子类。子类可以继承父类的方法和属性,因此通过继承可以提高代码的重用性,也可以提高软件的开发效率。

子类可以增加父类之外的新功能,因此也可以将子类称为父类的“扩展”。此外,子类还可以继承父类的构造函数,当子类被实例化时,PHP 会先在子类中查找构造函数。如果子类有自己的构造函数,PHP 会先调用子类中的构造函数。当子类中没有时,PHP 则会去调用父类中的构造函数。

在 PHP 中,类的继承需要通过 extends 关键字来实现。语法格式如下所示:
class 子类名 extends 父类名{ ... ... }

<?php
class A {
    public function __construct()
    {
        echo "这里是A的构造函数<br>";
    }
    public function __destruct()
    {
        echo "这里是A的析构函数<br>";
    }
    public function echoA()
    {
        echo "这里是A<br>";
    }
}

class B extends A {
    public function __construct()
    {
        echo "这里是B的构造函数<br>";
    }
    public function __destruct()
    {
        echo "这里是B的析构函数<br>";
    }
    public function echoB()
    {
        echo "这里是B<br>";
    }
}

class C extends B {
    public function __construct()
    {
        echo "这里是C的构造函数<br>";
    }
    public function __destruct()
    {
        echo "这里是C的析构函数<br>";
    }
    public function echoC()
    {
        $this->echoA();
        $this->echoB();
        echo "这里是C<br>";
    }
}

$c = new C();
$c->echoC();
?>

7、魔术方法

前面我们介绍的构造函数 __construct 和析构函数 __destruct 就属于 PHP 中的魔术方法。PHP 中的魔术方法如下表所示:

魔术方法 作用
__construct() 实例化类时自动调用
__destruct() 类对象使用结束时自动调用
__set() 在给未定义的属性赋值时自动调用
__get() 调用未定义的属性时自动调用
__isset() 使用 isset() 或 empty() 函数时自动调用
__unset() 使用 unset() 时自动调用
__sleep() 使用 serialize 序列化时自动调用
__wakeup() 使用 unserialize 反序列化时自动调用
__call() 调用一个不存在的方法时自动调用
__callStatic() 调用一个不存在的静态方法时自动调用
__toString() 把对象转换成字符串时自动调用
__invoke() 当尝试把对象当方法调用时自动调用
__set_state() 当使用 var_export() 函数时自动调用,接受一个数组参数
__clone() 当使用 clone 复制一个对象时自动调用
__debugInfo() 使用 var_dump() 打印对象信息时自动调用
1、__get() 方法

在调用或获取当前环境下未定义或不可见的类属性时,会自动调用 __get() 方法,定义该方法的语法格式如下: public function __get($name){ ... ... ; }

<?php
    class Site{
        public $url = 'xiyoudaodao.gitee.io';
        private $name = 'php学习';
        public function __get($name){
            echo '获取:“'.$name.'”失败!';
        }
    }
    $object = new Site();
    echo $object->url.'<br>';
    echo $object->name.'<br>';
    echo $object->title.'<br>';
?>
2、__set() 方法

在为当前环境下未定义或不可见的类属性赋值时,会自动调用 __set() 方法。定义该方法的语法格式如下: public function __set($key, $value){ ... ... ; }

<?php
    class Site{
        public $name;
        private $url;
        public function __set($key, $value){
            echo '为“'.$key.'”赋值“'.$value.'”失败!<br>';
        }
    }
    $object = new Site();
    $object->name = 'php学习';
    $object->url = 'xiyoudaodao.gitee.io';
    $object->title = 'PHP教程';
?>
3、__isset() 方法

当在类外部对类中不可访问或不存在的属性使用 isset() 或 empty() 函数时,会自动调用 __isset() 方法,该方法的语法格式如下: public function __isset($name){ … … ; }

参数 $name 为要访问的属性名称。

isset() 函数可以检查一个变量是否存在并且不为 NULL,传入一个变量作为参数,如果传入的变量存在则传回 true,否则传回 false。

empty() 函数可以检查一个变量是否为空,同样需要传入一个变量作为参数,如果变量并不存在,或者变量的值等于 FALSE,那么这个变量会被认为不存在。

通过前面的学习我们知道,类中的公有成员可以在类外访问,而私有成员则无法在类外访问。也就是说,我们可以使用 isset() 或 empty() 函数来检查类中的公有属性是否存在,而对类中的私有属性这两个函数就无效了。

如果想要使用 isset() 或 empty() 函数对类中的私有属性进行检测的话,我们只需要在类中添加一个 __isset() 方法就可以了,当在类外部使用 isset() 或 empty() 函数时,会自动调用类里面的 __isset() 方法。

<?php
class Site{
    public $url = 'xiyoudaodao.gitee.io';
    private $name = 'php学习';
    public function __isset($name){
        // property_exists(mixed $class, string $property): bool 检查对象或类是否具有该属性
        if(property_exists('Site', $name)){
            echo '成员属性:“'.$name.'”存在!<br>';
        }else{
            echo '成员属性:“'.$name.'”不存在!<br>';
        }
        //此处返回值会影响isset结果,不影响empty
        // return true;
    }
}
$object = new Site();
var_dump(isset($object->title));
var_dump(empty($object->title));
var_dump(isset($object->name));
var_dump(isset($object->url));
?>
4、__unset() 方法

当在类外部对类中不可访问或不存在的属性使用 unset() 函数时,__unset() 方法会被自动调用,该方法的语法格式如下: public function __unset($name){ … … ; }

参数 $name 为要访问的属性名称。

我们先来看一下 unset() 函数,unset() 函数的作用是删除指定的变量,需要传入一个或多个变量作为参数,另外,该函数没有返回值。

同样,我们也可以使用 unset() 函数在类外部去删除类中的成员属性。与上面介绍的 __isset() 方法相似,如果要删除类中的公有属性的话直接使用 unset() 函数即可;如果要删除类中的私有属性的话,则需要在类中添加一个 __unset() 方法。

<?php
    class Site{
        public $url = 'xiyoudaodao.gitee.io';
        private $name = 'php学习';
        public function __unset($name){
            if(property_exists('Site', $name)){
                unset($this->$name);
                echo '移除成员属性:“'.$name.'”成功!<br>';
            }else{
                echo '成员属性:“'.$name.'”不存在!<br>';
            }
        }
    }
    $object = new Site();
    unset($object->url);
    unset($object->name);
    unset($object->abc);
?>
5、__call() 方法

当调用类中一个不可访问或不存在的方法时,__call() 方法会被调用。该方法的语法格式如下: public function __call($name, $arguments){ … … ; }

其中,$name 为要调用的方法名称,$arguments 为传递给 $name 的参数所组成的数组。

当调用的方法不存在时会自动调用 __call() 方法,程序会继续执行下去,从而可以避免当调用方法不存在时产生错误所导致的程序终止。

<?php
    class Site{
        public function say(){
            echo 'Welcome 西柚叨叨的博客!<br>';
        }
        public function __call($name, $arguments){
            echo '你所调用的方法:'.$name;
            if(!empty($arguments)){
                echo '【以及参数:';
                print_r($arguments);
                echo '】';
            }
            echo ' 不存在!<br>';
        }
    }
    $obj = new Site();
    $obj -> say();
    $obj -> url('xiyoudaodao.gitee.io');
    $obj -> title();
?>
6、__autoload方法

本函数已自 PHP 7.2.0 起被废弃,并自 PHP 8.0.0 起被移除,使用 spl_autoload_register函数

spl_autoload_register('loadClass');
function loadClass($class){
    $file = './'.$class.'.php';
    include_once($file);
}
$obj = new Demo();

四、综合实例

1、王者荣耀面向对象输出

<?php
$heros = [
    "安琪拉" => [
        "皮肤" => ["暗夜萝莉", "玩偶对对碰", "魔法小厨娘"],
        "技能" => [
            "火球术" => [350, 390, 430],
            "混沌火种" => [130, 145, 160],
            "炽热光辉" => [700, 1050, 1400],
        ],
        "语音" => ["知识就是力量", "一不小心就干掉了个大家伙!", "生命就像人家的魔法师~涂涂改改又是一年!"]
    ],
    "亚瑟" => [

    ]
];

//过程式编程
$key = "安琪拉";
$hero = $heros[$key];

//mt_rand(0, 2)是生成随机数,随机数最小值是0,最大值是2
$pifu = $hero["皮肤"][mt_rand(0, 2)];

//array_keys是获取数组的中的key值,并组成一维数组,此处的结果是 ["火球术", "混沌火种", "炽热光辉"]
$jinengArrs = array_keys($hero["技能"]);

$jineng = $jinengArrs[mt_rand(0, 2)];
$shanghai = $hero["技能"][$jineng][mt_rand(0, 2)];

$yuyin = $hero["语音"][mt_rand(0, 2)];

echo "【".$pifu."】的【".$key."】对你使用了【".$jineng."】,造成了【".$shanghai."】点伤害,并对你说出了【".$yuyin."】";

//面向对象编程
class Heros
{
    private $n = "未知英雄";
    private $p = [];
    private $j = [];
    private $y = [];

    public function setName($name = "未知英雄")
    {
        $this->n = $name;
    }

    public function setPifu($pifu = [])
    {   
        $this->p = $pifu;
    }

    public function setJineng($jineng = [])
    {
        $this->j = $jineng;
    }

    public function setYuyin($yuyin = [])
    {
        $this->y = $yuyin;
    }

    private function getPifu()
    {
        if(count($this->p) > 0){
            $r = mt_rand(0, count($this->p) -1);
            return $this->p[$r];
        }
        return "未知皮肤";
    }

    private function getJineng()
    {
        $reuslt = [
            "jineng" => "未知技能",
            "shanghai" => "未知伤害",
        ];

        if(count($this->j) > 0){
            $jinengArrs = array_keys($this->j);
            $r = mt_rand(0, count($this->j) -1);
            $jineng = $jinengArrs[$r];

            $r = mt_rand(0, count($this->j) -1);
            $shanghai = $this->j[$jineng][$r];

            $reuslt["jineng"] = $jineng;
            $reuslt["shanghai"] = $shanghai;
        }
        return $reuslt;
    }

    private function getYuyin()
    {
        if(count($this->y) > 0){
            $r = mt_rand(0, count($this->y) -1);
            return $this->y[$r];
        }
        return "未知语音";
    }

    public function do()
    {
        echo "<br/>【".$this->getPifu()."】的【".$this->n."】对你使用了【".$this->getJineng()["jineng"]."】,造成了【".$this->getJineng()["shanghai"]."】点伤害,并对你说出了【".$this->getYuyin()."】";
    }
}

$h = new Heros();
$key = "安琪拉";

$h->setName($key);
$h->setPifu($heros[$key]["皮肤"]);
$h->setJineng($heros[$key]["技能"]);
$h->setYuyin($heros[$key]["语音"]);

$h->do();

2、王者荣耀面向对象处理

<?php
abstract class Hero
{
    abstract public function getName();
    abstract public function getPifu();
    abstract public function getJineng();
    abstract public function getYuyin();
    public function do()
    {
        echo "<br/>【".$this->getPifu()."】的【".$this->getName()."】对你使用了【".$this->getJineng()["jineng"]."】,造成了【".$this->getJineng()["shanghai"]."】点伤害,并对你说出了【".$this->getYuyin()."】";
    }
}

class AnQiLa extends Hero
{
    public function getName()
    {
        return "安琪拉";
    }

    public function getPifu()
    {
        $piFu = ["暗夜萝莉", "玩偶对对碰", "魔法小厨娘"];
        $r = mt_rand(0, count($piFu) -1);
        return $piFu[$r];
    }

    public function getJineng()
    {
        $jiNeng = [
            "火球术" => [350, 390, 430],
            "混沌火种" => [130, 145, 160],
            "炽热光辉" => [700, 1050, 1400],
        ];
        $jinengArrs = array_keys($jiNeng);
        $r = mt_rand(0, count($jiNeng) -1);
        $jineng = $jinengArrs[$r];

        $r = mt_rand(0, count($jiNeng) -1);
        $shanghai = $jiNeng[$jineng][$r];

        $reuslt["jineng"] = $jineng;
        $reuslt["shanghai"] = $shanghai;
        return $reuslt;
    }

    public function getYuyin()
    {
        $yuYin = ["知识就是力量", "一不小心就干掉了个大家伙!", "生命就像人家的魔法师~涂涂改改又是一年!"];
        $r = mt_rand(0, count($yuYin) -1);
        return $yuYin[$r];
    }
}

$a = new AnQiLa();
$a->do();
?>

关注微信公众号,与我交流吧~

分享