PHP基础之面向对象篇

前言

前面写的都是运算符、流程控制、排序查找等,下面说一说面向对象的一些内容。这是前面写的,有兴趣可以去看一看。
PHP入门之类型与运算符
PHP入门之流程控制
PHP入门之函数
PHP入门之数组
PHP基础之排序
PHP基础之查找
接下来写一下关于面向对象的内容。

类与对象基本概念

用一个案例入门:

<?php
//创建一个对象
class cat {
    public $name;
    public $age;
    public $color;
 }
//创建一个猫
$cat1= new cat;
$cat1->name="小刘";
$cat1->age=18;
$cat1->color="yellow";
//再创建一个猫
$cat2= new cat;
$cat2->name="小陈";
$cat2->age=16;
$cat2->color="pink";
//输出两个猫的信息
if ($cat1->name="小刘"){
    echo $cat1->name."||".$cat1->age."||".$cat1->color.'<br/>';
}if ($cat2->name="小陈"){
    echo $cat2->name."||".$cat2->age."||".$cat2->color;
}
?>

总结几句话:

  • ①类是抽象的,代表一类事物。
  • ②对象是具体,是类的一个具体实例。
  • ③类是对象的模板,对象是类的一个个具体实例。
    类的基本格式
    class 类名{
    成员属性(变量);

}
成员属性是从某个事物提取出来的,它可以是 基本数据类型,也可以是复合数据类型(数组,对象)
如何创建对象?
$对象名=new 类名();
$对象名=new 类名; //两种方式都可以
对象如何访问(使用)对象的属性?
$对象名->属性名;

对象在内存中存在形式

对象在内存中如何存在?
用下面代码说明:

<?php
class Person {
    public $name;
    public $age;
}
$p1= new Person();
$p1->name="小红";
$p1->age=18;
$p2=$p1;
echo $p1->name.'<br/>';
echo $p2->age.'<br/>';

?>

现在画一下内存图:

name="小红";age=18;变量$p1->name和age时就会由栈区指向堆区。
指向得是地址

函数接收对象时候,究竟接收得是值,还是地址?
看一段代码:

<?php
class Person {
    public $name;
    public $age;
}
$p1= new Person();
$p1->name="小红";
$p1->age=18;            #我们发现输出结果为大红,所以,函数接收对象时候,接收得是地址。
function test ($p){
    $p->name="大红";
}
test($p1);
echo $p1->name.'<br/>';
?>

如果给函数传递的是基本数据类型(整行,浮点型,布尔型),传递的是什么?
默认情况下传递的是值。如果希望传地址,那就加上&符。
如果给一个函数传递的是一个数组,则默认情况下是传值。
举个例子:

<?php
$arr=array($a1,$a2);
$a1=array(3,5,8);
$a2=array(5,7,9);
var_dump($arr);
?>

可以输出结果吗?答案是无法输出结果。会报变量没有定义的错误。因为是传值,所以第一行的$a1和第二行的$a1是两码事。
如果换一下顺序,就可以了。

<?php
$a1=array(3,5,8);
$a2=array(5,7,9);
$arr=array($a1,$a2);
var_dump($arr);
?>

这样就可以输出了,数组有值了。

构造函数

什么是构造函数(方法)?
想要知道什么是构造函数,我们先看一个需求,之前我们创建一个对象的时候,是创建好之后,再给对象的属性进行赋值,如果我们再创建对象的时候就直接给属性赋值,这样该如何做呢?下面我们就要引入构造函数了。
上面的问题,我们只需要定义一个构造函数就可以了。构造函数是类的一种特殊的函数,它的主要作用是完成对新对象的初始化。
构造函数特点:
①没有返回值。
②在创建一个类的新对象时,系统会自动的调用该类的构造函数完成对新对象的初始化。
用一个小案例说明:

<?php
class Person{
    public $name;
    public $age;
    function __construct($iname,$iage)
    {
        $name=$iname;
        $age=$iage;
        echo "我是构造函数";
        echo '<br/>';
    }
}
$p1=new Person("小可爱",18);
echo $p1->name;
echo '<br/>';
echo $p1->age;
?>

