猿创征文|学习记录之 PHP 中的面向对象编程
本文目录
- 一、什么是面向对象编程
- 二、类和对象
- (一)定义类
- (二)实例化类(创建对象)
- (三)类中的变量
- (四)类中的方法
- 1. 定义方法
- 2. 调用方法
- 3. 方法的参数
- 4. 方法参数的默认值
- 5. 方法的返回值
- 三、$this 操作符的使用
- 四、构造方法
- 五、析构方法
- 六、类的继承
- (一)如何继承类
- (二)方法的重写
- (三)调用父类中的方法
- 七、静态变量(方法)
- 八、final 类和方法
- 九、常量属性
- 十、abstract 类和方法
- 十一、接口
- 十二、魔术方法
- (一)__set() 方法
- (二)__get() 方法
- (三)__call() 方法
- (四)__toString() 方法
一、什么是面向对象编程
面向对象,准确地说应该叫做 “面向对象编程”。
面向对象编程( Object Oriented Programming,OOP
)是一种计算机编程架构,它能使代码更加简洁,更易于维护,并且具有更强的可重用性。
二、类和对象
类( class
)和对象( object
)是面向对象编程的核心概念。
类是对一类事物的描述,它定义了事物的抽象特点,类的定义包含了数据的形式以及对数据的操作。
对象是类的实例,是实际存在的该类事物的某个个体。
在计算机中,可以理解为类是一个抽象模型,而对象是实实在在存储在内存区域中的一个实体。
(一)定义类
PHP
也是通过关键字 class
加类名来声明类的,与一个类关联的代码必须用大括号括起来。
其定义的格式如下:
<?php
class Test
{
}
类名可以是任意数字和字母的组合,但不能以数字开头,一般采用首字母大写,而后每个单词首字母大写的形式,以便于阅读。
(二)实例化类(创建对象)
<?php
class Test
{
}
// 实例化类
$test = new Test();
(三)类中的变量
类中的变量,是指在 class
中声明的变量,也称成员变量(也称为属性),用于存放数据信息。
成员变量与普通变量相似,其定义的格式如下:
key $age = "23";
关键字 key
可以是 public
,protected
,private
,static
和 final
中的任意一个。
public
(公有):表示变量在类的内部和外部都可以被读取和修改。
protected
(受保护):表示变量可以被其自身以及其子类和父类读取和修改。
private
(私有):表示变量只能被其定义所在的类访问。
要访问成员变量,可以使用 “->” 符号连接对象和变量名。
<?php
class Test
{
// 声明成员变量
public $age = "23";
}
// 实例化类
$test = new Test();
// 读取成员变量 $age 的值
echo $test->age; // 23
也可以给成员变量赋值(public
修饰的):
<?php
class Test
{
public $age = "23";
}
// 实例化类
$test = new Test();
$test->age = 25;
echo $test->age; // 25
(四)类中的方法
类中的方法(又叫成员方法)是指在类中声明的特殊函数。
它与普通函数的区别在于,普通函数实现的是某个独立的功能;而成员方法是实现类的一个行为,是类的一部分。
1. 定义方法
<?php
class Test
{
public $age = "23";
// 定义 say 方法
public function say()
{
echo '这是say方法';
}
}
2. 调用方法
<?php
class Test
{
public $age = "23";
public function say()
{
echo '这是say方法';
}
}
// 实例化类
$test = new Test();
// 调用 say 方法
$test->say(); // 这是say方法
3. 方法的参数
<?php
class Test
{
public function say($name)
{
echo 'name的值为' . $name;
}
}
$test = new Test();
$test->say('tom'); // name的值为tom
如果声明类的方法时带有参数,而调用该方法时没有传递参数,或者参数数量不够,系统将会报错。如果参数数量超过方法本身定义参数的数量,PHP会忽略后面多出来的参数,不会报错。
4. 方法参数的默认值
<?php
class Test
{
public function say($name = '默认值')
{
echo 'name的值为' . $name;
}
}
$test = new Test();
// 调用时不传参
$test->say(); // name的值为默认值
5. 方法的返回值
<?php
class Test
{
public function say($name = '默认值')
{
// 使用 return 关键字返回值
return 'name的值为' . $name;
}
}
$test = new Test();
// 使用 echo 将返回值输出
echo $test->say('jack'); // name的值为jack
三、$this 操作符的使用
如果想在类内调用本类中的成员变量或成员方法,就要使用伪变量 $this->
。$this
就是指本身,所以 $this->
只能在类的内部使用。
class Test
{
public $name = '小明';
public $age = 23;
public function say()
{
// 使用 $this 访问本类中的 $name 变量
echo '我的名字是:' . $this->name;
echo '<br />';
// 使用 $this 访问本类中的 sayAge 方法
$this->sayAge();
}
public function sayAge()
{
echo '我的年龄是:' . $this->age;
}
}
$test = new Test();
$test->say(); // 我的名字是:小明 我的年龄是:23
四、构造方法
构造方法是一种特殊的方法,主要用于在创建对象时初始化对象,即为对象成员变量赋初始值,总与 new
运算符一起使用在创建对象的语句中。
class Test
{
// 定义成员变量
public $name;
public $age;
public $sex;
// 定义构造方法
public function __construct($name, $age, $sex)
{
// 为成员变量赋值
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}
}
// 实例化类并给构造方法传值
$test = new Test('小明', '25', '男');
echo $test->name;
echo $test->age;
echo $test->sex;
// 输出:小明 25 男
构造方法会在实例化类时自动执行。
注意:方法开始的 “__” 是两条下划线 “_” 。
五、析构方法
析构方法(析构函数)与构造方法正好相反,当对象结束其生命周期时(例如对象所在的函数已调用完毕),系统自动执行析构函数以释放内存。
class Test
{
public function __destruct()
{
echo '我是析构方法';
}
}
$test = new Test();
PHP 使用 “垃圾回收” 机制,自动清除不再使用的对象,释放内存。就是说即便不使用 unset 函数,系统也会自动调用析构方法,此处只是说明析构方法在何时会被调用。一般情况下不用手动创建析构方法。另外,当对象没有被引用时也同样会被销毁。
六、类的继承
类可以从其他类中扩展出来,扩展或派生出来的类拥有其基类(父类)的所有变量和函数,并包含所有派生类(子类)中定义的新功能,这称为继承。
继承是面向对象最重要的特点之一,可以实现对类的复用。
(一)如何继承类
PHP
是单继承的,一个扩充类只能继承一个基类,但一个父类却可以被多个子类所继承。
子类不能继承父类的私有属性和私有方法。
在 PHP 5
之后的版本中,类的方法可以被继承,类的构造函数也能被继承。
当子类被实例化时,PHP
会先在子类中查找构造方法,如果子类有自己的构造方法,PHP
会优先调用子类中的构造方法;当子类中没有时,PHP
会转而去调用父类中的构造方法。
继承使用关键字 extends
来声明:
<?php
// 定义父类 Test
class Test
{
public $name = '小明';
protected $age = 22;
private $sex = '男';
public function __construct()
{
echo '我是父类中的构造方法,当继承我的子类中没有构造方法时,我就执行。';
}
public function sayName()
{
echo $this->name;
}
protected function sayAge()
{
echo $this->age;
}
private function saySex()
{
echo $this->sex;
}
}
// 定义子类 Test2 继承父类 Test
class Test2 extends Test
{
public function __construct()
{
echo '我是子类中的构造方法,我会优先执行';
}
}
// 实例化子类
$test2 = new Test2();
echo $test2->name; // 输出:小明
echo $test2->age;
// 报错:Cannot access protected property Test2::$age
// protected 修饰的成员变量不能在类外使用
echo $test2->sex; // 无内容,private 修饰的成员变量不会被继承
$test2->sayName(); // 输出:小明
$test2->sayAge();
// 报错:Call to protected method Test::sayAge() from context
// protected 修饰的成员方法不能在类外使用
$test2->saySex(); // 无内容,private 修饰的成员方法不会被继承
$test2->sayAge2(); // 输出:22
// 在子类的内部,可以访问 protected 修饰的成员变量
总结权限修饰符的修饰范围:
(二)方法的重写
如果从父类继承的方法不能满足子类的需求,可以对其进行改写,该过程叫做方法的覆盖(override
),也称为方法的重写。
在对父类的方法进行重写时,子类中的方法必须与父类中对应的方法具有相同的名称。
<?php
class Test
{
protected function say()
{
echo '我是父类中的 say 方法。';
}
}
// 定义子类 Test2 继承父类 Test
class Test2 extends Test
{
// 此处的权限可以是 protected 或 public ,但不能是 private 。
// 可以加参数。
public function say($param = '')
{
echo '我是子类中的 say 方法。';
echo '父类中的 say 方法不满足我的要求,我被重写。';
}
}
$test2 = new Test2();
$test2->say();
// 我子父类中的 say 方法。父类中的 say 方法不满足我的要求,我被重写。
在重写方法时需注意以下几点:
- 子类中的覆盖方法不能使用比父类中被覆盖方法更严格的访问权限。在声明方法时如果没有定义访问权限,则权限默认为
public
。 - 子类中的覆盖方法可以拥有与父类中被覆盖方法不同的参数数量。
- 父类中的构造方法也可以被重写。
(三)调用父类中的方法
即使父类中的方法被重写,但父类中的方法仍保留其功能,可以使用 Parent
关键字调用父类中的方法。
<?php
class Test
{
protected function say()
{
echo '我是父类中的 say 方法。';
}
}
// 定义子类 Test2 继承父类 Test
class Test2 extends Test
{
public function say()
{
// 调用父类中的 say 方法
Parent::say();
echo '我子父类中的 say 方法。';
}
}
$test2 = new Test2();
$test2->say();
// 我是父类中的 say 方法。我子父类中的 say 方法。
七、静态变量(方法)
前面的内容中,类被当做模板,对象被当做活动组件,面向对象编程中的操作都是通过类的实例(对象)来完成的。
事实上,并不是所有的变量(方法)都要通过创建对象来调用。
声明类属性或方法为 static
(静态),就可以不实例化类而直接访问。
使用静态成员,除了不需要实例化对象外还有一个好处,就是在对象被销毁后,依然保存被修改的静态数据,以便下次继续使用。
<?php
class Test
{
public static $n = 1;
public static function test1()
{
// 在类内使用 self 关键字访问静态成员变量
echo self::$n;
}
}
// 类外访问静态成员变量 $n
echo Test::$n; // 1
// 类外访问静态成员方法 test1
Test::test1(); // 1
// 仍然可以实例化后调用静态方法
$t = new Test();
$t->test1(); // 1
静态属性不能通过一个类已实例化的对象来访问,但静态方法可以。由于静态方法不需要通过对象即可调用,所以伪变量 $this 在静态方法中不可用。静态属性不可以由对象通过 -> 操作符来访问。
<?php
class Test
{
public static $n = 1;
public static function test1()
{
echo '值为:' . self::$n;
self::$n++;
echo '<br />';
}
}
Test::test1(); // 值为:1
Test::test1(); // 值为:2
可以发现两次返回的结果是有联系的。
八、final 类和方法
继承为类的应用带来了巨大的灵活性。
通过覆写类和方法,调用同样的成员方法可以得到完全不同的结果,但有时候,也需要类或方法保持不变,这就需要用到 final
关键字。
<?php
// 使用 final 修饰类
final class Test {}
class Test2 extends Test {}
报错:Class Test2 may not inherit from final class (Test)
。
<?php
class Test
{
// 使用 final 修饰成员方法
final public function say()
{
echo 'function say';
}
}
class Test2 extends Test
{
// 子类重写父类 say 方法
public function say()
{
echo 'function say';
}
}
报错:Cannot override final method Test::say()
。
总结:
- 被
final
关键字修饰的类不能被继承。 - 被
final
关键字修饰的成员方法不能被重写。
当不希望一个类被继承时,可以将该类声明为 final
类型;当不希望类中的某个方法被子类重写时,可以设置其为 final
类型的方法。
九、常量属性
可以把在类中始终保持不变的值定义为常量。
PHP
中使用 const
关键字定义常量,在定义和使用常量时不需要使用 $
符号。
另外使用 const
定义的常量名称一般都大写。
类中常量的使用方法类似于静态变量,所不同的是它的值不能被改变。
<?php
class Test
{
// 定义常量
const VERSION = '1.0';
public function getVersion()
{
// 类内访问常量
echo self::VERSION;
}
}
// 类外访问常量
echo Test::VERSION; // 1.0
$t = new Test();
$t->getVersion(); // 1.0
常量的值必须是一个定值,不能是变量、类属性、数学运算的结果或函数调用。
十、abstract 类和方法
使用 abstract
关键字修饰的类或方法,称为抽象类或者抽象方法。
抽象类不能被直接实例化,只能作为其他类的父类来使用。
抽象方法只是声明了其调用方式(参数),不能定义其具体的功能实现。
子类可以继承它并通过实现其中的抽象方法,来使抽象类具体化。
任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么该类就必须被声明为抽象的。
抽象类可以像普通类那样去声明,但必须以分号而不是方法体结束。
<?php
// 使用 abstract 声明抽象类
abstract class Test
{
// 抽象方法只有方法的声明部分,没有方法体。
public abstract function say($name);
}
// 抽象类不能被实例化。
// $t = new Test();
// 报错:Cannot instantiate abstract class Test 。
// 抽象类只能作为其它类的父类来使用
class Test2 extends Test
{
// 继承一个抽象类的时候,父类中的所有抽象方法在子类中必须被重写。
// 这些方法的访问控制必须和父类中一样(或者更为宽松)。
public function say($name)
{
echo 'function say echo:' . $name;
}
}
$t = new Test2();
$t->say('hello'); // function say echo:hello
抽象方法只有方法的声明部分,没有方法体。
继承一个抽象类的时候,父类中的所有抽象方法在子类中必须被重写,这些方法的访问控制必须和父类中一样(或者更为宽松)。
例如某个抽象方法被声明为受保护的,那么子类中实现的方法就应该声明为受保护的或者公有的,而不能定义为私有的。
方法的调用方式必须匹配,即类型和所需参数数量必须一致。
十一、接口
PHP
只支持单继承,父类可以派生出多个子类,但一个子类只能继承自一个父类。
接口有效地解决了这一问题。
接口是一种类似于类的结构,使用它可以指定某个类必须实现哪些方法。
它只包含方法原型,不需要包含方法体。
这些方法原型必须被声明为 public
,不可以为 private
或 protected
。
<?php
// 声明接口 1
interface Test1
{
public function say1();
}
// 声明接口 2
interface Test2
{
public function say2();
}
// 实现接口
class Test implements Test1, Test2
{
public function say1()
{
echo '实现接口1中的say1方法';
}
public function say2()
{
echo '实现接口2中的say2方法';
}
}
$t = new Test();
$t->say1(); // 实现接口1中的say1方法
$t->say2(); // 实现接口2中的say2方法
实现接口的类中必须实现接口中定义的所有方法,除非该类被声明为抽象类。
十二、魔术方法
在 PHP
中以两个下划线 “__” 开头的方法被称为“魔术方法”,是系统预定义的方法。
如果需要使用这些魔术方法,必须先在类中定义。
构造方法 “__construct()” 和析构方法 “__destruct()” 都属于魔术方法。
魔术方法的作用、方法名、使用的参数列表和返回值都是规定好的,在使用这些方法时,需要用户自己根据需求编写方法体的内容。
使用时无须调用,它会在特定情况下自动被调用。
PHP 将所有以 __(两个下划线)开头的类方法保留为魔术方法。所以在定义类方法时,除魔术方法外,建议不要以 __ 为前缀。
(一)__set() 方法
在 PHP
程序试图给一个未定义的属性赋值时,就会调用 __set()
方法。
__set()
方法包含两个参数,分别表示变量名称和变量值,两个参数均不可省略。
<?php
class Test
{
// 定义 __set() 方法
public function __set($name, $value)
{
echo '正在试图给一个未定义的成员变量赋值,变量名称:' . $name . ',变量值:' . $value . '。';
}
}
$t = new Test();
$t->name = 'test'; // 正在试图给一个未定义的成员变量赋值,变量名称:name,变量值:test。
(二)__get() 方法
当需要调用一个未定义或不可见(私有)的成员变量时,可以使用 __get()
方法读取变量值。
__get()
方法有一个参数,表示要调用的变量名。
<?php
class Test
{
private $age = 21;
// 定义 __get() 方法
public function __get($name)
{
echo '正在试图获取一个未定义的成员变量的值,变量名称:' . $name . '。';
}
}
$t = new Test();
echo $t->name; // 正在试图获取一个未定义的成员变量的值,变量名称:name。
echo $t->age; // 获取私有成员变量也会触发 __get 方法。
(三)__call() 方法
程序试图调用不存在或不可见的成员方法时,PHP会自动调用 __call()
方法来存储方法名及其参数。
该方法包含 “方法名” 和 “方法参数” 两个参数,其中的 “方法参数” 以数组形式存在。
<?php
class Test
{
// 定义 _call() 方法
public function __call($name, $params)
{
echo '正在试图调用一个未定义的成员方法,方法名称:' . $name . '。';
echo '<br />';
echo '传递的参数有:';
print_r($params);
}
}
$t = new Test();
echo $t->say('a', 'b');
// 正在试图调用一个未定义的成员方法,方法名称:say。
// 传递的参数有:Array ( [0] => a [1] => b )
(四)__toString() 方法
__toString()
方法用于在使用 echo
或 print
输出对象时,将对象转化为字符串。
<?php
class Test
{
// 定义 __toString() 方法
public function __toString()
{
return 'string';
// 这里必须返回一个字符串
}
}
$t = new Test();
echo $t;