(十二)PHP类和对象详解

访问控制

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

  • public:可以在任何地方被访问
  • protected:可以被其自身以及其子类和父类访问
  • private:只能被其定义所在的类访问

构造函数和析构函数

函数原型

__construct ([ mixed $args [, $... ]] ) : void

__destruct ( void ) : void

作用

  • 构造函数用于在使用对象之前做一些初始化工作,在实例化对象的时候自动调用。
  • 析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时自动调用。

示例


class BaseClass {
   function __construct() {
       print "In BaseClass constructor\n";
   }
}

class SubClass extends BaseClass {
   function __construct() {
       parent::__construct();
       print "In SubClass constructor\n";
   }
}

class OtherSubClass extends BaseClass {
    // inherits BaseClass's constructor
}

// In BaseClass constructor
$obj = new BaseClass();

// In BaseClass constructor
// In SubClass constructor
$obj = new SubClass();

// In BaseClass constructor
$obj = new OtherSubClass();
?>

划重点

  • 如果子类未定义构造/析构函数,则会默认调用父类的构造/析构函数。
  • 如果子类定义了构造/析构函数,则不会调用父类的构造/析构函数,可以在子类的构造/析构函数中显示地调用父类构造/析构函数(parent::__construct)。

对象继承

关键字:extends
规则:

  • 子类只能继承父类的public/protected方法和属性
  • PHP中只支持单继承

Trait 特性

PHP 5.4.0 起,实现了一种代码复用的方法,称为 trait(特性),便于在不同层次结构内实现代码复用。不用通过extends关键字继承即可在一个类中使用另一个类的方法。

  • 特性类中可以有自己的方法和属性,也可以用静态方法、抽象方法,但是没有常量属性
  • 当普通类使用特性类的时候,必须实现特性类中的抽象方法
  • 特性可以被扩容,即特性B中可以use 特性A,此时特性B中不用实现特性A中的抽象方法
  • 如果普通类A继承了类B,使用了特性C,A、B、C中有同一个方法名,优先级是:当前类>特性类>基类,即特性类方法覆盖基类方法,当前类方法覆盖特性类方法
  • 如果普通类使用了多个特性类,当特性类中方法有重名是,需要用关键字“insteadof”设置优先级或者用“as”关键字重命名

示例


trait ezcReflectionReturnInfo {
    function getReturnType() { /*1*/ }
    function getReturnDescription() { /*2*/ }
}

class ezcReflectionMethod extends ReflectionMethod {
    use ezcReflectionReturnInfo;
    /* ezcReflectionMethod中可以使用 ezcReflectionReturnInfo中的方法*/
}

class ezcReflectionFunction extends ReflectionFunction {
    use ezcReflectionReturnInfo;
    /* ... */
}
?>
trait Win
{
	public function exec(){
		echo "Win exec";
	}
}

trait Mac
{
	public function exec(){
		echo "Mac exec";
	}
}

trait Config
{
	public function filename(){
		echo "php.ini";
	}
}

class Php{
	use Config {
		Config::filename as configFilename;
	}
	use Win,Mac {
		Win::exec insteadof Mac;
	}
}

范围解析操作符(::)

用于访问类的静态成员,常量类型,还可以用于在子类中显示访问父类中被子类覆盖的属性和方法(包括构造方法和析构方法)。


class MyClass {
    const CONST_VALUE = 'A constant value';

    protected function myFunc() {
        echo "MyClass::myFunc()\n";
    }
}

class OtherClass extends MyClass
{
    public static $my_static = 'static var';

    public static function doubleColon() {
        echo parent::CONST_VALUE . "\n"; //访问父类的常量
        echo self::$my_static . "\n";	 //访问自己的静态属性
    }

    // 覆盖了父类的定义
    public function myFunc()
    {
        parent::myFunc();	// 访问父类中被覆盖的方法
        echo "OtherClass::myFunc()\n";
    }
}

