目录
- 函数
- 面向对象
- 命名空间
函数
概念
关于函数的返回值、参数类型、匿名函数等内容,其实在前面章节我们已经分别提及了,所以函数不再增加单独章节,在此借实例简单重申下概念。
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。