2019-09-25补充: 使用clone关键字对对象进行克隆之后,会创建新的对象。如果定义了__clone方法,,则此魔术方法会被调用,此魔术方法不能直接被调用!
关于类
- 类是抽象的。类就是一些属性和方法的集合。
- 关于属性:
可以先声明属性并赋值,也可以声明属性先不赋值;
属性的值必须是一个直接的值,不能是表达式(例如1+2),也不能是函数的返回值(例如time());
但是经测试,属性值为表达式时是可以的,不能为函数返回值。 - 关于方法:类中的方法可以和可以与外部函数(比如全局函数)重名,例如:
class Test{
public $money=1-1;
public function time(){
echo time();
}
}
$test=new test();
var_dump($test->money);
echo '<br>';
$test->time();
- new 对象时发生了什么?
申请内存,生成对象;
如果有构造函数,则执行;
返回该对象地址。
构造函数与析构函数
- __construct()用来初始化对象:每当new一个对象时,就会自动对新new出来的对象发挥作用。
new ClassName($args)时,$args会原样传给构造函数。 - __destruct()在对象被显式销毁或者对象的所有引用都被删除时(比如unset()对象、把对象赋值为其他、脚本执行完毕)执行。
class Human
{
public $name;
public $job;
public function __construct($name, $job)
{
$this->name = $name;
$this->job = $job;
}
public function __destruct()
{
echo 'die<br>';
}
}
$a = new Human('苏大强', '坑儿子');
$b = new Human('苏明玉', '女强人');
$c = new Human('苏明成', '啃老');
echo $a->name;
echo '<br>';
echo $b->name;
echo '<br>';
echo $c->name;
echo '<br>';
/*
输出:
苏大强
苏明玉
苏明成
die
die
die
*/
回收机制
对象默认是采用引用传值,所以:
class Human
{
public $name;
public $job;
public function __construct($name, $job)
{
$this->name = $name;
$this->job = $job;
}
public function __destruct()
{
echo 'die<br>';
}
}
$a = new Human('苏大强', '坑儿子');
$b=$c=$a;
unset($a);
var_dump($b);//object(Human)#1 (2) { ["name"]=> string(9) "苏大强" ["job"]=> string(9) "坑儿子" }
echo '<hr>';
/*解释:$a,$b,$c指向内存中同一个地址,$a销毁后,因为还有$a,$b指向该内存,所以该内存并未被回收;最后一行代码(echo '<hr>')运行结束后,所有内存都被回收,所以会触发该对象的析构函数,会在页面的分割线下echo 一个die*/
$this绑定
$this 会绑定到调用他的对象上。
类的方法内,存、取对象的属性必须用$this。
封装
特点:
- 封装:就是把一些重要的属性封装在类内,然后通过一个开放的接口来操作。
- 对于一个对象,对外界开放一个接口,调用接口时,内部进行的操作外界不知道,隐藏了内部的一些实现细节。
封装MySQL类:
封装MySQL类
header("content-type:text/html;charset=gbk");
class sql
{
private $host;
private $user;
private $pwd;
private $dbname;
public $conn;//保存连接资源
public $char;
public function __construct()
{
$this->host = 'localhost';
$this->user = 'root';
$this->pwd = 'root';
$this->dbname = 'test';
$this->char = 'gbk';
//连接
$this->connect($this->host, $this->user, $this->pwd);
//切换库
$this->switchDb($this->dbname);
//设置字符集
$this->setChar($this->char);
}
//连接数据库服务器
public function connect($host, $user, $pwd)
{
$this->conn = @mysqli_connect($host, $user, $pwd);
if (!$this->conn) {
die('Connect Error(' . mysqli_connect_errno() . ')' . mysqli_connect_error());
}
}
//设置字符集
public function setChar($char)
{
mysqli_set_charset($this->conn, $char);
}
//查询语句
public function query($sql)
{
return mysqli_query($this->conn, $sql);
}
//切换数据库
public function switchDb($db)
{
$this->dbname = $db;
mysqli_select_db($this->conn, $db);
}
//查询一行
public function getRow($sql)
{
$re = $this->query($sql);
return mysqli_fetch_row($re);
}
//查询多行
public function getRows($sql)
{
$list = [];
$re = $this->query($sql);
while ($row = mysqli_fetch_assoc($re)) {
$list[] = $row;
}
return $list;
}
//查询单个值
public function getOne($sql)
{
$re = $this->query($sql);
return mysqli_fetch_row($re);
}
//关闭连接
public function close()
{
return mysqli_close($this->conn);
}
}
$mysql = new sql();
echo '<br>getRow: ';
var_dump($mysql->getRow('select * from m where mid=1'));
echo '<br>getRows: ';
var_dump($mysql->getRows('select * from m'));
echo '<br>getOne: ';
var_dump($mysql->getOne("select 'name' from girl where hid='B'"));
echo '<br>close: ';
var_dump($mysql->close());
权限修饰符
作用:用来说明属性/方法的权限特点
写在属性/方法的前面
共有3个:
public定义的属性/方法在任意位置都可以访问
private只能被其定义所在的类访问。
protected 可以被其自身以及其子类和父类访问
继承
语法:
子类 extends 父类{
}
注意:
- 子类只能继承一个父类。
- 子类可以添加属性/方法。
- public protected属性/方法可以继承,并拥有访问、修改的权限,并且继承后权限只能越来越宽松/不变;
- private的属性/方法可以继承,但是有个标记,标记从属父类而来,但是无法在子类里直接访问和修改。
利用trait实现变相多继承
优先级:优先顺序是来自当前类的成员覆盖了 trait 的方法,而 trait 则覆盖了被继承的方法。
冲突的解决 :为了解决多个 trait 在同一个类中的命名冲突,需要使用 insteadof 操作符来明确指定使用冲突方法中的哪一个。
别名:以上方式仅允许排除掉其它方法,as 操作符可以为某个方法引入别名。
<?php
trait A {
public function smallTalk() {
echo 'a';
}
public function bigTalk() {
echo 'A';
}
}
trait B {
public function smallTalk() {
echo 'b';
}
public function bigTalk() {
echo 'B';
}
}
class Talker {
use A, B {
B::smallTalk insteadof A;
A::bigTalk insteadof B;
}
}
class Aliased_Talker {
use A, B {
B::smallTalk insteadof A;
A::bigTalk insteadof B;
B::bigTalk as talk;
}
}
?>
修改方法的访问控制 : 使用 as 语法还可以用来调整方法的访问控制。
<?php
trait HelloWorld {
public function sayHello() {
echo 'Hello World!';
}
}
// 修改 sayHello 的访问控制
class MyClass1 {
use HelloWorld { sayHello as protected; }
}
// 给方法一个改变了访问控制的别名
// 原版 sayHello 的访问控制则没有发生变化
class MyClass2 {
use HelloWorld { sayHello as private myPrivateHello; }
}
?>
private属性的继承
一个小例子:
class Hu
{
private $a = '哥哥';
public function a()
{
echo $this->a;
}
}
class stu extends Hu{
private $a='姐姐';
public function cry(){
parent::a();
echo '<br>';
echo $this->a;
}
}
$lili=new stu();
echo '<br>';
$lili->cry();
/*哥哥
姐姐*/
public 和 protected属性和方法的继承
public的属性/方法在类外能调用;
protected的属性/方法在类外不能调用。
构造函数的继承
- 可以继承
- 如果子类有有构造函数,则在子类构造函数里要调用parent::__construct,以此来保证调用父类构造函数。
例如,父类为数据库操作类或者是model类,其构造函数会做很多初始化工作,如果子类直接重写构造函数,则有可能导致初始化工作完不成。
多态
多态就是只抽象的声明父类,具体的工作由子类对象来完成。
实现多态的思想步骤:
-
php是弱类型动态语言,传参时,参数没有强制的类型,任何类型变量都可以当做参数传。
-
php5后,引入了对于对象类型的参数检测(即:检测对象所属的类)。限制了其灵活性(以下截图来自php7.2参考手册–函数的参数)。
-
里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。
小例子:
class Light
{
public function lighter(Glass $d)
{
$d->display();
}
}
class Glass
{
}
class RedLight extends Glass
{
public function display()
{
echo 'red<br>';
}
}
class GreenLight extends Glass
{
public function display()
{
echo 'green<br>';
}
}
class pig
{
public function display()
{
echo 'pig<br>';
}
}
$red = new RedLight;
$green = new GreenLight();
$pig = new Pig();
$light = new Light();
$light->lighter($red);
$light->lighter($green);
$light->lighter($pig);
/*
输出:
red
green
Fatal error: Uncaught TypeError: Argument 1 passed to Light::lighter() must be an instance of Glass, instance of pig given, called in D:\phpStudy\PHPTutorial\WWW\01.php on line 385 and defined in D:\phpStudy\PHPTutorial\WWW\01.php:342 Stack trace: #0 D:\phpStudy\PHPTutorial\WWW\01.php(385): Light->lighter(Object(pig)) #1 {main} thrown in D:\phpStudy\PHPTutorial\WWW\01.php on line 342
*/
静态属性与静态方法
- 在属性/方法前,加static关键字修饰。
- 静态属性:
- 存放在类空间内;
- 访问:类名::$ 属性名(类声明完毕,该属性就已存在,不需要依赖对象访问)(静态属性不可以由对象通过 -> 操作符来访问 。 );
- 在内存中只有一个,为所有对象所共享,一个对象改变了静态属性,其他对象也受影响。例子:
class Human
{
static $head = 1;
public function getHead()
{
echo Human::$head;
}
public function changeHead()
{
Human::$head=2;
}
}
$a = new Human();
$b = new Human();
$a->getHead();
$a->changeHead();
$b->getHead();
//12
- 静态方法:
- 在内存中只有一个;
- 与普通方法的区别:
** | 普通方法 | 静态方法 |
---|---|---|
相同点 | 存放于类空间里,在内存中只有1份 | 存放于类空间,在内存中只有1份 |
不同点 | 需要用对象调用,所以需要绑定$this | 不属于哪个对象,所以不需要绑定$this |
调用方法 | $this->方法名 | 类名::方法名 |
注意:
- 静态属性不能通过一个类已实例化的对象来访问(但静态方法可以)。
- 为了兼容 PHP 4,如果没有指定访问控制,属性和方法默认为public。
调用方法方式 | 静态方法 | 非静态方法 |
---|---|---|
通过类调用 | ✔ | 报E_STRICT级别错误 |
通过对象调用 | ✔ | ✔ |
self和parent
self:本类:用来调用自身方法/静态属性
parent:父类:用来调用父类方法/静态属性
单例模式
主要思路:
- 声明一个protected/private构造函数,防止外部实例化
- 内部开放一个public静态方法,负责实例化
- 类内有一个静态属性用来存放对象。
/*
* final修饰的类不可以被继承。
* 这样可以避免继承single类后,重写构造函数,从而可以通过子类实例化多个对象的情况。
*/
final class Single
{
static $instance;
public $name;
/*
* 具有构造函数的类会在每次创建新对象时先调用此方法;
* 而被protected修饰的方法无法在类外调用,在类外调用会产生一个fetal error并终止脚本.
* 这样可以避免实例化多个对象。
*/
protected function __construct()
{
}
static function getInstance()
{
return self::$instance ?: self::$instance = new self();
}
}
关于final关键字
可以修饰类/方法,不能修饰属性!
final修饰后的类不能被继承!
final修饰后的方法能被继承,无法被重写!
魔术方法
PHP 将所有以 __(两个下划线)开头的类方法保留为魔术方法。所以在定义类方法时,除了上述魔术方法,建议不要以 __ 为前缀。
后面将使用"不可访问属性(inaccessible properties)"和"不可访问方法(inaccessible methods)"来称呼这些未定义或不可见的类属性或方法。
- __clone():当对象被克隆时,将会自动调用该方法。
- __call():在对象中调用一个不可访问方法时,__call() 会被调用。注意这个方法传的参数,第一个参数为调用的方法名,第二个为方法参数组成的数组。
- __callstatic():在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。可见性未设置为 public 或未声明为 static 的时候会产生一个警告。
- __get():读取不可访问属性的值时自动调用,并且自动传一个参数,参数值为此属性名称。
- __set():在给不可访问属性赋值时自动调用,阻止添加该属性,并且自动传2个参数,第一个参数是该属性名称,第二个参数是要赋的值。
- __isset():当对不可访问方法调用isset()/empty()时。
总结:
empty():当变量存在,并且是一个非空非零的值时返回 FALSE 否则返回 TRUE.
以下的东西被认为是空的:
•"" (空字符串)
•0 (作为整数的0)
•0.0 (作为浮点数的0)
•“0” (作为字符串的0)
•NULL
•FALSE
•array() (一个空数组)
•$var; (一个声明了,但是没有值的变量)
isset() 如果变量存在,并且值不是 NULL 则返回 TRUE,否则返回 FALSE。
两个方法虽然有些相似,但是一个判断变量存在时返回TRUE,一个返回FALSE,这点不一样。
发现: 当使用empty()时,如果类内有__get()方法和__isset()方法时,则会先调用__isset()方法,然后调用__get()方法。
__isset()方法里return的值会直接影响到方法外isset()的判断。例子如下:
class T
{
public $name = 'kk';
protected $age = 18;
private $money = 4000;
public function __isset($name)
{
// TODO: Implement __isset() method.
echo "嘻嘻,{$name}不可判断是否存在<br>";
return 0;
}
public function __get($name)
{
// TODO: Implement __get() method.
echo "{$name}不可访问或不存在<br>";
}
}
$a = new T;
var_dump(isset($a->money));
__isset()方法return值 | 0 | 1 | 0 | 1 |
---|---|---|---|---|
调用方法 | isset() | isset() | empty() | empty() |
var_dump()输出 | 嘻嘻,money不可判断是否存在;bool(false); | 嘻嘻,money不可判断是否存在;bool(true); | 嘻嘻,money不可判断是否存在;bool(true); | 嘻嘻,money不可判断是否存在;money不可访问或不存在;bool(true)(布尔返回值取决于__get()方法return的值!) |
- __unset():当对不可访问属性调用unset()时。
重写和重载
重写
指子类重写了父类的方法。
重载
函数重载指一个标识符被用作多个函数名,且能够通过函数的参数个数或参数类型将这些同名的函数区分开来,调用不发生混淆。即当调用的时候,虽然方法名字相同,但根据参数的不同可以自动调用相应的函数。
但是php中不允许存在多个同名的方法,要达到重载的效果,要采用一点小技巧。
- 燕十八老师给出的,用func_get_args()方法:
class T
{
public function test()
{
$arg = func_get_args();
$count = count($arg);
if ($count == 1) {
echo 3.14 * $arg[0] * $arg[0], '<br>';
} elseif ($count == 2) {
echo $arg[0] * $arg[1], '<br>';;
} else {
echo '??', '<br>';;
}
}
}
$a = new T;
$a->test(2);
$a->test(1, 2);
$a->test();
- 网上给出的,用魔术方法实现:
public function __call($method,$p)
{
if($method=="display"){
if(is_object($p[0])){
$this->displayObject($p[0]);
}else if(is_array($p[0])){
$this->displayArray($p[0]);
}else{
$this->displayScalar($p[0]);
}
}
}
//下面是对上面定义的调用
$ov=new overload;
$ov->display(array(1,2,3));
$ov->display('cat');
类常量、魔术常量与延期绑定
类常量
相当于值无法修改的静态属性。
定义:const 在定义和使用常量的时候不需要使用 $ 符号。
调用:与静态属性调用方法一样,但有一点:不需要加$。
存放:在内存中只有一份(这点与静态属性相同)。
魔术常量
__FILE__文件的完整路径和文件名
__DIR__文件所在的目录
__CLASS__ 类的名称
延期绑定
该功能从语言内部角度考虑被命名为"后期静态绑定"。"后期绑定"的意思是说,static:: 不再被解析为定义当前方法所在的类,而是在实际运行时计算的。
- self:: 的限制
使用 self:: 或者__CLASS__ 对当前类的静态引用,取决于定义当前方法所在的类:
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
self::who();
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test();//A
- 后期静态绑定的用法
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
static::who(); // 后期静态绑定从这里开始
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test();//B
抽象类
声明:用abstract声明。
特点:
- 不能被实例化;
- 任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象的;(但是抽象类中可以没有抽象方法)
- 被定义为抽象的方法只是声明了其调用方式(参数),不能定义其具体的功能实现。
- 2019-09-16补充: 非抽象方法可以有函数体,而且可以不被继承。
- 继承一个抽象类的时候,子类必须定义父类中的所有抽象方法;
- 此外方法的调用方式必须匹配,即类型和所需参数数量必须一致。例如,子类定义了一个可选参数,而父类抽象方法的声明里没有,则两者的声明并无冲突。
小例子:
abstract class AbstractClass
{
// 我们的抽象方法仅需要定义需要的参数
abstract protected function prefixName($name);
}
class ConcreteClass extends AbstractClass
{
// 我们的子类可以定义父类签名中不存在的可选参数
public function prefixName($name, $separator = ".") {
if ($name == "Pacman") {
$prefix = "Mr";
} elseif ($name == "Pacwoman") {
$prefix = "Mrs";
} else {
$prefix = "";
}
return "{$prefix}{$separator} {$name}";
}
}
$class = new ConcreteClass;
echo $class->prefixName("Pacman"), "\n";
echo $class->prefixName("Pacwoman"), "\n";
接口
定义
- 通过 interface 关键字来定义的;
- 但其中定义所有的方法都是空的;
- 接口中定义的所有方法都必须是public;
- 接口是一堆方法说明,不能有属性,可以有常量;
- 接口常量和类常量的使用方法完全相同,但是与类常量有一点不同,就是不能被子类或子接口所覆盖。
- 接口可以被继承。
interface animal
{
public function eat();
}
interface animal2 extends animal
{
public function smile();
}
实现
- 要实现一个接口,使用 implements 操作符;
- 类中必须实现接口中定义的所有方法,否则会报一个致命错误;
- 类可以实现多个接口,用逗号来分隔多个接口的名称;
- 实现多个接口时,接口中的方法不能有重名。
类的自动加载
spl_autoload_register() — 注册给定的函数作为 __autoload 的实现