GO语言入门(1)_GO语言之旅

目前打算的学习路线:
GO语言之旅
GO官方文档
有标注的例子们
书籍:The GO Programming Language
书籍:Go in action

基础

包、变量和函数

:按照约定,包名与导入路径的最后一个元素一致。例如,“math/rand” 包中的源码均以 package rand 语句开始。

导入
导出名:在 Go 中,如果一个名字以大写字母开头,那么它就是已导出的。

函数:类型在变量名之后,同类型可省略func add(x, y int) int { return x + y }

多值返回func swap(x, y string) (string, string) { return y, x }

命名返回值:没有参数的 return 语句返回已命名的返回值。也就是 直接 返回。

变量:var 语句可以出现在包或函数级别。

变量初始化var c, python, java = true, false, "no!"

短变量声明:函数外的每个语句都必须以关键字开始(var, func 等等),因此 := 结构不能在函数外使用。

基本类型int, uintuintptr 在 32 位系统上通常为 32 位宽,在 64 位系统上则为 64 位宽。

数值常量:数值常量是高精度的 值。
一个未指定类型的常量由上下文来决定其类型。
1<<100:MAX,再左移会溢出

流程控制语句:for、if、else、switch和defer

循环只有for

for i := 0; i < 10; i++ {
        sum += i
    }

for可以没有初始条件和后置语句,此时for就是while
for sum<1000{sum+=sum}

if :同 for 一样, if 语句可以在条件表达式前执行一个简单的语句。
该语句声明的变量作用域仅在 if 之内。

switch:Go 自动提供了在这些语言中每个 case 后面所需的 break 语句。 除非以 fallthrough 语句结束,否则分支会自动终止。

没有条件的 switch 同 switch true 一样。

switch {
    case t.Hour() < 12:
        fmt.Println("Good morning!")
    case t.Hour() < 17:
        fmt.Println("Good afternoon.")
    default:
        fmt.Println("Good evening.")
    }

defer :会将函数推迟到外层函数返回之后执行。推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
关闭文件流、关锁、printFooter

推迟的函数调用会被压入一个中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。
三个规则:

  1. A deferred function’s arguments are evaluated when the defer statement is evaluated. 立即求值
  2. Deferred function calls are executed in Last In First Out order after the surrounding function returns. 先进后出
  3. Deferred functions may read and assign to the returning function’s named return values. defer的函数可以修改返回值。作用:修饰错误结果
func c() (i int) {
    defer func() { i++ }()
    return 1
}

Defer相关:Panic和Recover
Panic:When the function F calls panic, execution of F stops, any deferred functions in F are executed normally, and then F returns to its caller.
Recover:If the current goroutine is panicking, a call to recover will capture the value given to panic and resume normal execution.

package main

import "fmt"

func main() {
    f()
    fmt.Println("Returned normally from f.")
}

func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
}

func g(i int) {
    if i > 3 {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i + 1)
}

输出

Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.

更多类型:Struct、slice 和 映射

指针:与 C 不同,Go 指针没有运算。GO函数都是传值,map则是GO通过包装省去了指针操作,依然是传值。map===*hmapchan=*hchan,slice也是模仿引用类型。

结构体是一组字段

type Vertex struct {
    X int
    Y int
}
fmt.Println(Vertex{1,2}) //{1 2}
v:=Vertex{1,2}
v.X=2
p:=&v
p.X=1e9 //p.X就是(*p).X

数组:数组的长度是其类型的一部分,因此数组不能改变大小。
var a [2]string

切片:切片为数组元素提供动态大小的、灵活的视角。
更改切片的元素会修改其底层数组中对应的元素。
与它共享底层数组的切片都会观测到这些修改。

