类与对象 - PHP手册笔记

原文:类与对象 - PHP手册笔记

基本概念

PHP对待对象的方式与引用和句柄相同,即每个变量都持有对象的引用,而不是整个对象的拷贝。

当创建新对象时,该对象总是被赋值,除非该对象定义了构造函数并且在出错时抛出了一个异常。类应在被实例化之前定义。

创建对象时,如果该类属于一个名字空间,则必须使用其完整名称。

在类定义内部,可以用new selfnew parent创建对象。

<?php
$instance = new stdClass();
$assigned = $instance;
$reference = & $instance;
$instance->var = '$assigned will have this value.';
$instance = null;
var_dump($instance);
var_dump($reference);
var_dump($assigned);

这段代码的输出如下,这是为什么呢?

null
null
object(stdClass)[1]
  public 'var' => string '$assigned will have this value.' (length=31)

PHP 5.3引进了两个新方法来创建一个对象的实例,可以使用下面的方法创建实例。

<?php
class Test {
	static public function getNew() {
		return new static;
	}
}
class Child extends Test {}
$obj1 = new Test();
$obj2 = new $obj1;
var_dump($obj1 !== $obj2);  // true
$obj3 = Test::getNew();
var_dump($obj3 instanceof Test);  // true
$obj4 = Child::getNew();
var_dump($obj4 instanceof Child);  // true
var_dump($obj1 == $obj2);  // true

PHP不支持多重继承,被继承的方法和属性可以通过同样的名字重新声明被覆盖,注意参数必须保持一致,当然构造函数除外。但是如果父类定义方法时使用了final,则该方法不可被覆盖。可以通过parent::来访问被覆盖的方法和属性,parent::只能访问父类中的常量const,不能访问变量。

<?php
class A {
	private $name = 'A';
	const conname = 'A';
	public function getName() {
		return $this->name;
	}
}
class B extends A {
	private $name = 'B';
	const conname = 'B';
	public function getName() {
		return $this->name;
	}
	public function getParent() {
		return parent::conname;
	}
}
class C extends B {
	private $name = 'C';
	const conname = 'C';
	public function getName() {
		return $this->name;
	}
	public function getParent() {
		return parent::conname;
	}
}
$a = new A;
var_dump($a->getName());  // A
$b = new B;
var_dump($b->getName());  // B
var_dump($b->getParent());  // A
$c = new C;
var_dump($c->getName());  // C
var_dump($c->getParent());  // B

自PHP 5.5起,关键词class也可用于类名的解析。使用ClassName::class你可以获取一个字符串,包含了类ClassName的完全限定名称。

<?php
namespace NS {
	class ClassName {}
	echo ClassName::class;  // NS\ClassName
}

属性

属性,也就是类的变量成员。属性中的变量可以初始化,但是初始化的值必须是常数。这里的常数是指 PHP 脚本在编译阶段时就可以得到其值,而不依赖于运行时的信息才能求值。

在类的成员方法里,访问非静态属性使用$this->property,访问静态属性使用self::$property。静态属性声明时使用static关键字。

类常量

在定义常量时不需要$符号和访问控制关键字。

接口(interface)中也可以定义常量。

自动加载类

写面向对象的应用程序时,通常对每个类的定义简历一个PHP源文件。当某个文件需要调用这些类时,需要在文件开头写一个长长的包含文件列表。其实,并不需要这样,可以定义一个__autoload()函数,它会在试图使用尚未被定义的类时自动调用。

手册Tip说,spl_autoload_register()提供了一种更加灵活的方式来实现类的自动加载,这个后面再看。

自动加载不可用于PHP的CLI交互模式,也就是命令行模式。

用户输入中可能存在危险字符,起码要在__autoload()时验证下输入。

可以通过下面的方式自动加载类。

<?php
function __autoload($class_name) {
	require_once $class_name.'.php';
}
$obj1 = new MyClass1();
$obj2 = new MyClass2();

对于异常处理,后面再看。

构造函数和析构函数

PHP 5允许开发者在一个类中定义一个方法作为构造函数,构造函数也不支持重载。