//在类外部访问常量成员
 echo MyClass::CONST_VALUE;

//在类外部访问静态方法
OtherClass::doubleColon();

$class = new OtherClass();
//OtherClass中的myFunc()
$class->myFunc();

static关键字

  • 可以修饰类的属性和方法以及普遍变量,但不能修饰类和函数
  • 静态方法是以类作为作用域的函数,静态变量是以类作为作用域的变量。因此,在静态方法中,只能调用静态变量,而不能调用普通变量,因为普通变量属于一个具体的某个对象。而普通方法则可以调用静态变量。
  • 静态方法和静态变量创建后始终使用同一块内存,不属于某个具体的对象。
  • 在类的内部可以用self:: 访问static方法或属性,在类外部需要用ClassName::来访问。


class Test {
    public static $a = 1;
    public $b = 1;

    public function __construct() {
        ++ self::$a;
        ++ $this->b;
    }

    public function getA() {
        echo self::$a;
    }

    public function getB() {
        echo $this->b;
    }
}

$test1 = new Test();
$test1->getA(); //2
$test1->getB(); //2

$test2 = new Test();
$test2->getA(); //3
$test2->getB(); //2

全局变量、静态全局变量、静态局部变量和局部变量的区别:

变量可以分为:全局变量、静态全局变量、静态局部变量和局部变量。
按存储区域分:全局变量、静态全局变量和静态局部变量都存放在内存的静态
存储区域,局部变量存放在内存的栈区。
按作用域分:全局变量在整个工程文件内都有效;静态全局变量只在定义它的
文件内有效;静态局部变量只在定义它的函数内有效,只是程序仅分配一次内存,
函数返回后,该变量不会消失;局部变量在定义它的函数内有效,但是函数返回
后失效。

abstract 抽象类

  • 抽象类不能实例化
  • 抽象类至少包含一个抽象方法,可以包含任意个普遍属性和方法
  • 抽象类的每个子类必须实现抽象类中的每个方法,除非子类把它自己也申明为抽象方法
  • 普通类继承抽象类,实现抽象类中的方法时,参数列表可不同
  • 抽象类中的抽象方法可以为protected和public类型

示例1:


abstract class AbstractClass
{
	//抽象方法
    abstract protected function getValue();
    abstract protected function prefixValue($prefix);

    //普通方法
    public function printOut() {
        print $this->getValue() . "\n";
    }
}

class ConcreteClass1 extends AbstractClass
{
    protected function getValue() {
        return "ConcreteClass1";
    }

    public function prefixValue($prefix) {
        return "{$prefix}ConcreteClass1";
    }
}

class ConcreteClass2 extends AbstractClass
{
    public function getValue() {
        return "ConcreteClass2";
    }

    public function prefixValue($prefix) {
        return "{$prefix}ConcreteClass2";
    }
}

$class1 = new ConcreteClass1;
$class1->printOut();  //ConcreteClass1
echo $class1->prefixValue('FOO_') ."\n";  //FOO_ConcreteClass1

$class2 = new ConcreteClass2;
$class2->printOut();  //ConcreteClass2
echo $class2->prefixValue('FOO_') ."\n";  //FOO_ConcreteClass2

示例2:子类实现的父类抽象方法参数可不同


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";  //Mr. Pacman
echo $class->prefixName("Pacwoman"), "\n";  //Mrs. Pacwoman

interface 接口

关键字:interface(定义接口)、implements(实现接口)、extends(继承接口)

实现接口的类必须实现接口类中的所有接口,除非它自己定义为abstract类。

  • 接口可以多继承,但所继承的接口中的方法不能重名
  • 普通类可以实现一个或多个接口
  • 接口类定义的方法必须声明为public
  • 接口中的常量不能被子类或子接口覆盖(普通类中的常量可以被覆盖)
  • 普通类实现接口中的方法时,参数要和接口方法定义相同。(不同于继承抽象类)。

示例1:



interface Test {
    public function f1();
    public function f2();
}