对比数组文法和切片文法:
数组:
[3]bool{true, true, false}
切片:创建一个相同的数组然后构建一个它的切片
[]bool{true, true, false}
切片的长度就是它所包含的元素个数。
切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
nil切片:切片的零值是 nil。
nil 切片的长度和容量为 0 且没有底层数组。
make创建切片:产生一个元素为0值的数组并返回它的切片
b := make([]int, 0, 5) //类型、长度、容量
切片的切片:切片可以包含任何类型,甚至是它的切片。
向切片追加元素:s=append(s,2,3)
GO切片:用法和本质
Go的数组是值语义。一个数组变量表示整个数组,它不是指向第一个元素的指针(不像 C 语言的数组)。(理解:指向数组的指针指向这一整个数组,数组是一个巨大的结构,其中的元素是其成员)
切片操作并不复制切片指向的元素。它创建一个新的切片并复用原来切片的底层数组。
a = append(a, b...) 切片展开作为元素

Range:用于遍历切片或映射。当使用 for 循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。
只需要索引:for i := range pow
map:映射

m:=make(map[string]Vertex) //初始化
m[key] = elem //赋值
elem = m[key] //获取
elem, ok := m[key] //获取,检验有无

函数值

func compute(fn func(float64, float64) float64) float64 {
    return fn(3, 4) //参数是一个func,执行func(3,4),返回float64
}

闭包:闭包函数会引用函数体以外的值,可以对其修改。闭包可以重复使用局部变量同时不污染全局。

  1. 外层函数嵌套内层函数
  2. 内层函数使用外层函数的局部变量
  3. 把内层函数作为外层函数的返回值
func adder() func(int) int { //返回值是一个函数:函数接收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),
        )
    }
}

0 0
1 -2
3 -6
6 -12
10 -20
15 -30
21 -42
28 -56
36 -72
45 -90
package main

import "fmt"

func fibonacci() func() int {
    x1 := 0
    x2 := 1
    return func() int {
        defer func() {
            x1 += x2
            x1, x2 = x2, x1
        }()
        return x1
    }
}

func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(f())
    }
}

方法与接口

方法:Go没有类。不过可以为结构体类型定义方法。方法就是一类带特殊的 接收者 参数的函数。

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

接收者的类型定义和方法声明必须在同一包内;不能为内建类型声明方法。
接收者的类型可以用 *T 的文法。由于方法经常需要修改它的接收者,指针接收者比值接收者更常用。
方法与指针重定向:带指针参数的函数必须接受一个指针;而以指针为接收者的方法被调用时,接收者既能为值又能为指针;Go 会将语句 v.Scale(5) 解释为 (&v).Scale(5)。
为什么使用指针接收者:

  1. 方法能够修改其接收者指向的值
  2. 以避免在每次调用方法时复制该值

接口:一组方法签名定义的集合。
接口的隐式实现:

type I interface {
    M()
}

type T struct {
    S string
}

// 此方法表示类型 T 实现了接口 I,但我们无需显式声明此事。
func (t T) M() {
    fmt.Println(t.S)
}

func main() {
    var i I = T{"hello"}
    i.M()
}

底层值为 nil 的接口值:优雅地处理它有木有

func (t *T) M() {
	if t == nil {
		fmt.Println("<nil>")
		return
	}
	fmt.Println(t.S)
}

nil接口值:
为 nil 接口调用方法会产生运行时错误,因为接口的元组内并未包含能够指明该调用哪个 具体 方法的类型。
空接口:空接口可保存任何类型的值。(因为每个类型都至少实现了零个方法。)
类型断言:提供了访问接口值底层具体值的方式。

t, ok := i.(T)

类型选择
按顺序从几个类型断言中选择分支的结构

func do(i interface{}) {
	switch v := i.(type) {
	case int:
		fmt.Printf("Twice %v is %v\n", v, v*2)
	case string:
		fmt.Printf("%q is %v bytes long\n", v, len(v))
	default:
		fmt.Printf("I don't know about type %T!\n", v)
	}
}

func main() {
	do(21)
	do("hello")
	do(true)
}
/*Twice 21 is 42
"hello" is 5 bytes long
I don't know about type bool!*/

Stringer