如果子类中定义了构造函数,则不会隐式调用父类的构造函数,否则会如同一个普通类方法那样从父类继承(前提是未被定义为private)。要执行父类的构造函数,需要在子类构造函数中调用parent::__construct()

与其它方法不同,当__construct()与父类__construct()具有不同参数时,可以覆盖。

自PHP 5.3.3起,在命名空间中,与类名同名的方法不再作为构造函数。

析构函数会在某个对象的所有引用都被删除或者对象被显示销毁时执行。析构函数即使在使用exit()终止脚本运行时也会被调用。

试图在析构函数中抛出异常,将会导致致命错误。

访问控制

类属性必须定义为公有、受保护、私有之一,不能省略关键字。如果类中方法没有设置访问控制的关键字,则该方法默认为公有。

同一个类的对象,即使不是同一个实例,也可以互相访问对方的私有与保护成员。示例程序如下。

<?php
Class Test {
	private $foo;
	public function __construct($foo) {
		$this->foo = $foo;
	}
	private function bar() {
		echo 'Accessed the private method.';
	}
	public function baz(Test $other) {
		$other->foo = 'hello';
		var_dump($other->foo);
		$other->bar();
	}
}
$test = new Test('test');
$test->baz(new Test('other'));

对象继承

如果一个类扩展了另一个,则父类必须在子类前被声明。

范围解析操作符

范围解析操作符,简单地说就是一对冒号,可以用于访问静态成员、类常量,还可以用于调用父类中的属性和方法。

当在类定义之外引用这些项目时,要使用类名。

static

使用static关键字可以用来定义静态方法和属性,也可用于定义静态变量以及后期静态绑定。声明类属性或方法为静态,就可以不实例化类而直接访问。

静态属性不能通过一个类已实例化的对象来访问,但静态方法可以。

如果没有指定访问控制,属性和方法默认为公有。

用静态方法调用一个非静态方法会导致一个E_STRICT级别的错误。

抽象类

PHP 5支持抽象类和抽象方法。类中如果有一个抽象方法,那这个类必须被声明为抽象的。

抽象类不能被实例化。抽象方法只是声明了其调用方式(参数),不能定义其具体的功能实现。继承抽象类时,子类必须定义父类中的所有抽象方法,且这些方法的访问控制必须和父类一样活更宽松。

方法的调用方式必须匹配。但是,子类定义了一个可选参数,而父类抽象方法的声明里没有,则两者的声明并无冲突。这也试用与PHP 5.4起的构造函数。可以在子类中定义父类签名中不存在的可选参数。

<?php
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');
echo $class->prefixName('Pacwoman');

对象接口

听说过接口,一直没用过。使用接口,可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容,也就是说接口中定义的所有方法都是空的。接口中定义的所有方法都必须是公有的,这是接口的特性。

接口也可以继承多个接口,用逗号分隔,使用extends操作符。类中必须实现接口中定义的所有方法,否则会报错。要实现一个接口,使用implements操作符。类可以实现多个接口,用逗号分隔。实现多个接口时,接口中的方法不能有重名。类要实现接口,必须使用和接口中所定义的方法完全一致的方式。

接口中也可定义常量。接口常量和类常量的使用完全相同,但是不能被子类或子接口覆盖。

traits

从PHP 5.4.0开始,可以使用traits实现代码复用。Traits 是一种为类似 PHP 的单继承语言而准备的代码复用机制。Trait 不能通过它自身来实例化。它为传统继承增加了水平特性的组合。

优先顺序是来自当前类的成员覆盖了 trait 的方法,而 trait 则覆盖了被继承的方法。

通过逗号分隔,在 use 声明列出多个 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;
		B::bigTalk as talk;
	}
}
$t = new Talker();
$t->smallTalk();  // b
$t->bigTalk();  // A
$t->talk();  // B

使用as操作符还可以用来调整方法的访问控制,或者给方法一个改变了访问控制的别名,原版方法的访问控制规则没有改变。

<?php
trait HelloWorld {
	public function sayHello() {
		echo 'Hello World.';
	}
}
class MyClass1 {
	use HelloWorld {
		sayHello as protected;
	}
}
class MyClass2 {
	use HelloWorld {
		sayHello as private myPrivateHello;
	}
}

