PHP基礎之物件導向篇

雪痕*發表於2020-09-19

前言

前面寫的都是運算子、流程控制、排序查詢等,下面說一說物件導向的一些內容。這是前面寫的,有興趣可以去看一看。
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()

小結

物件導向基本語法就上面那些,適合入門,希望對大家有所幫助。

相關文章