如果我们这样写,我们认为会输出:我是构造函数小可爱18,但是,最后只会输出我是构造函数。这位为什么呢?
之前我们说过,构造函数也是函数,也会开一个新栈。这里他会把$name和$age当成一个新的变量。并不会指向对象的属性。
所以,这里引入了一个重要的概念。$this(这个很重要)!!!!
如果使用$this,它就会指向当前对象,
再理解的深一点,就是这个对象的地址。哪个对象使用到$this,就是哪个对象地址。$this不能再类外部使用。
我们需要将上面的代码进行修改。

        $name=$iname;
        $age=$iage;

改为:

    $this->name=$iname;
    $this->age=$iage;

这样,程序就可以正常输出了。
这里需要注意的一点是,如果我们没有定义构造函数,系统会有一个默认的构造函数。
function __construct(){}
所以之前我们创建对象的时候都是 $p1= new person();
如果我们自定义了构造函数,再这样创建对象的时候,系统就会报错。
类中只能有一个构造函数(不能重载)
类的构造方法小结:

  • ①再PHP4中,构造方法名和类名相同,PHP5之后可以和类名相同也可以是__construct()。
  • ②构造方法没有返回值。
  • ③主要作用是完成对新对象的初始化,并不是创建对象本身。
  • ④在创建新对象后,系统自动的调用该类的构造方法。
  • ⑤一个类有且只有一个构造方法。
  • ⑥如果没有给类自动义构造方法,则该类使用系统默认的构造方法。
  • ⑦如果给类自定义了构造方法,则该类的默认构造方法被覆盖。
  • ⑧构造方法的默认访问修饰符是public。

析构函数

什么是析构函数?
析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。在PHP5中引用。
其实就是释放资源,比如(释放数据库的链接,图片资源,销毁某个变量...)等等。
用小案例入门:

<?php
 class Person{
     public $name;
     public $age;
     //构造函数
     function __construct($name,$age)
     {
         $this->name=$name;
         $this->age=$age;
     }
    //析构函数
     function __destruct()
     {
         // TODO: Implement __destruct() method.
         echo $this->name.'销毁资源'.'<br/>';
     }
 }
 $p1= new Person("小王",18);
 $p2=new Person("小张",20);
?>

运行程序,我们发现,析构函数会自动调用。主要用于销毁资源。
析构函数调用顺序是,先创建的对象后销毁。(想象一下子弹上膛,最后一颗子弹第一颗打出去,先进先出)。
所以上面的执行结果为:
小张销毁资源
小王销毁资源
什么时候系统会调用析构函数?

  • 一、程序运行完退出的时候。
  • 二、当对象没有变量指向它的时候,它会变成垃圾对象,会立刻调用析构函数回收。(和Java不一样)。
    还有两点需要注意:
  • 一、析构函数没有返回值。
  • 二、一个类最多只能有一个析构函数。

静态变量与静态方法

先提出一个需求:
如果现在有一群孩子在玩游戏,不停的有新得小朋友加入,统计小朋友的个数并输出。用面向对象的程序完成。
可以考虑全局变量的方式,但是不推荐,因为那就不算纯对象了。但是也可以做出来。
代码如下:

<?php
global $child_sums;
      $child_sums=0;
class Child
{
    public $name;

    function __construct($name)
    {
        $this->name = $name;
    }

    function JoinChild()
    {
        //申明使用全局变量
        global $child_sums;
        $child_sums+=1;
        echo $this->name . "加入游戏";
    }
} 
//创建三个小孩
$child1=new Child("拉拉");
$child1->JoinChild();
$child2=new Child("哈哈");
$child2->JoinChild();
$child3=new Child("哒哒");
$child3->JoinChild();
echo "<br/>"."有".$child_sums."个小朋友";
?>

虽然可以实现,但不推荐,下面我们使用静态变量的方法。
代码如下:

<?php
class Child{
    public $name;
    public static $sums=0;
    //构造函数
    function __construct($name)
    {
        $this->name=$name;
    }
    function JoinChild(){
        self::$sums+=1;
        echo $this->name.'加入游戏';
    }
}
//创建三个小孩
$child1=new Child("拉拉");
$child1->JoinChild();
$child2=new Child("哈哈");
$child2->JoinChild();
$child3=new Child("哒哒");
$child3->JoinChild();
//看看多少人
echo '<br/>'."一共".Child::$sums."个小孩";
?>

那什么是静态变量呢,就是所有对象共享的一个变量,它不在堆区,在全局区。对象想要访问它,就指向它的地址。
如何定义呢?
访问修饰符 static 变量名;
如何访问呢?
在类外部 类名::$类变量名
在类内部有两种 类名::$类变量名或者self::$类变量名。
这里需要注意的一点是,访问静态变量和是否创建对象无关,你不创建对象,也可以访问。
访问静态变量,禁止使用$this,会报错。

静态方法

静态方法和静态变量是对应的,只能调用静态变量,如果调用非静态变量它是会报错的。反过来就可以,就是普通成员函数是可以调用静态变量的。原因是静态变量和静态方法都属于这个类,都是公开的。
还是上面的例子,进行一下修改。

<?php
class Child{
    public $name;
    public static $sums=0;
    //构造函数
    function __construct($name)
    {
        $this->name=$name;
    }
   static function JoinChild(){
        //self::$sums+=1;
        Child::$sums+=1;
    }
    function haizi(){
        echo $this->name;
    }
}
//创建三个小孩
$child1=new Child("拉拉");
$child1->haizi();
$child1->JoinChild();
$child2=new Child("哈哈");
$child2->haizi();
$child1->JoinChild();
$child3=new Child("哒哒");
$child3->haizi();
$child1->JoinChild();
//看看多少人
echo '<br/>'."一共".Child::$sums."个小孩";
?>

我们只需要在普通方法前加关键字static,就可以成为静态方法,如下面这样:

   static function JoinChild(){
        //self::$sums+=1;
        Child::$sums+=1;
    }

有上面两种调用方法。

面向对象三大特性之封装

提到封装,应该先说一说修饰符。
public(公开的)、protected(受保护的)、private(私有的)
正因为有了protected(受保护的)、private(私有的)这两个修饰符,才能体现封装的概念。
写一个例子:

<?php
class Person{
    public $name;
    protected $age;
    private $wage;
    public function __construct($name,$age,$wage)
    {
       $this->name=$name;
        $this->age=$age;
        $this->wage=$wage;

    }
}
$p1=new Person("小利",18,1000);
echo $p1->name;
echo $p1->age;  #报错
echo $p1->wage; #报错
?>

这样就体现了封装的概念,protected(受保护的)、private(私有的)这两个修饰符修饰的变量不让你直接调用。
如果,我们想要调用呢,那就写一个公开的方法,调用那个方法就可以了。
把上面的例子改一下,再类里添加:

    public function PersonAge($age){
        echo $this->age=$age;
    }
    public function PersonWage($wage){
        echo $this->wage=$wage;
    }

然后类外调用这两个函数就可以了。

$p1->PersonAge(20);
$p1->PersonWage(3000);

你可能会有疑问,我们直接调就可以了,为什么要多走这一步,不是没事找事嘛。肯定是有原因的,方法里,我们可以对变量进一步控制,比如加个范围,权限再控制的细一些等等。
也可以用另外一种方法,PHP为我们提供的,叫做魔术方法:__set()、__get()
__set()对protected或是private属性,进行赋值操作。
__get()获取protected或是private属性的值。

面向对象三大特性之继承

先来看一个小问题,如果我们做一个学生管理系统,有小学生,大学生,研究生。如果我们创建三个类的话,那么我们就会发现一个问题,那就是代码重复。所以我们有了继承的概念。
写个小案例:

<?php
//父类
class Student{
    public $name;
    public $age;
    public $studentID;

    public function ShowInfo($name,$age){
        echo $this->name=$name."||".$this->age=$age;
    }
}
//子类
class universityStudent extends Student{

    public function study(){
        echo "大学生在学习";
    }
}
$u1=new universityStudent();
$u1->ShowInfo("小练习",18);
$u1->study();
?>

