"Go was born out of frustration with existing languages and environments for systems programming."
Go 是為了作為一個系統語言(systems programming language)而存在的, 它跟天下知名的C語言有同一個父親 - Ken Thompson , 作為一個系統語言, 它並不是直譯式語言, 也非跑在虛擬機(virtual machine)上, 而是貨真價實的像C一樣是先編譯(compile), 而且也是屬於strong and static type的語言, 這表示, 變數型別是預先宣告/決定的, 而且是不能半路變更的, 變數可以像這樣宣告:
var StrVariable string
var IntVariable int
這邊跟C, Java之類的語言不同的地方是, 型別定義是在後面不是放前面, 剛看到時我以為我會不習慣, 但卻花沒很多時間就適應了, 可能是我在很早以前寫過PASCAL的關係吧, PASCAL也是類似的寫法
但, Go其實還引入了一些dynamic language的特性, 因此在Go內也可以看到未經上面類型的宣告, 就直接指定的敘述, 像是:
strVariable2 := "Hello Go"
這敘述並不代表Go也有dynamic typed的設計, 其實這設計是同時結合了宣告跟指定(assignment), strVariable2並不是沒有型別或動態型別, 因為":="的關係, 使得strVariable2一開始就被宣告成後面值的型別, 因此上面那行跟下面一樣:
var strVariable2 string
strVariable2 = "Hello Go"
同理, "intVariable2 := 1"這敘述表示intVariable一開始就被宣告成整數(因為1是整數), 所以它還是一個strong typed的設計, 這邊比較要注意到的一個陷阱是"="和":=", 在已宣告過型別的變數, 是用正常的"="來指定值, 但在未宣告的變數, 必須要用 ":="
你不能做的是... "strVariable2 = 1", 因為strVariable2在前面已經":="被宣告成字串(string)
在Go, 你一樣可以自定型別, 結構(struct)
type MyString string
type User struct { Uid int Name string }
之後, 你就可以用MyString或是User來宣告變數, 像是
var M1 MyString var User1 User User2 := User {1, "julian"}
"type MyString string"有點像是C語言的typedef, 可以讓你以另一個型別(MyString)來替代原本的型別(string), 但它其實還有個妙用, 在後面會再提到
Go是一個functional programming language, 所以它並
沒有物件導向 觀念
沒有"類別"(class), 沒有物件/實體(object/instance), 但, 它卻有"方法"(method)和"介面"(interface), 聽起來有點四不像, 但其實這部份還蠻有趣的
先說到"方法"(method), 在Go, 你可以為你的型別設計一個"方法", 像是這樣:
func (u *User) Hi() { fmt.Printf("Hi! I'm %s. The %d user.\n", u.Uid, u.Name) }
這邊"Hi()"就是屬於"User"的一個方法, 呼叫"User2.Hi()"即可執行它, 方法跟一般的函數一樣, 所不同的是, 前面多了一個"(u *User)" 以這例子來說, 這邊就是定義了"Hi()"的母體是 *User (*是指標- pointer的意思, 就不在這邊解釋)
這邊有趣的地方是, 你可以為任何自定型別創造"方法", 包含函數(function), 這在"net/http"內就可以找到類似的應用:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
在這例子中, HandlerFunc有一個ServeHTTP的方法, 但HandlerFunc本身其實是一個function
"介面"(interface)也是一個蠻妙的東西, 在Java裡, interface必須要宣告"實作", 亦即"class MyImpl implements MyInterface", 也就是你必須指定某個class實作了某個interface
但在Go, 則是很不一樣, 在Go裡, 你可以宣告一個"介面"(interface) 像是
type MyInterface interface { Foo() }
但你不需要宣告某個型別"實作"了這個interface, 在Go, interface反而比較有"暗示"(imply)的意味, 也就是, 下面的例子, 不用任何宣告, 你可以把MyString自動當成它也可以是一個MyInterface:
func (s MyString) Foo() { }
因為在MyInterface的定義中, 它含有一個"Foo()", 當你替MyString宣告了一個"Foo()"的方法時, 就自動讓MyString變成了一個實作了MyInterface的型別
下面用一個比較完整的例子來總結:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main
import (
"fmt"
)
type MyHandler func(string)
type MyInterface interface {
Foo()
}
type Aa string
func (a *Aa) Foo() {
fmt.Println("Foo called : " + *a)
}
func (fs MyHandler) Foo() {
fs("foo")
}
func CallFoo(i MyInterface) {
i.Foo()
}
func main() {
x := Aa("test")
CallFoo(&x)
var fs MyHandler
fs = func(s string) {
fmt.Println("aaa:" + s)
}
CallFoo(&fs)
}
在這例子中, "CallFoo(MyInterface)"接受一個MyInterface的參數, 並執行它"Foo()"的方法, 由於"Aa"(字串)和"MyHandler"(函數)都是有一個"Foo()"的方法, 所以他們都可以被當做MyInterface作為參數, 同理, 回到前一個HandlerFunc範例, HandlerFunc其實也可以被當做Handler這個interface來使用, 在"net/http"的API中就是用到了這樣一個技巧