• go语言入门


    引言

    Go


    Go语言是谷歌2009发布的编程语言,它是一种并发的、带垃圾回收的、快速编译的语言。

    它结合了解释型语言的游刃有余,动态类型语言的开发效率,以及静态类型的安全性。它也打算成为现代的,支持网络与多核计算的语言。要满足这些目标,需要解决一些语言上的问题:一个富有表达能力但轻量级的类型系统,并发与垃圾回收机制,严格的依赖规范等等。这些无法通过库或工具解决好,因此Go也就应运而生了。

    优势

    1. 语法简单,上手快;
    2. 性能高,编译快,开发效率也不低;
    3. 丰富的标准库;
    4. 原生支持并发,协程模型是非常优秀的服务端模型,同时也适合网络调用;
    5. 部署方便,编译包小,除 glibc 外没有其他外部依赖;
    6. 自带完善的工具链, 大大提高了团队协作效率和一致性。 比如 gofmt ,gofix,,govet 等工具。

    Docker等很多 Go 产品的流行,更证明了 Go 语言的优秀。

    适用场景

    - 服务器编程,如:处理日志、数据打包、虚拟机处理、文件系统等;

    - 分布式系统,数据库代理器等;

    - 内存数据库,google开发的groupcache,couchbase的部分组建;

    - 云平台,目前国外很多云平台在采用Go开发,如:CloudFoundy(VMware推出的业界第一个开源PaaS云平台)的部分组建。

    缺点

    1. Go的import包不支持版本,有时候升级容易导致项目不可运行,需要自己控制相应的版本信息;

    2. Go的goroutine一旦启动之后,不同的goroutine之间切换不是受程序控制,需要严谨的逻辑;

    3. 没什么太多应用场景非要 Golang 才能做的

        3.1 开发 web 没有 php ruby 成熟、快速

        3.2 开发 server 没有 java 现成解决方案多


    GO指南

    环境搭建

    安装Golang的SDK


    (1) http://www.golangtc.com/download
    (2) 安装完成之后,打开终端,输入go、或者go version查看安装版本

    配置Go环境变量

            配置Go环境变量GOPATH和GOBIN

      (1)打开终端,cd ~

      (2)查看是否有.bash_profile文件:

         ls -all

      (3)有则跳过此步,没有则:

        1)创建:touch .bash_profile

        2)编辑:open -e .bash_profile

        3)自定义GOPATH和GOBIN位置:

    GOPATH:日常开发的根目录。GOBIN:是GOPATH下的bin目录。

    export GOPATH=/Users/yuan/go
    export GOBIN=$GOPATH/bin
    export PATH=$PATH:$GOBIN

      (4)编译:source .bash_profile

      (5)*查看Go环境变量:go env


    开发工具配置


    sublime text


    一定要先配置好Go环境变量GOPATH和GOBIN,再安装此插件,要不插件会提示找不到GOPATH和GOBIN;

    选用 sublime text 安装 gosublime 插件进行开发( golang 语法高亮提示)

      (1)安装 package controll(若已安装,请跳过)
      使用Ctrl+`快捷键或者通过View->Show Console菜单打开命令行,粘贴如下代码:

    import urllib.request,os; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); open(os.path.join(ipp, pf), 'wb').write(urllib.request.urlopen( 'http://sublime.wbond.net/' + pf.replace(' ','%20')).read()) 2


      (2)install go sublime
    Command+shift+p 输入并选择packageControl: install package
    然后输入并选择goSublime
    安装完成就OK啦~~


    Gogland


    选择Gogland, 下载安装即可,3个月
    https://www.jetbrains.com/go/download/

    LiteIDE


    国产IDE 
    http://golangtc.com/download/liteide


    小试牛刀


    在你的gopath目录下,新建main.go文件即可以进行编码了。

    package main
    import (
    	"fmt"
    )
    func main() {
    	fmt.Println("hello gopher~")
    }
    
    



    代码编写完成之后,使用command+b打开sublime text终端

    (一)编译+执行
    使用go build main.go对其进行编译,编译通过的结果信息如下:

    [ `go build main.go` | done: 420.495985ms ]
    提示编译成功之后,再执行shell命令,执行刚刚编译之后的文件./main即可看到运行结果:

    [ `./main` | done: 10.532868ms ]
    hello go

    (二)直接执行
    如果仅仅是只需要看到运行的结果,而不产生可执行文件(文件名和项目名一样)则在sublime text终端中直接使用go run xxx.go即可:

    [ `go run main.go` | done: 314.476988ms ]
    hello go


    基础

    包、变量、函数

    package main
    import "fmt"
    
    func main() {
    	var hello string = "Hello"
    	who := "gopher"
    	var s = hello+"," + who
    	fmt.Println(s)
    }


    1. 每个 Go 程序都是由包组成的。
    2. 程序运行的入口是包 main 。
    3. 按照惯例,包名与导入路径的最后一个目录一致。例如,"math/rand" 包由 package rand 语句开始。

    变量

    • 变量声明使用关键字var
    • 初始值存在时可省略类型声明
    • 短赋值语句:= 可以用于替代 var 的隐式类型声明(:=结构不能使用在函数外,函数外的每个语法块都必须以关键字开始)
    var name1 string //声明变量
    name1 = "tom" //给变量赋值
    var name2 string = "tom" //声明变量+赋值
    var name3 = "tom" // 声明时同时赋值,可以省略变量类型
    name4 := "tom" //短赋值语句
    // 多个变量
    var x, y, z int
    var c, python, java bool
    var x, y, z int = 1, 2, 3
    var c, python, java = true, false, "no!"
    c, python, java := true, false, "no!"


    函数

    • 可以返回任意数量的返回值
    • 类型声明在变量名之后
    • 同一类型的多个参数,最后一个参数需声明类型
    func swap(x, y string) (string, string) {
    	return y, x
    }



    • 命名返回值的参数
    func split(sum int) (x, y int) {
    	x = sum * 4/9
    	y = sum - x
    	return
    }



    基本类型

    bool
    string
    int int8 int16 int32 int64
    uint uint8 uint16 uint32 uint64 uintptr
    byte // uint8 的别名
    rune // int32 的别名
    // 代表一个Unicode码位
    
    float32 float64
    complex64 complex128

    常量

    const Pi = 3.14
    const World = "世界"
    const Truth = true


    运算符


    http://www.yiibai.com/go/go_operators.html

    注意:

    • 没有++i,--i,只有i++、i--
    • 不能使用i++、i--对变量直接赋值


    流程控制


    for


    Go 只有一种循环结构—— for 循环

    for i := 0; i < 10; i++ {
    	//do something
    }
    i := 0
    for ; i < 1000; {
    	//do something
    }
    for i < 1000 {
    	//do something
    } 
    for {
    	//死循环
    }

    if else

    if x < 0 {
    	return x
    }
    if v := 0; v < 5 {
    	return v
    }
    return 9

    switch


    case 语句匹配后会自动终止(无需break),除非用 fallthrough 语句作为结尾,则会强制执行下一条case的语句或者default语句,而不判断expression。

    	x := 2
    	switch x {
    	case 1:
    		fmt.Println(1)
    	case 2:
    		fmt.Println(2)
    		fallthrough
    	case 3:
    		fmt.Println(x > 1)
    	default:
    		fmt.Println("default")
    	}
    
    // 结果
    // 2
    // true



    defer


    延迟(defer)处理
    Defer用于确保在稍后的程序中,执行函数调用。
    defer语句在封装函数(main)结束时执行。

    package main
    import "fmt"
    import "os"
    func main() {
    	f := createFile("defer-test.txt")
    	defer closeFile(f)
    	writeFile(f)
    }
    func createFile(p string) *os.File {
    	fmt.Println("creating")
    	f, err := os.Create(p)
    	if err != nil {
    		panic(err)
    	}
    	return f
    }
    func writeFile(f *os.File) {
    	fmt.Println("writing")
    	fmt.Fprintln(f, "data")
    }
    func closeFile(f *os.File) {
    	fmt.Println("closing")
    	f.Close()
    }
    
    // output
    creating
    writing
    closing

    复杂类型


    struct


    要定义结构,必须使用type和struct语句。struct语句定义了一个新的数据类型,在程序中有多个成员。type语句在例子中绑定一个类型为struct的名字。 struct语句的格式如下:

    type person struct {
    	name string
    	age int
    }


    访问结构体成员使 .

    package main
    import "fmt"
    type person struct {
    	name string
    	age  int
    }
    func main() {
    	fmt.Println(person{"Bob", 20})
    	fmt.Println(person{name: "Alice", age: 30})
    	fmt.Println(person{name: "Fred"})
    	fmt.Println(&person{name: "Ann", age: 40})
    	s := person{name: "Sean", age: 50}
    	fmt.Println(s.name)
    	sp := &s
    	fmt.Println(sp.age)
    	sp.age = 51
    	fmt.Println(sp.age)
    }
    
    
    #output
    {Bob 20}
    {Alice 30}
    {Fred 0}
    &{Ann 40}
    Sean
    50
    51

    slice


    因为切片(Slice)是数组上的抽象。 它实际上使用数组作为底层结构体.len()函数返回切片中存在的元素数量,其中cap()函数返回切片(Slice)的容量(大小),即可容纳多少个元素。

    package main
    import "fmt"
    func main() {
    	var numbers = make([]int, 3, 5)
    	printSlice(numbers)
    }
    func printSlice(x []int) {
    	fmt.Printf("len=%d cap=%d slice=%v
    ", len(x), cap(x), x)
    }
    
    
    //output
    len=3 cap=5 slice=[0 0 0]

    map

    var map_variable map[key_data_type]value_data_type
    map_variable = make(map[key_data_type]value_data_type)

    map_variable := make(map[key_data_type]value_data_type)


    delete(map_variable, key)

    package main
    import "fmt"
    func main() {
    	var countryCapitalMap map[string]string = make(map[string]string)
    	/* create a map*/
    	// countryCapitalMap = make(map[string]string)
    	/* insert key-value pairs in the map*/
    	countryCapitalMap["France"] = "Paris"
    	countryCapitalMap["Italy"] = "Rome"
    	countryCapitalMap["Japan"] = "Tokyo"
    	countryCapitalMap["India"] = "New Delhi"
    	/* print map using keys*/
    	for country := range countryCapitalMap {
    		fmt.Println("Capital of", country, "is", countryCapitalMap[country])
    	}
    	/* test if entry is present in the map or not*/
    	capital, ok := countryCapitalMap["United States"]
    	/* if ok is true, entry is present otherwise entry is absent*/
    	if ok {
    		fmt.Println("Capital of United States is", capital)
    	} else {
    		fmt.Println("Capital of United States is not present")
    	}
    	/* delete an entry */
    	delete(countryCapitalMap, "France")
    	fmt.Println("Entry for France is deleted")
    	fmt.Println("Updated map")
    	/* print map */
    	for country := range countryCapitalMap {
    		fmt.Println("Capital of", country, "is", countryCapitalMap[country])
    	}
    }

    range

    range函数可以用来遍历array,slice和map。

    当用于遍历array和slice时,range函数返回索引和元素;

    当用于遍历map的时候,range函数返回key和value。

    package main
    import "fmt"
    func main() {
    	nums := []int{2, 3, 4}
    	sum := 0
    	for _, num := range nums {
    		sum += num
    	}
    	fmt.Println("sum:", sum)
    	for i, num := range nums {
    		if num == 3 {
    			fmt.Println("index:", i)
    		}
    	}
    	kvs := map[string]string{"a": "apple", "b": "banana"}
    	for k, v := range kvs {
    		fmt.Printf("%s -> %s
    ", k, v)
    	}
    	for k := range kvs {
    		fmt.Println("key:", k)
    	}
    }
    

    方法和接口

    方法

    Go中没有类,但是可以为结构体定义方法,方法就是一类带有特殊的接受者参数的函数。

    方法接受者位于func关键字和方法名之间。
    可以为非结构体类型声明方法,但不能为其它包内定义的类型的接收者声明方法,且不能为内建类型声明方法。

    package main
    import "fmt"
    func add(x int, y int) int {
    	return x + y
    }
    func main() {
    	fmt.Println(add(42, 13))
    }
    package main
    import "fmt"
    type rect struct {
    	width, height int
    }
    func (r *rect) area() int {
    	return r.width * r.height
    }
    func (r rect) perim() int {
    	return 2*r.width + 2*r.height
    }
    func main() {
    	r := rect{ 10, height: 5}
    	fmt.Println("area: ", r.area())
    	fmt.Println("perim:", r.perim())
    	rp := &r
    	fmt.Println("area: ", rp.area())
    	fmt.Println("perim:", rp.perim())
    }
    
    
    
    // output
      area:  50
      perim: 30
      area:  50
      perim: 30

    函数是完全闭包的

    package main
    import "fmt"
    // 函数 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),
    		)
    	}
    }
    
    

    接口

    接口类型是由一组方法签名的集合。
    接口类型的值可以保存任何实现了接口方法的变量。
    类型通过实现了一个接口的所有方法来实现这个接口,而不需要专门的显示声明也就是”implements”关键字来声明。

    package main

    import "fmt"
    import "math"

    type geometry interface {
    area() float64
    perim() float64
    }
    type rect struct {
    width, height float64
    }
    type circle struct {
    radius float64
    }

    func (r rect) area() float64 {
    return r.width * r.height
    }
    func (r rect) perim() float64 {
    return 2*r.width + 2*r.height
    }
    func (c circle) area() float64 {
    return math.Pi * c.radius * c.radius
    }
    func (c circle) perim() float64 {
    return 2 * math.Pi * c.radius
    }
    func measure(g geometry) {
    fmt.Println(g)
    fmt.Println(g.area())
    fmt.Println(g.perim())
    }
    func main() {
    r := rect{ 3, height: 4}
    c := circle{radius: 5}
    measure(r)
    measure(c)
    }

    // output
    {3 4}
    12
    14
    {5}
    78.53981633974483
    31.41592653589793


    error


    Go编程提供了一个非常简单的错误处理框架,以及内置的错误接口类型,如下声明:

    type error interface {
    	Error() string
    }


    Go函数通常返回错误作为最后一个返回值。 可使用errors.New来构造一个基本的错误消息

    package main
    import "errors"
    import "fmt"
    import "math"
    func Sqrt(value float64) (float64, error) {
    	if value < 0 {
    		return 0, errors.New("Math: negative number passed to Sqrt")
    	}
    	return math.Sqrt(value), nil
    }
    func main() {
    	result, err := Sqrt(-1)
    	if err != nil {
    		fmt.Println(err)
    	} else {
    		fmt.Println(result)
    	}
    	result, err = Sqrt(9)
    	if err != nil {
    		fmt.Println(err)
    	} else {
    		fmt.Println(result)
    	}
    }
    
    

    并发

    goroutine

    goroutine 是由 Go 运行时环境管理的轻量级线程。
    使用 go f(x, y, z) 开启一个新的 goroutine 执行。

    package main
    import "fmt"
    func f(from string) {
    	for i := 0; i < 3; i++ {
    		fmt.Println(from, ":", i)
    	}
    }
    func main() {
    	f("direct")
    	go f("goroutine")
    	go func(msg string) {
    		fmt.Println(msg)
    	}("going")
    	var input string
    	fmt.Scanln(&input)
    	fmt.Println("done")
    }
    
    
    // output
    
    $ go run goroutines.go
    direct : 0
    direct : 1
    direct : 2
    goroutine : 0
    going
    goroutine : 1
    goroutine : 2
    <enter>
    done

    channel

    channel 是有类型的管道,可以用 channel 操作符 <- 对其发送或者接收值。

    ch <- v // 将 v 送入 channel ch。
    v := <-ch // 从 ch 接收,并且赋值给 v。

    默认情况下,在另一端准备好之前,发送和接收都会阻塞。这使得 goroutine 可以在没有明确的锁或竞态变量的情况下进行同步。

    package main
    import "fmt"
    func main() {
    	messages := make(chan string)
    	go func() { messages <- "ping" }()
    	msg := <-messages
    	fmt.Println(msg)
    }
    
    

    channel 可以是带缓冲的。为 make 提供第二个参数作为缓冲长度来初始化一个缓冲 channel:

    ch := make(chan int, 100)
    向缓冲 channel 发送数据的时候,只有在缓冲区满的时候才会阻塞。当缓冲区清空的时候接受阻塞。

    package main
    import "fmt"
    func main() {
    	c := make(chan int, 2)
    	c <- 1
    	c <- 2
    	fmt.Println(<-c)
    	fmt.Println(<-c)
    }
    
    

    close

    发送者可以 close 一个 channel 来表示再没有值会被发送了。接收者可以通过赋值语句的第二参数来测试 channel 是否被关闭:当没有值可以接收并且 channel 已经被关闭,那么

    v, ok := <-ch
    ok 会被设置为 false。

    注意: 只有发送者才能关闭 channel,而不是接收者。向一个已经关闭的 channel 发送数据会引起 panic。

    package main
    import "fmt"
    func main() {
    	jobs := make(chan int, 5)
    	done := make(chan bool)
    	go func() {
    		for {
    			j, more := <-jobs
    			if more {
    				fmt.Println("received job", j)
    			} else {
    				fmt.Println("received all jobs")
    				done <- true
    				return
    			}
    		}
    	}()
    	for j := 1; j <= 3; j++ {
    		jobs <- j
    		fmt.Println("sent job", j)
    	}
    	close(jobs)
    	fmt.Println("sent all jobs")
    	<-done
    }
    
    

    select


    Go语言的选择(select)可等待多个通道操作。将goroutine和channel与select结合是Go语言的一个强大功能。

    select 会阻塞,直到条件分支中的某个可以继续执行,这时就会执行那个条件分支。如果有多个都准备好的时候,会随机选一个。

    package main
    import "time"
    import "fmt"
    func main() {
    	c1 := make(chan string)
    	c2 := make(chan string)
    	c3 := make(chan string)
    	t1 := time.Now().UnixNano()
    	go func() {
    		time.Sleep(time.Second * 1)
    		c1 <- "one"
    	}()
    	go func() {
    		time.Sleep(time.Second * 2)
    		c2 <- "two"
    	}()
    	go func() {
    		time.Sleep(time.Second * 2)
    		c3 <- "three"
    	}()
    	for i := 0; i < 3; i++ {
    		select {
    		case msg1 := <-c1:
    			fmt.Println("received", msg1)
    		case msg2 := <-c2:
    			fmt.Println("received", msg2)
    		case msg3 := <-c3:
    			fmt.Println("received", msg3)
    		}
    	}
    	t2 := time.Now().UnixNano()
    	dt := (t2 - t1) / 1e6
    	fmt.Println(dt)
    }
    

    Goroutine 调度

    结构——M, P, S
        Go的调度器内部有三个重要的结构:M,P,S
              

             M: 内核OS线程
             G: 一个goroutine,它有自己的栈,instruction pointer和其他信息(正在等待的channel等等),用于调度。
             P: 代表调度的上下文,可以把它看做一个局部的调度器,使go代码在一个线程上跑,它是实现从N:1到N:M映射的关键。
                 P的数量可以通过GOMAXPROCS()来设置,它其实也就代表了真正的并发度,即有多少个goroutine可以同时运行。
     
    线程阻塞——投奔其他线程
    • 图中看到,当一个OS线程M0陷入阻塞时,P转而在OS线程M1上运行。调度器保证有足够的线程来运行所以的context P。
     
    • 当MO返回时,它必须尝试取得一个context P来运行goroutine,一般情况下,它会从其他的OS线程那里steal偷一个context过来,
             如果没有偷到的话,它就把goroutine放在一个global runqueue里,然后自己就去睡大觉了(放入线程池里)。Contexts们也会周期性的检查global runqueue。


    分配不均——steal work

    1. global runqueue

    2. 其他的P
     
    常见开发陷阱
    • 开大括号不能放在单独的一行
    • 未使用的变量
    • 未使用的Imports
    • 不支持前置版本的自增和自减,也无法在表达式中使用这两个操作符。

             。。。

  • 相关阅读:
    软件开发 [CJOJ 1101] [NOIP 模拟]
    OI中卡常数技巧
    疫情控制 [NOIP2012]
    开车旅行 [NOIP 2012]
    观光公交 [NOIP 2011] [思维推导]
    选择客栈 [NOIP 2011]
    2016级算法期末上机-F.中等·AlvinZH's Fight with DDLs II
    2016级算法期末上机-E.中等·ModricWang's Fight with DDLs II
    2016级算法期末上机-D.简单·AlvinZH's Fight with DDLs I
    2016级算法期末上机-C.简单·Bamboo's Fight with DDLs III
  • 原文地址:https://www.cnblogs.com/1488boss/p/10864117.html
Copyright © 2020-2023  润新知