就像类能够使用trait那样,多个trait能够组合为一个trait

为了对使用的类施加强制要求,trait 支持抽象方法的使用。

<?php
trait Hello {
	public function sayHelloWorld() {
		echo 'Hello ' . $this->getWorld();
	}
	abstract public function getWorld();
}
class MyHelloWorld {
	private $world;
	use Hello;
	public function getWorld() {
		return $this->world;
	}
	public function setWorld($val) {
		$this->world = $val;
	}
}
$c = new MyHelloWorld;
$c->setWorld('world');
$c->sayHelloWorld();

如果trait定义了一个属性,那类将不能定义同样名称的属性,否则会产生错误。

重载

PHP提供的重载是指动态地创建类属性和方法,与其它绝大多数面向对象语言不同。通过魔术方法来实现。当使用不可访问的属性或方法时,重载方法会被调用。所有的重载方法都必须被声明为public

使用__get()__set()__isset()__unset()进行属性重载,示例如下。

<?php
class PropertyTest {
	private $data = array();
	public $declared = 1;
	private $hidden = 2;
	public function __set($name, $value) {
		echo "Setting $name to $value. " . '<br>';
		$this->data[$name] = $value;
	}
	public function __get($name) {
		echo "Getting $name. <br>";
		if(array_key_exists($name, $this->data)) {
			return $this->data[$name];
		}
		return null;
	}
	public function __isset($name) {
		echo "Is $name set? <br>";
		return isset($this->data[$name]);
	}
	public function __unset($name) {
		echo "Unsetting $name. <br>";
		unset($this->data[$name]);
	}
}
$obj = new PropertyTest;
$obj->a = 1;
var_dump($obj->a);
var_dump(isset($obj->a));
unset($obj->a);
var_dump(isset($obj->a));
var_dump($obj->declared);
var_dump($obj->hidden);

输出结果如下:

Setting a to 1.
Getting a.
int 1
Is a set?
boolean true
Unsetting a.
Is a set?
boolean false
int 1
Getting hidden.
null

在对象中调用一个不可访问方法时,__call()会被调用。用静态方式中调用一个不可访问方法时,__callStatic()会被调用。参数为调用方法的名称和一个枚举数组,注意区分大小写。

使用__call()__callStatic()对方法重载,示例如下。

<?php
class MethodTest {
	public function __call($name, $arguments) {
		echo "Calling object method $name " .
			implode(', ', $arguments) . '<br>';
	}
	public static function __callStatic($name, $arguments) {
		echo "Calling static method $name " .
			implode(', ', $arguments) . '<br>';
	}
}
$obj = new MethodTest;
$obj->runTest('in object context');
MethodTest::runTest('in static context');

遍历对象

对象可以用过单元列表来遍历,例如用foreach语句。默认所有可见属性都将被用于遍历。

<?php
class MyClass {
	public $var1 = 'value 1';
	public $var2 = 'value 2';
	public $var3 = 'value 3';
	private $var4 = 'value 4';
	protected $var5 = 'value 5';
}
$obj = new MyClass;
foreach($obj as $key => $value) {
	echo "$key => $value <br>";
}

示例程序2实现了Iterator接口的对象遍历,示例程序3通过实现IteratorAggregate来遍历对象。

魔术方法

PHP 将所有以__(两个下划线)开头的类方法保留为魔术方法。定义类方法时,除魔术方法外,建议不要以__为前缀。

前面遇到过的魔术方法有:__construct()__destruct()__call()__callStatic()__get()__set()__isset()__unset()。后面将会介绍:__sleep()__wakeup()__toString()__invoke()__set_state()__clone()__debugInfo()

__sleep__wakeup不清楚具体做什么用的,示例程序中给出了个数据库连接的例子。

__toString方法用于一个类被当成字符串时应怎样回应。此方法必须返回一个字符串,且不能再方法中抛出异常。如果将一个未定义__toString()方法的对象转换为字符串,将产生错误。

当尝试以调用函数的方式调用一个对象时,__invoke()方法会被调用。

当调用var_export()导出类时,__set_state()会被调用。

