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,屆時只需要隨需要組合即可。
