在面向对象的编程语言中,类是一个独立的程序单位,是具有相同属性和服务的一组对象的集合。它为属于该类的所有对象提供了统一的抽象描述,其内部包括成员属性和服务的方法两个主要部分。
[一些修饰类的关键字]class 类名{
类中成员;
}
下例中声明了一个Person类,在类中声明了三个成员属性:
class Person{
var $name;
var $age;
var $sex;
}
在Personl类的声明中可以看到,变量前面多使用一个关键字“var”来声明。声明变量不需要任何关键字修饰,而在类中声明成员属性时,变量前面一定要使用一个关键字,例如public、private、static等关键词来修饰。如果不需要有特定意义的修饰,就是用“var”关键字,一旦成员属性有其他的关键字修饰就需要去掉”var”。
class Person{
public $name;
private $age;
static $sex;
}
在对象中需要声明一些可以操作本对象成员属性的一些方法,来完成对象的一些行为,在类中直接声明的函数就称为成员方法。成员方法的声明和函数的声明完全一样,只不过可以加一些关键字的修饰来控制成员方法的一些权限。但声明的成员方法必须和对象相关,不能是一些没有意义的操作。
class Person{
function say(){
//方法体
}
function eat($food){
//方法体
}
private fucnction run(){
//方法体
}
}
$变量名=new 类名称([参数数列表]);
或
$变量名=new 类名称;
如果类中没有定义构造函数,PHP会自动创建一个不带参数的默认构造函数。
例如,通过上一节中声明的“Person”和“Phone”两个类,分别实例化出几个对象。
class Phone{
//类中成员
}
class Person{
//类中成员
}
$person1=new Person();
$person2=new Person();
$person3=new Person();
$phone1=new Phone();
$phone2=new Phone();
$phone3=new Phone();
?>
一个类可以实例化出多个对象,每个对象都是独立的。
栈的特点是空间小但被CPU访问的速度快,是用户存放程序中临时创建的变量。由于栈的后进先出特点,所以栈特别方便用来保存和恢复调用现场,从这个意义上讲,我们可以把堆栈看成一个临时数据寄存、交换的内存区,用于存储占用空间长度不变并且占用空间小的数据类型的内存段。
堆是 用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。用于存储数据长度可变或占用内存比较大的数据。
数据段用来存放可执行文件中已初始化全局变量,换句话说就是存放程序静态分配的变量。
代码段是用来存放可执行文件的操作指令,也就是说它是可执行程序在内存中的镜像。代码段需要防止在运行时被非法修改,所以只准许读取操作,而不允许写入(修改)操作。
$引用名=new 类名称([参数数列表]);
$引用名->成员属性=值;
echo $引用名->成员属性;
$引用名->成员方法;
在下例中声明了一个Person类,其中包含三个成员属性和两个成员方法,并通过Person类实例化出三个对象,而且使用运算符“->”分别访问三个对象中的每个成员属性和成员方法。
class Person{
var $name;
var $sex;
var $age;
function say(){
echo "这个人在说话
";
}
function run(){
echo "这个人在走路
";
}
}
$person1=new Person();
$person1->name="张三";
$person1->sex="男";
$person1->age=20;
//下面三行诗访问$person1对象中的成员属性
echo "person1对象的名字是:".$person1->name."
";
echo "person1对象的名字是:".$person1->sex."
";
echo "person1对象的名字是:".$person1->age."
";
//下面两行访问$person1对象中的方法
$person1->say();
$person1->run();
?>
对象一旦被创建,在对象中的每个成员方法里面都会存在一个特殊的对象引用“$this”。成员方法属于哪个对象,$this
引用就代表哪个对象,专门用来完成对象内部成员之间的访问。
class Person{
var $name;
var $sex;
var $age;
function say(){
echo "我的名字:".$this->name.",性别:".$this->sex.",年龄:".$this->age."
";
}
function run(){
echo $this->name."在走路
";
}
}
$person1=new Person();
$person1->name="张三";
$person1->sex="男";
$person1->age=20;
$person1->say();
$person1->run();
?>
void __construct ([ mixed $args [, $... ]] )
Note: 如果子类中定义了构造函数则不会隐式调用其父类的构造函数。要执行父类的构造函数,需要在子类的构造函数中调用 parent::__construct()
。如果子类没有定义构造函数则会如同一个普通的类方法一样从父类继承(假如没有被定义为 private 的话)。
在PHP中,同一个类中只能声明一个构造方法,原因是构造方法名称是固定的,在PHP中不能声明同名的两个函数,所以也就没有构造方法重载。但可以在声明构造方法时使用默认参数,实现其他面向对象的编程语言中构造方法重载的功能。
class BaseClass {
function __construct() {
print "In BaseClass constructor\n";
}
}
class SubClass extends BaseClass {
function __construct() {
parent::__construct();
print "In SubClass constructor\n";
}
}
class OtherSubClass extends BaseClass {
// inherits BaseClass's constructor
}
// In BaseClass constructor
$obj = new BaseClass();
// In BaseClass constructor
// In SubClass constructor
$obj = new SubClass();
// In BaseClass constructor
$obj = new OtherSubClass();
?>
为了实现向后兼容性,如果 PHP 5 在类中找不到 __construct()
函数并且也没有从父类继承一个的话,它就会尝试寻找旧式的构造函数,也就是和类同名的函数。因此唯一会产生兼容性问题的情况是:类中已有一个名为 __construct()
的方法却被用于其它用途时。
与其它方法不同,当 __construct()
被与父类 __construct()
具有不同参数的方法覆盖时,PHP 不会产生一个 E_STRICT
错误信息。
自 PHP 5.3.3 起,在命名空间中,与类名同名的方法不再作为构造函数。这一改变不影响不在命名空间中的类。
namespace Foo;
class Bar {
public function Bar() {
// treated as constructor in PHP 5.3.0-5.3.2
// treated as regular method as of PHP 5.3.3
}
}
?>
与构造方法相对应的就是析构方法,PHP将在对象被销毁前自动调用这个歌方法。
void __destruct ( void )
PHP 5 引入了析构函数的概念,这类似于其它面向对象的语言,如 C++。析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。
class MyDestructableClass {
function __construct() {
print "In constructor\n";
$this->name = "MyDestructableClass";
}
function __destruct() {
print "Destroying " . $this->name . "\n";
}
}
$obj = new MyDestructableClass();
?>
和构造函数一样,父类的析构函数不会被引擎暗中调用。要执行父类的析构函数,必须在子类的析构函数体中显式调用 parent::__destruct()。此外也和构造函数一样,子类如果自己没有定义析构函数则会继承父类的。
析构函数即使在使用 exit() 终止脚本运行时也会被调用。在析构函数中调用 exit() 将会中止其余关闭操作的运行。
Note:
析构函数在脚本关闭时调用,此时所有的 HTTP 头信息已经发出。脚本关闭时的工作目录有可能和在 SAPI(如 apache)中时不同。
Note:
试图在析构函数(在脚本终止时被调用)中抛出一个异常会导致致命错误。
封装性是面向对象编程中的三大特性之一,封装性就是把对象的成员属性和成员方法结合成一个独立的相同单位,并尽可能隐蔽对象的内部细节,包含如下两个含义:
把对象的全部成员属性和全部成员方法结合在一起,形成一个不可分割的独立单位(即对象)。
信息隐蔽,即尽可能隐蔽对象的内部细节,对外形成一个边界,只保留有限的对外接口使之与外部发生联系。
只要在声明成员属性或成员方法时,使用private关键字修饰就实现了对成员的封装。封装后的成员在对象的外部不能被访问,但在对象内部的成员方法中可以访问到自己对象内部被封装的成员属性和被封装的成员方法。
class Person{
private $name;
private $sex;
private $age;
function __construct($name="",$sex="男",$age=1){
$this->name=$name;
$this->sex=$sex;
$this->age=$age;
}
function run(){
echo $this->name."在走路时".$this->leftLeg()."再".$this->rightLeg()."
";
}
private function leftLeg(){
return "迈左腿";
}
private function rightLeg(){
return "迈右腿";
}
}
$person1=new Person();
$person1->run();
$person1->name="李四";
echo $person1->age;
$person1->leftLeg();
?>
在上面的程序中,使用Private关键字将成员属性和成员方法封装成私有属性之后,就不可以在对象的外部通过对象的引用直接访问了,试图去访问私有成员将发生错误。如果在成员属性前面使用了其他的关键词修饰,,就不要在使用“var”关键字修饰了。
类属性必须定义为公有,受保护,私有之一。如果用 var 定义,则被视为公有。
/**
* Define MyClass
*/
class MyClass
{
public $public = 'Public';
protected $protected = 'Protected';
private $private = 'Private';
function printHello()
{
echo $this->public;
echo $this->protected;
echo $this->private;
}
}
$obj = new MyClass();
echo $obj->public; // 这行能被正常执行
echo $obj->protected; // 这行会产生一个致命错误
echo $obj->private; // 这行也会产生一个致命错误
$obj->printHello(); // 输出 Public、Protected 和 Private
/**
* Define MyClass2
*/
class MyClass2 extends MyClass
{
// 可以对 public 和 protected 进行重定义,但 private 而不能
protected $protected = 'Protected2';
function printHello()
{
echo $this->public;
echo $this->protected;
echo $this->private;
}
}
$obj2 = new MyClass2();
echo $obj2->public; // 这行能被正常执行
echo $obj2->private; // 未定义 private
echo $obj2->protected; // 这行会产生一个致命错误
$obj2->printHello(); // 输出 Public、Protected2 和 Undefined
?>
Note: 为了兼容性考虑,在 PHP 4 中使用 var 关键字对变量进行定义的方法在 PHP 5 中仍然有效(只是作为 public 关键字的一个别名)。在 PHP 5.1.3 之前的版本,该语法会产生一个 E_STRICT 警告。
类中的方法可以被定义为公有,私有或受保护。如果没有设置这些关键字,则该方法默认为公有。
/**
* Define MyClass
*/
class MyClass
{
// 声明一个公有的构造函数
public function __construct() { }
// 声明一个公有的方法
public function MyPublic() { }
// 声明一个受保护的方法
protected function MyProtected() { }
// 声明一个私有的方法
private function MyPrivate() { }
// 此方法为公有
function Foo()
{
$this->MyPublic();
$this->MyProtected();
$this->MyPrivate();
}
}
$myclass = new MyClass;
$myclass->MyPublic(); // 这行能被正常执行
$myclass->MyProtected(); // 这行会产生一个致命错误
$myclass->MyPrivate(); // 这行会产生一个致命错误
$myclass->Foo(); // 公有,受保护,私有都可以执行
/**
* Define MyClass2
*/
class MyClass2 extends MyClass
{
// 此方法为公有
function Foo2()
{
$this->MyPublic();
$this->MyProtected();
$this->MyPrivate(); // 这行会产生一个致命错误
}
}
$myclass2 = new MyClass2;
$myclass2->MyPublic(); // 这行能被正常执行
$myclass2->Foo2(); // 公有的和受保护的都可执行,但私有的不行
class Bar
{
public function test() {
$this->testPrivate();
$this->testPublic();
}
public function testPublic() {
echo "Bar::testPublic\n";
}
private function testPrivate() {
echo "Bar::testPrivate\n";
}
}
class Foo extends Bar
{
public function testPublic() {
echo "Foo::testPublic\n";
}
private function testPrivate() {
echo "Foo::testPrivate\n";
}
}
$myFoo = new foo();
$myFoo->test(); // Bar::testPrivate
// Foo::testPublic
?>
同一个类的对象即使不是同一个实例也可以互相访问对方的私有与受保护成员。这是由于在这些对象的内部具体实现的细节都是已知的。
class Test
{
private $foo;
public function __construct($foo)
{
$this->foo = $foo;
}
private function bar()
{
echo 'Accessed the private method.';
}
public function baz(Test $other)
{
// We can change the private property:
$other->foo = 'hello';
var_dump($other->foo);
// We can also call the private method:
$other->bar();
}
}
$test = new Test('test');
$test->baz(new Test('other'));
?>
以上例程会输出:
string(5) "hello"
Accessed the private method.
对象中的成员属性一旦被private关键字封装成私有之后,就只能在对象内部的成员方法中使用,不能被对象外部直接赋值,也不能在对象外部直接获取私有属性的值。
class Person{
private $name;
private $sex;
private $age;
function __construct($name="",$sex="男",$age=1){
$this->name=$name;
$this->sex=$sex;
$this->age=$age;
}
public function getName(){
return $this->name;
}
public function setSex($sex){
if(sex=="男"||$sex=="女")
$this->sex=$sex;
}
public function setAge($age){
if($age>150||$age<0)
return;
$this->age=$age;
}
public function getAge(){
if($this->age>30)
return $this->age-10;
else
return $this->age;
}
function say(){
echo "我的名字:".$this->name.",性别:".$this->sex.",年龄:".$this->age."
";
}
}
$person1=new Person("王五","男",40);
echo $person1->getName()."
";
$person1->setSex("女");
$person1->setAge(200);
echo $person1->getAge()."
";
$person1->say();
?>
该程序运行后输出的结果为:
王五
30
我的名字:王五,性别:女,年龄:40
以下内容参照PHP官方文档
__construct()
,__destruct()
,__call()
, __callStatic()
, __get()
, __set()
,__isset()
,__unset()
,__sleep()
, __wakeup(),__toString()
,__invoke()
,__set_state()
,__clone()
和 __debugInfo()
等方法在 PHP 中被称为”魔术方法”(Magic methods)。在命名自己的类方法时不能使用这些方法名,除非是想使用其魔术功能。
PHP 将所有以__
(两个下划线)开头的类方法保留为魔术方法。所以在定义类方法时,除了上述魔术方法,建议不要以 __ 为前缀。
void __set(string name,mixed value)
该方法的作用是在程序运行过程中为私有的成员属性设置值,他不需要有任何返回值。但它需要两个参数,第一个参数需要传入在为私有属性设置值时的属性名,第二个参数则需要传入为属性设置的值。
class Person{
private $name;
private $sex;
private $age;
function __construct($name="", $sex="男", $age=1){
$this->name=$name;
$this->sex=$sex;
$this->age=$age;
}
private function __set($propertyName, $propertyValue){
if($propertyName=="sex"){
if(!($propertyValue=="男"||$propertyValue=="女"))
return;
}
if($propertyName=="age"){
if($propertyValue>150||$propertyValue<0)
return;
}
$this->$propertyName=$propertyValue;
}
public function say(){
echo "我的名字叫:".$this->name.",性别:",$this->sex.",年龄:".$this->age."
";
}
}
$person1=new Person("张三","男",20);
//下面三行自动调用了__set()函数,将属性名分别传给第一个参数,将属性值传给第二个参数
$person1->name="李四";
$person1->sex="女";
$person1->age="80";
$person1->sex="保密"; //“保密”是个非法值,所以这句给私有属性sex赋值失败
$person1->age=800;//800是个非法值,这条语句私有属性age赋值失败
$person1->say();
?>
如果在类中声明了__get()方法,则直接在对象的外部获取私有属性的值时,会自动调用此方法,返回私有属性的值。
mixed __get(string name)
该方法的作用是在程序运行过程中,通过可以在对象的外部获取私有成员属性的值。
class Person{
private $name;
private $sex;
private $age;
function __construct($name="", $sex="男", $age=1){
$this->name=$name;
$this->sex=$sex;
$this->age=$age;
}
private function __get($propertyName){
if($propertyName=="sex"){
return "保密";
}
else if($propertyName=="age"){
if($this->age>30)
return $this->age-10;
else
return $this->$propertyName;
}else{
return $this->$propertyName;
}
}
}
$person1=new Person("张三","男",40);
echo "姓名:".$person1->name."
";
echo "性别:".$person1->sex."
";
echo "年龄:".$person1->age."
";
?>
但如果在对象中存在“__isset()”方法,当在类外部使用“isset()”函数来测定对象里面的私有属性时,就会自动调用类里面的__isset()
方法帮助我们完成测定的操作。
bool __isset(string name) //传入对象中的成员属性名作为参数,返回测定后的结果
如果类中添加此方法,在对象的外部使用isset
方法测定对象中的成员时,就会自动调用对象中的__isset()
方法,间接地帮助我们完成对对象中私有成员属性的测定。
unset()
函数的作用是删除指定的变量,参数为要删除的变量名称,也可以使用这个函数在函数外部删除对象属性,但这个对象中的成员属性必须是公有的才可以直接删除。
void __unset(string name) //传入对象中的成员属性名作为参数,可以将私有成员属性删除
如果没有在类中加入这个方法,就不能删除对象中任何的私有成员属性。
class Person{
private $name;
private $sex;
private $age;
function __construct($name="",$sex="男",$age=1){
$this->name=$name;
$this->sex=$sex;
$this->age=$age;
}
private function __isset($propertyName){
if($propertyName=="name"){
return false;
}
return isset($this->$propertyName);
}
private function __unset($propertyName){
if($propertyName=="name"){
return;
}
unset($this->$propertyName);
}
public function say(){
echo "我的名字:".$this->name.",性别:".$this->sex.",年龄:".$this->age."
";
}
}
$person1=new Person("张三","男",30);
var_dump(isset($person1->name));
var_dump(isset($person1->sex));
var_dump(isset($person1->age));
var_dump(isset($person1->id));
unset($person1->name);
unset($person1->sex);
unset($person1->age);
?>
在PHP和Java语言一样没有多继承,只能使用单继承模式。
当类中的成员被定义为private,对于同一类里的成员都没有访问权限,但对于该类的外部代码是不允许改变甚至是操作的。
class MyClass {
private $var1=100;
private function printHello(){
echo "Hello
";
}
}
class MyClass2 extends MyClass {
function useProperty(){
echo "输出从父类继承过来的成员属性值".$this->var1."
";
$this->printHello();
}
}
$subObj=new MyClass2();
$subObj->useProperty();
?>
被修饰为protected的成员,对于该类的子类及子类的子类都有访问权限,但不能被该类外部代码访问。
class MyClass{
protected $var1=100;
protected function printHello() {
echo "hello
";
}
}
class MyClass2 extends MyClass{
function useProperty(){
echo "输出从父类继承过来的成员属性:".$this->var1."
";
}
}
$subObj=new MyClass2();
$subObj->useProperty();
echo $subObj->var1;
?>
class Person {
protected $name;
protected $sex;
protected $age;
function __construct($name="",$sex="男",$age=1){
$this->name=$name;
$this->sex=$sex;
$this->age=$age;
}
function say(){
echo "我的名字:".$this->name.",性别:".$this->sex.",年龄:".$this->age."
";
}
}
class Student extends Person{
private $school;
function __construct($name="",$sex="男",$age=1,$school=""){
parent::__construct($name,$sex,$age);
$this->school=$school;
}
function study(){
echo $this->name."正在".$this->school."学习
";
}
function say()
{
parent::say();
echo "在:".$this->school."学校上学
";
}
}
$student=new Student("张三","男",20,"edu");
$student->say();
?>
final关键字的作用:
- 使用final标识的类,不能被继承
- 在类中使用final标识的成员方法,在类中不能被覆盖
使用static关键字可以将类中的成员标识为静态的,既可以用来表示成员属性,也可以用来标识成员方法。
类名::静态成员属性;
类名::静态成员方法名();
self::静态成员属性名;
self::静态成员方法名();
单态模式的主要作用是保证在面向对象编程中,一个类只能有一个实例对象存在。
class DB{
private static $obj=null;
private function __construct(){
echo "连接数据库成功
";
}
static function getInstance(){
if(is_null(self::$obj))
self::$obj=new self();
return self::$obj;
}
function query($sql){
echo $sql;
}
}
$db=DB::getInstance();
$db->query("select * from user");
?>
将类中的成员属性使用const关键字标识为常量,其访问的方式和静态成员一样,都是通过类名或在成员方法中使用self关键字访问,也不会出现错误。注意:使用const声明的变量名称前不要使用“$”符号,而且常量名称通常都是大写的。
使用这个关键字可以确定一个对象是类的实例、类的子类,还是实现了某个特定接口,并进行相应的操作。
$man=new Person();
...
if($man instanceof Person)
echo '$man是Person类的实例对象';
这里需要注意亮点,一点是类名没有任何定界符(不适用引号),使用定界符将会导致语法错误;另外,如果比较失败,脚本将推出执行。
如果需要对克隆后的副本对象在克隆时重新为成员属性赋值,则可以在类中声明一个魔术方法“__clone()”。该方法是在对象克隆时自动调用的。
魔术方法__toString()是快速获取对象的字符串表示的最便捷的方式,它是在直接输出对象引用时自动调用的方法。
class TestClass{
private $foo;
function __construct($foo){
$this->foo=$foo;
}
public function __toString(){
return $this->foo;
}
}
$obj=new TestClass('Hello');
echo $obj;
?>
在PHP中,可以在类中添加一个“魔术”方法__call(),则调用对象中不存在的方法时就会自动调用该方法,并且程序也可以继续向下执行。__call()方法需要两个参数:第一个参数是调用不存在的方法时,接收这个方法名字字符串;而参数列表则以数组的形式传递到__call()方法的第二个参数中。
当你尝试使用一个PHP没有组织到的类时,它会寻找一个__autoload()的全局函数,如果存在这个函数,PHP会用一个参数调用它,参数即类的名称。不管调用时是大写还是小写,PHP将返回名称的小写。所以做项目时候,需要按照一定规则命名,也可以加上统一的前缀或后缀形成文件名。
对象通过写出描述自己状态的数值来记录自己,这个过程称为对象的串行化。串行化就是把整个对象转化成二进制字符串。
- 对象需要在网络中传输时,将对象串行化成二进制串后在网络中传输
- 对象需要持久保存时,将对象串行化后写入文件或是数据库中。
这里需要补充,暂空
使用抽象类就包含了继承关系,它是为它的子类定义公共接口,将它的操作交给子类去实现。就是将抽象类作为子类重载的模板使用,定义抽象类就相当于定义了一种规范,这种规范要求子类去遵循。
因为PHP只支持单继承,也就是说每个类只能继承一个父类,当声明的新类继承抽象类实现模板以后,它就不能再有其他父类了。接口中声明的方法必须都是抽象方法,另外不能在接口中声明变量,只能使用const关键字声明为常量的成员属性,而且接口中所有成员都必须有public的访问权限。
interface 接口名称{
//常量成员
//抽象方法
}
接口所有方法都要求是抽象方法,所以就不需要再方法前使用abstract关键字标识了。