当调用var_dump()时,__debugInfo会被调用。PHP 5.6新加入,没合适的环境无法测试。

final

果父类中的方法被声明为final,则子类无法覆盖该方法。如果一个类被声明为final,则不能被继承。属性不能被定义为final,只有类和方法才能被定义为final

对象复制

多数情况,我们不需要完全复制一个对象,但有时确实需要。对象复制可以通过clone关键字来完成。这种复制是通过调用对象的__clone()方法实现的,但是对象中的__clone()方法不能被直接调用。

对象比较

比较运算符==为真的条件是:两个对象的属性和属性值都相等,而且两个对象是同一个类的实例。

继承与统一个基类的两个子类的对象不会相等==

<?php
class Base {}
class A extends Base {}
class B extends Base {}
$a = new A;
$b = new B;
var_dump($a == $b);  // false

全等运算符===为真的条件是:两个对象变量一定要指向某个类的同一个实例(即同一个对象)。

类型约束

类型约束是指函数的参数可以指定必须为对象、接口、数组或者callable类型。但是类型约束不能用于标量类型如intstringtraits也不允许。类型约束允许NULL值。

后期静态绑定

后期静态绑定,用于在继承范围内引用静态调用的类。

转发调用,指的是通过以下几种方式进行的静态调用:self::parent::static::以及forward_static_call()

后期静态绑定的工作原理是,存储了上一个非转发调用的类名。

当进行静态方法调用时,该类名即为明确指定的那个;当进行非静态方法调用时,即为该对象所属的类。

使用self::或者__CLASS__对当前类的静态引用,取决于定义当前方法所在的类。

<?php
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
B::who();  // B

static::关键字表示运行时最初调用的类,后期静态绑定就是这样使用。如下面程序所示,也就是说调用test()时引用的类是B而不是A

<?php
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
B::who();  // B

示例2给出的是非静态环境下使用static::

后期静态绑定的解析,会一直到取得一个完全解析了的静态调用为止。另外,如果静态调用使用parent::self::将转发调用信息。

<?php
class A {
	public static function foo() {
		static::who();
	}
	public static function who() {
		echo __CLASS__;
	}
}
class B extends A {
	public static function test() {
		A::foo();
		parent::foo();
		self::foo();
	}
	public static function who() {
		echo __CLASS__;
	}
}
class C extends B {
	public static function who() {
		echo __CLASS__;
	}
}
C::test();  // ACC

那么问题来了,结果为什么是这样的呢?

对象和引用

默认情况下,对象时通过引用传递的。但这种说法不完全正确,其实两个对象变量不是引用的关系,只是他们都保存着同一个标识符的拷贝,这个标识符指向同一个对象的真正内容。

对象序列化

所有PHP里面的值,都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变为原来的值。

序列化一个对象,将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。为了能够unserialize()一个对象,这个对象的类必须已经定义过。在应用程序中序列化对象以便在之后使用,强烈推荐在整个应用程序都包含对象的类的定义。

(全文完)

时间: 2024-10-24 22:11:07

类与对象 - PHP手册笔记的相关文章

黑马程序员---Objective-C基础学习---类、对象、方法相关知识笔记

------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- 类.对象.方法相关知识笔记 Objective-C相对于C语言有了面向对象的特性,但是ObjC又没有其他面向对象语言那么多语法特性,ObjC本身对面向对象进行了精简.下面是一些相关知识笔记. 类定义 成员变量 方法和属性 self关键字 类定义 在C#.Java等其他高级语言中定义一个类是相当简单点的,直接一个关键字class加一对大括号基本就完成了,但是在ObjC中类的定义相对变化比较大.现

javascript学习笔记---ECMAScriptECMAScript 对象----定义类或对象

使用预定义对象只是面向对象语言的能力的一部分,它真正强大之处在于能够创建自己专用的类和对象. ECMAScript 拥有很多创建对象或类的方法. 原始的方式 因为对象的属性可以在对象创建后动态定义(后绑定),类似下面的代码: var oCar = new Object; oCar.color = "blue"; oCar.doors = 4; oCar.mpg = 25; oCar.showColor = function() { alert(this.color); };不过这里有一

