php类中static的作用及用法

6次浏览 更新日期:2026-11-08 22:51:26 分类:php基础 评论:0 IP属地:未知
上一篇:没有了 下一篇:没有了

本文档从「static 属性 vs 普通属性」「static 方法 vs 普通方法」讲起,最后重点拆解self:: 与 static::(后期静态绑定)问题。全程配可运行代码。

1. 先建立核心心智模型:类 vs 对象

理解 static 的一切,都建立在这一句话上:

普通成员属于「对象(实例)」,static 成员属于「类」本身。

  • 一个类可以 new 出无数个对象,每个对象有自己的普通属性。

  • 但「类」只有一个,所以 static 属性/方法全类共享一份,不需要 new 就能访问。

class Dog {
    public $name;              // 普通属性:每只狗各自的名字
    public static $count = 0;  // static 属性:全类共享的「狗的总数」

    public function __construct($name) {
        $this->name = $name;   // 用 $this 访问普通属性
        self::$count++;        // 用 self:: 访问 static 属性
    }
}

$d1 = new Dog('旺财');
$d2 = new Dog('来福');

echo $d1->name;        // 旺财
echo $d2->name;        // 来福
echo Dog::$count;      // 2(共享,不是每只狗各一份)

2. static 属性 vs 普通属性

特性static 属性普通属性
归属类本身每个对象实例
内存份数全类 1 份,共享每个对象各 1 份
访问语法类名::$属性 / self::$属性$对象->属性
是否需要 new不需要需要
在方法内访问self::$prop 或 static::$prop$this->prop
生命周期从类加载到脚本结束从对象创建到销毁

共享性是关键差异:

class Counter {
    public static $s = 0;   // 共享
    public $n = 0;          // 独立
}

$a = new Counter();
$b = new Counter();

$a->n = 10;
$a::$s = 10;      // 注意:虽然用 $a 调,改的是「类」的 static 属性

echo $a->n;       // 10
echo $b->n;       // 0   (各是各的)
echo Counter::$s; // 10  (共享,被改了)
echo $b::$s;      // 10  (通过 b 看也是 10)

注意 $a::$s = 10; 这种「用对象访问 static 属性」语法虽然合法,但极不推荐,因为会让人误以为是改 $a 自己的东西。请始终用 类名::$s 或 self::$s。

3. static 方法 vs 普通方法

特性static 方法普通方法
调用语法类名::方法() / self::方法()$对象->方法()
是否需要 new不需要需要对象
能否用 $this不能能
能否直接访问普通属性不能(需先 new)能
能否访问 static 成员能能
多态性依赖后期静态绑定(见第 5 节)天然基于对象
class MathTool {
    public static function add($a, $b) {
        return $a + $b;
    }

    public $base = 100;

    public function addBase($x) {
        return $this->base + $x;   // 普通方法可用 $this
    }
}

echo MathTool::add(1, 2);    // 3,无需 new
$m = new MathTool();
echo $m->addBase(5);         // 105

4. static 方法里能不能访问普通属性?

不能直接访问。 因为 static 方法里没有 $this(它不属于任何对象)。

class A {
    public $prop = 1;
    public static $sprop = 2;

    public static function f() {
        echo self::$sprop;     // ✅ OK,static 属性
        // echo $this->prop;   // ❌ 错误:static 方法里没有 $this
        // echo $this->prop;   // ❌ Fatal error
    }
}

如果 static 方法确实要用到普通属性,只能自己 new 一个对象:

public static function f() {
    $obj = new self();
    echo $obj->prop;   // ✅ 先实例化,再访问普通属性
}

反过来:普通方法里可以访问 static 成员:

class A {
    public static $sprop = 2;
    public function f() {
        echo self::$sprop;   // ✅ 普通方法里访问 static 成员没问题
    }
}

一句话记:普通方法「通吃」静态和普通;static 方法「只碰静态」,碰普通必须先 new。

5. 继承中的 static:self:: vs static::(重点 + 你的疑问)