abstract class AbTest implements Test {
    public function f1() {
        echo 1;
    }

    abstract public function f3();
}

class SubTest extends AbTest {
    public function f1() {
        echo 2;
    }

    public function f2() {

    }

    public function f3() {

    }
}
$test = new SubTest();
$test->f1();  //2

示例2:可以继承多个接口


interface a
{
    public function foo();
}

interface b
{
    public function bar();
}

interface c extends a, b
{
    public function baz();
}

class d implements c
{
    public function foo()
    {
    }

    public function bar()
    {
    }

    public function baz()
    {
    }
}

final关键字

  • final关键字只能用来修饰类和方法
  • final定义的类不能被继承
  • final标记的方法不能被子类覆盖(重载)

匿名类

从PHP7开始支持匿名类

$obj = new class {
	public function b($a) {
		return $a;
	}
}

echo $obj->b('a');

类方法覆盖

子类覆盖父类方法的原则

  • 除构造函数之外,其他函数在覆盖父类方法时,函数的参数列表必须相同。
  • 构造函数被覆盖后,实例化子类时并不会自动调用父类构造方法。
  • 如果父类要禁止方法被子类覆盖,可以使用final来声明方法,这时如果子类仍要覆盖父类方法,将会出错。

类的属性重定义

  • 如果子类重定义了父类的public和protected属性,则在子类中,父类的该属性将无法访问。父类方法被覆盖则不同,覆盖后,在子类中依旧可以访问。
  • 如果通过子类对象调用父类方法,那么该父类方法在访问属性时,对于重定义了的同名属性,public和protected的属性将访问到子类的属性,private属性将访问到父类的属性。即父类的public和protected属性可以被重定义,而private并未被重定义。但是,虽然是子类调用的父类中的该方法,该方法中的this还是指向父类自己。
  • 重定义父类同名属性时,属性的可访问性可以变得更开放或者同级,但不能更严格,否则程序运行错误。(即父类属性为protected,子类重定义该属性时,只能申明为public或protected)。

class MyClass
{
    public $a = 1;
    protected $b = 2;
    private $c = 3;
    public function f1() {
        echo "MyClass f1".'
'
; echo "a->$this->a; b->$this->b; c->:$this->c;".'
'
; } protected function f2() { echo "MyClass f2".'
'
; echo "a->$this->a; b->$this->b; c->:$this->c;".'
'
; } private function f3() { echo "MyClass f3\n".'
'
; } } class MySubClass extends MyClass { public $b = 22; public $c = 33; public function f1() { echo "MySubClass f1\n".'
'
; //首先在自己的类变量中找,没找到则访问父类的变量(可以访问的前提下,否则返回空) echo "a->$this->a; b->$this->b; c->:$this->c;".'
'
; //此时父类f1中的this指向的是MySubClass类 parent::f1(); //此时父类f1中的this指向的是MySubClass类 $this->f2(); } // 父类的f3()是私有的,这里的定义与父类无关 public function f3() { echo "MySubClass f3\n".'
'
; } } $b = new MySubClass; $b->f1(); echo "\n".'
'
; /* MySubClass f1 $a:1; $b:22; $c:33; MyClass f1 $a:1; $b:22; $c:3; MyClass f2 $a:1; $b:22; $c:3; */ $b->f3();echo "\n"; /* MySubClass f3 */

重载

PHP中的重载与其它绝大多数面向对象语言不同。传统的重载是用于提供多个同名的类方法,但各方法的参数类型和个数不同。

PHP中的重载是指动态地“创建”类属性和方法。通过魔术方法(magic methods)来实现。当调用当前环境下未定义或不可见的类属性或方法时,重载方法会被调用。

所有的重载方法都必须被声明为 public。


/**
 * 魔术方法
 * __get() 访问不可访问的属性或方法(未定义或不可见的类属性或方法)时被调用
 * __set() 给不可访问的属性(未定义或不可见的类属性)赋值时被调用
*/

