类和对象:类结构
这一节我们将详细分析 PHP 中类的结构。
类结构
从上一节我们学习到,类定义由关键字class
开头,后跟类名和一对花括号。最简单的类定义如下:
<?php
class Human
{
}
定义一个类我们往往还需要添加各种属性,从某种程度上,类也是一种自定义的数据类型。
假设我们需要为一个图书馆设计其借阅系统,主体有三类:图书、读者和管理员。
本节我们只创建图书类。首先我们设想一下图书馆的图书都有哪些特征:书名、ISBN号、作者、出版社、单价等。现在我们将这些特征加入到图书类,现在图书类看起来应该是这样:
<?php
// 图书类
// 文件:library/Book.php
class Book
{
public $name;
public $isbn;
public $author;
public $publish;
public $price;
}
有朋友可能会疑惑public
是什么意思,public
表示该属性的可见性是公开的。那么什么是可见性?
访问控制
访问控制就是可见性。通过对类属性或者方法前添加关键字来控制其访问范围。任何类属性或者方法都必须定义其可见性,如果没有显式定义,则默认为public
。
关键字public
表示该属性/方法是公有的,可以在任何地方访问。关键字var
也可以用来设置变量为公有,但仅限于设置属性,不能用于方法。为了代码的一致性,始终建议使用public
而不是var
。
除了public
关键字外,还有其他两个关键字表示不同的可见性:
private
:设置属性/方法为“私有”,表示只能被自身所在的类的内部访问protected
:设置属性/方法为“受保护的”,表示可以被自身以及其子类和父类访问
由于图书类的各项信息都不具备保密性,故都设置为public
。
类属性
其中public $name
等语句就是类属性定义,类属性也可以设置默认值。
非静态属性/方法在类内部使用$this
加对象运算符->
后跟属性/方法名进行访问,例如:$this->name
;
而静态属性/方法(属性/方法定义前添加了static
关键字)在类内部使用self::
后跟属性/方法名进行访问。有关静态关键字static
将在后面的章节详细讲解。
现在我们来尝试创建一个图书对象:
$book = new Book();
好像有点不对?我们创建图书对象的目的是给予不同图书不同的属性(这才是图书馆的样子),但是使用var_dump()
方法检查一下 Book 对象,发现$book
所有属性都是NULL
。
那么该怎么传值赋给对象属性呢?
有两种方式。
类方法
第一种,在类内部创建一个赋值方法,在类外部修改属性值的方法通常被命名为set
加属性名,例如:
public function setName($name)
{
$this->name = $name;
}
上述代码传递了变量$name
并赋给了类属性name
,现在我们继续创建一个 Book 对象,并调用该方法:
$book = new Book();
$book->setName('PHP教程');
var_dump($book);
/* 输出:
object(Book)#1 (5) {
["name"]=>
string(9) "PHP教程"
["isbn"]=>
NULL
["author"]=>
NULL
["publish"]=>
NULL
["price"]=>
NULL
}
*/
查看其输出,可以看到 Book 对象的name
属性已经被成功赋值。
构造函数__construct()
第二种方式是使用构造函数。它适合初始化对象。
PHP 允许在类中定义一个方法作为构造函数,每次创建对象时都会调用这个方法。
向Book
类中添加构造函数:
public function __construct($name, $author)
{
$this->name = $name;
$this->author = $author;
}
创建对象时传入两个变量(这里是书名和作者):
$book = new Book('PHP教程', '极速教程');
var_dump($book);
/* 输出:
object(Book)#1 (5) {
["name"]=>
string(9) "PHP教程"
["isbn"]=>
NULL
["author"]=>
string(12) "极速教程"
["publish"]=>
NULL
["price"]=>
NULL
}
*/
运行文件Book.php
并查看结果,可以发现 Book 对象中name
和author
属性被成功赋值。
如果有学习过 Java 的朋友可能会问 PHP 类的构造函数有没有重载,因为在 Java 中可以通过重载构造函数使对象拥有不同的初始化状态。 之前已经讲过,PHP 不直接支持重载。但我们可以通过另外的形式来“实现”重载。
比如说我们有一个这样的需求,添加书籍时只知道书名和作者或者只知道书名,我们可以使用下面的方式来“重载”构造函数:
public function __construct(...$args)
{
$count = count($args);
switch($count) {
case 0:// 默认没有参数输入
break;
case 1: {// 默认输入的唯一一个参数是书名
$this->name = $args[0];
break;
}
case 2: {// 默认输入的两个参数第一个是书名,第二个是作者
$this->name = $args[0];
$this->author = $args[1];
break;
}
}
}
利用函数的可变参数列表,通过switch
结构操作参数。不过该方法仅用于已知参数顺序的情况。现在你可以尝试创建对象时传入不同数量的值了。
除了构造函数用来初始化对象,也有析构函数用作销毁对象。
析构函数__destruct()
PHP脚本会在执行完毕或者关闭时销毁所有变量和引用,而析构函数在脚本关闭时自动调用,主要用于释放资源,不接受也不返回任何值。语法如下:
public function __destruct()
{
// 代码块
}
类常量
一个类中,除了可变化的属性值,还可能有保持不变的值,在 PHP 语法中它叫常量,使用define()
方法定义,但在类中,它被叫做类常量,使用关键字const
定义。例如:
const LIB_NAME = '图书馆名称';
其命名方式与常量命名相同。在类内部访问类常量时使用self
关键字加上范围解析操作符(::),如下:
public function opLibName()
{
echo self::LIB_NAME;
}
在类的外部访问类常量、静态属性和静态方法时,只需要使用类名加范围解析操作符后跟访问的名称,例如:
$lib = Book::LIB_NAME;
echo $lib;// 输出:图书馆名称
范围解析操作符
范围解析操作符由两个冒号组成,它可以访问类的类常量、静态属性和静态方法。在类的内部访问类常量、静态属性和静态方法时需要在冒号前添加self
、parent
或者static
关键字。
这三个关键字仅用于类内部访问,区别如下:
self
:引用self
所在类本身parent
:引用parent
所在类的基类(父类)本身static
:静态延迟绑定,引用实际运行时绑定的类
静态属性和静态方法
在图书馆借阅系统中,管理员想知道库中当前一共有多少本书,那么我们是否可以设置一个不会被初始化、并且可以改变的值来记录当前图书的库存呢?
在“语法-变量与常量”一章中我们知道静态变量“只初始化一次,值可变”,而在 PHP 类中,同样可使用static
关键字定义静态属性和静态方法。
静态属性与静态变量一样,只能被初始化为文字或常量,不能使用表达式。例如:
static public $total = 0;// 图书的总数量
// 总数量加一
static public function add()
{
self::$total++;
return self::$total;
}
静态方法和静态属性同样通过范围解析操作符访问:
$book = new Book('PHP教程', '极速教程');
echo Book::add();// 输出:1
echo Book::add();// 输出:2
echo $book::add();// 输出:3
$book2 = new Book();
echo $book2::add();// 输出:4
echo Book::$total;// 输出:4
通过上面的测试代码可知,静态属性只作用于类本身,不因对象(类的实例)的改变而改变。
通过本节的学习,我们了解到组织类的基本结构有:
- 类常量:使用关键字
const
定义 - 类属性:必须设置访问控制(
public
、protected
、private
)- 静态属性:使用关键字
static
定义 - 非静态
- 静态属性:使用关键字
- 类方法:必须设置访问控制(
public
、protected
、private
)- 构造函数
__construct()
- 析构函数
__destruct()
- 静态方法:使用关键字
static
定义 - 非静态方法
- 构造函数
本节完整示例代码Book.php
如下:
<?php
// 图书类
// 文件:library/Book.php
class Book
{
public $name;
public $isbn;
public $author;
public $publish;
public $price;
const LIB_NAME = '极速教程';
static public $total = 0;// 图书的总数量
public function __construct(...$args)
{
$count = count($args);
switch($count) {
case 0:// 默认没有参数输入
return $this;
case 1: {// 默认输入的唯一一个参数是书名
$this->name = $args[0];
return $this;
}
case 2: {// 默认输入的两个参数第一个是书名,第二个是作者
$this->name = $args[0];
$this->author = $args[1];
return $this;
}
case 5: {// 默认输入五个参数:书名 ISBN 作者 出版社 单价
$this->name = $args[0];
$this->isbn = $args[1];
$this->author = $args[2];
$this->publish = $args[3];
$this->price = $args[4];
}
}
}
public function __destruct()
{
// 代码块
}
public function setName($name)
{
$this->name = $name;
}
public function opLibName()
{
echo self::LIB_NAME;
}
static public function add()
{
self::$total++;
return self::$total;
}
}
// 测试代码:
// $book1 = new Book();
// // $book->setName('PHP教程');
// var_dump($book1);
// $book2 = new Book('PHP教程');
// var_dump($book2);
// echo $book2::LIB_NAME;
$book = new Book('PHP教程', '极速教程');
echo Book::add();// 输出:1
echo PHP_EOL;
echo Book::add();// 输出:2
echo PHP_EOL;
echo $book::add();// 输出:3
$book2 = new Book();
echo PHP_EOL;
echo $book2::add();// 输出:4
echo PHP_EOL;
echo Book::$total;// 输出:4