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時就會由棧區指向堆區。
| | 數據區 | 棧區 | //指向得是地址
|堆區 |全局區(靜態區)| |
| ———— $p1 |
| 小紅 | | |
| 18 | 數據區 | |
| | 常量區 | |
| ———– |
| | | |
| | 代碼區 | |
| | | |
|———————————–

函數接收對象時候,究竟接收得是值,還是地址?
看一段代碼:

<?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()

小結

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

Tags: