PHP vs GoLang
定義變數-Variables
PHP
GoLang
區分為「新定義」、「預先定義」、「自動新定義」、「覆蓋」。
1 2 3 4 5 6 7 8 9 10 11 var a string = "foo" var b string c := "bar" a = "fooooooo"
輸出-Echo
在 PHP 中常用到 echo
來顯示文字。
PHP
1 2 3 4 5 6 7 8 9 10 echo "Foo" ; $A = "Bar" echo $A ; $B = "Hello" echo $B . ", world!" ; $C = [1 , 2 , 3 ];echo var_dump ($C );
GoLang
在 Golang 中需要 fmt
套件。
1 2 3 4 5 6 7 8 9 10 11 fmt.Println("Foo" ) A := "Bar" fmt.Println(A) B := "Hello" fmt.Printf("%s, world!" , B) C := []int {1 , 2 , 3 } fmt.Println(C)
函式-Function
相差較少
PHP
1 2 3 4 5 function test ( ) { return "Hello, world!" ; } echo test ();
GoLang
Golang 須在函式後面宣告會回傳的資料型別。
1 2 3 4 5 func test () string { return "Hello, world!" } fmt.Println(test())
多值回傳-Multiple Value
PHP
在 PHP 中,回傳多個資料需使用陣列,將資料放入陣列裡。
1 2 3 4 5 6 7 8 9 function test ( ) { return [ 'username' => 'yu0628' , 'time' => 123456 ]; } $data = test ();echo $data ['username' ], $data ['time' ];
GoLang
在 Golang 中不必用到一個陣列,函式可以一次回傳多個值:
1 2 3 4 5 6 7 func test () (string , int ) { return "yu0628" , 123456 } username, time := test() fmt.Println(username, time)
匿名函式-Anonymous Function
PHP
1 2 3 4 5 $a = function ( ) { echo "Hello, world!" ; }; $a ();
GoLang
1 2 3 4 5 a := func () { fmt.Println("Hello, world!" ) } a()
多資料儲存型態-Stores
PHP
PHP 儲存皆用陣列即可。
1 2 3 $array = [1 , 2 , 3 , 4 , 5 ];$array2 = ['username' => 'yu0628' , 'password' => '2019 Dec' ];
GoLang
Golang 中有這些型態:array
, slice
, map
, interface
Golang 是個強型別語言 ,意思是陣列中只能有一種型態,means當決定此陣列是擺放字串資料時,就只能放字串。沒有數值、沒有布林值。
1. 陣列-Array
一個存放固定長度的陣列。
Golang 定義陣列時,須給一個長度及其內容存放的資料型態,陣列內容不一定要填滿其長度,但陣列內容不能超過當初定義的長度。
PHP
1 2 3 $a = ["foo" , "bar" ];echo $a [0 ];
GoLang
1 2 3 4 5 6 var a [2 ]string a[0 ] = "foo" a[1 ] = "bar" fmt.Println(a[0 ])
2. 切片-Slice
可供「裁切」而且供自由擴展的陣列。
相較於陣列的好處:「不用定義其最大長度,而且你可以直接賦予值」。
PHP
1 2 3 4 $a = ["foo" , "bar" ];echo $a [0 ];
GoLang
1 2 3 a := []string {"foo" , "bar" } fmt.Println(a[0 ])
像是 PHP 中的 array_slice()
,但 Golang 直接讓 Slice
「內建」了,其用法是:slice[開始:結束]
。
GoLang
1 2 3 4 5 6 p := []int {1 , 2 , 3 , 4 , 5 , 6 } fmt.Println(p[0 :1 ]) fmt.Println(p[1 :1 ]) fmt.Println(p[1 :]) fmt.Println(p[:1 ])
PHP
1 2 3 4 5 6 $p = [1 , 2 , 3 , 4 , 5 , 6 ];echo array_slice ($p , 0 , 1 ); echo array_slice ($p , 1 , 1 ); echo array_slice ($p , 1 ); echo array_slice ($p , 0 , 1 );
3. 映照-Map
有鍵名和鍵值的陣列。
「需要事先定義其鍵名、鍵值的資料型態」,仍限制無法在映照中存放多種不同型態的資料。
PHP
1 2 3 4 $data ["username" ] = "yu0628" ;$data ["password" ] = "2019 Dec" ;echo $data ["username" ];
GoLang
在 Golang 需要先用 make()
宣告 map
。
1 2 3 4 5 6 data := make (map [string ]string ) data["username" ] = "yu0628" data["password" ] = "2019 Dec" fmt.Println(data["username" ])
4. 接口-Interface
一個可存放多種資料型態的陣列。
PHP
1 2 3 $mixedData = ["foobar" , 123456 ];$mixedData2 = ['username' => 'yu0628' , 'time' => 123456 ];
GoLang
Golang 的 interface{}
可以接受任何內容,可以存放任何型態的資料
1 2 3 4 5 mixedData := []interface {}{"foobar" , 123456 } mixedData2 := make (map [string ]interface {}) mixedData2["username" ] = "yu0628" mixedData2["time" ] = 123456
不定值-Mixed Type
有個不定值的變數,在 PHP 可以直接將一個變數定義成字串、數值、空值。
PHP
1 2 3 4 5 6 7 8 $mixed = 123 ;echo $mixed ; $mixed = 'Moon, Dalan!' ;echo $mixed ; $mixed = ['A' , 'B' , 'C' ];echo $mixed ;
GoLang
在 Golang 中給予變數一個指定的資料型別
1 2 3 4 5 6 7 8 9 10 11 var mixed interface {}mixed = 123 fmt.Println(mixed) mixed = "Moon, Dalan!" fmt.Println(mixed) mixed = []string {"A" , "B" , "C" } fmt.Println(mixed)
逆向處理-Defer
當程式中不需要繼續使用到某個資源或是發生錯誤時,會將其關閉或是拋棄來節省資源開銷,如 PHP 裡的讀取檔案:
PHP
1 2 3 4 5 6 7 8 9 $handle = fopen ('example.txt' , 'r' );if ($errorA ) errorHandlerA (); if ($errorB ) errorHandlerB (); fclose ($handle );
GoLang
在 Golang 中,使用 defer
來在函式結束的時候自動執行某些程式(其執行方向為反向)。就不需要在函式最後面結束最前面的資源。
defer
可被稱為「推遲執行」,實際上就是在函式結束後會「反序」執行的東西
例如按照了這樣的順序定義 defer: A->B->C->D,那麼執行的順序其實會是 D->C->B->A,這用在程式結束時還蠻有用的
1 2 3 4 5 6 7 8 9 10 handle := file.Open("example.txt" ) defer file.Close() if errorA { errorHandlerA() } if errorB { errorHandlerB() }
迴圈-Loops
Golang 中僅有 for
一種迴圈但卻能夠達成 foreach
、while
、for
多種用法。
PHP
1 2 3 4 5 6 for ($i = 0 ; $i < 3 ; $i ++) echo $i ; $j = 0 ;for ($j ; $j < 5 ; $j ++) echo $j ;
GoLang
在 Golang 請記得:若 i
先前並不存在,就需要定義它,所以下面這個範例會 i := 0
。
1 2 3 4 5 6 7 8 9 for i := 0 ; i < 3 ; i++ { fmt.Println(i) } j := 0 for ; j < 5 ; j++ { fmt.Println(j) }
每個-Foreach
PHP
1 2 3 4 5 6 7 8 9 10 $data = ['a' , 'b' , 'c' ];foreach ($data as $index => $value ) echo $index . $value . '|' ; foreach ($data as $index => $value ) echo $index . '|' ; foreach ($data as $value ) echo $value . '|' ;
GoLang
1 2 3 4 5 6 7 8 9 10 11 12 13 14 data := []string {"a" , "b" , "c" } for index, value := range data { fmt.Printf("%d%s|" , index, value) } for index := range data { fmt.Printf("%d|" , index) } for _, value := range data { fmt.Printf("%s|" , value) }
重複-While
while(條件)
迴圈在 PHP 裡面可以不斷地執行區塊中的程式,直到 條件 為 false
為止。
PHP
1 2 3 4 5 6 7 8 9 $i = 0 ;while ( $i < 3 ) { $i ++; echo $i ; } while (true ) echo "WOW"
GoLang
在 Golang 裡也有相同的做法,但仍是透過 for
迴圈,此 for
迴圈並沒有任何的分號(;),而且一個沒有條件的 for
迴圈會一直被執行。
1 2 3 4 5 6 7 8 9 10 11 i := 0 for i < 3 { i++ fmt.Println(i) } for { fmt.Println("WOW" ) }
做 … 重複-Do While
PHP
1 2 3 4 5 6 $i = 0 ;do { $i ++; echo $i ; } while ($i < 3 );
GoLang
在 Golang 中則沒有相關函式,但可以透過一個無止盡的 for
迴圈加上條件式使其結束迴圈。
1 2 3 4 5 6 7 8 9 10 11 12 i := 0 for { i++ fmt.Println(i) if i > 2 { break } }
日期-Date
PHP
1 echo date ("Y-m-d H:i:s" );
GoLang
Golang 並不是以 Y-m-d
此格式做為定義,而是 1、2、3
,這令人需要去翻閱文件,才能夠知道 1 的定義是代表什麼。
1 2 fmt.Println(time.Now().Format("2006-2-1 03:04:00" )) fmt.Println(time.Now().Format("Mon, Jan 2, 2006 at 3:04pm" ))
切割字串-Split
PHP
1 2 $data = 'a, b, c, d' ;$array = explode (', ' , $data );
GoLang
1 2 data := "a, b, c, d" array := strings.Split(data, ", " )
記得引用 strings
套件。
關聯陣列-Associative Array
PHP
1 2 3 4 $data = ['username' => 'yu0628' , 'password' => '2019 Dec' ]; echo $data ["username" ];
GoLang
1 2 3 4 5 data := map [string ]string { "username" : "yu0628" , "password" : "2019 Dec" } fmt.Println(data["username" ])
是否存在-Isset
PHP
1 2 3 4 if (isset ($data ['username' ])) { $username = $data ['username' ]; }
GoLang
在 Golang 裡面很簡單的能夠這樣辦到(僅適用於 map
)。
1 2 3 4 5 username, exists := data["username" ] if !exists { fmt.Printf("你要找的資料不存在。" ) }
指針-Pointer
指針(參照)是一個像是「變數別名」的方法,此方法讓人不用整天覆蓋舊的變數
假設 A = 1; B = A; 此時 B 會複製一份 A 且兩者不相干,倘若希望修改 B 的時候實際上也會修改到 A 的值,就會需要指針。
指針比起複製一個變數,會建立一個指向到某個變數的記憶體位置,這就是為什麼改變指針,實際上是在改變某個變數。
PHP
1 2 3 4 5 6 7 8 function zero (&$number ) { $number = 0 ; } $A = 5 ;zero ($A );echo $A ;
GoLang
在 Golang 需要用上 *
還有 &
符號。
1 2 3 4 5 6 7 8 9 10 11 func zero (number *int ) { number = 0 } func main () { A := 5 ; zero(&A) fmt.Printf("%d" , A) }
錯誤處理-Error Exception
有時回傳的陣列裡可能有資料還有錯誤代號,且會用條件式判斷錯誤代號是否非空值。
PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function foo ($number ) { if ($number !== 1 ) return ['number' => -1 , 'error' => '$number is not 1' ]; return ['number' => $number , 'error' => null ]; } $bar = foo (0 );if ($bar ['error' ]) echo $bar ['number' ], $bar ['error' ];
GoLang
在 Golang 中函式可以一次回傳多個值。不需要真的回傳一個陣列,不過要注意的是將會回傳一個屬於 error
資料型態的錯誤,所以需引用 errors
套件幫助。
Golang 沒有 try … catch,因為 Golang 推薦 ,應在每一次執行可能會發生錯誤的程式時就處理錯誤,而非後來用 try 到處包覆程式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import "errors" func foo (number int ) (int , error ) { if number != 1 { return -1 , errors.New("$number is not 1" ) } return number, nil } if bar, err := foo(0 ); err != nil { fmt.Println(bar, err) }
if
條件式裡宣告變數會只能在 if
內部使用這個變數,而不會污染到全域範圍。
拋出和捕捉異常-Try & Catch
PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function foo ($number ) { if ($number < 10 ) throw new Exception ('$number is less than 10' ); else if ($number > 10 ) throw new Exception ('$number is greater than 10' ); } try { foo (9 ); } catch (Exception $e ) { echo $e ->getMessage (); } try { foo (11 ); } catch (Exception $e ) { echo $e ->getMessage (); }
GoLang
可以使用 Golang 中另類處理錯誤的方式(可以的話盡量避免使用這種方式):panic(), recover(), defer
。
可把 panic()
當作是 throw
(丟出錯誤),一但你執行了 panic()
程式就會宣告而終,程式結束的時候會呼叫 defer
,所以接下來要在 defer
停止 panic()
。接著要在 defer
內使用 recover()
讓程式不再繼續進行結束動作,這就像是捕捉異常。
recover()
可以看作 catch
(捕捉),在 defer
裡面用 recover()
解決 panic()
,如此程式就會回歸正常而不會被結束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 func try (fn func () , handler func (interface {}) ) { defer func () { if err := recover (); err != nil { handler(err) } }() fn() } func foo (number int ) { if number < 10 { panic ("number is less than 10" ) } if number > 10 { panic ("number is greater than 10" ) } } func main () { try(func () { foo(9 ) }, func (e interface {}) { fmt.Println(e) }) try(func () { foo(11 ) }, func (e interface {}) { fmt.Println(e) }) }
套件/匯入/匯出-Package / Import / Export
PHP
1 2 3 4 <?php $foo = "bar" ; ?>
1 2 3 4 5 6 <?php include "a.php" ; echo $foo ; ?>
GoLang
Golang 怎麼透過「套件」解決這個問題
1 2 3 4 package mainvar foo string = "bar"
1 2 3 4 5 6 7 8 package mainimport "fmt" func main () { fmt.Println(foo) }
main.go
中除了引用 fmt
套件(為了要輸出結果用的套件)之外完全沒有引用到 a.go
。
因為兩者都是屬於 main
套件,因此共享同一個區域 。
1. 套件-Package
套件是每一個 .go
檔案都必須聲明在 Golang 原始碼中最開端的東西:
package main
意味著目前的檔案是屬於 main
套件,那麼要如何讓同個套件之間的函式溝通呢?
PHP
1 2 3 4 5 6 <?php function foo ( ) { } ?>
1 2 3 4 5 6 <?php include "a.php" ; foo (); ?>
GoLang
1 2 3 4 5 6 package mainfunc foo () { }
1 2 3 4 5 6 package mainfunc main () { foo() }
2. 匯入-Import
在 Golang 中沒有引用單獨檔案的方式,必須匯入一整個套件,且記住:「一旦匯入,就一定要使用它」。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport ( "fmt" "time" "github.com/yamiodymel/teameow" ) func main () { fmt.XXX() time.XXX() teameow.XXX() }
假如不希望使用匯入的套件,只是為了要觸發那個套件的 main()
函式而引用,那麼可在前面加上一個底線(_
)。
如果套件出現了名稱衝突,可以在套件來源前面給一個新的名稱。
1 2 3 4 5 6 7 8 9 10 import ( "github.com/karisu/teameow" neko "github.com/yamiodymel/teameow" ) func main () { teameow.XXX() neko.XXX() }
3. 匯出-Export
同個套件內的函式還有共享變數確實可以直接用,但不表示可以給其他套件使用,其方法取決於函式/變數的「開頭大小寫」 。
Golang 依照一個函式/變數的開頭大小寫決定這個東西是否可供「匯出」 。
1 2 3 4 5 6 7 8 9 10 11 package hellovar Foo string = "bar" func World () { }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package testimport ( "hello" "fmt" ) func main () { fmt.Println(hello.Foo) hello.World() }
用在區別函式時格外有用,小寫開頭的任何事物都是不供匯出的,反之,大寫開頭的任何事物都是用來匯出供其他套件使用的。
類別-Class
Golang 中沒有類別,但有所謂的「建構體(Struct)」和「接口(Interface)」
PHP
1 2 3 4 5 6 7 8 9 10 class Foobar { public $a = "hello, world" ; public function test ( ) { echo $this ->a; } } $b = new Foobar ();$b ->test ();
GoLang
首先知道在 Golang 中「類別」的成員還有方法都是在「類別」外面所定義的,與 PHP 在類別內定義的方式不同
1 2 3 4 5 6 7 8 9 10 11 12 13 type Foobar struct { a string } func (f *Foobar) test () { fmt.Println(f.a) } b := &Foobar{a: "hello, world!" } b.test()
1. 建構子-Constructor
在 PHP 中,當有一個類別被 new
的時候會自動執行該類別內的建構子(__construct()
),通常會用這個來初始化一些類別內部的值。
PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Test { public $a ; function __construct ( ) { $this ->a = "foobar" ; } function show ( ) { echo $this ->a; } } $b = new Test ();$b ->show ();
GoLang
但在 Golang 裡因為沒有類別,也就沒有建構子,不巧的是建構體本身也不帶有建構子的特性,此時只能自己在外部建立一個建構用函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type Test struct { a string } func (t *Test) show() { fmt.Println(t.a) } func newTest () (test *Test) { test = &Test{a: "foobar" } return } b := newTest() b.show()
2. 嵌入-Embed
假設有兩個類別,會把其中一個類別傳入到另一個類別裡面使用
PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Foo { public $msg = "Hello, world!" ; } class Bar { public $foo ; function __construct ($foo ) { $this ->foo = $foo ; } function show ( ) { echo $this ->foo->msg; } } $a = new Foo ();$b = new Bar ($a );$b ->show ();
GoLang
在 Golang 中也有相同的用法,但請記得:「任何東西都是在「類別」外完成建構的 」。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 type Foo struct { msg string } type Bar struct { *Foo } func (b *Bar) show() { fmt.Println(b.msg) } a := &Foo{msg: "Hello, world!" } b := &Bar{a} b.show()
3. 遮蔽-Shadowing
在 PHP 中沒有相關的範例
Golang 在進行 Foo
嵌入 Bar
時,會自動將 Foo
的成員暴露在 Bar
底下,那麼假設「雙方之間有相同的成員名稱」呢?
此時被嵌入的成員就會被「遮蔽」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 type Foo struct { msg string } type Bar struct { *Foo msg string } a := &Foo{msg: "Hello, world!" } b := &Bar{Foo: a, msg: "Moon, Dalan!" } fmt.Println(b.msg) fmt.Println(b.Foo.msg)
4. 多形-Polymorphism
雖然都是呼叫同一個函式,但這個函式可以針對不同的資料來源做出不同的舉動,這就是多形。
也能夠把這看作是:「訊息的意義由接收者定義,而不是傳送者」。
目前 PHP 中沒有真正的「多形」,不過仍可以做出同樣的東西。
PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Foo { public $msg = "hello" ; } class Bar { public $msg = "world!" ; } class Handler { public function process ($class ) { switch (get_class ($class )) { case 'Foo' : echo '處理 Foo | ' . $class ->msg . ', world!' ; break ; case 'Bar' : echo '處理 Bar | ' . 'hello, ' . $class ->msg; break ; } } } $foo = new Foo ();$bar = new Bar ();$handler = new Handler ();$handler ->process ($foo ); $handler ->process ($bar );
GoLang
在 Golang 中有 interface 可以幫忙完成這個工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 type Foo struct { msg string } type Bar struct { msg string } type Handler interface { process() } func (f Foo) process() { fmt.Printf("處理 Foo | %s, world!" , f.msg) } func (b Bar) process() { fmt.Printf("處理 Bar | hello, %s" , b.msg) } foo := Foo{msg: "hello" } bar := Bar{msg: "world!" } Handler.process(foo) Handler.process(bar)