在类的秘密第一篇的时候, 我们提到为属性赋初始值的例子. 类的每个实例都可能会有不同的初始值, 是不是可以在创建实例的时候, 就一起为属性赋值呢?
当然可以, 这就是今天要讲到的构造函数.
基础知识
构造函数是一种特殊的成员方法, 用来为创建对象时, 初始化对象属性. 创建构造函数时, 注意是两条下划线:
function __construct($parameters,....) {statements}
它的基本规则:
-
它是在每次创建对象时自动执行的, 可以为成员变量赋初值, 也可以执行一些其他初始化的操作, 比如打印欢迎语.
-
php规定, 每个类只能有一个构造函数.
-
因为其意义, 构造函数不能定义为静态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();
通过上例, 我们可以得出构造函数的几个用法:
-
定义属性时, 我们可以在属性前面的问号 ? , 自 PHP 7.1.0 起,类型声明允许前置一个问号 (?) 用来声明这个值允许为指定类型,或者为 null。
-
构造函数中可以给属性赋默认值, 可以指定属性数据类型, 可以新增属性.
-
还可以给新增的属性指定可见度, 但如果原属性已经有可见度了, 就不能再改了.
-
创建对象时, 将构造器参数放在类名后的括号里调用。
-
其他成员方法可以调用构造函数.
构造函数的继承
我们通过实例, 看一下子类的构造函数和父类构造函数的关系:
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
从上例, 我们可以看到:
-
当子类对象被创建时, PHP 会优先使用子类自己的构造函数.
-
如果子类中没有自己的构造方法,则 PHP 会调用父类中的构造方法.
-
子类构造方法可以设置任意参数和方法体, 不受父类构造函数限制. 这一点和我们之前讲过的重写不一样. 可以理解为, 子类和父类的构造函数是独立的.
如何在一个类中实现多个构造函数?
我们之前提到每个 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,name) 等价于 new Product(id,name). 然后将新建的对象赋值给$new, 作为函数的返回值, 赋值给$p1. 后面两个例子都是一个意思. PHP管它叫"后期静态绑定", 不用太纠结这个概念.
第二个点, 这里将构造函数__contruct()设置为了private, 这样就无法在类外通过new来创建对象, 会导致报错, 只能通过同类的static静态方法进行调用. 这里大家可以思考一下, 如果删掉构造函数__contruct()是不是可以?
其实也是可以的, 我们可以对上面的例子进行适当改写, 一样可以达到通过调用不同函数创建按不累同类对象的目的. 但是呢, 就无法限制在类外直接new一个新对象了.
析构函数 __destruct()
最后我们再来看看析构函数, 我们既然可以创建对象, 当然也可以销毁一个对象. 这就用到了析构函数, 析构函数往往用来做"清理善后" 的工作,释放内存.
function __destruct() //是双下划线
{
表达式;
}
析构函数的规则:
-
析构函数会在到某个对象的所有引用都被删除, 或者当对象被显式销毁时, 系统会自动执行;
-
析构函数必须是public, 否则系统会报错;
-
子类可以定义自己的析构函数, PHP会优先执行子类的析构函数, 如果子类没有定义析构函数则会继承父类的。
-
子类要执行父类的析构函数,必须在子类的析构函数体中显式调用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
可以从输出结果看到, 互为引用的foo和bar在程序都运行完毕后才被清除. 而foo3和foo4在创建完后就被清除了. 原因在于, foo和bar互为引用, 即便之后他们被重新赋值, 但是他们的引用关系依然保存在了系统内存当中. 这会导致系统无法调用析构函数销毁他们, 直到整个程序结束.
最后, 我们总结一下:
- 我们讲了构造函数的基本规则, 并举例了它的不同用法.
- 构造函数的继承特点, 和普通成员方法不一样.
- 以及如何通过static这个代词来包装构造函数, 以实现不同输入格式的对象
- 最后是析构函数, 以及它的内在机制.
总结不易, 请勿私自转载, 否则别怪老大爷不客气!
参考资料:
www.php.net PHP官方文档
零基础PHP学习笔记 明日科技
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!