5. 对象
5.1. 任何变量均可成为具有属性的对象(exemple_14)
<?php
// 任何变量在构造时都可能具有属性
$obj1->attr1 = "un";
$obj1->attr2 = 100;
// 显示对象
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
// 修改了该对象
$obj1->attr2+=100;
// 显示对象
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
// 将对象1的值赋给对象2
$obj2 = $obj1;
// 修改对象2
$obj2->attr2 = 0;
// 显示这两个对象
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
print "objet2=[$obj2->attr1,$obj2->attr2]\n";
// 将对象1的引用赋值给对象3
$obj3 = &$obj1;
// 修改对象3
$obj3->attr2 = 10;
// 显示两个对象
print "objet1=[$obj1->attr1,$obj1->attr2]\n";
print "objet3=[$obj3->attr1,$obj3->attr2]\n";
// 对象是否为字典?
print count($obj1)."\n";
while (list($attribut, $valeur) = each($obj1))
print "obj1[$attribut]=$valeur\n";
// 结束
exit;
?>
结果:
objet1=[un,100]
objet1=[un,200]
objet1=[un,0]
objet2=[un,0]
objet1=[un,10]
objet3=[un,10]
1
obj1[attr1]=un
obj1[attr2]=10
注释
- 第 4 行:$obj->attr 表示变量 $obj 的属性 attr。 如果该属性不存在,则会创建它,从而将变量 $obj 转换为一个具有属性的对象。
- 第 13 行:当 $obj1 是一个对象时,表达式 $obj2=$obj1 属于按引用复制对象。 因此,$obj2 和 $obj1 都是指向同一个对象的引用。该对象本身可以通过任一引用进行修改。
- 第 20 行:表达式 $obj3=&$obj1 将变量 $obj1 的引用(地址)赋值给 $obj3。 如果 $obj1 是一个对象(如本例所示),则上述表达式等同于 $obj3=$obj1。
- 第 28-29 行:表明对象可以像字典一样进行遍历。字典的键是属性的名称,字典的值则是这些属性的值。
- 第27行:函数count可以应用于一个对象,但并不会像预期的那样返回属性数量。因此,对象与字典有相似之处,但并非字典。
5.2. 未声明属性的 Personne 类 (exemple_15)
<?php
class Personne {
// 类的属性
// 未声明——可动态创建
// 方法
function identité() {
// 初步判断,使用了不存在的属性
return "[$this->prénom,$this->nom,$this->âge]";
}
}
// 测试
// 属性为公共属性,可动态创建
$p = new Personne();
$p->prénom = "Paul";
$p->nom = "Langevin";
$p->âge = 48;
// 调用方法
print "personne=" . $p->identité() . "\n";
// 结束
exit;
?>
结果:
注释
- 第 3-13 行:定义了一个类 Personne。类是用于创建对象的模板。类是由属性及称为方法的函数组成的集合。属性并非必须声明。
- 第9-12行:方法identité显示了类中三个未声明属性的值。关键字$this指代应用该方法的对象。
- 第 17 行:创建一个类型为 Personne 的 $p 对象。 关键字 new 用于创建新对象。该操作返回对所创建对象的引用(即一个地址)。有多种写法:new Personne()、new Personne、new personne。类名不区分大小写。
- 第 18-20 行:在 $p 对象中创建了 identité 方法所需的三个属性。
- 第 22 行:对 $p 对象应用了 Personne 类的 identité 方法。 在方法 identité 的代码(第 9-11 行)中,$this 引用的对象与 $p 相同。
5.3. 具有声明属性的 Personne 类(exemple_16)
<?php
class Personne {
// 类属性
var $prénom;
var $nom;
var $âge;
// 方法
function identité() {
return "[$this->prénom,$this->nom,$this->âge]";
}
}
// 测试
// 属性是公有的
$p = new Personne();
$p->prénom = "Paul";
$p->nom = "Langevin";
$p->âge = 48;
// 调用方法
print "personne=" . $p->identité() . "\n";
// 结束
exit;
?>
结果:
注释
- 第 6-8 行:类的属性使用 var 关键字显式声明。
5.4. 带有构造函数的 Personne 类 (exemple_17)
前面的示例展示了类似 Personne 这种在 PHP 4 中常见的特殊类。不建议效仿这些示例。 现在,我们将介绍一个符合 PHP 5 最佳实践的 Personne 类:
<?php
class Personne {
// 类的属性
private $prénom;
private $nom;
private $âge;
// 获取器和设置器
public function getPrénom() {
return $this->prénom;
}
public function getNom() {
return $this->nom;
}
public function getAge() {
return $this->âge;
}
public function setPrénom($prénom) {
$this->prénom = $prénom;
}
public function setNom($nom) {
$this->nom = $nom;
}
public function setAge($age) {
$this->âge = $age;
}
// 构造函数
function __construct($prénom, $nom, $âge) {
// 通过 set 方法
$this->setPrénom($prénom);
$this->setNom($nom);
$this->setAge($âge);
}
// 方法toString
function __toString() {
return "[$this->prénom,$this->nom,$this->âge]";
}
}
// 测试
// 创建一个人员对象
$p = new Personne("Paul", "Langevin", 48);
// 该人员的身份
print "personne=$p\n";
// 更改年龄
$p->setAge(14);
// 该人的身份
print "personne=$p\n";
// 结束
exit;
?>
结果:
注释
- 第3-48行:类Personne
- 第 6-8 行:类的私有(private)属性。这些属性仅在类内部可见。其他可用的关键字包括:
- public:将属性设为公共属性,可在类外部访问
- protected:将属性设为受保护属性,可在类内部及其派生类中访问。
- 由于属性是私有的,因此无法从类外部访问它们。因此无法编写以下代码:
此处位于 Personne 类外部。由于 nom 属性为私有,因此第 2 行代码是错误的。要初始化 $p 对象的私有字段,有两种方法:
- (续)
- 使用第11至33行中的公共方法set和get(这些方法的名称可以任意设定)。此时可以编写如下代码:
- (续)
- 使用第 36-41 行中的构造函数。此时应写为:
上述代码会自动调用 Personne 类中名为 __construct 的方法。
- 第54行:该行将对象$p以字符串形式输出。为此,使用了Personne类中名为__toString的方法。
5.5. 在构造函数中包含有效性检查的 Personne 类 (exemple_18)
类的构造函数是验证对象初始化值是否正确的理想位置。但对象也可以通过其方法 set 或等效方法进行初始化。为避免在两个不同位置重复相同的验证,应将这些验证放入方法 set 中。 如果对象的初始化值被发现不正确,将抛出异常。以下是一个示例:
<?php
class Personne {
// 类的属性
private $prénom;
private $nom;
private $âge;
// 获取器和设置器
public function getPrénom() {
return $this->prénom;
}
public function getNom() {
return $this->nom;
}
public function getAge() {
return $this->âge;
}
public function setPrénom($prénom) {
// 名字字段不能为空
if (!preg_match("/^\s*(.+?)\s*$/", $prénom, $champs)) {
throw new Exception("Le prénom doit être non vide");
} else {
$this->prénom = $champs[1];
}
}
public function setNom($nom) {
// 姓氏不能为空
if (!preg_match("/^\s*(.+?)\s*$/", $nom, $champs)) {
throw new Exception("Le nom doit être non vide");
} else {
$this->nom = $champs[1];
}
}
public function setAge($âge) {
// 年龄必须有效
if (!preg_match("/^\s*(\d+)\s*$/", $âge, $champs)) {
throw new Exception("L'âge doit être un entier positif ou nul");
} else {
$this->âge = $champs[1];
}
}
// 构造函数
function __construct($prénom, $nom, $âge) {
// 通过 set 方法
$this->setPrénom($prénom);
$this->setNom($nom);
$this->setAge($âge);
}
// 方法 toString
function __toString() {
return "[$this->prénom,$this->nom,$this->âge]";
}
}
// 测试
// 创建 Personne 对象
$p = new Personne("Paul", "Langevin", 48);
// 此人的身份
print "personne=$p\n";
// 创建错误的Personne对象
try {
$p = new Personne("xx", "yy", "zz");
} catch (Exception $e) {
print $e->getMessage();
}
// 结束
exit;
?>
结果:
注释
- 第23-30行:初始化属性prénom并验证初始化值
- 第 25 行:使用正则表达式。名字是由一个或多个字符组成的字符串,前后可能带有空格。
- 第 26 行:如果姓氏为空,则抛出异常;否则,名字将被存储(第 28 行)
- 第 66 行:使用 Personne 类的构造函数
- 第 68 行:使用类 Personne 的方法 __toString
- 第70-74行:处理Personne类构造函数可能抛出的异常。
5.6. 添加一个充当第二个构造函数的方法(exemple_19)
在 PHP 5 中,无法拥有多个具有不同参数的构造函数,从而无法以多种方式构建对象。因此,可以使用充当构造函数的方法:
<?php
class Personne {
// 类的属性
private $prénom;
private $nom;
private $âge;
// 获取器和设置器
public function getPrénom() {
return $this->prénom;
}
….
// 构造函数
function __construct($prénom, $nom, $âge) {
// 通过 set 方法
$this->setPrénom($prénom);
$this->setNom($nom);
$this->setAge($âge);
}
// 方法
public function initWithPersonne($p) {
// 使用 $p 对象初始化当前对象
$this->__construct($p->prénom, $p->nom, $p->âge);
}
// 方法toString
function __toString() {
return "[$this->prénom,$this->nom,$this->âge]";
}
}
// 测试
// 创建一个 Personne 对象
$p = new Personne("Paul", "Langevin", 48);
// 该人的身份
print "personne=$p\n";
// 创建第二个人员
$p2 = new Personne("Laure", "Adeline", 67);
// 使用第二个人的值初始化第一个
$p->initWithPersonne($p2);
// 验证
print "personne=$p\n";
// 结束
exit;
?>
结果:
注释
- 第 26-29 行:方法 initWithPersonne 用于将另一个对象 Personne 的属性值赋给当前对象。此处它调用了构造函数 __construct,但这并非强制要求。
5.7. Personne 对象数组(exemple_20)
以下示例说明可以创建对象数组。
<?php
class Personne {
// 类的属性
private $prénom;
private $nom;
private $âge;
// 获取器和设置器
public function getPrénom() {
return $this->prénom;
}
...
// 构造函数
function __construct($prénom, $nom, $âge) {
// 通过 set 方法
$this->setPrénom($prénom);
$this->setNom($nom);
$this->setAge($âge);
}
// 方法
public function initWithPersonne($p) {
….
}
// 方法toString
function __toString() {
...
}
}
// 测试
// 创建一个 Personne 对象数组
$groupe = array(new Personne("Paul", "Langevin", 48), new Personne("Sylvie", "Lefur", 70));
// 这些人的身份
for ($i = 0; $i < count($groupe); $i++)
print "groupe[$i]=$groupe[$i]\n";
// 结束
exit;
?>
结果:
注释
- 第39行:创建一个包含2人的数组
- 第42行:遍历数组
- 第43行:$groupe[$i]是Personne类型的对象。使用__toString方法来显示它。
5.8. 创建从 Personne 类(exemple_21)派生的类
<?php
class Personne {
// 类的属性
private $prénom;
private $nom;
private $âge;
// 获取器和设置器
public function getPrénom() {
return $this->prénom;
}
...
// 构造函数
function __construct($prénom, $nom, $âge) {
// 通过 set 方法
$this->setPrénom($prénom);
$this->setNom($nom);
$this->setAge($âge);
}
// 方法
public function initWithPersonne($p) {
// 使用 $p 对象初始化当前对象
$this->__construct($p->prénom, $p->nom, $p->âge);
}
// 方法toString
function __toString() {
return "[$this->prénom,$this->nom,$this->âge]";
}
}
// 一个从 Person 派生的类
class Enseignant extends Personne {
// 属性
private $discipline; // 教授的学科
// getter 和 setter
public function getDiscipline() {
return $this->discipline;
}
public function setDiscipline($discipline) {
$this->discipline = $discipline;
}
// 构造函数
public function __construct($prénom, $nom, $âge, $discipline) {
// 父类属性
parent::__construct($prénom, $nom, $âge);
// 其他属性
$this->setDiscipline($discipline);
}
// 重载父类的 __toString 函数
public function __toString() {
return "[" . parent::__toString() . ",$this->discipline]";
}
}
// 测试
// 创建“Person”对象及其派生对象的数组
$groupe = array(new Enseignant("Paul", "Langevin", 48, "anglais"), new Personne("Sylvie", "Lefur", 70));
// 这些人员的身份
for ($i = 0; $i < count($groupe); $i++)
print "groupe[$i]=$groupe[$i]\n";
// 结束
exit;
?>
结果:
注释
- 第 3-35 行:类 Personne
- 第 38 行:类 Enseignant 继承自类 Personne.。派生类 Enseignant 继承了其父类的属性和方法。
- 第 41 行:类 Enseignant 添加了一个属于它自己的新属性 discipline
- 第 55 行:类 Enseignant 的构造函数接收 4 个参数
- 其中 3 个用于初始化其父类(名字、姓氏、年龄)
- 1 个用于自身初始化(学科)
- 第 57 行:派生类通过关键字 parent:: 访问其父类的方法和构造函数。此处将参数(名字、姓氏、年龄)传递给父类的构造函数。
- 第 64 行:派生类的 __toString 方法调用了父类的 __toString 方法。
- 第 71 行:一个包含 Enseignant 对象和 Personne 对象的数组。
- 第 74 行:遍历数组中的元素
- 第 75 行:将调用每个元素 $groupe[$i] 的方法 __toString。 类 Personne 有一个方法 __toString。 类 Enseignant 有两个方法:其父类的和它自己的。人们可能会问哪个会被调用。运行结果表明,被调用的是类 Enseignant 的方法。 情况总是如此:当在对象上调用方法时,系统会按以下顺序进行查找:首先在对象本身中查找,如果该对象有父类,则在其父类中查找,然后在父类的父类中查找,以此类推……一旦找到该方法,搜索即告结束。
5.9. 创建一个从 Personne 类(exemple_22)派生的第二个类
以下示例创建了一个从类 Personne 派生的类 Etudiant:
<?php
class Personne {
// 类的属性
private $prénom;
private $nom;
private $âge;
// 获取器和设置器
public function getPrénom() {
return $this->prénom;
}
...
// 构造函数
function __construct($prénom, $nom, $âge) {
// 通过 set 方法
$this->setPrénom($prénom);
$this->setNom($nom);
$this->setAge($âge);
}
// 方法
public function initWithPersonne($p) {
// 使用 $p 对象初始化当前对象
...
}
// 方法toString
function __toString() {
return "[$this->prénom,$this->nom,$this->âge]";
}
}
// 从 Person 派生的类
class Enseignant extends Personne {
// 属性
private $discipline; // 教授学科
// getter 和 setter
...
// 构造函数
public function __construct($prénom, $nom, $âge, $discipline) {
// 父类属性
parent::__construct($prénom, $nom, $âge);
// 其他属性
$this->setDiscipline($discipline);
}
// 重载父类的 _identité__toString 函数
public function __toString() {
return "[" . parent::__toString() . ",$this->discipline]";
}
}
// Personne类的派生类
class Etudiant extends Personne {
// 属性
private $formation; // 教授学科
// getter 和 setter
public function getFormation() {
return $this->formation;
}
public function setFormation($formation) {
$this->formation = $formation;
}
// 构造函数
public function __construct($prénom, $nom, $âge, $formation) {
// 父类属性
parent::__construct($prénom, $nom, $âge);
// 其他属性
$this->setFormation($formation);
}
// 重载父类的 __toString 函数
public function __toString() {
return "[" . parent::__toString() . ",$this->formation]";
}
}
// 测试
// 创建人员对象及其派生对象数组
$groupe = array(new Enseignant("Paul", "Langevin", 48, "anglais"), new Personne("Sylvie", "Lefur", 70), new Etudiant("Steve", "Boer", 23, "iup2 qualité"));
// 这些人员的身份
for ($i = 0; $i < count($groupe); $i++)
print "groupe[$i]=$groupe[$i]\n";
// 结束
exit;
?>
结果:
groupe[0]=[[Paul,Langevin,48],anglais]
groupe[1]=[Sylvie,Lefur,70]
groupe[2]=[[Steve,Boer,23],iup2 qualité]
5.10. 接口 (exemple_23)
接口是一种定义方法原型的结构。实现接口的类会定义该接口中所有方法的代码。
<?php
// 一个接口
interface IExemple {
function ajouter($i, $j);
function soustraire($i, $j);
}
// 接口的实现 1
class Classe1 implements IExemple {
public function ajouter($a, $b) {
return $a + $b + 10;
}
public function soustraire($a, $b) {
return $a - $b + 20;
}
}
// 接口的第二个实现
class Classe2 implements IExemple {
public function ajouter($a, $b) {
return $a + $b + 100;
}
public function soustraire($a, $b) {
return $a - $b + 200;
}
}
// 函数
function calculer($a, $b, IExemple $interface) {
print $interface->ajouter($a, $b)."\n";
print $interface->soustraire($a, $b)."\n";
}
// ------------------- main
// 创建 2 个类型为 Classe1 和 Classe2 的对象
$c1 = new Classe1();
$c2 = new Classe2();
// 调用函数calculer
calculer(4, 3, $c1);
calculer(14, 13, $c2);
?>
结果
注释
- 第 4-8 行:接口 IExemple 定义了两个方法:函数 ajouter(第 5 行)和函数 soustraire(第 7 行)。 该接口并未定义这些方法的代码。具体实现将由实现该接口的类来完成。
- 第 11 行:类 Classe1 实现了接口 IExemple。 因此,它为方法 ajouter(第 13 行)和 soustraire(第 17 行)定义了代码。
- 第 24-34 行:类 Classe2 亦是如此
- 第37行:方法calculer接受三个参数: 两个整数 $a 和 $b,以及一个实现 IExemple 接口的对象 $interface。 我们本可以写成 function calculer($a,$b,$interface),而不指定参数 $interface 的类型 IExemple。 通过指定类型,可以使解释器 PHP 验证实际参数是否确实是一个实现了接口 IExemple 的对象。
- 第 38-39 行:调用对象 $interface 的方法 ajouter 和 soustraire。 由于该对象实现了 IExemple 接口,因此我们知道它拥有这两个方法。
- 第 44-45 行:创建两个类型不同但都实现了 IExemple 接口的对象。
- 第 47 行:将类型为 Classe1 且实现 IExemple 接口的对象 $c1 传递给函数 calculer。
- 第 48 行:调用函数 calculer,类型为 Classe2 的对象 $c2 实现了接口 IExemple。