我们发现,子类可以使用父类的方法,这就解决了刚才的问题,解决了代码的重复性。如果想要使用继承,关键字extends不能少。
其实所谓继承,就是子类通过extends关键字,把父类的(public、protected)属性和(public、protected)方法继承下来。
我们还要注意,只能继承(public、protected)属性和(public、protected)方法,private的属性和方法只能本类使用。
注意:
子类最多只能继承一个父类(指直接继承)
在创建某个子类对象时,默认情况不会自动调用其父类的构造函数。(和Java不一样)。
举个例子:将上面的代码修改

<?php
class Student{
    public $name;
    public $age;
    public $studentID;
    function __construct()
    {
        echo "我是父类的构造函数"."<br/>";
    }
    public function ShowInfo($name,$age){
        echo $this->name=$name."||".$this->age=$age;
    }
}
class universityStudent extends Student{
    public function __construct()
    {
        echo "我是子类的构造函数"."<br/>";
    }

    public function study(){
        echo "大学生在学习";
    }
}
$u1=new universityStudent();
$u1->ShowInfo("小练习",18);
$u1->study();
?>

上面的代码会输出:

我是子类的构造函数
小练习||18大学生在学习

父类的构造函数不会自动调用。那如果想调用父类的构造函数呢。只需要在子类的代码中加入:父类名::构造函数名或者parent::构造函数名两种方法都可以。

    public function __construct()
    {
        Student::__construct();
        echo "我是子类的构造函数"."<br/>";
    }

这样的话,会输出:

我是父类的构造函数
我是子类的构造函数
小练习||18大学生在学习

如果子类的方法名和父类的方法名相同,这叫做方法的重写(覆盖),这就是多态了,后面再详细说多态。

面向对象三大特性之多态

多态是一种概念,下面说两个知识点。

函数重载

“重载”是类的多态的一种实现,是指的是一个标识符被用作多个函数名,并且能够通过参数个数或者参数类型将这些同名的函数区分开,调用不发生混淆。
PHP虽然支持重载,但重载在具体实现上,和其他语言有较大的差别。举个例子:

class A{
    public $name;
    public $age;
    public function test(){
        echo "hello,123";
    }
    public function test($a){       #如果我们这么写,PHP会报错!!!!其他的语言可以,Java这么写的话没问题。
        echo "hello,456";
    }
}
$a=new A();
$a->test();
$a->test($a);

上面的是错误的写法。PHP有自己的方法,这里PHP引进了魔术方法。魔术方法:__call()
这个方法比较神奇。下面看代码:

class A{
    public $name;
    public $age;
    public function test1($a){
        echo "hello,123";
    }
    public function test2($a){
        echo "hello,456";
    }
    public function __call($name, $arguments)
    {
        var_dump($arguments);
        if($name=="test"){
            if(count($arguments)==1){
                $this->test1($arguments);
            }elseif (count($arguments)==2){
                $this->test2($arguments);
            }
        }
        // TODO: Implement __call() method.
    }
}
$a=new A();
$a->test(1);
$a->test(2,6);
/*执行结果为:
array(1) { [0]=> int(1) } hello,123array(2) { [0]=> int(2) [1]=> int(6) } hello,456
我们发现执行成功了,实现了函数重载。这是多态的一种体现。*/

我们需要知道一些魔术常量:

echo "<br/>".__LINE__;
echo "<br/>".__DIR__;
echo "<br/>".__FILE__;
echo "<br/>".__CLASS__;
echo "<br/>".__TRAIT__;
echo "<br/>".__FUNCTION__;
echo "<br/>".__METHOD__;
echo "<br/>".__NAMESPACE__;
输出结果为:
150
D:\phpstudy_pro\WWW\PHP
D:\phpstudy_pro\WWW\PHP\object02.php
A
test1
A::test1
array(2) { [0]=> int(2) [1]=> int(6) } hello,456

方法重写(覆盖)

