类和对象:类结构

这一节我们将详细分析 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 对象中nameauthor属性被成功赋值。

如果有学习过 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;// 输出:图书馆名称
范围解析操作符

范围解析操作符由两个冒号组成,它可以访问类的类常量、静态属性和静态方法。在类的内部访问类常量、静态属性和静态方法时需要在冒号前添加selfparent或者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定义
  • 类属性:必须设置访问控制(publicprotectedprivate
    • 静态属性:使用关键字static定义
    • 非静态
  • 类方法:必须设置访问控制(publicprotectedprivate
    • 构造函数__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