0713-----C++Primer听课笔记----------类和对象

1.要求:将一篇文章录入,然后实现查询: a)查询可以输入字符或者字符串 b)将包含它们的单词输出 c)允许重复 d)如果输入查询有空格,当多个处理 Ex: 输入ro,应该打印出“microsift” 输入”he wr”,则查询两次 1.1  不用类实现 #include <iostream> #include <string> #include <vector> #include <fstream> #include <stdexcept> u

04Objective-C-&gt;02面向对象-&gt;类和对象 --------笔记

这些天遇到了好多事,所以自己信誓旦旦说好的会在一天之内看完的视频,最后却没有做到.每每想到总会有几分歉疚. 昨天终于结束了那段视频,今天可以开始整理笔记了. OC 大概有了自己理解的一套方法,可是在一些细节里面有的时候会钻牛角尖,每次遇到钻牛角尖的问题,我自己都十分明白这是钻牛角尖,这一定是. 而且这是我 学习任何学科的过程中都会有情况,我 自己也清楚知道,但我同时也知道钻牛角是代表我用心去思考了. 不知道怎么回事,看视频的时候我老是会想到高中的自己, 那短短的三年我用了两种态度去面对 在高一,

Java笔记十四.深入理解类和对象(1)

Java是一种完全面向对象的编程语言(C是面向过程).所谓面向对象编程(OOP),即是一种编程风格,它通过给程序中加入扩展语句,把函数"封装"进编程所必需的"对象"中.OOP 达到了软件工程的三个主要目标:重用性.灵活性和扩展性.其实,面向对象就是把一切东西看成一个个对象,比如人,车,面包,等等,然后把这些对象拥有的属性变量,比如年龄,民族,工作地点,变质期,寿命,还有操作这些属性变量的函数(方法)打包成一个类来表示,这个类的一个抽象就是一个对象.在Java程序中,

Java 第七章 类和对象 笔记

一.对象的特征--类的属性 每个对象的每个属性都有特定的值 对象的操作--类的方法 二.封装:对象同时具有属性和方法两项特性.     对象的属性和方法通常被封装在一起,共同体现事物的特性,     二者相辅相成,不能分割. 三.类和对象的区别:     类:抽象的概念,是一个模子,确定了对象的属性和方法.    对象:能够看得见摸得着的实物.具有静态和动态特征. 四.定义类的步骤:     1.定义类名      2.定义类的属性 访问修饰符 属性类型 属性名称=默认值:     3.定义类的

C++笔记二:类和对象

类和对象 1:面向对象基础 2: 定义类和对象 3: 类和对象的使用 4: 构造函数和析构函数 5: this指针和复制构造函数 6: static静态成员 7: const对象和const成员 8: friend友元 ---------------------------------------------------------------------------------------------------------------------- (1)面向对象基础 一.什么是面向对象程序

Java学习笔记--类和对象

1.介绍面向对象的编程 面向对象是现在主流的编程样例,它替代了以前C语言使用时的"结构体",Java是一门面向对象的语言,所以需要熟悉面向对象的概念.面向对象的程序由很多对象组成,每个函数对于用户而言,都有特殊的功能.程序里面的很多对象都可以直接从公共库里面拿来直接用.不用去研究这些功能怎么去实现的.传统的结构的编程由一系列算法步骤来解决问题.一旦这些步骤确定下来,也要同时确定存储数据的方式.也就是数据结构一开始学习的:算法+数据结构=程序. 先决定算法,再决定使用什么样的结构来存储数

java类和对象的基础(笔记)

封装: 首先封装可以保护对象,防止用户直接存取对象的内部细节:其次封装也保护了客户端,防止对象实现部分的改变可能产生的副作用,即实现部分的改变不会影响到客户端的改变. 私有代码和数据仅能被对象本身的其他部分访问,不能被该对象外的任何程序部分所访问.当代码或数据是公有的时,虽然它们是定义在对象中的,但程序的其他部分也可以访问. 继承性体现了类之间的是一种(IS-A)关系. 类之间的关系还有组合.关联等. 类的修饰符 : 类的访问修饰符可以是public或者缺省.若类用public修饰,则该类称为公