提一个问题,如果我们设计一个类,提取一些相同的特征,设计成父类,并有一些函数。如果子类中想要完善父类的方法,只需要在子类中方法的命名和父类相同,参数完全相同就可以。我们把它叫做方法的重写(覆盖)。如果子类想要调用父类的方法,可以使用parent::方法名()就可以。
子类方法不能缩小父类方法的访问权限,可以扩大。
上面的内容体现了面向对象的多态性。

抽象类

提一个问题,为什么设计抽象类。
为了快速开发,我们可能有这样的类,是其他类的父类,但它本身并不需要实例化,主要用途是用于子类去继承。这样可以达到代码复用,并且利于项目设计者设计类。
设计成抽象类二点格式:
abstract class 类名{
abstract 修饰符 function 函数名(参数列表);//这里要注意,没有方法体。
}
注意事项

  • 抽象类不能被实例化。
  • 抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法。
  • 一旦类包含了abstract方法,则这个类必须声明为abstract。
  • 抽象方法不能有函数体。
  • 如果一个类继承了某个抽象类,则它必须实现该抽象类的所有抽象方法(除非自己也声明为抽象类)。

接口

为什么有接口,肯定是为了方便,也是为了规范,因为只要你要实现我这个接口,就比如实现里面的所有方法。
小案例入门:

<?php
interface iTest{
    public function start();
    public function stop();
}
class camera implements iTest{
    public function start(){
        echo "相机开始工作";
    }
    public function stop(){
        echo "相机停止工作";
    }
}
class phone implements iTest{
    public function start(){
        echo "手机开始工作";
    }
    public function stop(){
        echo "手机停止工作";
    }
}
$c1=new camera();
$c1->start();
$c1->stop();
echo "<br/>";
$p1=new phone();
$p1->start();
$p1->stop();
?>

输出结果:
相机开始工作相机停止工作
手机开始工作手机停止工作
接口细节讨论:
接口比抽象类更抽象,所以,接口更不能被实例化了。
接口中所有的方法都不能有主体。
一个类可以实现多个接口,逗号隔开,变相的完善了类继承(直接继承)的不足。
语法:
public class A implements 接口1,接口2{
}
接口中可以有属性,但必须是常量,默认是public。
接口中的方法必须是public,默认就是public,你想想,你接口就是给别人用的,你不公开那不是闲的没事嘛。
一个接口不能继承其他的类,但是可以继承别的接口。
语法:
interface 接口名 extends 接口1,接口2{
}

final关键字

如果我们希望某个类不被其他类继承,我们可以使用final关键字来修饰这个类。
如果我们用final来修饰某个类中的方法,则这个方法无法被重写。
final不能用来修饰成员属性。

const概念

当不希望一个成员变量被修改,希望该变量的值是固定不变的,这时候可以用const来修饰该成员变量。
基本用法:
const 常量名=值;
访问:
类名::常量名或者接口名::常量名
常量名应该全部大写,并且前面不要有$

PHP如何对错误进行处理

如果我们尝试打开一个文件:

<?php
$fp=fopen("123.txt","r");
echo '<br/>继续执行';
?>

上面这个代码,打开文件没有做任何验证,这是不对的。
系统会给一个默认警告:
Warning: fopen(123.txt): failed to open stream: No such file or directory in D:\phpstudy_pro\WWW\PHP\error.php on line 2
因为你不知道文件到底在不在,应该先判断。所以将上面的代码进行修改。

<?php
/*$fp=fopen("123.txt","r");
echo '<br/>继续执行';*/
if (!file_exists("abc.txt")){
    echo "文件不存在!!";
    exit();
}else{
    $fp=fopen("abc.txt","r");
        echo "文件打开成功";
        fclose($fp);  //这个必须有!!!

}
?>

输出结果:
文件不存在!!
还有一种简单得处理错误得方式

<?php
if (!file_exists("abc.txt")){
    die("文件不存在!");
}else{
    //文件处理。。。。
}
?>

或者直接:

file_exists("abc.txt") or die("文件不存在!!!!");
#文件存在向下执行,不存在得话执行die()

小结

面向对象基本语法就上面那些,适合入门,希望对大家有所帮助。

posted @ 2020-09-19 18:46  雪痕*  阅读(127)  评论(0编辑  收藏