这是你之前不理解的代码,我们彻底讲透。

5.1 先看完整可运行的例子

class A {
    public static $val = 'A的值';
    public static function test() {
        echo self::$val,   PHP_EOL;   // self
        echo static::$val, PHP_EOL;   // static
    }
}

class B extends A {
    public static $val = 'B的值';     // 子类重新声明了同名 static 属性
}

A::test();   // 输出:A的值 / A的值
B::test();   // 输出:A的值 / B的值

重点看 B::test():

  • self::$val → A的值

  • static::$val → B的值

同样的方法代码,为什么两个结果不同?这正是「早绑定」和「晚绑定」的区别。

5.2 self:: = 早绑定(编译期就定死)

self 在编译时就解析为「写这段代码时所在的类」,也就是 A。

所以 test() 方法里写的 self::$val,在编译那一刻就被钉死成 A::$val。无论你后面用 A::test() 还是 B::test() 调用,它永远是 A 的 $val。

记忆:self = 「我(写代码的这个类)自己」——写死在 A 里,就是 A,不会因为子类调用而改变。

5.3 static:: = 晚绑定(运行时才定)

static:: 不在编译时确定,而是在运行时根据「你实际调用的是哪个类」来决定,这个机制叫 后期静态绑定(Late Static Binding,LSB)。

  • A::test() → 运行时调用类是 A → static::$val 取 A::$val → A的值

  • B::test() → 运行时调用类是 B → static::$val 取 B::$val → B的值

记忆:static = 「调用我的那个类」——跟着实际调用者走。

5.4 用一个流程图理解

调用 B::test()
        │
        ▼
  进入 A 里定义的 test() 方法
        │
        ├── self::$val  ── 编译期已定死为 A ──▶ A的值
        │
        └── static::$val ── 运行时看「谁调的」是 B ──▶ B的值

5.5 再举一例(方法版,更容易体会)

class A {
    public static function who() { echo 'A', PHP_EOL; }
    public static function test() { static::who(); }   // 晚绑定
}
class B extends A {
    public static function who() { echo 'B', PHP_EOL; }
}

B::test();   // 输出 B

如果 test() 里写的是 self::who();,就会永远输出 A,因为 self 死绑成 A。

5.6 关键前提:子类得「重新声明」

static::$val 能拿到 B 的值,前提是 B 自己重新声明了 $val。如果 B 没声明,B::$val 会继承 A 的值,那时 self::$val 和 static::$val 结果就一样了。

class B extends A {}          // B 没重新声明 $val
B::test();   // 输出:A的值 / A的值(因为 B 继承了 A 的 $val)

6. self::、static::、parent:: 三者对比

符号绑定时机指向B 调用 A 里的方法时指向
self::编译期(早)定义该方法的类A
static::运行期(晚)实际调用的类B
parent::编译期父类A 的父类
class Grand {
    public static $v = 'Grand';
}
class ParentC extends Grand {
    public static $v = 'ParentC';
    public static function show() {
        echo 'self:   ', self::$v,   PHP_EOL;    // 定义方法的类 ParentC
        echo 'static: ', static::$v, PHP_EOL;    // 实际调用的类
        echo 'parent: ', parent::$v, PHP_EOL;    // 父类 Grand
    }
}
class Child extends ParentC {
    public static $v = 'Child';
}

Child::show();
// self:   ParentC
// static: Child
// parent: Grand

7. 后期静态绑定的真实用途

为什么 PHP 要设计 static::?因为 self:: 写死了类名,父类的通用方法无法「感知」到被哪个子类调用。举两个经典场景。

7.1 场景一:通用工厂方法

class Model {
    public static function create() {
        return new static();     // 关键:用 static,返回「调用者」的实例
    }
}

class User extends Model {}

$u = User::create();
var_dump($u instanceof User);   // true ✅

// 如果写成 new self(),就会返回 Model,而不是 User ❌

7.2 场景二:单例模式

