PHP类的秘密(五) 构造函数详解

在类的秘密第一篇的时候, 我们提到为属性赋初始值的例子. 类的每个实例都可能会有不同的初始值, 是不是可以在创建实例的时候, 就一起为属性赋值呢?

当然可以, 这就是今天要讲到的构造函数.

基础知识

构造函数是一种特殊的成员方法, 用来为创建对象时, 初始化对象属性. 创建构造函数时, 注意是两条下划线:

function __construct($parameters,....) {statements}

它的基本规则:

  1. 它是在每次创建对象时自动执行的, 可以为成员变量赋初值, 也可以执行一些其他初始化的操作, 比如打印欢迎语.

  2. PHP规定, 每个类只能有一个构造函数.

  3. 因为其意义, 构造函数不能定义为静态static, 所以不能在类外直接调用, 但可以被其他成员方法调用.

我们看一个典型的构造函数:

class Sport2    
{
    public ? string $name;
    public $sex;
    public ? $weight;
    function __construct(? string $name, protected $age, ? int $weight, $sex='male')
    {
        $this->name=$name;
        $this->sex=$sex;
        $this->age=$age;
        $this->weight=$weight;
        echo '对象初始化完成';
    }
    function showMe() 
    {
        self::__construct('test',5,10); //可以通过其他成员方法调用构造函数;
    }
}
$player2=new Test1('Snow',35, 88); //声明对象的同时,自动调用构造函数,赋予初始值
$player2->shouMe();
复制代码

通过上例, 我们可以得出构造函数的几个用法:

  1. 定义属性时, 我们可以在属性前面的问号 ? , 自 PHP 7.1.0 起,类型声明允许前置一个问号 (?) 用来声明这个值允许为指定类型,或者为 null。

  2. 构造函数中可以给属性赋默认值, 可以指定属性数据类型, 可以新增属性.

  3. 还可以给新增的属性指定可见度, 但如果原属性已经有可见度了, 就不能再改了.

  4. 创建对象时, 将构造器参数放在类名后的括号里调用。

  5. 其他成员方法可以调用构造函数.

构造函数的继承

我们通过实例, 看一下子类的构造函数和父类构造函数的关系:

class BaseClass {                    
    function __construct(protected $a, int $b)
    {  //父类构造函数, 带两个必选参数
        print "BaseClass\n";
    }
}

class SubClass extends BaseClass {
    function __construct($a)         //子类构造函数, 只引用一个父类属性
    { 
        parent::__construct(5,5);  //调用父类构造函数
        print "SubClass\n";
    }
}

class OtherSubClass extends BaseClass {
    // 子类, 无自己的构造函数
}

$obj1 = new SubClass();     //BaseClassSubClass
$obj2 = new OtherSubClass(5,9); //BaseClass
复制代码

从上例, 我们可以看到:

  1. 当子类对象被创建时, PHP 会优先使用子类自己的构造函数.

  2. 如果子类中没有自己的构造方法,则 PHP 会调用父类中的构造方法.

  3. 子类构造方法可以设置任意参数和方法体, 不受父类构造函数限制. 这一点和我们之前讲过的重写不一样. 可以理解为, 子类和父类的构造函数是独立的.

如何在一个类中实现多个构造函数?

我们之前提到每个 class 只能有一个构造器。 然而在某些情况下,需要针对不同的情况实现不同的方式构造对象。除了通过子类以外, 我们还可以通过static静态方法.

举个例子:

class Product {              
private ?int $id;              //数据类型前面加?,表示要么是int,要么是null
private ?string $name;

//设置构造器为 private 或 protected,防止自行额外调用。
private function __construct(?int $id = null, ?string $name = null) 
{
    $this->id = $id;
    $this->name = $name;
}

public static function fromBasicData(int $id, string $name)
{     
    $new = new static($id, $name);   //将用户输入的变量带入构造函数, 并新建对象;
    return $new;             //返回新建对象;
}                                       //static指代调用对象的类;

public static function fromArray(array $arr)  {
    $data = $arr;
    return new static($data['id'], $data['name']);   //将数组元素传入构造函数
}

public static function fromString(string $stringvar){
    $data = explode('@',$stringvar);       //将字符串转换为数组
    $new = new static();                  //构造函数的参数可以为null
    $new->id = $data['id'];               //再进行属性赋值;
    $new->name = $data['name'];
    return $new;
}
}
$p1 = Product::fromBasicData(5, 'Widget');   //调用静态方法, 直接赋值;
$p2 = Product::fromArray($arr);               //从数组中为对象赋值
$p3 = Product::fromString($stringvar);          //从字符串中为对象赋值
复制代码

上面这个例子中, 有2个很重要的点:

$new = new static($id, $name)

这个语句, 实际上是在类内部调用构造函数. 其中static在这里是代词, 指代调用它的类, 在上面的例子中, 就是Product::. 那么 new static (id,id, name) 等价于 new Product(id,id, name). 然后将新建的对象赋值给$new, 作为函数的返回值, 赋值给$p1. 后面两个例子都是一个意思. PHP管它叫”后期静态绑定”, 不用太纠结这个概念.

第二个点, 这里将构造函数__contruct()设置为了private, 这样就无法在类外通过new来创建对象, 会导致报错, 只能通过同类的static静态方法进行调用. 这里大家可以思考一下, 如果删掉构造函数__contruct()是不是可以?

其实也是可以的, 我们可以对上面的例子进行适当改写, 一样可以达到通过调用不同函数创建按不累同类对象的目的. 但是呢, 就无法限制在类外直接new一个新对象了.

析构函数 __destruct()

最后我们再来看看析构函数, 我们既然可以创建对象, 当然也可以销毁一个对象. 这就用到了析构函数, 析构函数往往用来做”清理善后” 的工作,释放内存.

function __destruct()    //是双下划线
{
    表达式;
}
复制代码

析构函数的规则:

  1. 析构函数会在到某个对象的所有引用都被删除, 或者当对象被显式销毁时, 系统会自动执行;

  2. 析构函数必须是public, 否则系统会报错;

  3. 子类可以定义自己的析构函数, PHP会优先执行子类的析构函数, 如果子类没有定义析构函数则会继承父类的。

  4. 子类要执行父类的析构函数,必须在子类的析构函数体中显式调用parent::__destruct()

我们来验证一下析构函数的规则:

class Foo4 {

private $name;   
private $link;   

public function __construct($name) {
    $this->name = $name;
}

public function setLink($link){   
    $this->link = $link;
}

public function __destruct() {
    echo 'Destroying: ', $this->name, PHP_EOL;
}
复制代码

}

$foo = new Foo4('Foo 1');
$bar = new Foo4('Foo 2');
$foo->setLink($bar);   //让两个对象互相引用
$bar->setLink($foo);
$foo1 = null;         //将两个变量重新赋值;
$bar1 = null;

$foo1 = new Foo4('Foo 3');
$bar1 = new Foo4('Foo 4');

echo 'End of script', PHP_EOL; 

//output:
Destroying: Foo 3
Destroying: Foo 4
End of script
Destroying: Foo 1
Destroying: Foo 2
复制代码

可以从输出结果看到, 互为引用的foofoo和bar在程序都运行完毕后才被清除. 而foo3和foo4在创建完后就被清除了. 原因在于, foofoo和bar互为引用, 即便之后他们被重新赋值, 但是他们的引用关系依然保存在了系统内存当中. 这会导致系统无法调用析构函数销毁他们, 直到整个程序结束.

最后, 我们总结一下:

  1. 我们讲了构造函数的基本规则, 并举例了它的不同用法.
  2. 构造函数的继承特点, 和普通成员方法不一样.
  3. 以及如何通过static这个代词来包装构造函数, 以实现不同输入格式的对象
  4. 最后是析构函数, 以及它的内在机制.

总结不易, 请勿私自转载, 否则别怪老大爷不客气!

参考资料:

www.php.net PHP官方文档

零基础PHP学习笔记 明日科技

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享