美文网首页PHP入门系列
PHP入门系列(五)——函数&OOP&namespace

PHP入门系列(五)——函数&OOP&namespace

作者: RabbitMask | 来源:发表于2019-05-16 17:32 被阅读0次

    目录

    • 函数
    • 面向对象
    • 命名空间

    函数

    概念

    关于函数的返回值、参数类型、匿名函数等内容,其实在前面章节我们已经分别提及了,所以函数不再增加单独章节,在此借实例简单重申下概念。

    function add($a,$b){
        $count = $a + $b;
        return $count;
    }
    echo add(1,2);
    
    #输出
    3
    
    生成器

    生成器的本质是一种函数,这个可以参考之前python系列教程对迭代、生成器的解释(语言是相通的),在PHP中同样使用yield 关键字。
    生成器允许你在 foreach 代码块中写代码来迭代一组数据而不需要在内存中创建一个数组, 那会使你的内存达到上限,或者会占据可观的处理时间。相反,你可以写一个生成器函数,就像一个普通的自定义函数一样, 和普通函数只返回一次不同的是, 生成器可以根据需要 yield 多次,以便生成需要迭代的值。简单说,生成器会对PHP应用的性能有非常大的影响,PHP代码运行时节省大量的内存,比较适合计算大量的数据。

    接下来我们借助实例来说明一下,首先来一个正常逻辑,写一个数组生成函数,然后进行实例化,然后输出出来:

    function demo($number){
        $data = [];
        for($i=0;$i<$number;$i++){
            $data[] = time();
        }
        return $data;
    }
    
    $data=demo(10);
    foreach($data as $v){
        sleep(1);
        echo $v.'<br>';
    }
    
    #输出
    1557987201
    1557987201
    1557987201
    1557987201
    1557987201
    1557987201
    1557987201
    1557987201
    1557987201
    1557987201
    

    在输出时,每输出一个sleep一秒钟,但数组中的数据是在实例化时,一并生成并储存在内存中的,所以返回的time()完全相等(确切的说时间小到忽略不计),然后我们进行生成器改写:

    function demo($number){
        for($i=0;$i<$number;$i++){
            yield time();
        }
    }
    
    $data=demo(10);
    foreach($data as $v){
        sleep(1);
        echo $v.'<br>';
    }
    
    #输出
    1557987242
    1557987243
    1557987244
    1557987245
    1557987246
    1557987247
    1557987248
    1557987249
    1557987250
    1557987251
    

    跟正确逻辑的区别只是数组生成函数改变了,即使进行了实例化,但数组重点数据并没有在使用前预生成,而是在使用时(即foreach中)才出现在了内存中。可能从这个小例子无法良好的体现出其性能优势,但当数据量足够大时,正常逻辑极易造成内存溢出,而生成器的使用却可以良好的弥补这一点。在这里,使用了yield关键字的demo()函数我们称之为生成器函数,这里的yield关键字效果与其他语言类似,自带return效果。

    面向对象

    概念

    一个类可以包含有属于自己的常量,变量(称为“属性”)以及函数(称为“方法”)。

    #类语法格式
    类使用 class 关键字后加上类名定义。
    类名后的一对大括号({})内可以定义变量和方法。
    类的变量使用 var 来声明, 变量也可以初始化值。
    函数定义类似 PHP 函数的定义,但函数只能通过该类及其实例化的对象访问。
    
    伪变量 $this

    $this代表自身的对象,用以属性、方法在类定义内部的调用。$this是一个到主叫对象的引用(通常是该方法所从属的对象,但如果是从第二个对象静态调用时也可能是另一个对象)。

    属性

    类的变量成员叫做“属性”,或者叫“字段”、“特征”。属性声明是由关键字 public,protected 或者 private 开头,然后跟一个普通的变量声明来组成。属性中的变量可以初始化,但是初始化的值必须是常数。
    在类的成员方法里面,可以用 ->(对象运算符):$this->property(其中 property 是该属性名)这种方式来访问非静态属性。静态属性则是用 ::(双冒号):self::$property 来访问。
    注意:如果直接使用 var 声明属性,而没有用 public,protected 或 private 之一,PHP 5 会将其视为 public。

    class user{
         var $name;
         var $age;
         static $sex='boy';
    
         function setname($name){
            $this->name=$name;
         }
         function setage($age){
             $this->age=$age;
         }
         function getname(){
             echo $this->name.PHP_EOL;
         }
         function getage(){
             echo $this->age.PHP_EOL;
         }
         function getsex(){
             echo self::$sex;
         }
     }
    
    类常量

    可以把在类中始终保持不变的值定义为常量。在定义和使用常量的时候不需要使用 $ 符号。常量的值必须是一个定值,不能是变量,类属性,数学运算的结果或函数调用。
    相对于静态变量,两者都是类的属性,类的属性都用双冒号访问(::),通过对象或者类名都可以访问。但常量是不可变的,静态变量可以通过self来赋值改变。即const常量:类的不变属性,static变量:类的可变属性。

    class user{
         static $sex='boy';
         const sex='boy';
         
    function getsex(){
             echo self::$sex;
             self::$sex='girl';
             echo self::sex;
         }
    }
    

    其中$sex可重新赋值,self::sex='girl';将会直接报错。

    访问控制

    PHP 对属性或方法的访问控制,是通过在前面添加关键字 public(公有),protected(受保护)或 private(私有)来实现的。

    public(公有):公有的类成员可以在任何地方被访问。
    protected(受保护):受保护的类成员则可以被其自身以及其子类和父类访问。
    private(私有):私有的类成员则只能被其定义所在的类访问。
    

    类属性必须定义为公有,受保护,私有之一。如果用 var 定义,则被视为公有。

    构造函数&析构函数

    构造函数是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,在创建对象的语句中与 new 运算符一起使用。
    析构函数(destructor) 与构造函数相反,当对象结束其生命周期时(例如对象所在的函数已调用完毕),系统自动执行析构函数。

    <?php header('Content-Type:text/html; charset=utf-8;')?>
    <?php
    class user{
        var $name;
        var $age;
    
        function __construct($name,$age)
        {
            $this->name=$name;
            $this->age=$age;
            echo '初始成功<br>';
        }
        function __destruct()
        {
            // TODO: Implement __destruct() method.
            echo '实例销毁';
        }
    }
    $user1=new user('rabbit',18);
    <?
    
    #输出
    初始成功
    实例销毁
    
    接口

    使用接口(interface),可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。接口是通过 interface 关键字来定义的,就像定义一个标准的类一样,但其中定义所有的方法都是空的。接口中定义的所有方法都必须是公有,这是接口的特性。要实现一个接口,使用 implements 操作符。类中必须实现接口中定义的所有方法。

    interface userrule{
        function setname($name);
        function setage($age);
        function getname();
        function getage();
    }
    
     class user implements userrule{
         var $name;
         var $age;
    
         function setname($name){
            $this->name=$name;
         }
         function setage($age){
             $this->age=$age;
         }
         function getname(){
             echo $this->name.PHP_EOL;
         }
         function getage(){
             echo $this->age.PHP_EOL;
         }
     }
    
    抽象类

    任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象的。定义为抽象的类不能被实例化。被定义为抽象的方法只是声明了其调用方式(参数),不能定义其具体的功能实现。显然,刚刚提到的接口就是一种抽象类,抽象类一般用作模板/标准/规范用以被继承。
    子类必须把父类中的抽象方法全部都实现,否则子类中还存在抽象方法,那么子类还是抽象类,还是不能实例化类。
    太抽象了?来个实例吧:

    abstract class userrule{
        var $name;
        var $age;
        abstract function setname($name);
        abstract function setage($age);
        function getname(){
            echo $this->name.PHP_EOL;
        }
        function getage(){
            echo $this->age.PHP_EOL;
        }
    }
     class user extends userrule{
         var $name;
         var $age;
    
         function setname($name){
            $this->name=$name;
         }
         function setage($age){
             $this->age=$age;
         }
     }
    
    继承

    PHP不支持多继承,确切的说不支持类的多继承,但是却支持接口多继承,类的集成使用extends关键字。
    另外PHP中的继承不包含构造函数的继承,继承构造函数需要调用parent::__construct()

    class user{
        var $name;
        var $age;
        function __construct($name,$age)
        {
            $this->name=$name;
            $this->age=$age;
        }
    }
    class admin extends user{
        function __construct($name, $age)
        {
            parent::__construct($name, $age);
        }
    }
    
    重写

    如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。

    static

    声明类属性或方法为静态,就可以不实例化类而直接访问。但静态属性不再支持实例化对象被访问,但静态方法依然可以。

    class user{
        static $name='rabbit';
        static $age=18;
        function __construct($name,$age)
        {
            $this->name=$name;
            $this->age=$age;
        }
    }
    echo user::$name.'<br>';
    echo user::$age;
    
    #输出
    rabbit
    18
    
    Final

    PHP 5 新增了一个 final 关键字。如果父类中的方法被声明为 final,则子类无法覆盖该方法。如果一个类被声明为 final,则不能被继承。

    对象序列化

    所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变回php原来的值。 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。
    为了能够unserialize()一个对象,这个对象的类必须已经定义过。

    class user{
        var $name='rabbit';
        var $age=18;
        function __construct($name,$age)
        {
            $this->name=$name;
            $this->age=$age;
        }
    }
    $user1=new user('rabbit',18);
    $data= serialize($user1);
    
    echo $data.'<br>';
    $result=unserialize($data);
    echo var_dump($result);
    
    #输出
    O:4:"user":2:{s:4:"name";s:6:"rabbit";s:3:"age";i:18;}
    object(user)#2 (2) { ["name"]=> string(6) "rabbit" ["age"]=> int(18) }
    
    魔术方法

    __construct(), __destruct(), __call(), __callStatic(), __get(), __set(), __isset(), __unset(), __sleep(), __wakeup(), __toString(), __invoke(), __set_state(), __clone() 和 __debugInfo() 等方法在 PHP 中被称为魔术方法(Magic methods),用于类中调用,此处暂不做拓展。

    命名空间

    请饶恕我将命名空间强行塞入了这一章节~
    PHP 命名空间(namespace)是在PHP 5.3中加入的,如果你学过C#和Java,那命名空间就不算什么新事物。 不过在PHP当中还是有着相当重要的意义。

    PHP 命名空间可以解决以下两类问题:

    • 用户编写的代码与PHP内部的类/函数/常量或第三方类/函数/常量之间的名字冲突。
    • 为很长的标识符名称(通常是为了缓解第一类问题而定义的)创建一个别名(或简短)的名称,提高源代码的可读性。

    如果你看过官方手册还一脸懵逼(是笔者本人了),那就听我掰扯掰扯吧。
    我们来写两个使用了命名空间的脚本文件:

    #a.php
    namespace a;
    class Demo{
        function demo(){
            echo "It's a";
        }}
    
    #b.php
    namespace b;
    class Demo{
        function demo(){
            echo "It's b";
        }}
    

    亦或者,单个文件定义多个命名空间的情况:

    #ab.php
    namespace a;
    class Demo{
        function demo(){
            echo "It's a";
        }}
    namespace b;
    class Demo{
        function demo(){
            echo "It's b";
        }}
    

    然而,按照官方建议,针对这种多个命名空间的情况,我们还是使用下面的大括号形式的语法:

    #ab.php
    namespace a{
    class Demo{
        function demo(){
            echo "It's a";
        }}}
    namespace b{
    class Demo{
        function demo(){
            echo "It's b";
        }}}
    

    接下来我们来使用一下,首先是最基本用法:

    #run.php
    require 'ab.php';
    $a=new a\Demo();
    $a->demo();
    echo '<br>';
    $b=new b\Demo();
    $b->demo();
    
    #输出
    It's a
    It's b
    

    然而,有时候我们会单独使用某个命名空间内的类多次,这可以使用use关键字:

    #run.php
    require 'ab.php';
    use a\Demo;
    $a=new Demo();
    $a->demo();
    echo '<br>';
    $b=new Demo();
    $b->demo();
    echo '<br>';
    $c=new Demo();
    $c->demo();
    
    #输出:
    It's a
    It's a
    It's a
    

    但是,还是会有意外的,如果同时多次使用多个命名空间,同时use多个命名空间的情况下依然会发生冲突,这时候可以使用as关键字,即别名:

    #run.php
    require 'ab.php';
    use a\Demo as ADemo;
    use b\Demo as BDemo;
    $a=new ADemo();
    $a->demo();
    echo '<br>';
    $b=new ADemo();
    $b->demo();
    echo '<br>';
    $c=new BDemo();
    $c->demo();
    
    #输出:
    It's a
    It's a
    It's b
    

    在实例之后,我们再来补充下抽象的理论内容作为结语:

    非限定名称,限定名称和完全限定名称的命名空间的区别?
    • 非限定名称,或不包含前缀的类名称,例如 $a = new Demo();。如果当前命名空间是a,Demo将被解析为a\Demo()。如果使用Demo的代码不包含在任何命名空间中的代码(全局空间中),则Demo会被解析为Demo。
    • 限定名称,或包含前缀的名称,例如 $a = new a\Demo();。如果当前的命名空间是b,则Demo会被解析为b\a\Demo。如果使用Demo的代码不包含在任何命名空间中的代码(全局空间中),则Demo会被解析为Demo。
    • 完全限定名称,或包含了全局前缀操作符的名称,例如 $a= new \a\Demo();。在这种情况下,Demo总是被解析为\a\Demo。

    相关文章

      网友评论

        本文标题:PHP入门系列(五)——函数&OOP&namespace

        本文链接:https://www.haomeiwen.com/subject/peppiqtx.html