type Stringer interface {
    String() string
}
func (p Person) String() string {
	return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}
func (ip IPAddr) String() string {
	return fmt.Sprintf("%v.%v.%v.%v", ip[0], ip[1], ip[2], ip[3])
}

Error

package main

import (
	"fmt"
	"math"
)

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
	return fmt.Sprintf("cannot Sqrt negative number: %v\n",float64(e))
}

func Sqrt(x float64) (float64, error) {
	if x >= 0 {
		return math.Sqrt(x), nil
	} else {
		return 0, ErrNegativeSqrt(x)
	}

}

func main() {
	fmt.Println(Sqrt(2))
	fmt.Println(Sqrt(-2))
}

io.Reader

func (T) Read(b []byte) (n int, err error)
//Read 用数据填充给定的字节切片并返回填充的字节数和错误值。

例子:

package main

import (
	"io"
	"os"
	"strings"
)

type rot13Reader struct {
	r io.Reader
}

func rot13(out byte) byte { //字母转换
	switch {
	case out >= 'A' && out <= 'M' || out >= 'a' && out <= 'm':
		out += 13
	case out >= 'N' && out <= 'Z' || out >= 'n' && out <= 'z':
		out -= 13
	}
	return out
}

func (f rot13Reader) Read(b []byte) (int, error) {
	n, err := f.r.Read(b)
	for i:=0;i<n;i++{
		b[i]=rot13(b[i])
	}
	
	return n, err
}

func main() {
	s := strings.NewReader("Lbh penpxrq gur pbqr!")
	r := rot13Reader{s}
	io.Copy(os.Stdout, &r)
}

并发

Goroutine: Go 运行时管理的轻量级线程。
信道:带有类型的管道,可以通过它用信道操作符 <- 来发送或者接收值。

ch := make(chan int) //创建信道。
ch <- v    // 将 v 发送至信道 ch。
v := <-ch  // 从 ch 接收值并赋予 v。

带缓冲信道ch := make(chan int, 100)
信道的关闭
发送者关闭:close(c)
接收者测试:v, ok := <-ch
注意: 只有发送者才能关闭信道,而接收者不能。向一个已经关闭的信道发送数据会引发程序恐慌(panic)。
还要注意: 信道与文件不同,通常情况下无需关闭它们。只有在必须告诉接收者不再有需要发送的值时才有必要关闭,例如终止一个 range 循环。
select 语句:使一个 Go 程可以等待多个通信操作

package main

import "fmt"

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)
}

select 可以加入default
练习:等价查找二叉树(居然自己写出来了,太感人了)

package main

import "golang.org/x/tour/tree"
import "fmt"


// Walk 步进 tree t 将所有的值从 tree 发送到 channel ch。
func Walk(t *tree.Tree, ch chan int){
	for i:=range(t.String()){
		ch<-i
	}
	close(ch)
}

// Same 检测树 t1 和 t2 是否含有相同的值。
func Same(t1, t2 *tree.Tree) bool{
	ch1:=make(chan int)
	ch2:=make(chan int)
	go Walk(t1,ch1)
	go Walk(t2,ch2)
	var v2 int
	var ok2 bool
	for v1,ok:=<-ch1;ok==true;v1,ok=<-ch1{
		v2,ok2=<-ch2
		if ok2==false || v1!=v2{
			return false
		}
	}
	v2,ok2=<-ch2
	if ok2==true{
		return false
	}
	return true
}

func main() {
	if Same(tree.New(1), tree.New(2)){
		fmt.Println("Same.")
	}else {
		fmt.Println("Different.")
	}
}

最后两个练习 :sync.Mutex、Web爬虫,注意到多线程要等待结束要使用sync.Waitgroup,它确保所有线程完成才退出

官方所有题解

关于waitgroup和官方用的channel的比较点这里注意这里标答的channel是不符合GO语言风格的:

Channel
passing ownership of data,
distributing units of work,
communicating async results

Mutex
caches,
state

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值