2.函数参数类型在变量名 _之后_
func add(x int, y int) int {
return x + y
}
3.当两个或多个连续的函数命名参数是同一类型,则除了最后一个类型之外,其他都可以省略。
func add(x, y int) int {
return x + y
}
4.函数可以返回任意数量的返回值。
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world")
fmt.Println(a, b)
}
5.命名返回值
Go 的返回值可以被命名,并且像变量那样使用。
返回值的名称应当具有一定的意义,可以作为文档使用。
没有参数的 return 语句返回结果的当前值。也就是`直接`返回。
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
6.变量定义: var 语句定义了一个变量的列表;跟函数的参数列表一样,类型在后面。
var c, python, java bool
func main() {
var i int
fmt.Println(i, c, python, java)
}
7.初始化变量
变量定义可以包含初始值,每个变量对应一个。
var i, j int = 1, 2
变量在定义时没有明确的初始化时会赋值为_零值_(数值类型为 `0`,布尔类型为 `false`,字符串为 `""`(空字符串))
8.函数内短声明变量
在函数中,`:=` 简洁赋值语句在明确类型的地方,可以用于替代 var 定义。
func main() {
var i, j int = 1, 2
k := 3
c, python, java := true, false, "no!"
fmt.Println(i, j, k, c, python, java)
}
9.基本类型
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 的别名
rune // int32 的别名
// 代表一个Unicode码
float32 float64
complex64 complex128
10.类型转换: 需要显式转换
var i int = 42
var f float64 = float64(i)
11.常量
常量的定义与变量类似,只不过使用 const 关键字。
常量可以是字符、字符串、布尔或数字类型的值。
常量不能使用 := 语法定义。
const World = "世界"
const Truth = true
12.循环结构
Go 只有一种循环结构——`for` 循环。基本的 for 循环除了没有了 `( )` 之外(甚至强制不能使用它们),看起来跟 C 或者 Java 中做的一样,而 `{ }` 是必须的。跟 C 或者 Java 中一样,可以让前置、后置语句为空。
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)
}
func main() {
sum := 1
for sum < 1000 {
sum += sum
}
fmt.Println(sum)
}
死循环
for {
}
13. if: if 语句除了没有了 `( )` 之外(甚至强制不能使用它们),看起来跟 C 或者 Java 中的一样,而 `{ }` 是必须的。
func sqrt(x float64) string {
if x < 0 {
return sqrt(-x) + "i"
}
return fmt.Sprint(math.Sqrt(x))
}
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}
// 这里开始就不能使用 v 了
return lim
}
14.switch: 除非以 fallthrough 语句结束,否则分支会自动终止。switch 的条件从上到下的执行,当匹配成功的时候停止。
func main() {
fmt.Print("Go runs on ")
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.", os)
}
}
没有条件的 switch: 同 `switch true` 一样。这一构造使得可以用更清晰的形式来编写长的 if-then-else 链。
func main() {
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
}
15.defer: defer 语句会延迟函数的执行直到上层函数返回。
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
输出: hello
world
16.指针 : Go 具有指针。 指针保存了变量的内存地址。
类型 *T 是指向类型 T 的值的指针。其零值是 `nil`。
var p *int
& 符号会生成一个指向其作用对象的指针。
i := 42
p = &i
* 符号表示指针指向的底层的值。
fmt.Println(*p) // 通过指针 p 读取 i
*p = 21 // 通过指针 p 设置 i
与 C 不同,Go 没有指针运算。
func main() {
i, j := 42, 2701
p := &i // point to i
fmt.Println(*p) // read i through the pointer
*p = 21 // set i through the pointer
fmt.Println(i) // see the new value of i
p = &j // point to j
*p = *p / 37 // divide j through the pointer
fmt.Println(j) // see the new value of j
}
17.结构体: 结构体字段使用点号来访问, 也可以通过结构体指针来访问
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X)
}
func main() {
v := Vertex{1, 2}
p := &v
p.X = 1e9
fmt.Println(v)
}
18.数组: 类型 [n]T 是一个有 n 个类型为 T 的值的数组。
var a [10]int 定义变量 a 是一个有十个整数的数组。
func main() {
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)
}
19.slice: 一个 slice 会指向一个序列的值,并且包含了长度信息。[]T 是一个元素类型为 T 的 slice。
func main() {
p := []int{2, 3, 5, 7, 11, 13}
fmt.Println("p ==", p)
for i := 0; i < len(p); i++ {
fmt.Printf("p[%d] == %d\n", i, p[i])
}
}
对 slice 切片: slice 可以重新切片,创建一个新的 slice 值指向相同的数组。
s[lo:hi]表示从 lo 到 hi-1 的 slice 元素,含两端
省略下标代表从 0 开始, 省略上标代表到 len(s) 结束
构造 slice: slice 由函数 make 创建。这会分配一个零长度的数组并且返回一个 slice 指向这个数组:a := make([]int, 5) // len(a)=5
slice 的零值是 `nil`。
func main() {
var z []int
fmt.Println(z, len(z), cap(z))
if z == nil {
fmt.Println("nil!")
}
}
向 slice 添加元素: 内建函数 `append`. append 的结果是一个包含原 slice 所有元素加上新添加的元素的 slice(类似于STL的vector push_back)。
var a []int
printSlice("a", a)
// append works on nil slices.
a = append(a, 0)
for 循环的 range 格式可以对 slice 或者 map 进行迭代循环。
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
}
20:map
type Vertex struct {
Lat, Long float64
}
var m map[string]Vertex
func main() {
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
}
在 map m 中插入或修改一个元素:
m[key] = elem
获得元素:
elem = m[key]
删除元素:
delete(m, key)
通过双赋值检测某个键存在:
elem, ok = m[key]
如果 key 在 m 中,`ok` 为 true 。否则, ok 为 `false`,并且 elem 是 map 的元素类型的零值。
同样的,当从 map 中读取某个不存在的键时,结果是 map 的元素类型的零值
21.函数的闭包: 函数别名
Go 函数可以是闭包的。闭包是一个函数值,它来自函数体的外部的变量引用。 函数可以对这个引用值进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。
例如,函数 adder 返回一个闭包。每个闭包都被绑定到其各自的 sum 变量上。
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}
22.类: Go 没有类。然而,仍然可以在结构体类型上定义方法。
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
//*Vertex 指针类型:避免值拷贝和修改接收者指向的值
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := &Vertex{3, 4}
fmt.Println(v.Abs())
}
你可以对包中的 任意 类型定义任意方法,而不仅仅是针对结构体。但是,不能对来自其他包的类型或基础类型定义方法。
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
func main() {
f := MyFloat(-math.Sqrt2)
fmt.Println(f.Abs())
}
接口: 接口类型是由一组方法定义的集合
type Abser interface {
Abs() float64
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
23.错误: Go 程序使用 error 值来表示错误状态。
与 fmt.Stringer 类似,`error` 类型是一个内建接口:
type error interface {
Error() string
}
通常函数会返回一个 error 值,调用的它的代码应当判断这个错误是否等于 `nil`, 来进行错误处理。
i, err := strconv.Atoi("42")
if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
}
fmt.Println("Converted integer:", i)
error 为 nil 时表示成功;非 nil 的 error 表示错误。
24.Web 服务器: 包 http 通过任何实现了 http.Handler 的值来响应 HTTP 请求:
package http
type Handler interface {
ServeHTTP(w ResponseWriter, r *Request)
}
import "net/http"
func main() {
http.HandleFunc("/", hello)
http.ListenAndServe(":8080", nil)
}
func hello(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello!"))
}
25.goroutine: goroutine 是由 Go 运行时环境管理的轻量级线程。 go funcname() 开启一个新的 goroutine 执行
每一个goroutine都会默认在heap当中分配一个4k的内存块来作为该goroutine的stack。当goroutine stack满时,golang会再分配一个块内存来补充,从而形成一个无限大的stack。
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
26.channel:
channel 是有类型的管道,可以用 channel 操作符 <- 对其发送或者接收值。
默认情况下,在另一端准备好之前,发送和接收都会阻塞。这使得 goroutine 可以在没有明确的锁或竞态变量的情况下进行同步。
ch <- v // 将 v 送入 channel ch。
v := <-ch // 从 ch 接收,并且赋值给 v。
(“箭头”就是数据流的方向。)
func sum(a []int, c chan int) {
sum := 0
for _, v := range a {
sum += v
}
c <- sum // 将和送入 c
}
func main() {
a := []int{7, 2, 8, -9, 4, 0}
c := make(chan int) //和 map 与 slice 一样,channel 使用前必须创建make:
go sum(a[:len(a)/2], c)
go sum(a[len(a)/2:], c)
x, y := <-c, <-c // 从 c 中获取
fmt.Println(x, y, x+y)
}
channel缓冲区:
channel 可以是 _带缓冲的_。为 make 提供第二个参数作为缓冲长度来初始化一个缓冲 channel:
ch := make(chan int, 100)
向缓冲 channel 发送数据的时候,只有在缓冲区满的时候才会阻塞。当缓冲区清空的时候接受阻塞。
27. channel range 和 close:
发送者可以 close 一个 channel 来表示再没有值会被发送了。接收者可以通过赋值语句的第二参数来测试 channel 是否被关闭:当没有值可以接收并且 channel 已经被关闭,那么经过
v, ok := <-ch
之后 ok 会被设置为 `false`。
循环 `for i := range c` 会不断从 channel 接收值,直到它被关闭。
注意: 只有发送者才能关闭 channel,而不是接收者。向一个已经关闭的 channel 发送数据会引起 panic。 还要注意: channel 与文件不同;通常情况下无需关闭它们。只有在需要告诉接收者没有更多的数据的时候才有必要进行关闭,例如中断一个 `range`。
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
28. select: select 语句使得一个 goroutine 在多个通讯操作上等待。
select 会阻塞,直到条件分支中的某个可以继续执行,这时就会执行那个条件分支。当多个都准备好的时候,会随机选择一个。
当 select 中的其他条件分支都没有准备好的时候,`default` 分支会被执行。为了非阻塞的发送或者接收,可使用 default 分支:
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
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)
}
29.Web 爬虫: http://go-tour-zh.appspot.com/concurrency/9
30. golang的同步: sync.WaitGroup只有3个方法,Add(),Done(),Wait()。其中Done()是Add(-1)的别名。简单的来说,使用Add()添加计数,Done()减掉一个计数,计数不为0, 阻塞Wait()的运行。
package main import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup for i := 0; i < 100; i++ { wg.Add(1) } for i := 0; i < 100; i++ { go done(&wg) } wg.Wait() fmt.Println("exit") } func add(wg sync.WaitGroup) { wg.Add(1) } func done(wg *sync.WaitGroup) { wg.Done() }
如何编写 Go 代码: https://golang.org/doc/code.html
Go标准库包手册: http://golang.org/pkg/
Go入门指南: https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/directory.md
Network programming with Go: https://jan.newmarch.name/go/
Writing Web Applications: https://golang.org/doc/articles/wiki/
Go Web 编程: https://github.com/astaxie/build-web-application-with-golang/tree/master/zh
Go 语言规范: https://golang.org/ref/spec
使用Golang 搭建http web服务器: http://www.cnblogs.com/yjf512/archive/2012/09/03/2668384.html
使用Golang实现简单Ping过程: http://www.tuicool.com/articles/E7rArq
-
使用Golang提供的net包中的相关函数可以快速构造一个IP包并自定义其中一些关键参数,而不需要再自己手动填充IP报 文。
-
使用 encoding/binary 包可以轻松获取结构体struct的内存数据并且可以规定字节序(这里要用网络字节序BigEndian),而不需要自己去转换字节序。
-
使用 container/list包,方便进行结果统计
-
使用time包实现耗时和超时处理
有疑问加站长微信联系(非本文作者)