《Head First Go》笔记
Hello World 1 2 3 4 5 6 7 package mainimport "fmt" func main () { fmt.Println("Hello, World!" ) }
典型的Go文件布局:
package子句
任何import语句
实际代码
每个Go文件都必须以package子句开头。
每个Go文件都必须导入它引用的每个包。
Go文件必须只导入它们引用的包。
Go查找名为main的函数并首先运行。
Go中的所有内容都区分大小写。
Println函数是fmt包的一部分,因此Go需要在函数调用之前使用报名。
导入多个包 1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "fmt" "math" "strings" ) func main () { fmt.Println(math.Floor(2.75 )) fmt.Println(strings.Title("head first go" )) }
符文 字符串通常用于表示一系列文本字符,而Go的符文(rune)则用于表示单个字符。
字符串字面量由双引号(”)包围,但rune字面量由单引号(’)包围。
布尔值 布尔值只能是两个值中的一个:true或false。
类型
你可以通过将任何值传递给reflect包的TypeOf函数,来查看它们的类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( "fmt" "reflect" ) func main () { fmt.Println(reflect.TypeOf(42 )) fmt.Println(reflect.TypeOf(3.14 )) fmt.Println(reflect.TypeOf(true )) fmt.Println(reflect.TypeOf("Hello, Go!" )) }
声明变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 var quantity int var length, width float64 var customerName string quantity = 2 customerName = "Damon Cole" length, width = 1.2 , 2.4 var quantity int = 4 var length, width float64 = 1.2 , 2.4 var customerName string = "Damon Cole" var quantity = 4 var length, width = 1.2 , 2.4 var customerName = "Damon Cole" quantity := 4 length, width := 1.2 , 2.4 customerName := "Damon Cole"
如果声明一个变量而没有给它赋值,该变量将包含其类型的零值。对于数值类型,零值实际上就是0,字符串变量的零值是空字符串,布尔变量的零值是false。
一个变量只能声明一次。
只能给变量赋相同类型的值。
所有声明的变量都必须在程序中使用,如果删除了使用变量的代码,必须也要删除声明。
命名规则 语言强制规则:
名称必须以字母开头,并且可以有任意数量的额外的字母和数字。
如果变量、函数或类型的名称以大写字母开头,则认为它是导出的,可以从当前包之外的包访问它。
社区遵循的约定:
如果一个名称由多个单词组成,那么第一个单词之后的每个单词都应该首字母大写,并且它们应该连接在一起,中间没有空格,比如topPrice、RetryConnection,等等。(名称的第一个字母只有在你想从包中导出时才应大写。)
当名称的含义在上下文中很明显时,Go社区的惯例是缩写它:用i代替index,用max代替maximum,等等。
转换 1 2 3 var myInt int = 2 fmt.Println(reflect.TypeOf(myInt)) fmt.Println(reflect.TypeOf(float64 (myInt)))
编译Go代码 1 2 3 4 5 6 7 8 9 10 11 12 go fmt hello.go go build hello.go ./hello go run hello
调用方法 1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "fmt" "time" ) func main () { var now time.Time = time.Now() var year int = now.Year() fmt.Println(year) }
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( "fmt" "strings" ) func main () { broken := "G# r#cks!" replacer := strings.NewReplacer("#" , "o" ) fixed := replacer.Replace(broken) fmt.Println(fixed) }
注释 单行注释和多行注释和C++一样。
条件语句 实例,评分程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package mainimport ( "bufio" "fmt" "log" "os" "strings" "strconv" ) func main () { fmt.Print("Enter a grade: " ) reader := bufio.NewReader(os.Stdin) input, err := reader.ReadString('\n' ) if err != nil { log.Fatal(err) } input = strings.TrimSpace(input) grade, err := strconv.ParseFloat(input, 64 ) if err != nil { log.Fatal(err) } var status string if grade >= 60 { status = "passing" } else { status = "failing" } fmt.Println("A grade of" , grade, "is" , status) }
循环 实例,猜数字游戏:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 package mainimport ( "bufio" "fmt" "log" "math/rand" "os" "strconv" "strings" "time" ) func main () { seconds := time.Now().Unix() rand.Seed(seconds) target := rand.Intn(100 ) + 1 fmt.Println("I've chosen a random number between 1 and 100." ) fmt.Println("Can you guess it?" ) reader := bufio.NewReader(os.Stdin) success := false for guesses := 0 ; guesses < 10 ; guesses++ { fmt.Println("You have" , 10 - guesses, "guesses left." ) fmt.Print("Make a guess: " ) input, err := reader.ReadString('\n' ) if err != nil { log.Fatal(err) } input = strings.TrimSpace(input) guess, err := strconv.Atoi(input) if err != nil { log.Fatal(err) } if guess < target { fmt.Println("Oops. Your guess was LOW." ) } else if guess > target { fmt.Println("Oops. Your guess was HIGH." ) } else { success = true fmt.Println("Good job! You guessed it!" ) break } } if !success { fmt.Println("Sorry, you didn't guess my number. It was:" , target) } }
定义函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport "fmt" func paintNeeded (width float64 , height float64 ) (float64 , error ) { if width < 0 { return 0 , fmt.Errorf("a width of %0.2f is invalid" , width) } if height < 0 { return 0 , fmt.Errorf("a height of %0.2f is invalid" , height) } area := width * height return area / 10.0 , nil } func main () { amount, err := paintNeeded(4.2 , 3 ) if err != nil { log.Fatal(err) } fmt.Printf("%0.2f liters needed\n" , amount) }
指针 1 2 3 4 5 6 7 8 9 10 11 12 func main () { amount := 6 double(&amount) fmt.Println(amount) } func double (number *int ) { *number *= 2 }
与其他语言不同,在Go中,返回一个指向函数局部变量的指针是可以的。即使该变量不在作用域内,只要你仍然拥有指针,Go将确保你仍然可以访问该值。
工作区 Go工具在你的计算机上名为工作区的特殊目录(文件夹)中查找包代码。默认情况下,工作区是当前用户主目录中名为go的目录。
1 2 3 4 5 6 7 8 go // 工作区目录 bin // 保存可执行程序 pkg // 保存已编译的包代码 src // 保存源代码 doodad // 包 gizmo // 包 gizmo.go plug.go
创建一个包 1 2 3 4 go src greeting greeting.go
1 2 3 4 5 6 7 8 9 10 11 package greetingimport "fmt" func Hello () { fmt.Println("Hello!" ) } func Hi () { fmt.Println("Hi!" ) }
通常,包名应该与保存它的目录名相匹配,但是main包是该规则的一个例外。
使用greeting包:
1 2 3 4 5 6 7 8 package mainimport "greeting" func main () { greeting.Hello() greeting.Hi() }
包命名规范
包名应全部为小写。
如果含义相当明显,名称应该缩写(如fmt)。
如果可能的话,应该是一个词。如果需要两个词,不应该用下划线分隔,第二个词也不应该大写。(strconv包就是一个例子。)
导入的包名可能与本地变量名冲突,所以不要使用包用户可能也想使用的名称。(例如,如果fmt包被命名为format,那么导入该包的任何人如果把一个局部变量也命名为format,则将面临冲突的风险)。
常量
使用const关键字而不是var关键字。
必须在声明常量时赋值;不能像变量那样以后赋值。
变量有:=短变量声明语法,但是常量没有等效的语法。
1 2 const TriangleSides int = 3 const SquareSides = 4
go install 当我们使用 go run 时,Go必须编译程序以及它所依赖的所有包,然后才能执行。当编译完成后,它会丢弃编译后的代码。
go build
命令进行编译并把可执行二进制文件(即使没有安装Go也可以执行的文件)保存在当前目录中。
go install
命令也保存编译后的可执行程序的二进制版本,但保存在定义良好、易于访问的位置:Go工作区中的bin目录。
请确保将“src”中的目录名传递给“go install”,而不是.go文件名!默认情况下,“go install”未设置成直接处理.go文件。
GOPATH环境变量 GOPATH
是一个环境变量,Go工具会参考它来查找工作区位置。大多数Go开发人员将他们所有代码都保存在一个工作区中,并且不会更改其默认位置。但是如果你愿意,可以使用GOPATH
将你的工作区转移到其他目录。
go get 使用 go get
下载和安装包。
1 go get github.com/headfirstgo/greeting
go工具将连接到github.com,在/headfirstgo/greeting路径下载Git存储库,并将其保存在Go工作区的src目录中。
go doc 使用 go doc
阅读包文档,文档来自于文档注释。
例如,我们可以通过运行 go doc strconv
获得关于strconv包的信息。
我们可以使用 go doc strconv ParseFloat
来查看包内函数的文档。
文档注释 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package keyboardimport ( "bufio" "os" "strconv" "strings" ) func GetFloat () (float64 , error ) { }
本地文档Web服务器
除了来自Go标准库的包之外,godoc工具还为Go工作区中的任何包构建HTML文档。这些包可能是你安装的第三方的包,也可能是你自己编写的包。
数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 var notes [7 ]string notes[0 ] = "do" notes[1 ] = "re" notes[2 ] = "mi" fmt.Println(notes[0 ]) fmt.Println(notes[1 ]) var notes [7 ]string = [7 ]string {"do" , "re" , "mi" , "fa" , "so" , "la" , "ti" }var primes [5 ]int = [5 ]int {2 , 3 , 5 , 7 , 11 }primes := [5 ]int {2 , 3 , 5 , 7 , 11 } for i := 0 ; i < len (notes); i++ { fmt.Println(i, notes[i]) } for index, note := range notes { fmt.Println(index, note) } for _, note := range notes { fmt.Println(index, note) }
读取文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package mainimport ( "bufio" "fmt" "log" "os" ) func main () { file, err := os.Open("data.txt" ) if err != nil { log.Fatal(err) } scanner := bufio.NewScanner(file) for scanner.Scan() { fmt.Println(scanner.Text()) } err = file.Close() if err != nil { log.Fatal(err) } if scanner.Err() != nil { log.Fatal(scanner.Err()) } }
切片 1 2 var myArray [5 ]int var mySlice []int
不像数组变量,声明切片变量并不会自动创建一个切片。为此,你可以调用内建的make函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var notes []string notes = make ([]string , 7 ) primes := make ([]int , 5 ) fmt.Println(len (notes)) fmt.Println(len (primes)) for i := 0 ; i < len (notes); i++ { fmt.Println(notes[i]) } for _, note := range notes { fmt.Println(note) } notes := []string {"do" , "re" , "mi" , "fa" , "so" , "la" , "ti" }
每一个切片都构建于一个底层的数组之上。实际上是底层的数组存储了切片的数据;切片仅仅是数组中的一部分(或者所有)元素的视图。
1 2 3 underlyingArray := [5 ]string {"a" , "b" , "c" , "d" , "e" } slice1 := underlyingArray[0 :3 ] fmt.Println(slice1)
使用append函数向切片中追加数据,如果底层数组容量不够则会重新创建容量更大的底层数组然后把数据拷贝过去,类似C++中的vector。
1 2 3 4 5 6 slice := []string {"a" , "b" } fmt.Println(slice, len (slice)) slice = append (slice, "c" ) fmt.Println(slice, len (slice)) slice = append (slice, "d" , "e" ) fmt.Println(slice, len (slice))
处理命令行参数 os.Args
是一个切片,第一个元素是程序名。
1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" "os" ) func main () { fmt.Println(os.Args) }
可变长参数 1 2 3 4 5 6 7 8 9 func serveralInts (numbers ...int ) { fmt.Println(numbers) } func main () { severalInts(1 ) severalInts(1 , 2 , 3 ) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport ( "fmt" "math" ) func maximum (numbers ...float64 ) float64 { max := math.Inf(-1 ) for _, number := range numbers { if number > max { max = number } } return max } func main () { fmt.Println(maximum(71.8 , 56.2 , 89.5 )) fmt.Println(maximum(90.7 , 89.7 , 98.5 , 92.3 )) }
为可变长参数函数传入切片:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func severalInts (numbers ...int ) { fmt.Println(numbers) } func mix (num int , flag bool , strings ...string ) { fmt.Println(num, flag, strings) } func main () ( intSlice := []int {1 , 2 , 3 } severalInts(intSlice...) stringSlice := []string {"a" , "b" , "c" , "d" } mix(1 , true , stringSlice...) )
映射 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var ranks map [string ]int ranks = make (map [string ]int ) ranks := make (map [string ]int ) ranks := map [string ]int {"bronze" : 3 , "silver" : 2 , "gold" : 1 } fmt.Println(ranks["gold" ]) fmt.Println(ranks["bronze" ]) elements := map [string ]string { "H" : "Hydrogen" , "Li" : "Lithium" } fmt.Println(elements["H" ]) fmt.Println(elements["Li" ])
获取某个键的值的时候,如果不存在则返回零值。
取值的时候判断值是否已存在:
1 2 3 4 5 6 7 8 9 10 11 12 13 counters := map [string ]int {"a" : 3 , "b" : 0 } var value int var ok bool value, ok = counters["a" ] delete (counters, "b" )grades := map [string ]float64 {"Alma" : 74.2 , "Rohit" : 86.5 , "Carl" : 59.7 } for name, grade := range grades { fmt.Printf("%s has a grade of %0.1f%%\n" , name, grade) }
struct 1 2 3 4 5 6 7 8 9 10 11 12 var subscriber struct { name string rate float64 active bool } subscriber.name = "Aman Singh" subscriber.rate = 4.99 subscriber.active = true fmt.Println("Name:" , subscriber.name) fmt.Println("Monthly rate:" , subscriber.rate) fmt.Println("Active?" , subscriber.active)
将结构定义成类型:
1 2 3 4 5 6 7 8 9 10 type car struct { name string topSpeed float64 } var porsche carporsche.name = "Porsche 911 R" porsche.topSpeed = 323 fmt.Println("Name:" , porsche.name) fmt.Println("Top speed:" , porsche.topSpeed)
传递结构的指针作为参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "fmt" type subscriber struct { name string rate float64 active bool } func applyDiscount (s *subscriber) { s.rate = 4.99 } func main () { var s subscriber applyDiscount(&s) fmt.Println(s.rate) }
结构字面量:
1 s := subscriber{name: "Aman Singh" , rate: 4.99 , active: true }
匿名字段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 type Subscriber struct { Name string Rate float64 Active bool HomeAddress Address } type Address struct { Name string } var s Subscribers.HomeAddress.Name = "abc" type Subscriber struct { Name string Rate float64 Active bool Address } type Address struct { Name string } var s Subscribers.Address.Name = "abc" s.Name = "abc"
定义方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" type MyType string func (m MyType) sayHi() { fmt.Println("Hi from" , m) } func main () { value := MyType("a MyType value" ) value.sayHi() anotherValue := MyType("another value" ) anotherValue.sayHi() }
如果想要修改接收器的值,接收器参数需要使用指针类型:
1 2 3 4 5 6 7 8 9 10 11 type Number int func (n *Number) Double { *n *= 2 } func main () { number := Number(4 ) number.Double() }
为了一致性,你所有的类型函数接受值类型,或者都接受指针类型,但是你应该避免混用的情况。
封装 变量名、类型名或函数名小写字母开头表示不导出,大写字母开头表示导出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package calendarimport "erros" type Date struct { year int month int day int } func (d *Date) Year() int { return d.year } func (d *Date) Month() int { return d.month } func (d *Date) Day() int { return d.day } func (d *Date) SetYear(year int ) error { if year < 1 { return erros.New("invalid year" ) } d.year = year return nil } func (d *Date) SetMonth(month int ) error { if month < 1 || month > 12 { return errors.New("invalid month" ) } d.month = month return nil } func (d *Date) SetDay(day int ) error { if day < 1 || day > 31 { return errors.New("invalid day" ) } d.day = day return nil }
接口 在Go中,一个接口被定义为特定值预期具有的一组方法。你可以把接口看作需要类型实现的一组行为。
使用interface关键字定义一个接口类型,后面跟着一个花括号,内部含有一组方法,以及方法期望的参数和返回值。
任何拥有接口定义的所有方法的类型被称作满足那个接口。一个满足接口的类型可以用在任何需要接口的地方。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package mainimport "fmt" type Whistle string func (w Whistle) MakeSound() { fmt.Println("Tweet!" ) } type Horn string func (h Horn) MakeSound() { fmt.Println("Honk!" ) } type Robot string func (r Robot) MakeSound() { fmt.Println("Beep Boop" ) } func (r Robot) Walk() { fmt.Println("Powering legs" ) } type NoiseMaker interface { MakeSound() } func play (n NoiseMaker) { n.MakeSound() } func main () { play(Robot("Botco Ambler" )) }
类型断言 类似于C++中的向下转型,将接口类型转型为具体类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type Robot string func (r Robot) MakeSound() { fmt.Println("Beep Boop" ) } func (r Robot) Walk() { fmt.Println("Powering legs" ) } type NoiseMaker interface { MakeSound() } func main () { var noiseMaker NoiseMaker = Robot("Botco Ambler" ) noiseMaker.MakeSound() var robot Robot = noiseMaker.(Robot) robot.Walk() }
如果转型失败将产生异常。
1 2 3 4 5 robot, ok := noiseMaker.(Robot) if ok { robot.Walk() }
error 接口 1 2 3 type error interface { Error() string }
Stringer 接口 1 2 3 type Stringer interface { String() string }
空接口 空接口 interface{}
可接收任何类型的值。
延迟函数调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" func Socialize () { defer fmt.Println("Goodbye!" ) fmt.Println("Hello!" ) fmt.Println("Nice weather, eh?" ) } func main () { Socialize() }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package mainimport ( "fmt" "log" ) func Socialize () { defer fmt.Println("Goodbye!" ) fmt.Println("Hello!" ) return fmt.Errorf("I don't want to talk." ) fmt.Println("Nice weather, eh?" ) return nil } func main () { err := Socialize() if err != nil { log.Fatal(err) } }
“defer”关键字确保函数调用发生,即使调用函数提前退出了。
实例,使用延迟函数调用确保文件关闭:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 func OpenFile (fileName string ) (*os.File, error ) { fmt.Println("Opening" , fileName) return os.Open(fileName) } func CloseFile (file *os.File) { fmt.Println("Closing file" ) file.Close() } func GetFloats (fileName string ) ([]float64 , error ) { var numbers []float64 file, err := OpenFile(fileName) if err != nil { return nil , err } defer CloseFile(file) scanner := bufio.NewScanner(file) for scanner.Scan() { number, err := strconv.ParseFloat(scanner.Text(), 64 ) if err != nil { return nil , err } number = append (numbers, number) } if scanner.Err() != nil { return nil , scanner.Err() } return numbers, nil }
递归列出目录中的文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package mainimport ( "fmt" "io/ioutil" "log" "path/filepath" ) func scanDirectory (path string ) error { fmt.Println(path) files, err := ioutil.ReadDir(path) if err != nil { return err } for _, file := range files { filePath := filepath.Join(path, file.Name()) if file.IsDir() { err := scanDirectory(filePath) if err != nil { return err } } else { fmt.Println(filePath) } } return nil } func main () { err := scanDirectory("go" ) if err != nil { log.Fatal(err) } }
发起一个panic 当程序出现panic时,当前函数停止运行,程序打印日志消息并崩溃。
1 2 3 4 5 package mainfunc main () { panic ("oh, no, we're going down" ) }
当程序发生panic时,panic输出中包含堆栈跟踪,即调用堆栈列表。这对于确定导致程序崩溃的原因很有用。
当程序出现panic时,所有延迟的函数调用仍然会被执行。如果有多个延迟调用,它们的执行顺序将与被延迟的顺序相反。
事实上,调用panic并不是处理错误的理想方法。
无法访问的文件、网络故障和错误的用户输入通常应该被认为是“正常的”,应该通过错误值来进行适当的处理。通常,调用panic应该留给“不可能的”情况:错误表示的是程序中的错误,而不是用户方面的错误。
恢复panic:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func calmDown () { recover () } func freakOut () { defer calmDown() panic ("oh no" ) fmt.Println("I won't be run!" ) } func main () { freakOut() fmt.Println("Exiting normally" ) }
不鼓励使用panic Go语言本身的设计不鼓励使用panic和recover。在2012年的一次主题会议上,Rob Pike(Go的创始人之一)把panic和recover描述为“故意笨拙”。这意味着,在设计Go时,创作者们没有试图使panic和recover被容易或愉快地使用,因此它们会很少使用。
这是Go设计者对exception的一个主要弱点的回应:它们可以使程序流程更加复杂。相反,Go开发人员被鼓励以处理程序其他部分的方式处理错误:使用if和return语句,以及error值。当然,直接在函数中处理错误会使函数的代码变长,但这比根本不处理错误要好得多。(Go的创始人发现,许多使用exception的开发人员只是抛出一个exception,之后并没有正确地处理它。)直接处理错误也使错误的处理方式一目了然——你不必查找程序的其他部分来查看错误处理代码。
goroutine goroutine可以让程序同时处理几个不同的任务。goroutine可以使用channel来协调它们的工作,channel允许goroutine互相发送数据并同步,这样一个goroutine就不会领先于另一个goroutine。goroutine让你充分利用具有多处理器的计算机,让程序运行得尽可能快!
http.Get 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package mainimport ( "fmt" "io/ioutil" "log" "net/http" ) func main () { responseSize("https://example.com" ) responseSize("https://golang.org" ) responseSize("https://golang.org/doc" ) } func responseSize (url string ) { fmt.Println("Getting" , url) response, err := http.Get(url) if err != nil { log.Fatal(err) } defer response.Body.Close() body, err := ioutil.ReadAll(response.Body) if err != nil { log.Fatal(err) } fmt.Println(len (body)) }
并发 在Go中,并发任务称为goroutine。其他编程语言有一个类似的概念,叫作线程,但是goroutine比线程需要更少的计算机内存,启动和停止的时间更少,这意味着你可以同时运行更多的goroutine。
要启动另一个goroutine,可以使用go语句,它只是一个普通的函数或方法调用,前面有go关键字。
每个Go程序的main函数都是使用goroutine启动的,因此每个Go程序至少运行一个goroutine。
在正常情况下,Go不能保证何时在goroutine之间切换,或者切换多长时间。这允许goroutine以最有效的方式运行。但是,如果goroutine运行的顺序对你很重要,那么你需要使用channel来同步它们。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package mainimport ( "fmt" "io/ioutil" "log" "net/http" ) func main () { sizes := make (chan int ) go responseSize("https://example.com" , sizes) go responseSize("https://golang.org" , sizes) go responseSize("https://golang.org/doc" , sizes) fmt.Println(<-sizes) fmt.Println(<-sizes) fmt.Println(<-sizes) } func responseSize (url string , channel chan int ) { fmt.Println("Getting" , url) response, err := http.Get(url) if err != nil { log.Fatal(err) } defer response.Body.Close() body, err := ioutil.ReadAll(response.Body) if err != nil { log.Fatal(err) } channel <- len (body) }
自动化测试 Go包含一个testing包,你可以用来为代码编写自动化测试,还有一个go test命令,你可以用来运行这些测试。
测试文件中的代码由普通的Go函数组成,但需要遵循一定的约定才能使用go test工具:
你不需要将测试代码与正在测试的代码放在同一个包中,但是如果你想从包中访问未导出的类型或函数,则需要这样做。
测试需要使用testing包中的类型,所以需要在每个测试文件的顶部导入该包。
测试函数名应该以Test开头。(名字的其余部分可以是你想要的任何内容,但它应该以大写字母开头。)
测试函数应该接受单个参数:一个指向testing.T值的指针。
你可以通过对testing.T值调用方法(例如Error)来报告测试失败。大多数方法都接受一个字符串,其中包含解释测试失败原因的信息。
要运行测试,可以使用go test命令。该命令采用一个或多个包的导入路径。它将在那些包目录中找到所有名字以_test.go结尾的文件,并运行名字以Test开头的文件中包含的每个函数。
测试驱动开发步骤:
编写测试
确保通过
重构代码
net/http 包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package mainimport ( "log" "net/http" ) func write (writer http.ResponseWriter, message string ) { _, err := writer.Write([]byte (message)) if err != nil { log.Fatal(err) } } func englishHandler (writer http.ResponseWriter, request *http.Request) { write(writer, "Hello, web!" ) } func frenchHandler (writer http.ResponseWriter, request *http.Request) { write(writer, "Salut web!" ) } func hindiHandler (writer http.ResponseWriter, request *http.Request) { write(writer, "Namaste, web!" ) } func main () { http.HandleFunc("/hello" , englishHandler) http.HandleFunc("/salut" , frenchHandler) http.HandleFunc("/namaste" , hindiHandler) err := http.ListenAndServe("localhost:8080" , nil ) log.Fatal(err) }