ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# PHP 中的面向对象编程 II > 原文: [https://zetcode.com/lang/php/oopii/](https://zetcode.com/lang/php/oopii/) 在 PHP 教程的这一章中,我们将继续描述 PHP 中的 OOP。 ## PHP `static`关键字 我们可以将类属性和方法声明为`static`。 `static`属性和方法不属于该类的实例。 他们属于阶级本身。 可通过范围解析运算符`::`访问它们。 `staticmethod.php` ```php <?php class Sys { public static function println($string) { echo "$string\n"; } } Sys::println("PHP"); Sys::println("PERL"); Sys::println("Python"); Sys::println("Pike"); ``` 在上面的 PHP 脚本中,我们有一个静态的`println()`方法。 它打印一个字符串并开始新的一行。 此示例受 Java 语言启发。 ```php Sys::println("PHP"); ``` 我们不需要对象来调用`println()`方法。 我们通过指定类名称,后跟双冒号运算符和方法名称来调用`static`方法。 ```php $ php static1.php PHP PERL Python Pike ``` 这是脚本的输出。 `staticvariable.php` ```php <?php class Math { public static $PI = 3.14159265359; } echo Math::$PI . "\n"; ``` 现在,我们有一个带有`static`变量的示例。 ```php echo Math::$PI . "\n"; ``` 我们通过指定类名,范围解析运算符和变量名来访问变量。 ## PHP `final`关键字 最终方法不能被覆盖,最终类不能被扩展。 `final`关键字取决于应用的设计。 某些类不应扩展,某些方法不应重写。 此行为由`final`关键字强制执行。 `finalmethod.php` ```php <?php class Base { final function say() { echo "Base class"; } } class Derived extends Base { function say() { echo "Derived class"; } } ``` 该 PHP 脚本无法编译。 我们收到一个错误“无法覆盖最终方法`Base::say()`”。 `finalclass.php` ```php <?php final class Math { static function getPI() { return 3.141592; } } class DerivedMath extends Math { function say() { echo "DerivedMath class"; } } ``` 在先前的 PHP 脚本中,我们有一个原型基础`Math`类。 该类的唯一目的是为程序员提供一些有用的方法和常量。 (出于简单起见,在我们的例子中,我们只有一种方法。)它不是为了扩展而创建的。 为了防止不知情的其他程序员从此类中派生,创建者创建了`final`类。 如果您尝试运行此 PHP 脚本,则会出现以下错误:“致命错误:类`DerivedMath`可能不会从最终类(`Math`)继承”。 ## PHP 深拷贝与浅拷贝 数据复制是编程中的重要任务。 对象是 OOP 中的复合数据类型。 对象中的成员字段可以按值或按引用存储。 可以以两种方式执行复制。 浅表副本将所有值和引用复制到新实例中。 引用所指向的数据不会被复制; 仅指针被复制。 新的引用指向原始对象。 对引用成员的任何更改都会影响两个对象。 深拷贝将所有值复制到新实例中。 如果成员存储为引用,则深层副本将对正在引用的数据执行深层副本。 创建引用对象的新副本,并存储指向新创建的对象的指针。 对这些引用对象的任何更改都不会影响该对象的其他副本。 深拷贝是完全复制的对象。 在 PHP 中,我们有一个`copy`关键字,默认情况下会执行浅表复制。 它调用对象的`__clone()`方法。 我们可以实现创建自定义深层副本的方法。 在 PHP 中,所有对象都是通过引用分配的。 接下来的两个示例对对象执行浅复制和深复制。 `shallowcopy.php` ```php <?php class Object { public $id; public $size; public $color; function __construct($id, $size, $color) { $this->id = $id; $this->size = $size; $this->color = $color; } } class Color { public $red; public $green; public $blue; function __construct($red, $green, $blue) { $this->red = $red; $this->green = $green; $this->blue = $blue; } } $color = new Color(23, 42, 223); $object1 = new Object(23, "small", $color); $object2 = clone $object1; $object2->id++; $object2->color->red = 255; $object2->size = "big"; print_r($object1); print_r($object2); ``` 在上面的 PHP 脚本中,我们定义了两个自定义对象:`Object`和`Color`。 `Object`对象将具有对`Color`对象的引用。 ```php $color = new Color(23, 42, 223); ``` 我们创建`Color`对象的实例。 ```php $object1 = new Object(23, "small", $color); ``` 创建对象对象的实例。 它将`Color`对象的实例传递给其构造器。 ```php $object2 = clone $object1; ``` 我们执行`Object`对象的浅表副本。 ```php $object2->id++; $object2->color->red = 255; $object2->size = "big"; ``` 在这里,我们修改克隆对象的成员字段。 我们增加 id,更改颜色对象的红色部分,并将大小更改为`big`。 ```php print_r($object1); print_r($object2); ``` 我们使用`print_r()`函数比较结果。 ```php $ php shallowcopy.php Object Object ( [id] => 23 [size] => small [color] => Color Object ( [red] => 255 [green] => 42 [blue] => 223 ) ) Object Object ( [id] => 24 [size] => big [color] => Color Object ( [red] => 255 [green] => 42 [blue] => 223 ) ) ``` 我们可以看到 ID 不同:23 与 24。大小不同:`small`与`big`。 但是,这两个实例的颜色对象的红色部分相同:255。更改克隆对象的成员值不会影响原始对象。 更改引用对象的成员也影响了原始对象。 换句话说,两个对象都引用内存中的同一颜色对象。 要更改此行为,我们接下来将做一个深层复制。 `deepcopy.php` ```php <?php class Object { public $id; public $size; public $color; function __construct($id, $size, $color) { $this->id = $id; $this->size = $size; $this->color = $color; } function __clone() { $red = $this->color->red; $green = $this->color->green; $blue = $this->color->blue; $this->color = new Color($red, $green, $blue); } } class Color { public $red; public $green; public $blue; function __construct($red, $green, $blue) { $this->red = $red; $this->green = $green; $this->blue = $blue; } } $color = new Color(23, 42, 223); $object1 = new Object(23, "small", $color); $object2 = clone $object1; $object2->id++; $object2->color->red = 255; $object2->size = "big"; print_r($object1); print_r($object2); ``` 在此 PHP 脚本中,我们实现了`__clone()`方法。 ```php function __clone() { $red = $this->color->red; $green = $this->color->green; $blue = $this->color->blue; $this->color = new Color($red, $green, $blue); } ``` 在`__clone()`方法内部,我们复制红色,绿色和蓝色成员字段并创建一个新的`Color`对象。 现在,`$color`字段指向另一个`Color`对象。 ```php $ php deepcopy.php Object Object ( [id] => 23 [size] => small [color] => Color Object ( [red] => 23 [green] => 42 [blue] => 223 ) ) Object Object ( [id] => 24 [size] => big [color] => Color Object ( [red] => 255 [green] => 42 [blue] => 223 ) ) ``` 现在,引用的`Color`对象的红色部分不相同。 原始对象保留了其先前的 23 值。 ## PHP 异常 异常是为处理异常的发生而设计的,这些特殊情况会改变程序执行的正常流程。 引发或引发异常并引发异常。 在执行应用期间,许多事情可能出错。 磁盘可能已满,我们无法保存文件。 互联网连接可能断开,我们的应用尝试连接到站点。 所有这些都可能导致我们的应用崩溃。 为避免发生这种情况,我们必须应对可能发生的所有可能的错误。 为此,我们可以使用异常处理。 从 PHP 5 开始,可以使用异常。大多数 PHP 错误仍然使用旧的错误报告,而不是异常。 使用`set_error_handler()`,我们可以解决此问题。 `zerodiv.php` ```php <?php set_error_handler("error_handler"); function error_handler($errno, $errstring, $errfile, $line, $trace) { throw new ErrorException($errstring, $errno, 0, $errfile, $line); } try { $a = 0; $b = 32; $c = $b / $a; } catch(ErrorException $e) { echo "Error occurred\n"; echo $e->getMessage(), "\n"; } ``` 在上面的 PHP 脚本中,我们有意将数字除以零。 这会导致错误。 该错误也不是异常,并且不会被`catch`关键字捕获。 ```php set_error_handler("error_handler"); ``` `set_error_handler()`函数设置用户定义的错误处理器函数。 ```php function error_handler($errno, $errstring, $errfile, $line, $trace) { throw new ErrorException($errstring, $errno, 0, $errfile, $line); } ``` 在`set_error_handler()`函数内部,我们抛出了`ErrorException`。 稍后,此异常由`catch`关键字捕获。 ```php try { $a = 0; $b = 32; $c = $b / $a; } ``` 我们正在检查错误的代码放在`try`关键字之后的块中。 ```php } catch(Exception $e) { echo $e->getMessage(); } ``` `catch`关键字用于捕获异常。 要了解更多信息,我们在异常对象上调用`getMessage()`方法。 ```php $ php zerodiv.php Error occurred Division by zero ``` 这是我们的 PHP 脚本的输出。 `Exception`是所有异常的基类。 我们可以创建派生自该基类的异常。 `myexception.php` ```php <?php define("LIMIT", 333); class BigValueException extends Exception { public function __construct($message) { parent::__construct($message); } } $a = 34325; try { if ($a > LIMIT) { throw new BigValueException("Exceeded the maximum value allowed\n"); } } catch (BigValueException $e) { echo $e->getMessage(); } ``` 假设我们处于无法处理大量数字的情况。 ```php define("LIMIT", 333); ``` 大于此常量的数字在我们的 PHP 脚本中被视为`big`。 ```php class BigValueException extends Exception { ``` 我们有一个`BigValueException`类。 此类通过`extends`关键字从`Exception`类派生。 ```php public function __construct($message) { parent::__construct($message); } ``` 在构造器内部,我们称为父级的构造器。 ```php if ($a > LIMIT) { throw new BigValueException("Exceeded the maximum value allowed\n"); } ``` 如果该值大于限制,则抛出自定义异常。 我们给异常消息`"Exceeded the maximum value allowed\n"`。 ```php } catch (BigValueException $e) { echo $e->getMessage(); } ``` 我们捕获到异常并将其消息打印到控制台。 ## PHP 构造器重载 PHP 不支持直接的构造器重载。 换句话说,每个类只能定义一个构造器。 许多已经知道 Java 或 C# 语言的程序员都在寻找 PHP 中的类似功能。 我们有两种方法可以处理此问题。 第一种解决方案基于`func_get_args()`函数。 第二种解决方案使用工厂模式。 `constructors.php` ```php <?php class Book { private $author = "not specified"; private $title = "not specified"; private $year = "not specified"; public function __construct() { $args = func_get_args(); foreach(["title", "author", "year"] as $item) { if(empty($args)) { break; } $this->$item = array_shift($args); } } public function __toString() { return "Author: $this->author\nTitle: $this->title\nPublished: $this->year\n\n"; } } $book1 = new Book("Stephen Prata", "C Primer Plus"); echo $book1; $book2 = new Book("Joshua Bloch", "Effective Java", 2008); echo $book2; ``` 在上面的脚本中,我们有一个`Book`类。 我们使用 2 和 3 参数实例化该类。 ```php private $author = "not specified"; private $title = "not specified"; private $year = "not specified"; ``` 我们定义了三个成员字段。 它们的初始值为`"not specified"`。 ```php $args = func_get_args(); ``` `func_get_args()`函数返回一个包含函数的参数列表的数组。 这样的想法是:构造器中的代码是动态的; 它取决于传递给它的参数。 ```php foreach(["title", "author", "year"] as $item) { ``` 我们使用`foreach`关键字浏览所有成员字段。 ```php $this->$item = array_shift($args); ``` 构造器中最基本的任务之一是初始化类的成员字段。 这是通过上面的代码行完成的。 `array_shift()`函数从数组中删除第一项并返回它。 ```php $book1 = new Book("Stephen Prata", "C Primer Plus"); ... $book2 = new Book("Joshua Bloch", "Effective Java", 2008); ``` 我们有两个不同的构造器。 第一个带有 2 个参数,第二个带有 3 个参数。 ```php $ php constructors.php Author: C Primer Plus Title: Stephen Prata Published: not specified Author: Effective Java Title: Joshua Bloch Published: 2008 ``` 这是脚本的结果。 下一个代码示例使用工厂模式模拟构造器重载。 它是 OOP 中的创建模式之一。 该模式创建对象时未指定要创建的对象的确切类。 更一般地,术语工厂方法通常用于指代主要目的是创建对象的任何方法。 `factory.php` ```php <?php class Cat { private $name = "unspecified"; private $age = "unspecified"; public static function withName($name) { $cat = new Cat(); $cat->name = $name; return $cat; } public static function withAge($age) { $cat = new Cat(); $cat->age = $age; return $cat; } public static function fullCat($name, $age) { $cat = new Cat(); $cat->name = $name; $cat->age = $age; return $cat; } public function __toString() { return "Name: $this->name, Age: $this->age\n"; } } $cici = Cat::withName("Cici"); echo $cici; $missy = Cat::withAge(6); echo $missy; $lucky = Cat::fullCat("Lucky", 4); echo $lucky; ``` 上面的 PHP 脚本中有一个`Cat`工厂类。 它具有三种不同的静态函数。 它们每个都返回一个特定的`Cat`对象。 ```php private $name = "unspecified"; private $age = "unspecified"; ``` 我们有两个成员字段。 它们的初始值为`"not specified"`。 ```php public static function withName($name) { $cat = new Cat(); $cat->name = $name; return $cat; } ``` 这是静态的`withName()`函数。 此函数创建`Cat`类的实例。 它设置`name`成员字段并返回对象。 ```php $cici = Cat::withName("Cici"); echo $cici; ``` 我们使用其中一种工厂方法创建猫的实例。 我们回荡对象。 即调用该类的`__toString()`方法。 ```php $ php factory.php Name: Cici, Age: unspecified Name: unspecified, Age: 6 Name: Lucky, Age: 4 ``` 脚本的输出。 在 PHP 教程的这一部分中,我们继续讨论 PHP 中的面向对象编程。