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

目录

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

函数

概念

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

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。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351