Download & Install
- Go程式語言是Google所推出,欲使用此程式語言需有Go compiler與文書編輯器。
- 到官方網頁下載compiler(go1.11.windows-amd64.msi為編輯時版本,若有更新版本請下載最新版本)。
- 點擊下載檔案安裝,一路按next即可,最後會安裝在資料夾C:\Go。
- 到控制台\所有控制台項目\系統,選進階系統設定 >> 環境變數,在path內檢查是否有c:\Go\bin(應該會自動產生加入),若是沒有則自行加上。
- 接下來選擇一個編輯器,原則上一般的文書編輯器即可(e.g. Nodepad++),在此使用Visual Studio Code,請下載適合電腦作業系統的版本,下載後點擊安裝,一樣一路按next即可。
- 安裝完成後,可能需要重開機一次。在電腦中建立一個資料夾(通常稱為workspace),再在其中開啟一個名為src的資料夾。在src中建立另一個資料夾,名稱為此次專案的名稱(e.g. go1)。
- 接著打開Visual Studio Code,會出現一些訊息要求安裝,按全部安裝。之後打開一個新檔案,在其中輸入以下程式碼:
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
Basic
- 根據hello world的例子,可以看出go的語法是:
- Package Declaration: 定義package(e.g. main),這行指令一定要做。
- Import Packages: 導入package(e.g. fmt),讓compiler知道要納入在fmt這個package內的檔案。
- functions: 定義函數,而main()這個函數是程式開始的點。
- Statements and Expressions: 程式指令,e.g. fmt.Println("Hello, World!")是使用fmt裡面的Println函數來印出字串(請注意P要大寫而且指令最後沒有分號)。
- Go的註解方式與Java和C++相同,單行為//,段落為/**/。
-
命名時使用英文字母、底線(_)與數字(數字不可在前),不要使用標點(e.g. ,;:)、符號(e.g. @$%)與空白。
- 也不可以使用關鍵字(keywords)命名,Go的關鍵字列表如下:
break default func interface select case defer Go map Struct chan else Goto package Switch const fallthrough if range Type continue for import return Var - 使用fmt.Println()來印出資訊(不同類型資訊使用逗點分隔),或使用fmt.Printf()來印出格式化資訊,Printf()常用的格式如下:。
- %v: value in default format
- %s: string
- %b: base 2, decimalless scientific notation with exponent a power of two, e.g. 123345p-77
- %d: int, int8, etc.(base 10)
- %o: base 8
- %x: base 16, lower-case, two characters per bytw
- %t: bool
- %g: float32, complex64, etc.(for large exponents)
- %p: pointer
- %f: decimal point(can specify the width, e.g. %9f, %.2f, %9.2f, %9.f)
- %e: scientific notation, e.g. 123e+88
- %%: literal percent sign
Data Types & Variables
- Go中的資料型態可區分為如下:
- Boolean: true & false。
- Numeric: integer & floating point values。
- uint8(byte): 8-bit >> 0~255(u stands for unsigned)。
- uint16: 16-bit >> 0~65535(u stands for unsigned)。
- uint32: 32-bit >> 0~4294967295(u stands for unsigned)。
- uint64: 64-bit >> 0~18446744073709551615(u stands for unsigned)。
- int8: 8-bit >> -128~127。
- int16: 16-bit >> -32768~32767。
- int32(rune): 32-bit >> -2147483648~2147483647。
- int64: 64-bit >> -9223372036854775808~9223372036854775807。
- float32: 32-bit。
- float64: 64-bit。
- complex64: complex number >> float32 real and imaginary parts。
- complex128: complex number >> float64 real and imaginary parts。
- String: 字串。
- Derived: 此型態包含Pointer, Array, Structure, Union, Function, Slice, Interface, Map & Channel。
- 變數的宣告(宣告時若使用byte, int, float32為基本型態,使用預設長度):
- 宣告變數可以使用以下兩種方式:
package main
import "fmt"
func main() {
var yourAge int
yourAge = 19
var hisAge = 18.5 // hisAge := 18.5
fmt.Println("You are", yourAge, "years old.")
fmt.Println("He is", hisAge, "years old.")
fmt.Printf("%T\n", yourAge)
fmt.Printf("%T\n", hisAge)
}
- 需使用關鍵字var來宣告。
- var yourAge int: 宣告變數名為yourAge,型態是int。
- var hisAge = 18.5: 宣告變數名為hisAge,型態由給定初值決定,在此為float(可使用:=來省略var關鍵字)。
- 使用fmt.Printf("%T", variable)來得到變數型態。
- 宣告變數時,可以使用小括號將其一併包含,例如:
package main
import "fmt"
func main() {
var(
yourAge int
hisAge = 18.5
)
yourAge = 19
fmt.Println("You are", yourAge, "years old.")
fmt.Println("He is", hisAge, "years old.")
fmt.Printf("%T\n", yourAge)
fmt.Printf("%T\n", hisAge)
}
import (
"fmt"
"math"
)
package main
import "fmt"
func main() {
const pi = 3.14159 // const pi float64= 3.14159
var radius = 10.0
fmt.Println("Periphery = ", 2*radius*pi)
}
package main
import "fmt"
func main() {
fmt.Println("He is \"so\" young.\nShe is \\\tsuch\t\\ a beautiful girl.")
}
- 可用的escape sequence與C++差不多:\\, \", \?, \a, \b, \f, \n, \r, \t, \v, \ooo, \xhh。
Operators
- Go的運算子包括Arithmetic, Relational, Logical, Bitwise, Assignment, & Miscellaneous:
- Arithmetic Operators: +, -, *, /, %, ++, --。
package main
import "fmt"
func main() {
var a int = 10
var b int = 20
fmt.Println(a, "+", b, "=", a+b)
fmt.Println(a, "-", b, "=", a-b)
fmt.Println(a, "*", b, "=", a*b)
fmt.Println(a, "/", b, "=", float32(a)/float32(b))
fmt.Println(a, "%", b, "=", a%b)
a++
fmt.Println("a++ =", a)
b--
fmt.Println("b-- =", b)
}
- 使用float32(a)將a cast成為float32。
- ++加1,--減1。
package main
import "fmt"
func main() {
var a int = 10
var b int = 20
fmt.Println(a, "==", b, ">>", a==b)
fmt.Println(a, "!=", b, ">>", a!=b)
fmt.Println(a, ">", b, ">>", a>b)
fmt.Println(a, "<", b, ">>", a<b)
fmt.Println(a, ">=", b, ">>", a>=b)
fmt.Println(a, "<=", b, ">>", a<=b)
}
package main
import "fmt"
func main() {
var a bool = true
var b bool = false
fmt.Println("a && b >>", a&&b)
fmt.Println("a || b >>", a||b)
fmt.Println("!a >>", !a)
}
package main
import "fmt"
func main() {
var a uint = 10 // 10 = 0000 1010
var b uint = 5 // 5 = 0000 0101
var c uint
c = a&b
fmt.Println(a, "&", b, "=", c)
c = a|b
fmt.Println(a, "|", b, "=", c)
c = a^b
fmt.Println(a, "^", b, "=", c)
c = a << 2
fmt.Println(a, "<< 2 =", c)
c = b >> 1
fmt.Println(b, ">> 1 =", c)
}
package main
import "fmt"
func main() {
var a int = 10 // 10 = 0000 1010
var b int
b = a
fmt.Println("b =", b)
b += a
fmt.Println("b =", b)
b -= a
fmt.Println("b =", b)
b *= a
fmt.Println("b =", b)
b /= a
fmt.Println("b =", b)
b <<= 2
fmt.Println("b =", b)
b >>= 2
fmt.Println("b =", b)
b &= a
fmt.Println("b =", b)
b ^= a
fmt.Println("b =", b)
b |= a
fmt.Println("b =", b)
}
package main
import "fmt"
func main() {
var a int = 10 // 10 = 0000 1010
var b int32
var c float32
var ptr* int
fmt.Printf("Type of a = %T\n", a)
fmt.Printf("Type of b = %T\n", b)
fmt.Printf("Type of c = %T\n", c)
ptr = &a
fmt.Printf("Value of a = %d\n", a)
fmt.Printf("Value of ptr = %d\n", *ptr)
}
- &表示變數記憶體位址,*表示指向某記憶體位址之pointer。
- () [] -> . ++ --
- + - ! ~ ++ - - (type)* & sizeof
- * / %
- + -
- << >>
- < <= > >=
- == !=
- &
- ^
- |
- &&
- ||
- ?:
- = += -= *= /= %=>>= <<= &= ^= |=
- ,
Flow Control
- 包含if...else..., switch, select, for loop and functions。
- : if...else...
package main
import "fmt"
func main() {
var a int = 20
if(a < 10){
fmt.Println("a < 10")
}else{
fmt.Println("a >= 10")
}
}
package main
import "fmt"
func main() {
var w float32 = 45
var h float32 = 162
var bmi = w/(h/100)/(h/100)
if(bmi < 18.5){
fmt.Println("You are too thin.")
}else if(bmi >= 18.5 && bmi < 23.9){
fmt.Println("You are in good shape.")
}else if(bmi >= 23.9 && bmi < 27.9){
fmt.Println("You are over-weighted.")
}else{
fmt.Println("You are obese.")
}
}
- if之後的條件不需要小括號,可將其去除。
package main
import "fmt"
func main() {
var weekday string
var iweekday int = 1
switch iweekday{
case 1: weekday = "Monday"
case 2: weekday = "Tuesday"
case 3: weekday = "Wednesday"
case 4: weekday = "Thursday"
case 5: weekday = "Friday"
case 6: weekday = "Saturday"
case 7: weekday = "Sunday"
default: weekday = "Error..."
}
switch{
case weekday=="Monday":
fmt.Println("First workday in a week")
case weekday=="Friday":
fmt.Println("Friday night party")
case weekday=="Saturday" || weekday=="Sunday":
fmt.Println("Take a break")
default:
fmt.Println("Working day")
}
}
- 如果switch之後接變數(int),則使用變數值來判斷使用哪一個case。
- 如果swithc之後沒有變數,則case使用bool來判斷。
- 與其他語言不同處是不需要break。
- Type switch。
package main
import "fmt"
func main() {
var x interface{} // = 10
switch i := x.(type){
case nil:
fmt.Printf("x is %T", i)
case int:
fmt.Printf("x is int")
case float32:
fmt.Printf("x is float32")
case bool:
fmt.Printf("x is boolean")
default:
fmt.Printf("x is not int, float32, boolean.");
}
}
package main
import "fmt"
func main() {
var c1, c2, c3 chan int
select{
case v1:=(<-c1):
fmt.Printf("received %v from c1\n", v1)
case v2:=(<-c2):
fmt.Printf("received %v from c2\n", v2)
case c3<-12:
fmt.Printf("sent %v to c3\n", 12)
default:
fmt.Printf("No communications")
}
}
- chan關鍵字用來定義channel,channel於之後討論。
package main
import "fmt"
func main() {
a:=10
b:=20
for i:=0; i < 10; i++{ // for 1
fmt.Printf("i = %d\n", i)
}
for a<b{ // for 2
fmt.Println("a = ", a)
a++
}
}
- 與其他語言(e.g. c++ or java)不同處是for之後沒有小括號,但是還是需要大括號(e.g. for 1)。
- 雖然沒有while loop,但可使用for做類似使用(e.g. for 2)。
- 可以在loop內使用關鍵字break, continue, 與goto。
package main import "fmt" func main() { a:=10 b:=30 Print: fmt.Println("a = ", a) for (a<b){ a++ if a%2!=0{ continue }else if a > 25{ break }else{ goto Print } } }
package main
import (
"fmt"
"math"
)
func circleArea(radius float64) float64{
return math.Pi*math.Pow(radius,2)
}
func main() {
r:=10.0
ca:=circleArea(r)
fmt.Println(ca)
}
- 傳回值形態寫在函數名之後,若沒有傳回值則不用寫。
- Go的函數可以傳回多個值。
package main
import (
"fmt"
"math"
)
func tri(a, b float64) (float64, float64){
return a*a, b*b
}
func len(a2,b2 float64) float64{
return math.Sqrt(a2+b2)
}
func main() {
var a float64 = 3
b:= 4.0
fmt.Println(len(tri(a,b)))
}
package main
import (
"fmt"
)
func nextNumber() func() int{
i:=0
return func() int{
i++
return i
}
}
func main() {
next:=nextNumber()
for i:=0; i < 3; i++{
fmt.Println(next())
}
next2:=nextNumber()
for i:=0; i < 3; i++{
fmt.Println(next2())
}
}
package main
import (
"fmt"
"math"
)
type Circle struct{ //define structure of a circle
radius float64
}
func (cir Circle) area() float64{ // method
return math.Pi*cir.radius*cir.radius
}
func main() {
cir:= Circle{radius:10}
fmt.Println("Circle Area: ", cir.area())
}
- 定義Circle的形態,使用關鍵字type與struct,Circle的參數只有一個radius。
- area()是method,專用於Circle這個structure(類似於物件內的方法)。
package main
import "fmt"
func fibonacci(i int) int{
if i==0{
return 0
}
if i==1{
return 1
}
return fibonacci(i-1)+fibonacci(i-2)
}
func main() {
for i:=0; i<10; i++{
fmt.Print(fibonacci(i), " ")
}
}
- 或使用closure也可求Fibonacci sequence。
package main
import (
"fmt"
)
func fibonacci() func() int{
a:=0
b:=1
return func() int{
temp:=a
a = b
b = b+temp
return temp
}
}
func main() {
f:=fibonacci()
for i:=1; i<10; i++{
fmt.Print(f(), " ")
}
}
Pointers
- 與C++類似,Go可以使用pointers。
- : 使用&符號取得變數位址(pointer),使用*符號來dereference一個pointer。
package main
import (
"fmt"
)
func main() {
a:=20
var ip* int
var pip** int
fmt.Printf("Value of *ip: %v\n", ip)
fmt.Printf("Value of **pip: %v\n", pip)
ip = &a // ip point to the address of a
pip = &ip // pip point to the address of ip
fmt.Println("Address of a:", &a)
fmt.Printf("Address stored in ip: %x\n", ip)
fmt.Printf("Value of *ip: %v\n", *ip)
if(ip==nil){
fmt.Println("ip is nil")
}else{
fmt.Println("ip is not nil")
}
fmt.Println("Address of pip:", pip)
fmt.Println("Address of ip:", *pip)
fmt.Println("Value of **pip:", **pip)
}
- 使用*符號來宣告變數為pointer,若是沒有給初始值,pointer指向
。 - 使用**符號來宣告變數為pointer的位址(pointer的pointer),若是沒有給初始值,一樣指向
。 - 使用*符號來dereference,所以*ip為a的值,*pip為ip的address,**pip為*ip(也就是a)。
package main
import (
"fmt"
"math"
)
func circleArea(radius *float64) float64{
return math.Pi*(*radius)*(*radius)
}
func main() {
r:=10.0
fmt.Printf("Area:%g", circleArea(&r))
}
Array & Structure
- 與其他語言類似,Go可將資料儲存於Array,也可使用Structure來定義不同型態的資料(類似物件)
Array
- Array用來儲存一組資料,每一資料在Array的cell內,每一個cell有一個index。
- : 宣告array與取得array內資料。
package main
import (
"fmt"
"math/rand"
)
func printArray(a [] int, len int){
for i:=0; i < len; i++{
fmt.Println("i:", a[i])
}
}
func main() {
var a = [] int {1,2,3,4,5}
var b [5] int
printArray(a, 5)
rand.Seed(1)
for i:=0; i < 5; i++{
b[i] = rand.Intn(100)
}
for i:=0; i < 5; i++{
fmt.Println("b[", i, "]=", b[i])
}
}
- 宣告array a並在一開始給初值,而array b則無初值。
- 使用rand.Intn(100)來產生100內的隨機整數,可以使用rand.Seed()來指定seed值,使用時須import math/rand這個package。
- 因為array b宣告時給定array長度([5]),而function printArray()只接受無定義長度的array,所以無法使用,除非定義argument為a[5]int。
package main
import (
"fmt"
)
const len = 5 // global variable
func main() {
a := [] int {1,2,3,4,5}
var ptr [len] *int
for i:=0; i <len; i++{
ptr[i] = &a[i]
}
for i:=0; i < len; i++{
fmt.Printf("ptr[%d]=%d\n", i, *ptr[i])
}
}
- 設計一個array包含pointers,讓每一個cell儲存array a的元素的pointer。
package main
import (
"fmt"
"math/rand"
)
func main() {
rand.Seed(20)
var a[3][2] int
for i:=0; i<3; i++{
for j:=0; j<2; j++{
a[i][j] = rand.Intn(100)
}
}
fmt.Printf("%v", a)
}
- 宣告array a為3-row, 2-col的array,使用fmt.Printf("%v", a)來列印。
Slice
- Slice用來取得array中的部分元素
- : 使用[begin:end]來取得array中自begin到end-1位置的元素。
package main
import (
"fmt"
)
func main() {
a := [] int {1,2,3,4,5,6}
var b = a[1:5]
fmt.Println(b)
var c = a[1:]
fmt.Println(c)
var d = a[:5]
fmt.Println(d)
}
- c = a[1:]與d = a[:5]表示一端到array的最遠端點。
package main
import (
"fmt"
"math/rand"
)
func main() {
var a [] int //長度不確定的slice
rand.Seed(9)
for i:=0; i<10; i++{
a = append(a,rand.Intn(100))
}
fmt.Println("Slice a: len =", len(a),"cap =", cap(a),"a =", a, "a[5]=", a[5])
var b = make([]int, 3, 5) // [] int{0,0,0,0,0} >> length=5, capacity=5
fmt.Println("Slice b: len =", len(b),"cap =", cap(b),"b =", b)
b = append(b, 10,20,30)
fmt.Println("Slice b: len =", len(b),"cap =", cap(b),"b =", b)
var c = make([]int, len(b), cap(b))
copy(c,b)
fmt.Println("Slice c: len =", len(c),"cap =", cap(c),"c =", c)
}
- 宣告長度不確定的slice使用var a [] int,長度與容量確定則使用make()。
- 長度(len)指的是目前元素數,容量(cap)指的是可容納元素數,len(a)與cap(a)兩函數分別傳回slice的長度與容量。
- 使用append(slice, elements)可以添加元素至slice,若超過容量則會自動增加容量(類似vector或arraylist)。
- copy(slice1, slice2)可將slice2複製到slice1中。
- 若是宣告長度不確定的slice,其容量初始值為1,當添加元素大於容量時,容量x2(double)。
Map
- Map是資料成對(key, value)的array,與Python中的dict類似。
- map宣告方式需要使用make 。
package main
import (
"fmt"
)
func main() {
var mapOne map[string]string
mapOne = make(map[string]string)
// insert data
mapOne["1"] = "one"
mapOne["2"] = "two"
mapOne["3"] = "three"
mapOne["4"] = "four"
mapOne["5"] = "five"
mapOne["a"] = "first"
mapOne["b"] = "second"
mapOne["c"] = "third"
mapOne["d"] = "fourth"
mapOne["e"] = "fifth"
fmt.Println(mapOne["1"], "", mapOne["c"])
}
package main
import (
"fmt"
)
func main() {
var mapOne = map[string]int{"one":1, "two":2, "three":3, "four":4, "five":5}
fmt.Println(mapOne["one"] + mapOne["two"])
delete(mapOne, "five")
fmt.Println(mapOne)
value, ok := mapOne["six"]
if ok{
fmt.Println(value, "exists.")
}else{
fmt.Println("no value contained.", value)
}
}
- value, ok := mapOne["six"]表示如果mapOne["six"]存在,value = mapOne["six"]的值,ok為true,否則ok為false,value為0。
Range
- range是用來在for loop中iterate over一個array, slice, channel or map的關鍵字。
- : 建立一個iterable物件並使用for loop搭配range。
package main
import (
"fmt"
)
func main() {
var a [] int //長度不確定的slice
rand.Seed(9)
for i:=0; i<5; i++{
a = append(a,rand.Intn(100))
}
var sum = 0
for i:=range a{
sum = sum + a[i]
}
fmt.Printf("sum = %d\n", sum)
sum = 0
for i:=0; i<len(a); i++{
sum = sum + a[i]
}
fmt.Printf("sum = %d\n", sum)
}
- i:=range a中i為a的index。
package main
import (
"fmt"
)
func main() {
var mapOne = map[string]int{"one":1, "two":2, "three":3, "four":4, "five":5}
for k:=range mapOne{
fmt.Println(k, ">>", mapOne[k])
}
fmt.Println(mapOne)
for k, v := range mapOne{
fmt.Println(k, ">>", v)
}
}
List
- List類似於Python中的List,是可自行增減長度的Array。
- : 使用時須先import package container/list。
package main
import (
"fmt"
"container/list"
)
func printList(alist list.List){
for ele:=alist.Front(); ele!=nil; ele=ele.Next(){
fmt.Print(ele.Value, " ")
}
fmt.Println()
}
func main() {
var list1 list.List
e1:=list1.PushBack(1)
e2:=list1.PushBack(2)
list1.PushBack(3)
list1.PushFront(4)
e5:=list1.PushFront(5)
list1.InsertBefore(6, e5)
e7:=list1.InsertAfter(7, e1)
printList(list1)
list1.MoveAfter(e1, e5)
list1.MoveBefore(e2, e7)
printList(list1)
list1.MoveToBack(e7)
list1.MoveToFront(e1)
printList(list1)
var list2 list.List
list2.Init()
list2.PushBack(10)
list2.PushBack(20)
list1.PushBackList(&list2)
printList(list1)
list1.PushFrontList(&list2)
printList(list1)
list1.Remove(e7)
printList(list1)
list1.Init()
list1.PushBack("Hello")
printList(list1)
}
- PushFront()與PushBack():加入元素於list之前(後)。
- InsertBefore()與InsertAfter():加入元素於特定元素(mark element)之前(後)。
- MoveBefore()與MoveAfter():移動某元素至特定元素(mark element)之前(後)。
- MoveToFront()與MoveToBack():移動某元素至List之前(後)。
- PushFrontList()與PushBackList():將某list的copy(&list2)加到另一list(list1)之前(後)。
- Remove():移除某元素。
- Init():初始化(或是清空)某個list。
- List內的元素可為不同型態(int, string, array, etc)。
Structures & Error Handling
- Structures類似於物件的設計,Error Handling類似於Exception Handling。
Structures
- 對於某一類別的物件設計一個Structures來描述該類別的特性,是一個包含多個資料項的變數型態
- 使用type與struct兩關鍵字來敘述structure。
package main
import "fmt"
import "math"
type Circle struct{
x,y,radius float64
}
func (cir Circle) area() float64{
return math.Pi*cir.radius*cir.radius
}
func (cir Circle) isWithin(x float64, y float64) bool{
dis := math.Sqrt((cir.x-x)*(cir.x-x)+(cir.y-y)*(cir.y-y))
if dis < cir.radius{
return true
}else{
return false
}
}
func main() {
var cir1, cir2 Circle // Declare cir1&cir2 of type Circle
var x1 float64 = 5.0
var y1 float64 = 15.0
cir1.x = 10.0
cir1.y = 10.0
cir1.radius = 10.0
cir2.x = 0.0
cir2.y = 5.0
cir2.radius = 20.0
fmt.Printf("circle 1 is centered at (%f, %f) with radius = %f\n", cir1.x, cir1.y, cir1.radius)
fmt.Printf("circle 2 is centered at (%f, %f) with radius = %f\n", cir2.x, cir2.y, cir2.radius)
fmt.Println("area of circle 1 =", cir1.area(), "area of circle 2 =", cir2.area())
fmt.Printf("(%f, %f) is within circle 1? %t", x1, y1, cir1.isWithin(x1,y1))
}
- 使用.來取得struct內的元件。
- 如前所述,可以針對設計struct設計method(e.g. area() and isWithin())。
package main
import "fmt"
import "math"
type Circle struct{
x,y,radius float64
}
func (cir Circle) area() float64{
return math.Pi*cir.radius*cir.radius
}
func (cir Circle) isWithin(x float64, y float64) bool{
dis := math.Sqrt((cir.x-x)*(cir.x-x)+(cir.y-y)*(cir.y-y))
if dis < cir.radius{
return true
}else{
return false
}
}
func printCircle(cir Circle){
fmt.Println("x =", cir.x, "y =", cir.y, "radius =", cir.radius, "area =", cir.area())
}
func main() {
var cir1, cir2 Circle // Declare cir1&cir2 of type Circle
cir1.x = 10.0
cir1.y = 10.0
cir1.radius = 10.0
cir2.x = 0.0
cir2.y = 5.0
cir2.radius = 20.0
printCircle(cir1)
printCircle(cir2)
}
package main
import "fmt"
import "math"
type Circle struct{
x,y,radius float64
}
func (cir Circle) area() float64{
return math.Pi*cir.radius*cir.radius
}
func (cir Circle) isWithin(x float64, y float64) bool{
dis := math.Sqrt((cir.x-x)*(cir.x-x)+(cir.y-y)*(cir.y-y))
if dis < cir.radius{
return true
}else{
return false
}
}
func printCircle(cir *Circle){
fmt.Println("x =", cir.x, "y =", cir.y, "radius =", cir.radius, "area =", cir.area())
}
func main() {
var cir1, cir2 Circle // Declare cir1&cir2 of type Circle
cir1.x = 10.0
cir1.y = 10.0
cir1.radius = 10.0
cir2.x = 0.0
cir2.y = 5.0
cir2.radius = 20.0
printCircle(&cir1)
printCircle(&cir2)
}
- cir *Circle接收到記憶體位址,使用structure的參數時使用pointer.argument(e.g. cir.x)即可。
Interface
- Go也提供inteferface的設計,此種type為abstract
- : 設計時將struct用關鍵字interface取代。
package main
import "fmt"
import "math"
type Shape interface{
area() float64
}
type Circle struct{
x,y,radius float64
}
type Rectangle struct{
width, height float64
}
type Triangle struct{
base, height float64
}
func (cir Circle) area() float64{
return math.Pi*cir.radius*cir.radius
}
func (rect Rectangle) area() float64{
return rect.width*rect.height
}
func (tri Triangle) area() float64{
return tri.base*tri.height/2.0
}
func getArea(shape Shape) float64{
return shape.area()
}
func main() {
var cir = Circle{x:0.0, y:0.0, radius:10}
//var rect = Rectangle{width:10, height:10}
var rect = new(Rectangle)
rect.width = 10
rect.height = 10
var tri = Triangle{base:10, height:10} // <==> var tri = Triangle{10, 10}
fmt.Println(getArea(cir), " ", getArea(rect), " ", getArea(tri))
fmt.Println(cir.area(), " ", rect.area(), " ", tri.area())
}
- 設計interface Shape,其中包含area()方法,並設計getArea(shape Shape)函數來傳回area()。
- 針對每一個shape都設計area()方法,最後可使用getArea(shape)來取得面積。
- 可以使用new來配置一個零初始化的物件(e.g. var rect = new(Rectangle)),之後再對變數賦值。
Error Handling
- Error Handling的意思是當潛在錯誤可能發生時,先行進行處理
- : error是Go內建的interface,在設計函數時,同時回傳可能的error。
package main
import (
"fmt"
"errors"
)
func ratio(a float64, b float64) (float64, error){
if(b == 0.0){
return 0, errors.New("Denominator cannot be 0.")
}else{
return a/b, nil
}
}
func main() {
a:=10.0
b:=0.0
r, err:=ratio(a,b)
if err != nil{
fmt.Println(err)
}else{
fmt.Println(r)
}
}
- 使用errors.New()來建構錯誤訊息。
- if err != nil表示error產生,否則err == nil。
package main
import "fmt"
func f1() {
fmt.Println("f1")
panic(1)
fmt.Println("f2")
}
func main() {
defer func() {
fmt.Println("One")
if err := recover(); err != nil {
fmt.Println(err)
}
fmt.Println("Two")
}()
f1()
}
- panic表示錯誤,產生panic即跳往defer並準備關閉程式,。
- 若沒有使用recover(),執行完defer函數後產生錯誤訊息跳出。使用recover()捕捉panic後,執行完defer函數後跳出。
Concurrency
- 使用concurrency來撰寫程式,可以進行平行處理(parallel computing)來提升效能。Go關於concurrency的內容包含goroutines, channels, range&close, select等
Goroutine
- Goroutine類似thread,是做為並行執行的程式碼區塊。
- : 使用時僅須在函數前加上關鍵字go即可。
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(2000 * time.Millisecond)
fmt.Println(s, " ", i)
}
}
func main() {
go say("world")
time.Sleep(time.Second*5)
go say("hello")
time.Sleep(time.Second*8)
}
- 使用time.Sleep()函數讓程式休息但不關閉,否則main thread關閉程式便結束了,看不到其他結果。
- 使用time.Sleep()函數前須記得import time這個package。
- 每一個routing(thread)爭搶程式執行權,得到者執行程式,所以無法有固定順序執行。
Channel
- 前述的goroutine相互獨立,而Go使用channel在不同並行程式間傳遞資料。
- 需先宣告channel後方能使用,宣告使用make()函數。
package main
import (
"fmt"
)
func trans(s string, c chan string){
c <- s
}
func main() {
message := make(chan string)
go trans("Hello World!", message)
msg := <-message
fmt.Println(msg)
}
- 使用make(chan string)來建立一個傳遞string的channel。
- msg := <-message是將資訊由message傳遞到msg,<-箭頭表示傳遞方向。
package main
import (
"fmt"
"math/rand"
)
func sum(a [] int, c chan int){
s:=0
for _, v:= range a{
s = s + v
}
c <- s
}
func main() {
rand.Seed(10)
var a[10]int
for i:=range a{
a[i] = rand.Intn(100)
}
c:=make(chan int)
go sum(a[:len(a)/2], c)
go sum(a[len(a)/2:], c)
x:= <-c
y:= <-c
fmt.Println(x, y, x+y)
}
- 第一個go計算前半段的array元素和,第二個計算後半段。
package main
import "fmt"
func main() {
c:=make(chan string, 2)
c <- "Hello"
c <- "World"
fmt.Println(<-c)
fmt.Println(<-c)
}
- 設置兩個buffer,傳送(<-)兩次(也就是說傳送3次會出現錯誤,但可以增加goroutine的方式傳送,e.g.)。
package main
import "fmt"
func main() {
c:=make(chan string, 2)
c <- "Hello"
c <- "World"
go func(){c<-"Tom"}()
fmt.Println(<-c)
fmt.Println(<-c)
fmt.Println(<-c)
}
package main
import "fmt"
func main() {
c:=make(chan string, 5)
c <- "Hello"
c <- "World"
close(c)
fmt.Println(<-c)
fmt.Println(<-c)
fmt.Println(<-c)
}
- 原本設置5個buffer,使用兩個後關閉,再使用傳回空。
- channel跟file不同,除非要強調不再有資料傳入,否則不需特別關閉(close),以fibonacci sequence為例。
package main
import "fmt"
func fibonacci(n int, c chan int){
a,b := 0,1
for i:=0; i<n; i++{
c <- a
a, b = b, a+b
}
close(c)
}
func main() {
c:=make(chan int, 10)
go fibonacci(cap(c), c)
for i:=range c{
fmt.Println(i)
}
}
package main
import "fmt"
import "time"
func main() {
c1:=make(chan string)
c2:=make(chan string)
f1 := func(){
time.Sleep(time.Second * 1)
c1 <- "Function 1"
}
f2 := func(){
time.Sleep(time.Second * 1)
c2 <- "Function 2"
}
go f1()
go f2()
for i:=0; i<2; i++{
select{
case m1:= <-c1:
fmt.Println("Received from", m1)
case m2:= <-c2:
fmt.Println("Received from", m2)
}
}
}
- 出現順序不固定,看哪一個goroutine先搶到執行。
- 試試看給每個channel一個buffer,去除time.Sleep()。
- 使用select方式建立另一個求取fibonacci sequence的程式。
package main
import "fmt"
func fibonacci(c, quit chan int){
a,b := 0,1
for{
select{
case c <- a:
a,b=b, a+b
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func(){
for i:=0; i<10; i++{
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
package main
import "fmt"
import "time"
func main() {
tick := time.Tick(time.Millisecond*1000)
after := time.After(time.Millisecond*5000)
second:=1
for{
select{
case <- tick:
fmt.Println(second)
second++
case <- after:
fmt.Println(second, "Time is up.")
return
default:
fmt.Print(".")
time.Sleep(100*time.Millisecond)
}
}
}
IO
- 此章介紹IO與其他。
Writing & Reading Files
- Go可以讓我們讀寫檔案資料
- Writing to a file......。
package main
import "fmt"
import "os"
func main() {
file, err := os.Create("output.txt")
if err != nil{
panic(err)
}
defer file.Close()
//b := []byte("Here something is written into the file\n......")
//len, err := file.Write(b)
len, err := file.WriteString("Write something into the file...")
if err != nil{
fmt.Println("Failed writing to file:", err)
}
fmt.Println("Length of file:", len, "bytes")
fmt.Println("File Name:", file.Name())
}
- 因為要操作檔案建立,記得import "os",使用os.Create(filename)來建立檔案。
- defer file.Close()用來確保完成後檔案關閉。
- 使用file.WriteString(string)來將字串寫入檔案,會傳回檔案資料長度,使用file.Name()可得到檔案名。
- 也可以使用file.Write(b),參數須為byte,所以需先將字串改為b := []byte("Here something is written into the file\n......")。
- 也可以直接使用ioutil內的WriteFile(),若寫入檔案不存在則自動建立,第二的參數是byte。這方法不適用於大檔案。
package main
import (
"fmt"
"io/ioutil"
)
func main() {
b := []byte("Write something...\nWrite more...")
err := ioutil.WriteFile("output1.txt", b, 0644)
if err != nil{
fmt.Println("Something wrong")
panic(err)
}
}
package main
import (
"fmt"
"bufio"
"os"
)
func main() {
file, err := os.OpenFile("output2.txt", os.O_CREATE, 0644)
if err != nil{
panic(err)
}
defer file.Close()
w := bufio.NewWriter(file)
n, err := w.WriteString("Write Something into file...\nWrite more...")
fmt.Printf("Wrote %d bytes", n)
w.Flush()
}
package main
import (
"fmt"
"os"
"io/ioutil"
)
func main() {
file, err := os.Open("output.txt")
if err != nil{
panic(err)
}
defer file.Close()
data, err := ioutil.ReadFile("output.txt")
if err != nil{
fmt.Println("Fail to read from file.")
}
fmt.Println("Length of data:", len(data))
fmt.Printf("Data: %s", data)
fmt.Println("\nError:", err)
}
- 此處讀檔時需使用package "io/ioutil",所以要記得import "io/ioutil"。
- 首先還是要先開啟檔案,使用os.Open(filename)。
- 接下來使用ioutil.ReadFile(filename)來讀取檔案內容,最後使用fmt.Printf("%s", data)將其印出。
- 原則上使用io/ioutil不需先行開啟,直接讀取即可。
package main
import (
"fmt"
"io/ioutil"
)
func main() {
b, err := ioutil.ReadFile("output.txt")
if err != nil{
panic(err)
}
fmt.Printf("%s", b)
}
package main
import (
"fmt"
"bufio"
"os"
)
func main() {
file, err := os.Open("output2.txt")
if err != nil{
panic(err)
}
defer file.Close()
r := bufio.NewReader(file)
//n, err := r.Peek(10)
// fmt.Printf("%s", n)
var s string
for{
s, err = r.ReadString('\n')
fmt.Println(s)
if err != nil{
break
}
}
file.Close()
}
package main
import (
"fmt"
"bufio"
"os"
)
func main() {
file, err := os.Open("output.txt")
if err != nil{
panic(err)
}
defer file.Close()
r := bufio.NewReader(file)
for {
line, err := r.ReadSlice('\n')//line, err := r.ReadBytes('\n')
fmt.Printf("%s", line)
if err!=nil{
break
}
}
file.Close()
}
package main
import (
"fmt"
"bufio"
"os"
)
func main() {
file, err := os.Open("output.txt")
if err != nil{
panic(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan(){
fmt.Println(scanner.Text())
}
if err := scanner.Err(); err != nil {
fmt.Println(os.Stderr, "reading standard input:", err)
}
}
time
- 使用package time可以取得或計算時間
- 使用前必須先import time這個package。
package main
import (
"fmt"
"time"
)
func main() {
p := fmt.Println
now := time.Now()
p(now)
sometime := time.Date(2018, 9, 23, 15, 30, 10, 12, time.Local) //time.UTC
p(sometime)
p(sometime.Year())//Month(), Day(), Hour(), Minute(), Second(), Nanosecond(), Location()
p(sometime.Weekday())
p(sometime.Before(now))//After(now), Equal(now)
diff := now.Sub(sometime)
p(diff)
p(diff.Hours())//Minutes(), Seconds(), Nanoseconds()
p(sometime.Add(diff))// Add(-diff)
}
- p := fmt.Println是為了減少打字,之後使用p()即等於fmt.Println()。
- time.Now()傳回目前時間。
- time.Date()可以設定一個時間,使用time.Local取得目前時區(time.UTC為標準時間)。
- p(time1.Before(time2))用來判斷設定之時間是否早於另一時間。
- 使用time1.Sub(time2)來求得兩時間之差。
- 使用time1.Add(diff)來加上(使用-diff來減去)一段時間。
strings
- 關於字串的操作
- : 需要import package strings。
package main
import (
"fmt"
"strings"
)
func main() {
s := "This is a string."
p := fmt.Println
p(strings.Contains(s, "is"))
p(strings.Count(s, "s"))
p(strings.HasPrefix(s, "Th"))
p(strings.HasSuffix(s, "ing."))
p(strings.Index(s, "is"))
p(strings.Join([]string{"abc", "xyz"}, " to "))
p(strings.Repeat(s, 2))
p(strings.Replace(s, "is", "at", 2))
p(strings.ToUpper(s))
p(strings.ToLower(s))
sub:=strings.Split(s, " ")
for k,v:=range sub{
p(k, " ", v)
}
}
- sub是一個array。
Web
- Go內建網路伺服器的package,所以可用於設計web程式,成為主機或進行網路資料傳輸。
Get & Post
- Go提供一個package名為"net/http"來協助搭建Web服務
- : 使用Go http中的Get來取得某網頁資料。
package main
import (
"fmt"
"net/http"
"io/ioutil"
)
func httpGet(){
resp, err := http.Get("http://www.yahoo.com")
if err != nil{
panic(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
fmt.Printf("%s", body)
if err != nil{
fmt.Printf("Something Wrong.")
}
}
func main() {
httpGet()
}
- 使用http.Get將request送給Yahoo網站並取得其回應(resp),使用ioutil.ReadAll()函數讀取response的Body資料。
package main
import (
"fmt"
"net/http"
"io/ioutil"
"net/url"
)
func httpPost(){
//resp, err := http.Post("https://tw.yahoo.com", "appiication/x-www.form-urlencoded", strings.NewReader("name=test"))
resp, err := http.PostForm("http://tw.yahoo.com/", url.Values{"key":{"Value"}, "id":{"test"}})
if err != nil{
panic(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
fmt.Printf("%s", body)
if err != nil{
fmt.Printf("Something Wrong.")
}
}
func main() {
httpPost()
}
- 使用http.PostForm()較為簡潔,使用http.Post()需要import "strings"。
Go Web
- 可使用Go建立一個web server。
- : 下例說明如何建立一個基本的web server,需import "net/http"。
package main
import "fmt"
import "net/http"
func handlerFunc(w http.ResponseWriter, r *http.Request){
fmt.Fprint(w, "Hello, Welcome to my web site!")
}
func main() {
http.HandleFunc("/", handlerFunc)
http.ListenAndServe(":3000", nil)
}
- 記得import "net/http這個package。
- 使用http.HandleFunc()來註冊請求"/"的router規則,然後轉到HandleFunc這個函數(serveHTTP)。
- 然後使用http.ListenAndServe(addr string, handler Handler)函數來傾聽在TCP網路上的位址並使用handler呼叫伺服器來處理requests。
- handler通常為nil(使用預設之router -> DefaultServeMux),而ListenAndServe通常傳回non-nil error。
package main
import "fmt"
import "net/http"
func handlerFunc(w http.ResponseWriter, r *http.Request){
fmt.Fprint(w, "Hello, Welcome to my web site!")
}
func main() {
http.HandleFunc("/", handlerFunc)
http.HandleFunc("/PageOne", func(w http.ResponseWriter, r *http.Request){
fmt.Fprintf(w, "Welcome to Page One.")
})
http.ListenAndServe(":3000", nil)
}
- 到http://localhost:3000/PageOned可看到另一頁的內容。
package main
import ("fmt"; "net/http"; "sync"; "strconv")
var (counter int; mutex = &sync.Mutex{})
func increment(w http.ResponseWriter, r *http.Request){
fmt.Fprint(w, "You are guest nmber ")
mutex.Lock()
counter++
fmt.Fprintf(w, strconv.Itoa(counter))
mutex.Unlock()
}
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
fmt.Fprint(w, "Hello, Welcome to my web site! ")
})
http.HandleFunc("/increment", increment)
http.HandleFunc("/PageOne", func(w http.ResponseWriter, r *http.Request){
fmt.Fprintf(w, "Welcome to Page One.")
})
http.ListenAndServe(":3000", nil)
}
- sync.Mutex{}用來確保只有一個goroutine可以取用變數,先使用mutex.Lock()鎖定,再使用mutex.Unlock()解鎖。
package main
import ("fmt"; "net/http"; "sync"; "strconv")
var (counter int; mutex = &sync.Mutex{})
func increment(w http.ResponseWriter, r *http.Request){
fmt.Fprint(w, "You are guest nmber ")
mutex.Lock()
counter++
fmt.Fprintf(w, strconv.Itoa(counter))
mutex.Unlock()
}
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
//fmt.Fprint(w, "Hello, Welcome to my web site! ")
http.ServeFile(w, r, r.URL.Path[1:])
})
http.HandleFunc("/increment", increment)
http.HandleFunc("/PageOne", func(w http.ResponseWriter, r *http.Request){
fmt.Fprintf(w, "Welcome to Page One.")
})
http.ListenAndServe(":3000", nil)
}
- http.ServeFile(w, r, r.URL.Path[1:])若將1改為0則顯示根目錄。
- 顯示中若有html file可直接顯示html內容。
- 僅http.ServeFile(w, r, r.URL.Path[1:])與fmt.Fprint()並用則顯示連結文字。
package main
import ("fmt"; "net/http"; "sync"; "strconv")
var (counter int; mutex = &sync.Mutex{})
func increment(w http.ResponseWriter, r *http.Request){
fmt.Fprint(w, "You are guest nmber ")
mutex.Lock()
counter++
fmt.Fprintf(w, strconv.Itoa(counter))
mutex.Unlock()
}
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
fmt.Fprint(w, "Hello, Welcome to my web site! ")
//http.ServeFile(w, r, r.URL.Path[1:])
})
fs := http.FileServer(http.Dir("static/"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
http.HandleFunc("/increment", increment)
http.HandleFunc("/PageOne", func(w http.ResponseWriter, r *http.Request){
fmt.Fprintf(w, "Welcome to Page One.")
})
http.ListenAndServe(":3000", nil)
}
- 在http://localhost:3000/static/可看到static資料夾中的檔案。
- http.StripPrefix("/static/", fs):in order to strip away part of the URL path to correctly serve files from system。
package main
import ("fmt"; "net/http"; "sync"; "strconv"; "html/template")
var (counter int; mutex = &sync.Mutex{})
var tpl *template.Template
func init(){
tpl = template.Must(template.ParseFiles("static/static1.html")) //tpl, _ := template.ParseFiles("static/static1.html")
}
func increment(w http.ResponseWriter, r *http.Request){
fmt.Fprint(w, "You are guest nmber ")
mutex.Lock()
counter++
fmt.Fprintf(w, strconv.Itoa(counter))
mutex.Unlock()
}
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
tpl.ExecuteTemplate(w, "static1.html", nil)
fmt.Fprint(w, "Hello, Welcome to my web site! ")
http.ServeFile(w, r, r.URL.Path[1:])
})
fs := http.FileServer(http.Dir("static/"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
http.HandleFunc("/increment", increment)
http.HandleFunc("/PageOne", func(w http.ResponseWriter, r *http.Request){
fmt.Fprintf(w, "Welcome to Page One.")
})
http.ListenAndServe(":3000", nil)
}
- template.Must()也可以置於tpl.ExecuteTemplate(w, "static1.html", nil)之前(若此去除init())。
- template.Must()也可改為tpl, _ := template.ParseFiles("static/static1.html"),應為tpl, err := template.ParseFiles("static/static1.html")之後再接if err!=nil{...},因不處理err,使用_代替。記得此時需去除tpl的宣告。
- 此時http://localhost:3000/可看到static1.html的內容。
template
- 使用MVC(Model, View, Controller)設計模式,Models解析data,views顯示結果,controllers處理使用者的requests。在views的部分,常使用static HTML files當作模組,僅改變部分要顯示的資料,此稱為template,template可以方便重複使用。
- : 先建立一個簡單的template,需要import "html/template"。
package main
import (
"html/template"
"os"
)
type User struct{
UserName string
Email string
}
func temp(){
t := template.New("a template")
t, _ = t.Parse("hello {{.UserName}} {{.Email}}")
u := User{UserName: "Tom Smith", Email: "tomsmith@gmail.com"}
t.Execute(os.Stdout, u)
}
func main() {
temp()
}
- 建立一個type名為User,其中僅有一個變數UserName。
- New()一個template,使用Parse()函數解析型態,然後使用Execute()將物件(user)與模組(t)合併。
package main
import (
"html/template"
"os"
)
type User struct{
UserName string
Emails [] string
}
func temp(){
t := template.New("a template")
t, _ = t.Parse(
`hello {{.UserName}}
{{range.Emails}}
Email: {{.}}
{{end}}
`)
u := User{UserName: "Tom Smith",
Emails: [] string {"tomsmith@gmail.com", "tom@yahoo.com"}}
t.Execute(os.Stdout, u)
}
func main() {
temp()
}
- 使用``代替"",字串可以換行。
package main
import (
"html/template"
"os"
)
type Account struct{
ID int
Password string
}
type User struct{
UserName string
Emails [] string
Accounts [] *Account
}
func temp(){
t := template.New("a template")
t, _ = t.Parse(
`hello {{.UserName}}
{{range.Emails}}
Email: {{.}}
{{end}}
{{with .Accounts}}
{{range .}}
Account: {{.ID}} {{.Password}}
{{end}}
{{end}}
`)
a1 := Account{ID:1, Password:"123"}
a2 := Account{ID:2, Password:"password"}
u := User{UserName: "Tom Smith",
Emails: [] string {"tomsmith@gmail.com", "tom@yahoo.com"},
Accounts: [] *Account {&a1, &a2}}
t.Execute(os.Stdout, u)
}
func main() {
temp()
}
- 使用with .Accounts表示對於參數accounts[],因為accounts是陣列,所以再使用range .。
- 如果accounts不是array,那首先去除template中的{{range .}}與{{end}},然後使用Accounts: &a1即可,若不傳址,也可傳值。
package main
import (
"html/template"
"os"
)
type Account struct{
ID int
Password string
Closed bool
}
type User struct{
UserName string
Emails [] string
Accounts [] *Account
}
func temp(){
t := template.New("a template")
t, _ = t.Parse(
`hello {{.UserName}}
{{range.Emails}}
Email: {{.}}
{{end}}
{{with .Accounts}}
{{range .}}
{{if .Closed}}
Account: {{.ID}} is closed.
{{else}}
Account: {{.ID}} {{.Password}}
{{end}}
{{end}}
{{end}}
`)
a1 := Account{ID:1, Password:"123", Closed:true}
a2 := Account{ID:2, Password:"password", Closed:false}
u := User{UserName: "Tom Smith",
Emails: [] string {"tomsmith@gmail.com", "tom@yahoo.com"},
Accounts: [] *Account {&a1, &a2}}
t.Execute(os.Stdout, u)
}
func main() {
temp()
}
- if之後的condition是boolean值,不能做運算(e.g. !.Closed)。
layout.html
<h1>{{.PageTitle}}<h1>
go1.go
package main
import (
"fmt"
"net/http"
"html/template"
)
var tpl *template.Template
func main() {
tpl, _ := template.ParseFiles("static/layout.html")
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
err:=tpl.Execute(w, map[string]string{"PageTitle" : "The Title"})
if err != nil{
panic(err)
}
//tpl.ExecuteTemplate(w, "layout.html", map[string]string{"PageTitle" : "The Title"})
fmt.Fprint(w,"Something else")
})
http.ListenAndServe(":3000", nil)
}
- layout.html置於以下檔案路徑workspace\src\go1\static,go1.go的檔案目錄則為workspace\src\go1(go1為project name)。
- 可以使用tpl.Execute()或tpl.ExecuteTemplate()來執行(輸入參數不同)。
- 使用map來對應layout.html中的PageTitle與其值。
package main
import (
"fmt"
"net/http"
"html/template"
)
var tpl *template.Template
type Page struct{
PageTitle string
}
func main() {
tpl, _ := template.ParseFiles("static/layout.html")
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
p := Page{"The Page Title"}
err:=tpl.Execute(w, p)
if err != nil{
panic(err)
}
//tpl.ExecuteTemplate(w, "layout.html", map[string]string{"PageTitle" : "The Title"})
fmt.Fprint(w,"Something else......")
})
http.ListenAndServe(":3000", nil)
}
layout.html
<h1>{{.PageTitle}}<h1> <ul> {{range .Items}} <li>{{.ItemTitle}}</li> {{end}} </ul>
go1.go
package main
import (
"fmt"
"net/http"
"html/template"
)
var tpl *template.Template
type Item struct{
ItemTitle string
}
type Page struct{
PageTitle string
Items [] Item
}
func main() {
tpl, _ := template.ParseFiles("static/layout.html")
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
items := [] Item {Item{"item 1",}, Item{"item 2",}}
p := Page{"The Page Title", items}
err:=tpl.Execute(w, p)
if err != nil{
panic(err)
}
//tpl.ExecuteTemplate(w, "layout.html", map[string]string{"PageTitle" : "The Title"})
fmt.Fprint(w,"Something else................")
})
http.ListenAndServe(":3000", nil)
}
- 另外設計一個type Item並在type Page中增加一個Item的array。
- 在layout.html中使用range...end來對每一個item進行表列。
layout.html
<head> <style> .done{ color: red; } </style> </head> <h1>{{.PageTitle}}<h1> <ul> {{range .Items}} {{if .Done}} <li class="done">{{.ItemTitle}}</li> {{else}} <li>{{.ItemTitle}}</li> {{end}} {{end}} </ul>
go1.go
package main
import (
"fmt"
"net/http"
"html/template"
)
var tpl *template.Template
type Item struct{
ItemTitle string
Done bool
}
type Page struct{
PageTitle string
Items [] Item
}
func main() {
tpl, _ := template.ParseFiles("static/layout.html")
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
items := [] Item {Item{"item 1", true}, Item{"item 2",false}, Item{"item 3", true}, Item{"item 4", false}, Item{"item 5", false}}
p := Page{"The Page Title", items}
err:=tpl.Execute(w, p)
if err != nil{
panic(err)
}
//tpl.ExecuteTemplate(w, "layout.html", map[string]string{"PageTitle" : "The Title"})
fmt.Fprint(w,"Something else................")
})
http.ListenAndServe(":3000", nil)
}
- 如前所述,if僅接受boolean值,不能做運算。
layout.gohtml
{{define "layout"}} <html> <head> <title>Go Web Server</title> <style> .done{ color: blue; } </style> </head> <body> <h1>{{.PageTitle}}<h1> <ul> {{range .Items}} {{if .Done}} <li class="done">{{.ItemTitle}}</li> {{else}} <li>{{.ItemTitle}}</li> {{end}} {{end}} </ul> {{template "bodyLayout" .}} </body> </html> {{end}} {{define "bodyLayout"}} <div style="background: #f1f1f1">This page is a demonstration of <span style="color: red">{{.ComLang}}</span>.</div> {{end}}
go1.go
package main
import (
"fmt"
"net/http"
"html/template"
)
var tpl *template.Template
type Item struct{
ItemTitle string
Done bool
}
type Page struct{
PageTitle string
ComLang string
Items [] Item
}
func main() {
tpl, _ := template.ParseFiles("static/layout.gohtml")
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
items := [] Item {Item{"item 1", true}, Item{"item 2",false}, Item{"item 3", true}, Item{"item 4", true}, Item{"item 5", false}}
p := Page{PageTitle: "The Page Title", ComLang: "Go", Items: items}
tpl.ExecuteTemplate(w, "layout", p)
fmt.Fprint(w,"<br>Something else................")
})
http.ListenAndServe(":3000", nil)
}
- 在Go的設計中,為了區別也可以將template的延伸檔名訂為gohtml,在此例中使用gohtml(但此檔案在有些編輯器無法對關鍵字顯示顏色)。
- 在layout.gohtml中使用{{define "layout"}}...{{end}}來含括一個tempalte,{{end}}表示template的結尾。因此可以再設計另一個template在{{define "bodyLayout"}}...{{end}}。
- 請注意在template layout中包含另一個template bodyLayout,此稱為Nested template。因為bodyLayout要取得目前的資料,所以在納入時要寫{{template "bodyLayout" .}},其中的.表示目前資料。
layout.html
{{define "layout"}} <html> <head> <title>Go Web Server</title> <style> .done{ color: blue; } </style> </head> <body> <h1>{{.PageTitle}}<h1> <ul> {{range .Items}} {{if .Done}} <li class="done">{{.ItemTitle}}</li> {{else}} <li>{{.ItemTitle}}</li> {{end}} {{end}} </ul> {{template "bodyLayout" .}} <br><br><br> </body> <footer> {{template "footerLayout" .}} </footer> </html> {{end}}
bodyLayout.html
{{define "bodyLayout"}} <div style="background: #f1f1f1;">This page is a demonstration of <span style="color: red">{{.ComLang}}</span>.</div> {{end}}
footerLayout.html
{{define "footerLayout"}} <div style="background: #a7a9aa;">© written by <span style="color: gold">{{.Author}}</span>.</div> {{end}}
go1.go
package main
import (
"net/http"
"html/template"
)
var tpl *template.Template
type Item struct{
ItemTitle string
Done bool
}
type Page struct{
PageTitle string
ComLang string
Author string
Items [] Item
}
func main() {
tpl, _ := template.ParseFiles("static/layout.html", "static/bodyLayout.html", "static/footerLayout.html")
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
items := [] Item {Item{"item 1", true}, Item{"item 2",false}, Item{"item 3", true}, Item{"item 4", true}, Item{"item 5", false}}
p := Page{PageTitle: "The Page Title", ComLang: "Go", Author:"Tom Smith", Items: items}
tpl.ExecuteTemplate(w, "layout", p)
})
http.ListenAndServe(":3000", nil)
}
- 在layout.html內包含了bodyLayout與footerLayout兩個tempalte,所以設計三個檔案並以layout.html為主體。
- 因為超過一個template檔案,所以必須在template.ParseFiles()函數內將所有使用到的template檔案都納入。
footerLayout.html
{{define "footerLayout"}} <div style="background: #a7a9aa;">© written by <span style="color: blue">{{.FirstName}} {{.LastName}}</span>.</div> {{end}}
layout.html
{{define "layout"}} <html> <head> <title>Go Web Server</title> <style> .done{ color: blue; } </style> </head> <body> <h1>{{.PageTitle}}<h1> <ul> {{range .Items}} {{if .Done}} <li class="done">{{.ItemTitle}}</li> {{else}} <li>{{.ItemTitle}}</li> {{end}} {{end}} </ul> {{template "bodyLayout" .}} <br><br><br> </body> <footer> {{template "footerLayout" .Author}} </footer> </html> {{end}}
go1.go
package main
import (
"net/http"
"html/template"
)
var tpl *template.Template
type Item struct{
ItemTitle string
Done bool
}
type AuthorInfo struct{
FirstName string
LastName string
}
type Page struct{
PageTitle string
ComLang string
Author AuthorInfo
Items [] Item
}
func main() {
tpl, _ := template.ParseFiles("static/layout.html", "static/bodyLayout.html", "static/footerLayout.html")
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
items := [] Item {Item{"item 1", true}, Item{"item 2",false}, Item{"item 3", true}, Item{"item 4", true}, Item{"item 5", false}}
p := Page{PageTitle: "The Page Title", ComLang: "Go", Author:AuthorInfo{"Tom", "Smith"}, Items: items}
tpl.ExecuteTemplate(w, "layout", p)
})
http.ListenAndServe(":3000", nil)
}
- type AuthorInfo將fistname與lastname分為兩個參數,所以在footerLayout.html內使用{{.FirstName}} {{.LastName}}。
- 在嵌入{{template "footerLayout" .Author}}時使用.Author表示使用目前資料內的Author的資料。
- 也可以使用{{template "footerLayout" .}},footerLayout.html內則需使用{{.Author.FirstName}} {{.Author.LastName}}。
- 綜合以上,可以設計一個基本的版面模組,其中且分為header,body,footer等不同部分,分別根據每一部分設計多個不同的template,屆時只需要隨需要組合即可。