前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >php面试之面向对象的相关知识(下篇)

php面试之面向对象的相关知识(下篇)

作者头像
友儿
发布2022-09-11 15:21:36
发布2022-09-11 15:21:36
57800
代码可运行
举报
文章被收录于专栏:友儿友儿
运行总次数:0
代码可运行

面向对象分上下篇,这里下篇涉及到的内容有:六、什么是对象克隆?  七、this、self和parent的区别是什么?  八、抽象类与接口有什么区别与联系?  九、PHP面向对象的常考面试题讲解

六、什么是对象克隆? 对于对象而言,PHP用的是引用传递,也就是说,对象间的赋值操作只是赋值了一个引用的值,而不是整个对象的内容,下面通过一个例子来说明引用传递存在的问题:

<?php

class My_Class {

代码语言:javascript
代码运行次数:0
复制
public $color;

}

$obj1 = new My_Class (); $obj1->color = "Red"; $obj2 = $obj1; $obj2-&gt;color ="Blue"; //$obj1->color的值也会变成"Blue"

?>

因为PHP使用的是引用传递,所以在执行obj1和obj2都是指向同一个内存区(它们在内存中的关系如下图所示),任何一个对象属性的修改对另外一个对象也是可见的。

在很多情况下,希望通过一个对象复制出一个一样的但是独立的对象。PHP提供了clone关键字来实现对象的复制。如下例所示:

<?php

代码语言:javascript
代码运行次数:0
复制
class My_Class {
  public $color;

}

$obj1 = new My_Class ();
$obj1-&gt;color = "Red";
$obj2 = clone $obj1;
$obj2-&gt;color ="Blue";     //此时$obj1-&gt;color的值仍然为"Red"

?>

obj2 = clone obj1把obj1的整个内存空间复制了一份存放到新的内存空间,并且让obj2指向这个新的内存空间,通过clone克隆后,它们在内存中的关系如下图所示。

此时对obj2的修改对obj1是不可见的,因为它们是两个独立的对象。 在学习C++的时候有深拷贝和浅拷贝的概念,显然PHP也存在相同的问题,通过clone关键字克隆出来的对象只是对象的一个浅拷贝,当对象中没有引用变量的时候这种方法是可以正常工作的,但是当对象中也存在引用变量的时候,这种拷贝方式就会有问题,

下面通过一个例子来进行说明:

<?php

代码语言:javascript
代码运行次数:0
复制
class My_Class {
    public $color;

}

$c ="Red";
$obj1 = new My_Class ();
$obj1-&gt;color =&amp;$c;   //这里用的是引用传递
$obj2 = clone $obj1;  //克隆一个新的对象
$obj2-&gt;color="Blue";  //这时,$obj1-&gt;color的值也变成了"Blue"

?> 在这种情况下,这两个对象在内存中的关系如下图所示。

从上图中可以看出,虽然obj1与obj2指向的对象占用了独立的内存空间,但是对象的属性color仍然指向一个相同的存储空间,因此当修改了obj2->color的值后,意味着c的值被修改,显然这个修改对obj1也是可见的。这就是一个非常典型的浅拷贝的例子。为了使两个对象完全独立,就需要对对象进行深拷贝。那么如何实现呢,PHP提供了类似于__clone方法(类似于C++的拷贝构造函数)。把需要深拷贝的属性,在这个方法中进行拷贝:

使用示例如下:

<?php

代码语言:javascript
代码运行次数:0
复制
class My_Class {

  public $color;

  public function __clone() {
    $this-&gt;color = clone $this-&gt;color;

  }

}

$c ="Red";
$obj1 = new My_Class ();
$obj1-&gt;color =&amp;$c;   
$obj2 = clone $obj1;  
$obj2-&gt;color="Blue";  //这时,$obj1-&gt;color的值仍然为"Red"

?> 通过深拷贝后,它们在内存中的关系如图1-4所示。

通过在__clone方法中对对象的引用变量color进行拷贝,使obj1与obj2完全占用两块独立的存储空间,对obj2的修改对obj1也不可见。

七、this、self和parent的区别是什么? this、self、parent三个关键字从字面上比较好理解,分别是指这、自己、父亲。其中,this指的是指向当前对象的指针(暂用C语言里面的指针来描述),self指的是指向当前类的指针,parent指的是指向父类的指针。 以下将具体对这三个关键字进行分析。

this关键字