class ParentTest {
    public function getAddress() {
        return "
"
."this is address"; } } class Person extends ParentTest{ //访问不可访问的属性时被调用 function __get($propety) { $method = "get{$propety}"; if(method_exists($this, $method)) { return $this->$method(); } } private function getName() { return "
"
."this is name"; } function getAge() { return "
"
."this is age"; } } $test = new Person(); echo $test->name; // "this is name" echo $test->address; // "this is address"

遍历对象

例如用 foreach 语句遍历对象,默认情况下,所有可见属性都将被用于遍历。


class MyClass
{
    public $var1 = 'value 1';
    public $var2 = 'value 2';
    public $var3 = 'value 3';

    protected $protected = 'protected var';
    private   $private   = 'private var';

    function iterateVisible() {
       echo "MyClass::iterateVisible:\n";
       foreach($this as $key => $value) {
           print "$key => $value\n";
       }
    }
}

$class = new MyClass();

foreach($class as $key => $value) {
    print "$key => $value\n";
}
echo "\n";

$class->iterateVisible();

/*
var1 => value 1
var2 => value 2
var3 => value 3

MyClass::iterateVisible:
var1 => value 1
var2 => value 2
var3 => value 3
protected => protected var
private => private var
*/

对象复制

用等号复制对象,并没有为等号左值b开辟新的空间,只是让其指向(引用)了等号右值a所指向的空间。通过b修改对象的某个属性,当利用a访问该属性时,发现这个值被修改了。这称为浅拷贝。
可以利用clone关键字来完成深拷贝,如果实现了__clone()方法,该方法将被调用。


class Test
{
    public $num = "test";

    public function __construct() {
        echo "call __construct";
    }

    public function __clone() {
        echo "call __clone";
    }
}


$test1 = new Test();
$test2 = $test1;

$test2->num = "test2";

echo $test1->num;

$other1 = new Test();
$other2 = clone $other1;

$other2->num = "other2";
echo $other1->num;

/*
call __construct
test2
call __construct
call __clone
test
*/

对象比较

  • == 如果两个对象的属性和属性值 都相等,而且两个对象是同一个类的实例,那么这两个对象变量相等。
  • === 这两个对象变量一定要指向某个类的同一个实例(即同一个对象)

function bool2str($bool)
{
    if ($bool === false) {
        return 'FALSE';
    } else {
        return 'TRUE';
    }
}

function compareObjects(&$o1, &$o2)
{
    echo 'o1 == o2 : ' . bool2str($o1 == $o2) . "\n";
    echo 'o1 != o2 : ' . bool2str($o1 != $o2) . "\n";
    echo 'o1 === o2 : ' . bool2str($o1 === $o2) . "\n";
    echo 'o1 !== o2 : ' . bool2str($o1 !== $o2) . "\n";
}

class Flag
{
    public $flag;

    function Flag($flag = true) {
        $this->flag = $flag;
    }
}

class OtherFlag
{
    public $flag;

    function OtherFlag($flag = true) {
        $this->flag = $flag;
    }
}

$o = new Flag();
$p = new Flag();
$q = $o;
$r = new OtherFlag();

echo "Two instances of the same class\n";
compareObjects($o, $p);

echo "\nTwo references to the same instance\n";
compareObjects($o, $q);

echo "\nInstances of two different classes\n";
compareObjects($o, $r);

/*
Two instances of the same class
o1 == o2 : TRUE
o1 != o2 : FALSE
o1 === o2 : FALSE
o1 !== o2 : TRUE

Two references to the same instance
o1 == o2 : TRUE
o1 != o2 : FALSE
o1 === o2 : TRUE
o1 !== o2 : FALSE

Instances of two different classes
o1 == o2 : FALSE
o1 != o2 : TRUE
o1 === o2 : FALSE
o1 !== o2 : TRUE
*/

你可能感兴趣的:(#,PHP源码学习)