class Singleton {
    private static $instance = null;

    protected function __construct() {}
    private function __clone() {}

    public static function getInstance() {
        if (static::$instance === null) {      // 用 static,让每个子类有独立实例
            static::$instance = new static();
        }
        return static::$instance;
    }
}

class Db extends Singleton {}
class Cache extends Singleton {}

var_dump(Db::getInstance() === Db::getInstance());       // true
var_dump(Db::getInstance() === Cache::getInstance());    // false(各是各的)

用 self::$instance 会导致所有子类共享同一个实例,用 static::$instance 才能让每个子类各自独立。

7.3 static::class 取运行时类名

非 static 方法里也常用:

class A {
    public function f() {
        echo static::class;   // 运行时实际对象类名
    }
}
class B extends A {}

(new B())->f();   // 输出 B

8. 常见坑与注意事项

坑 1:外部不能用 static::

static::$val;   // ❌ 语法错误,只能在类内部使用

坑 2:用 -> 访问 static 属性会报错

class A { public static $v = 1; }
$a = new A();
echo $a->v;    // ❌ Undefined property(static 不能通过 -> 访问)
echo $a::$v;   // ⚠️ 合法但不推荐(通过对象调 static)
echo A::$v;    // ✅ 正确

坑 3:static 方法里用 $this

class A {
    public static function f() {
        // return $this;   // ❌ Fatal error: Using $this when not in object context
    }
}

坑 4:static:: 要生效,子类必须重新声明同名属性/方法

class A { public static $v = 'A'; public static function f(){ echo static::$v; } }
class B extends A {}          // 没重新声明
B::f();                       // 输出 A(继承来的)

坑 5:self:: 不能用于「多态期望」

任何你希望「子类调用时表现得像子类」的地方,都不能用 self::,必须用 static::。

坑 6:static 是全局状态,注意测试与并发

static 属性是脚本级全局共享状态,单元测试之间可能相互污染,需谨慎使用。

9. 使用场景决策表

你的需求选什么
工具函数(无状态,如 Str::random())static 方法
全类共享计数器/缓存/配置static 属性
单例、工厂(要返回子类实例)static 方法 + new static()
每个对象有独立数据(用户、订单…)普通属性/方法
需要 $this、依赖注入、易测试普通方法
父类方法要「认子类」static:: / 后期静态绑定
固定用当前类的成员self::

10. 速查总结

属性/方法层面:

  • static 属于「类」,共享一份,无需 new;普通属于「对象」,各自一份,需 new。

  • static 方法里没有 $this,不能直接碰普通属性;普通方法里两种都能碰。

继承与绑定层面(你的疑问):

  • self:: = 「定义我的那个类」 → 编译期早绑定,写死在 A 就永远是 A。

  • static:: = 「调用我的那个类」 → 运行期晚绑定(后期静态绑定),B 调用就用 B。

  • parent:: = 父类,早绑定。

一句话:

想让父类的通用方法在子类调用时「认子类」,用 static::;想固定当前类的成员,用 self::。两者都只作用于 static 成员(self:: / static:: 也可用于常量,但属性/方法要分清 static 与普通)。

附:一个综合练习(建议自己跑一遍)

class Animal {
    public static $count = 0;
    public static function create() {
        static::$count++;
        return new static();
    }
    public static function report() {
        echo static::class, ' 累计:', static::$count, PHP_EOL;
    }
}
class Cat extends Animal { public static $count = 0; }
class Dog extends Animal { public static $count = 0; }

Cat::create(); Cat::create();
Dog::create();

Cat::report();   // Cat 累计:2
Dog::report();   // Dog 累计:1
Animal::report(); // Animal 累计:0(父类自己的 $count 没被动过)
我来说两句
只看楼主 热门回复 正序 倒序
  • 还没有评论,快来说两句吧~

版主信息
发布新帖
相关推荐
  • 暂无相关内容
登录后参与评论… 登录
发帖子 回顶部