goroot和gopath
- Goroot是存放go編譯器的位置
- Gopath是存放源代碼的位置。gopath下必須有三個目錄,src, pkg, bin如果是從外部導入的代碼,需要放在src目錄下,比如$gopath/src/github.com
go面向對象
- 封裝
- 繼承
- 多態
匿名字段:只提供類型,而不寫字段名的字段
http服務器
package main
import "net/http"
func HandleCon(w http.ResponseWriter, req *http.Request){
w.Write([]byte("hello go"))
}
func main(){
http.HandleFunc("/", HandleCon) //註冊回調函數
http.ListenAndServe(":8000",nil) //綁定端口
}
在服務端打斷點才能看到實際運行
init函數
init函數有一下幾點特性:
- init函數在main執行之前,自動被調用執行的,不能顯示調用
- 每個包的init函數在包被引用時,自動被調用
- 每個包可以有多個init函數
- 同一個文件中可定義多個init()函數
map轉json
//map轉json
m := map[string][]string{
"level":{"debug"},
"message":{"File not found", "stack overflow"},
}
//將map解析成Json格式
if data, err := json.Marshal(m); err == nil{
fmt.Printf("%s\n",data)
}
結構體轉json
package main
import (
"encoding/json"
"fmt"
)
//變量名必須大寫
type IT struct {
Company string `json:"company"`
Subject []string`json:"subjects"`
Isok bool `json:"-"` //不輸出
Price float64 `json:",string"`
}
func main() {
//定義一個結構體變量 同時初始化
s :=IT{"itcast", []string{"GO","C++","Python","Java"}, true, 666.66}
//編碼 根據內容生成Json文本
//buf, err := json.Marshal(s)
buf, err := json.MarshalIndent(s,""," ")
if err != nil{
fmt.Printf("error ", err)
}
fmt.Printf("%+v", string(buf))
}
map轉json
func main() {
//創建一個map
m := make(map[string]interface{}, 4)
m["company"]="itcast"
m["subjects"]=[]string{"GO","C++","Java"}
m["isok"]=true
m["price"]=666.666
//編碼成json
//result, err := json.Marshal(m)
result, err := json.MarshalIndent(m,""," ")
if err != nil{
fmt.Printf("error",err)
return
}
fmt.Printf("result = %v", string(result))
}
方法和函數
黑馬視頻
方法是一個包含了接收者的函數,指定了調用者,對象或指針,方法的名字可以相同,但是接收者必須不同。
對比函數:
A:意義
方法:某個類別的行爲功能,需要指定的接收者調用。
函數:一段獨立功能的代碼,可以直接調用。
B:語法:
方法:方法名可以相同,只要接收者不同。
函數:命名不能衝突
package main
import "fmt"
//定義結構體
type Worker struct{
name string
age int
sex string
}
//定義結構體
type Cat struct{
name string
age int
}
//定義方法
func (w Worker) work(){
fmt.Println(w.name, "在工作")
}
//定義方法
func (p *Worker) rest(){
fmt.Println(p.name, "在休息")
}
func(p * Worker) printInfo(){
fmt.Printf( "%+v\n",p)
}
func(p * Cat) printInfo(){
fmt.Printf( "%+v\n",p)
}
func main() {
w := Worker{name: "yao jun", age: 14, sex: "male"}
w.work()
w.rest()
w.printInfo()
c := Cat{"tian tian", 18}
c.printInfo()
}
繼承
使用結構體嵌套,模擬繼承。
package main
import "fmt"
//1. 父類
type Person struct{
name string
age int
}
//2. 子類
type Student struct{
Person //結構體嵌套 模擬繼承性
school string
}
//3. 父類方法
func (p *Person) eat(){
fmt.Println("父類喫窩窩頭...")
}
//4. 子類新增方法
func (s *Student) study(){
fmt.Println("子類學習...")
}
//5. 子類重寫父類方法
func (s *Student) eat(){
fmt.Println("子類喫炸雞...")
}
func main(){
p := Person{"yao jun", 19}
p.eat()
s := Student{p, "重慶大學"}
s.eat() //重寫父類方法
fmt.Println(s.name) //s.Person.name 子類直接訪問父類的字段 提升字段
s.study()
}
接口
功能的定義和實現分類,解耦合。
package main
import "fmt"
//1. 定義接口
type USB interface {
start()
end()
}
//2. 定義實現類
type Mouse struct{
name string
}
type FlaskDisk struct{
name string
}
//這裏只能是對象 不能是指針
func (m Mouse) start(){
fmt.Println("鼠標開始")
}
func (m Mouse) end(){
fmt.Println("鼠標結束")
}
func (f FlaskDisk) start(){
fmt.Println("U盤開始")
}
func (f FlaskDisk) end(){
fmt.Println("U盤結束")
}
//3. 測試方法
func testInterface(usb USB){
usb.start()
usb.end()
}
func main(){
m := Mouse{"羅技"}
f := FlaskDisk{"閃迪"}
m.start()
m.end()
f.start()
f.end()
fmt.Println(m.name)
testInterface(m)
testInterface(f)
var usb USB
usb = m
testInterface(usb)
usb.start()
usb.end()
//fmt.Println(usb.name) 接口對象不能方位實現類中的屬性
}
多態
- 子類是一個特殊的父類類型,可以看作父類對象
- 子類可以看作子類對象
一個函數如果接受接口類型作爲參數,那麼實際上可以傳入接口的任意實現類型對象作爲參數。定義一個類型爲接口類型,實際上可以賦值爲任意實現類的對象。
鴨子類型:dock type
www.json.cn檢查json格式
- 通過結構體生成json
- 通過map生成json
結構體變量名首字母必須大寫
函數
package main
import "fmt"
//不定參 函數
func ff(args ...int){
for i := 0; i < len(args); i++{
fmt.Printf("args[%d] = %d\n", i, args[i])
}
}
func main(){
ff(1,3,5)
}
println和printf的區別
a := 100
fmt.Println("a = ",a) //一段一段處理 自動換行
fmt.Printf("a = %d\n", a) //格式化處理
匿名變量 配合函數返回值 纔有優勢
函數類型(對應於C函數指針)
package main
import "fmt"
func add(a, b int) int{
return a+b
}
func sub(a, b int) int{
return a - b
}
type FuncType func(a, b int) int
func main(){
//函數也是一種數據類型,通過type給一個函數類型起名 必須要有同樣的參數和返回值
var fTest FuncType //聲明一個函數類型的變量
//指向加法
fTest = add
res := fTest(4, 6)
fmt.Println(res)
//指向減法 實現多態
fTest = sub
res = fTest(88,66)
fmt.Println(res)
}
回調函數:函數有一個參數是函數類型
package main
import (
"fmt"
)
func add(a, b int) int{
return a+b
}
func sub(a, b int) int{
return a - b
}
type FuncType func(a, b int) int
func calc(a, b int, fTest FuncType)(result int){
fmt.Printf("calc = ")
result = fTest(a, b)
fmt.Println(result)
return
}
func mul(a, b int)int{
return a*b
}
func div(a, b int)int{
if b==0{
panic("除數爲0")
}
return a/b
}
func main(){
calc(5,6,add)
calc(5,2, sub)
calc(4,3, mul)
calc(6,2, div)
}
匿名函數:不需要定義函數名的一種函數實現
閉包:一個函數捕獲了和它在同一作用域的其他常量和變量。
func main(){
a := 10
b := 20
//匿名函數
f := func()int {
fmt.Println(a+b)
return a+b
}
f()
//定義匿名函數 同時調用
func(c int) int{
fmt.Println(a-b+c)
return a - b + c
}(5) //立即執行函數
}
閉包以引用方式捕獲外部變量 內部變 外部也跟着變。只要閉包還在使用,捕獲的變量就會一直存在,閉包裏面變量的生命週期不是由它的作用域決定的。
package main
import "fmt"
func test() func()int{
var x int
return func() int {
x++
return x*x
}
}
func main(){
f := test()
fmt.Println(f())
fmt.Println(f())
fmt.Println(f())
fmt.Println(f())
}
字符串常用API
package main
import (
"fmt"
"strings"
)
func main(){
//Contains包含
fmt.Println(strings.Contains("hellogo", "hello")) //判斷是否包含
//Join拼接
s := []string{"abc", "hello", "world", "go"}
fmt.Println(strings.Join(s, "_"))
//Index索引
fmt.Println(strings.Index("helloWorld","World"))
//Repeat重複拼接
fmt.Println(strings.Repeat("abc", 3))
//Split分割
fmt.Println(strings.Split("hello go world"," "))
//Trim去掉兩頭空格
fmt.Println(strings.Trim(" hello world ", " "))
//Fields提取單詞 空格分割
fmt.Println(strings.Fields("hello world are you ok!"))
}
其他類型轉換爲字符串
package main
import (
"fmt"
"strconv"
)
func main(){
var str string
str = strconv.FormatInt(44,32)
fmt.Println(str)
//整型轉字符串
str = strconv.Itoa(19)
fmt.Println(str)
//字符串轉整形
a,_ := strconv.Atoi("234")
fmt.Println(a)
}
正則表達式
package main
import (
"fmt"
"regexp"
)
func main(){
buf := "abc azc atc a2c a4c at"
//解釋器
reg := regexp.MustCompile(`a.c`)
if reg == nil{
panic("reg error")
}
//根據規則提取信息
res := reg.FindAllStringSubmatch(buf,-1)
fmt.Println(res)
}
反轉字符串
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main(){
//反轉字符串
bf := bufio.NewReader(os.Stdin)
ss, _ := bf.ReadString('\n')
res := strings.Fields(ss)
n := len(res)
for i := 0; i < n/2; i++{
res[i], res[n-1-i] = res[n-1-i], res[i]
}
for i := 0; i < n; i++{
if i!= n-1{
fmt.Printf("%s ", res[i])
}else{
fmt.Printf("%s\n", res[i])
}
}
}
資源管理和出錯處理
defer是一個棧,後出現的defer先執行,先進後出。
測試
go test .
package awesomeProject
import "testing"
/*傳統測試
1. 測試數據和測試邏輯混在一起
2. 出錯信息不明確
表格驅動測試
1. 測試數據和邏輯分離
2. 明確的出錯信息
3. 可以部分失敗
4. GO語言的語法使表格驅動測試更適合
*/
func TestAdd(t *testing.T){
tests := [] struct{a, b, c int}{
{3, 4, 7},
{5, 7, 12},
{8, 11, 19},
}
for _, tt := range tests{
if actual := add(tt.a, tt.b); actual != tt.c{
t.Errorf("add(%d, %d); actual is %d, expected %d\n", tt.a, tt.b, actual,tt.c)
}
}
}
//同目錄下的函數
package awesomeProject
func add(a, b int)int{
return a+b
}
duck typing
描述事物的外部行爲而非內部結構。像鴨子走路,長得像鴨子,那就是鴨子。
接口由使用者定義。使用者download,實現者retriever。實現者不需要說我實現了哪個接口,只要實現接口裏的方法。由使用者規定實現者必須有某個方法。
package main
import (
"fmt"
"net/http"
"net/http/httputil"
)
type Retriever interface { //定義一個接口 和一個方法
Get(url string) string
}
func download(r Retriever) string{ //這是一個函數 參數是接口 必須要事先實現接口中的方法
return r.Get("http://www.imooc.com")
}
type Retrieve struct { //這是一個結構體 讓這個結構體來實現接口中的方法
content string
}
func (r Retrieve) Get(url string) string{ //實現的方法
resp, err := http.Get(url)
if err != nil{
panic(err)
}
result, err := httputil.DumpResponse(resp, true)
defer resp.Body.Close()
if err!= nil{
panic(err)
}
return string(result[:100])
}
func main() {
var r Retrieve
r = Retrieve{"fasdf"}
fmt.Println(download(r))
}
可以把實現類結構體賦值給對應的接口。即使實現的方法是值類型,但是傳遞的時候可以傳值也可以傳指針,語法糖。但是指針類型實現的方法,不能傳值類型。
接口是種類型。任何類型都實現了空接口。
子類接口可以轉換爲父類接口,反之不行。
package main
import (
"fmt"
)
type Human interface { //父
hello()
}
type Person interface { //子
Human
sing(lrc string)
}
type Student struct {
name string
age int
}
func main() {
var ip Person
var ih Human
ih = ip //子接口 可以轉換爲 父接口
fmt.Println(ih)
}
空接口的類型斷言
if斷言
package main
import "fmt"
type Student struct {
name string
age int
}
func main() {
m := make([] interface{}, 3)
m[0] = 1 //int
m[1] = "hello go" // string
m[2] = Student{"mike", 666} //Student
//類型斷言 第一個返回下標 第二個返回下標對應的值
for index, data := range m{
//第一個返回值 本身
//第二個返回值 真假
if value, ok := data.(int); ok==true{
fmt.Printf("m[%d] = %d\n", index, value)
} else if value, ok := data.(string); ok==true{
fmt.Printf("m[%d] = %s\n", index, value)
} else if value, ok := data.(Student); ok{
fmt.Printf("m[%d] = %v\n", index, value)
}
}
}
switch斷言
package main
import "fmt"
type Student struct {
name string
age int
}
func main() {
m := make([] interface{}, 3)
m[0] = 1 //int
m[1] = "hello go" // string
m[2] = Student{"mike", 666} //Student
//類型斷言 第一個返回下標 第二個返回下標對應的值
for index, data := range m{
switch value := data.(type) {
case int:
fmt.Printf("m[%d] = %d\n", index, value)
case string:
fmt.Printf("m[%d] = %s\n", index, value)
case Student:
fmt.Printf("m[%d] = %v\n", index, value)
}
}
}
實現接口
同一個結構體可以實現多個接口
接口可以嵌套
package main
import "fmt"
type animal interface {
mover
eater
}
type mover interface {
move()
}
type eater interface {
eat()
}
type Cat struct {
name string
feet int8
}
//Cat實現了mover接口
func (c *Cat) move(){
fmt.Printf("走貓步...")
}
//Cat實現了eater接口
func (c * Cat) eat(){
fmt.Println("貓喫魚...")
}
func main() {
}
go語言的接口的獨特之處在於,它是隱式實現。對於一個具體的類型,無須聲明它實現了哪些接口,只要提供接口所必須的方法即可。
如果一個類型實現了一個接口要求的所有方法,那麼這個類型實現了這個接口。
接口賦值:僅當一個表達式實現了一個接口, 這個表達式纔可以賦給該接口。
Go語言中,還可以使用new關鍵字對類型(包括結構體、整型、浮點數、字符串等)進行實例化,結構體在實例化後會形成指針類型的結構體。
我們在實現File的時候,可能並不知道上面4個接口的存在,但不管怎樣,File實現了上面所有的4個接口。我們可以將File對象賦值給上面任何一個接口。
File在實現的時候,並不需要指定實現了哪個接口,它甚至根本不知道這4個接口的存在。
我們可以將一個實現接口的對象實例賦值給接口,也可以將另外一個接口賦值給接口。
sort自定義排序
- 排序字符串數組string
package main
import (
"fmt"
"sort"
)
type StringSlice []string
func(p StringSlice) Len()int{
return len(p)
}
func(p StringSlice) Less(i, j int)bool{
return p[i] < p[j]
}
func(p StringSlice) Swap(i, j int){
p[i],p[j] = p[j],p[i]
}
func main(){
names :=[]string{"yao","jun","ni","hao"}
//sort.Sort(StringSlice(names))
sort.Strings(names) //字符串slice的排序
fmt.Println(names)
}
- 排序整型數組int
package main
import (
"fmt"
"sort"
)
type intSlice []int
func(p intSlice) Len()int{
return len(p)
}
func(p intSlice) Less(i, j int)bool{
return p[i] < p[j]
}
func(p intSlice) Swap(i, j int){
p[i],p[j] = p[j],p[i]
}
func main(){
ints := []int{1,4,3,2,3,6,9,10}
//sort.Ints(ints) ^^+R
sort.Sort(intSlice(ints))
fmt.Println(ints)
}
- 結構體排序struct
最核心是實現比較函數
package main
import (
"fmt"
"sort"
)
type Student struct{
name string
age int
}
type stuSlice []Student
func(p stuSlice) Len()int{
return len(p)
}
func(p stuSlice) Less(i, j int)bool{
return p[i].age < p[j].age || p[i].age==p[j].age && p[i].name < p[j].name
}
func(p stuSlice) Swap(i, j int){
p[i],p[j] = p[j],p[i]
}
func main(){
students := []Student{{"mike",16},{"jane",22},{"Ben",19}}
sort.Sort(stuSlice(students))
fmt.Println(students)
}
- 牛客網考研題成績排序
package main
import (
"fmt"
"sort"
)
type Student struct{
id int
score int
}
type stuSlice []Student
func(p stuSlice) Len()int{
return len(p)
}
func(p stuSlice) Less(i, j int)bool{
return p[i].score < p[j].score || p[i].score==p[j].score && p[i].id < p[j].id
}
func(p stuSlice) Swap(i, j int){
p[i],p[j] = p[j],p[i]
}
func main(){
var a,b,n int
fmt.Scanf("%d", &n)
var students []Student
for i:= 0;i < n ;i++{
fmt.Scanf("%d %d", &a, &b)
students = append(students, Student{a,b})
}
sort.Sort(stuSlice(students))
for i:= 0; i < n; i++{
fmt.Printf("%d %d\n", students[i].id, students[i].score)
}
}
- 逆序操作
sort.Sort(sort.Reverse(stuSlice(students)))
goroutine併發
1.計算和動畫同時執行
package main
import (
"fmt"
"time"
)
func main(){
go spinner(100*time.Microsecond)
const n = 45
fibN := fib(n)
fmt.Printf("\rfib(%d) = %d\n", n, fibN)
}
func spinner(delay time.Duration){
for {
for _,r := range `-\|/`{
fmt.Printf("\r%c",r)
time.Sleep(delay)
}
}
}
func fib(n int)int{
if n < 2{
return 1
}else{
return fib(n-1)+fib(n-2)
}
}
- 打印數字和字母
當新的goroutine開始時,goroutine調用立即返回。與函數不同,go不等待goroutine執行結束,當goroutine調用,並且goroutine的任何返回值被忽略之後,go立即執行到下一行代碼。
main的goroutine應該爲其他的goroutines執行,如果main的goroutine終止了,程序將被終止,而其他goroutine將不會繼續執行。
goroutine在64位計算機申請棧空間1GB
多個goroutine可以使用睡眠時長來人工設定goroutine順序執行
package main
import (
"fmt"
"time"
)
func main(){
go hello()
for i := 0; i < 100; i++{
fmt.Printf("In main A %d\n", i)
}
//等待go協程執行完畢 兩個協程交替執行 沒有順序限制
time.Sleep(time.Second*1)
fmt.Println("main over...")
}
func hello(){
for i := 0; i < 100; i++{
fmt.Printf("In hello %d\n", i)
}
}
- 併發調度
g-p-m模型 runtime.h
Sched proc.c
Goroutine machine processor
如果兩個M在同一個CPU上運行就是併發,如果在不同CPU上運行就是並行。
- chan通道
package main
import (
"fmt"
)
func main(){
var ch chan bool
ch = make(chan bool)
go func() {
for i := 0; i < 10; i++{
fmt.Printf("in goroutine %d \n", i)
}
ch<-true //寫入通道
fmt.Println("in goroutine over...")
}()
data := <-ch //讀出通道的值
fmt.Println("main over...", data)
}
- chan阻塞讀取
package main
import (
"fmt"
)
func main(){
ch := make(chan int)
go func() {
for i := 0; i < 10; i++{
fmt.Printf("in goroutine %d \n", i)
}
data := <- ch //從ch中讀入數據
fmt.Println("in goroutine over...", data)
}()
ch <- 10
fmt.Println("main over...")
}
select語句
package main
import (
"fmt"
"time"
)
//每個case都必須是一個通信
//所有Chanel表達式都會被求值
//所有被髮送的表達式都會被求值
//如果有多個表達式可以運行,select會隨機公平地選出一個執行,其他不會執行。
//否則:如果有default子句,則執行該語句。如果沒有default子句,select將阻塞,直到某個通信可以運行,go不會重新對channel或值進行求值。
func main(){
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
time.Sleep(3*time.Second)
ch1<-100
}()
go func() {
time.Sleep(3*time.Second)
ch2<-200
}()
select {
case num1 := <-ch1:
fmt.Println("ch1中獲取到的數據...",num1)
case num2, ok := <-ch2:
if ok{
fmt.Println("ch2中讀取的數據...",num2)
}else{
fmt.Println("ch2通道已關閉...")
}
default:
fmt.Println("default語句...")
}
fmt.Println("main over...")
}
ID生成 雪花算法
https://github.com/bwmarrin/snowflake
package main
import (
"fmt"
"snowflake"
)
func main(){
var n, _ = snowflake.NewNode(1)
for i := 0; i < 3; i++{
id := n.Generate()
fmt.Println("id = ", id)
fmt.Println(
"node:", id.Node(),
"step:", id.Step(),
"time:", id.Time(),
)
}
}
注意點
- 數組是值傳遞
package main
import "fmt"
//數組是值傳遞,無法通過修改數組類型的參數返回結果
func main(){
x := [3]int{1,2,3}
func(arr [3]int){
arr[0] = 7
fmt.Println(arr)
}(x)
fmt.Println(x)
}
-
map遍歷時順序不固定,是哈希表。
-
recover必須在defer函數中運行
-
main函數提前退出,無法保證後臺的goroutine正常完成。
-
Goroutine是協作式搶佔調度,Goroutine本身不會主動放棄CPU,可能會有goroutine獨佔CPU導致其他goroutine餓死。
-
Go語言帶有內存自動回收的特性,因此內存一般不會泄漏。但是Goroutine確實存在泄漏的情況,同時泄漏的Goroutine引用的內存同樣無法被回收。
自重寫
/*Go quine */
package main
import "fmt"
func main(){
fmt.Printf("%s%c%s%c\n",q,0x60,q,0x60)
}
var q = `/* Go quine */
package main
import "fmt"
func main(){
fmt.Printf("%s%c%s%c\n",q,0x60,q,0x60)
}
var q=`
RPC
滿足Go語言的RPC規則:方法只能有兩個可序列化的參數,其中第二個參數是指針類型,並且返回一個error類型,同時必須是公開的方法。
array的長度是類型的一部分
[3]int 和 [4]int 是不同的類型
slice 本質也是傳值,但看起來像傳遞了底層數組的引用
協程 corotine
-
輕量級“線程”
-
非搶佔式多任務處理,由協程主動交出控制權
-
編譯器/解釋器/虛擬機層面的多任務
-
多個協程可能在一個或多個線程上運行
runtime.Gosched() //主動交出協程的控制權
main函數也是一個協程
main 和 dowork雙向通信
python 3.5 加入了async def對協程原生支持。使用yield關鍵字實現協程。
goroutine的定義
- 任何函數只需加上go就能送給調度器運行
- 不需要在定義時區分是否是異步函數
- 調度器在合適的點進行切換
- 使用-race來檢測數據訪問衝突
goroutine可能的切換點
- IO和select
- channel
- 等待鎖
- 函數調用(有時)
- Runtime.Gosched()
package
包內的函數必須首字母大寫纔可以被外部的包調用到,小寫是調用不到的。
init方法
-
在main函數被執行之前,所有依賴package的init方法都會被執行
-
不同包的init函數按照包導入的依賴關係決定執行順序
-
每個包可以有多個init函數
-
包的每個源文件也可以有多個init函數,這點比較特殊。按照init的先後順序執行。
協程
Thread VS Coroutine
- 創建時默認的stack的大小
- JDK5以後Java Thread stack 默認爲1M
- Goroutine的stack初始化大小爲2K
- 和KSE(kernel space entity)的對應關係
- java thread 是1:1
- Goroutine是M:N
Go Test
通常test文件的命名規範是xxx_test.go
test case的函數命名爲TestXxx形式 否則go test會跳過該test不執行
-
t *testing.T 普通測試函數
-
b*testing.B benchMark測試
go test -v 輸出test的詳細信息
go test hello_test.go 輸入某個特定的test
新建一個工程 使用go mod管理 初始化一個mod
go mod init
三個文件
haha.go
haha_test.go
go.mod
//haha.go
package main
func F(a,b int)int{
return a+b
}
//haha_test.go
package main
import (
"testing"
)
func TestF(t *testing.T){
res := F(3,4)
if res != 7{
t.Errorf("add wrong!")
}
}
//go.mod
module code.byted.org/ee/test
go 1.14
t.SkipNow()爲跳過當前test,並且直接按pass處理繼續下一個testcase
func TestF(t *testing.T){
t.SkipNow() //直接跳過這個test case的測試 並PASS
res := F(3,4)
if res != 20{
t.Errorf("add wrong!")
}
}
Go test不保證多個test按照順序執行,但是我們通常會要求按照順序執行。
使用t.Run來執行subtests可以做到控制test執行的順序
func TestF(t *testing.T){
t.Run("a1", func(t *testing.T) {fmt.Println("a1")})
t.Run("a2", func(t *testing.T) {fmt.Println("a2")})
t.Run("a3", func(t *testing.T) {fmt.Println("a3")})
}
使用TestMain作爲初始化test,並且使用m.Run()來調用其他tests可以完成一些需要初始化操作的testing,比如數據庫連接,文件打開,rest服務登錄等。
如果沒有在TestMain中調用m.Run()則除了TestMain以外的其他tests都不會被執行
func TestMain(m *testing.M){
fmt.Println("test main first")
m.Run()
}
benchmark
benchmark函數一般以BenchMark開頭
benchmark函數的case一般會跑b.N次,而且每次執行都會如此
在執行過程中會根據實際case的執行時間是否穩定會增加b.N的次數以達到穩態
go test -bench=. //命令行
func BenchmarkF(b *testing.B) {
for i:=0; i < b.N; i++{
F(100,10)
}
}
benchmark也會受限於m.Run()
如果benchmark測試的函數執行時間始終無法趨於穩態 則永遠無法執行完
func unstable(n int){
for n > 0{
n--
}
}
func BenchmarkF(b *testing.B) {
for i:=0; i < b.N; i++{
unstable(i) //如果benchmark測試的函數執行時間始終無法趨於穩態 則永遠無法執行完
}
}
http server
package main
import (
"net/http"
)
//w 寫回給客戶端的數據 r 從客戶端讀入的數據
func handler(w http.ResponseWriter, r *http.Request){
w.Write([]byte("hello world"))
}
func main(){
//註冊回調函數
http.HandleFunc("/hello", handler)
//綁定監聽地址
http.ListenAndServe("127.0.0.1:8000", nil)
}
切片
爲什麼用切片?
- 數組的容量固定,不能自動拓展。
- 值傳遞。數組作爲函數參數時,將整個數組值拷貝一份給形參。