<?php class UserName {

代码语言:javascript
代码运行次数:0
复制
private $name;    // 定义成员属性
function __construct($name) {
    $this-&gt;name = $name; // 这里已经使用了this指针
}

// 析构函数
function __destruct() {

}

// 打印用户名成员函数
function printName() {
    print ($this-&gt;name."

") ; // 又使用了this指针

代码语言:javascript
代码运行次数:0
复制
}

}

// 实例化对象nameObject = new UserName ( "heiyeluren" );// 执行打印nameObject2 = new UserName ( "PHP5" );// 执行打印nameObject 对象,那么执行第12行打印的时候就把print(this->name)变成了print (

对于第二个实例化对象,print( $this- &gt;name )变成了print( $nameObject2->name ),于是就输出了"PHP5"。

所以,this就是指向当前对象实例的指针,不指向任何其他对象或类。

2.self关键字 先要明确一点,self是指向类本身,也就是self是不指向任何已经实例化的对象,一般self用来访问类中的静态变量。

<?php

代码语言:javascript
代码运行次数:0
复制
 class Counter {
      // 定义属性,包括一个静态变量
      private  static  $firstCount = 0;
      private  $lastCount;
      
      // 构造函数
      function __construct() {
          // 使用self来调用静态变量,使用self调用必须使用::(域运算符号)
          $this-&gt;lastCount = ++ selft::$firstCount;
      }

      // 打印lastCount数值
      function printLastCount() {
          print ($this-&gt;lastCount) ;
      }

  }

   // 实例化对象
  $countObject = new Counter ();
  $countObject-&gt;printLastCount (); // 输出 1

?>上述示例中,在第4行定义了一个静态变量

3.parent关键字 parent是指向父类的指针,一般使用parent来调用父类的构造函数。

<?php // 基类

class Animal {

代码语言:javascript
代码运行次数:0
复制
// 基类的属性
public $name; // 名字

// 基类的构造函数
public function __construct($name) {
    $this-&gt;name = $name;

}

}

// 派生类 class Person extends Animal // Person类继承了Animal类 {

代码语言:javascript
代码运行次数:0
复制
public $personSex; // 性别
public $personAge; // 年龄

// 继承类的构造函数
function __construct($personSex, $personAge) {

    parent::__construct ( "heiyeluren" ); // 使用parent调用了父类的构造函数
    $this-&gt;personSex = $personSex;
    $this-&gt;personAge = $personAge;
}

function printPerson() {

    print ($this-&gt;name . " is " . $this-&gt;personSex . ",this year " . $this-&gt;personAge) ;
}

}

// 实例化Person对象 $personObject = new Person ( "male", "21" );

// 执行打印 $personObject->printPerson (); // 输出:heiyeluren is male,this year 21

?> 上例中,成员属性都是public的,特别是父类的,是为了供继承类通过this来访问。第18行: parent::__construct( "heiyeluren" ),使用了parent来调用父类的构造函数进行对父类的初始化,因为父类的成员都是public的,于是就能够在继承类中直接使用 this来访问从父类继承的属性。

八、抽象类与接口有什么区别与联系? 抽象类应用的定义如下:

abstract class ClassName{

}

抽象类具有以下特点: 1)定义一些方法,子类必须实现父类所有的抽象方法,只有这样,子类才能被实例化,否则子类还是一个抽象类。 2)抽象类不能被实例化,它的意义在于被扩展。 3)抽象方法不必实现具体的功能,由子类来完成。 4)当子类实现抽象类的方法时,这些方法的访问控制可以和父类中的一样,也可以有更高的可见性,但是不能有更低的可见性。例如,某个抽象方法被声明为protected的,那么子类中实现的方法就应该声明为protected或者public的,而不能声明为private。 5)如果抽象方法有参数,那么子类的实现也必须有相同的参数个数,必须匹配。但有一个例外:子类可以定义一个可选参数(这个可选参数必须要有默认值),即使父类抽象方法的声明里没有这个参数,两者的声明也无冲突。

下面通过一个例子来加深理解:

<?php

代码语言:javascript
代码运行次数:0
复制
abstract class A{

    abstract protected function greet($name);

}

class B extends A {

    public function greet($name, $how="Hello ") {
        echo $how.$name."

";

代码语言:javascript
代码运行次数:0
复制
    }

}

$b = new B;
$b-&gt;greet("James");
$b-&gt;greet("James","Good morning ");

?> 程序的运行结果为

Hello James Good morning James

定义抽象类时,通常需要遵循以下规则: 1)一个类只要含有至少一个抽象方法,就必须声明为抽象类。 2)抽象方法不能够含有方法体。

接口可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。在PHP中,接口是通过interface关键字来实现的,与定义一个类类似,唯一不同的是接口中定义的方法都是公有的而且方法都没有方法体。接口中所有的方法都是公有的,此外接口中还可以定义常量。接口常量和类常量的使用完全相同,但是不能被子类或子接口所覆盖。要实现一个接口,可以通过关键字implements来完成。实现接口的类中必须实现接口中定义的所有方法。虽然PHP不支持多重继承,但是一个类可以实现多个接口,用逗号来分隔多个接口的名称。

下面给出一个接口使用的示例:

<?php

interface Fruit {

代码语言:javascript
代码运行次数:0
复制
 const MAX_WEIGHT = 3;   //静态常量
 function setName($name);
 function getName();

}

class Banana implements Fruit

{

代码语言:javascript
代码运行次数:0
复制
 private $name;

 function getName() {

    return $this-&gt;name;
 }

 function setName($_name) {

    $this-&gt;name = $_name;
 }

}

b = new Banana(); //创建对象 b->setName("香蕉"); echo

?> 程序的运行结果为

香蕉 3 接口和抽象类主要有以下区别: 抽象类:PHP5支持抽象类和抽象方法。被定义为抽象的类不能被实例化。任何一个类,如果它里面至少有一个方法是被声明为抽象的,那么这个类就必须被声明为抽象的。被定义为抽象的方法只是声明了其调用方法和参数,不能定义其具体的功能实现。抽象类通过关键字abstract来声明。

接口:可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。在这种情况下,可以通过interface关键字来定义一个接口,在接口中声明的方法都不能有方法体。

二者虽然都是定义了抽象的方法,但是事实上两者区别还是很大的,主要区别如下: 1)对接口的实现是通过关键字implements来实现的,而抽象类继承则是使用类继承的关键字extends实现的。 2)接口没有数据成员(可以有常量),但是抽象类有数据成员(各种类型的成员变量),抽象类可以实现数据的封装。 3)接口没有构造函数,抽象类可以有构造函数。 4)接口中的方法都是public类型,而抽象类中的方法可以使用private、protected或public来修饰。 5)一个类可以同时实现多个接口,但是只能实现一个抽象类。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • this关键字
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档