Golang简明学习教程一起来学习Go编程语言

已收录   阅读次数: 744
2021-05-3120:25:09 发表评论
摘要

伯衡君日前正在学习Golang这门编程语言,发现它和kotlin以及ruby很类似,于是带着之前的经验学习还挺快,而且该编程语言自从2007年由Google发布问世以后,巍巍然后超越Java的架势,相信有志于学习该门编程语言的人不在少数,伯衡君准备一边学习,一边在本篇文章来完整的将一些主要知识,以及学习中的笔记记录在此……

分享至:
Golang简明学习教程一起来学习Go编程语言

开篇寄语

伯衡君日前正在学习Golang这门编程语言,发现它和kotlin以及ruby很类似,于是带着之前的经验学习还挺快,而且该编程语言自从2007年由Google发布问世以后,巍巍然后超越Java的架势,相信有志于学习该门编程语言的人不在少数,伯衡君准备一边学习,一边在本篇文章来完整的将一些主要知识,以及学习中的笔记记录在此。

前情提要

内容详情

Hello World

Go是一门开源的编程语言,它能够轻而易举构建简单,可靠以及功能强大的应用软件,它是由Google在2007年发布的。

最简单的Hello, World!的例子:

package main

import "fmt"

func main() {
	fmt.Println("Hello, World!")
}

每一个Go程序都是由packages组成,开始运行主要使用的是main这个package,这也就是上例的样子,除了main这个package外,还有很多其他的package,其中最普遍使用的,当属“fmt”,这个是英文format的缩写,它是提供输入和输出的函数,如果想引用它,则需要用到“import”,类似于上例所述。

如果想引用更多的package,可以这样书写:

import (
  "fmt"
  "math"
)

Go的注释书写方式,类似于Javascript等编程语言,“//“是单行注释,“/* */”是多行注释,如下所示:

//单行注释
/*
多行
注释
*/
基础概念
  • 变量

在 Go 中,var 关键字用于声明变量,举例如下:

var i int

上面的代码声明了一个名为 i 的 int 类型变量。

int 代表整数,代表整数。

还可以赋值给i,举例如下:

var i int = 9

还可以多项赋值,举例如下:

var i,j = 1,2

另一种剪短写法则是:

i,j := 1,2
  • 数据类型
    • float32:单精度浮点数
    • float64:双精度浮点数
    • string:文本值
    • bool:逻辑值true或者false
    • int:整数值
var x int = 1
var y float32 = 1.25
var name string = "zhangboheng"
var online bool = true
  • 赋值固定值
const pi = 3.14
  • 数学运算

包括“+”,“-”,“*”,“\”,“%”等等,与Javascript的运算逻辑很类似。

  • 比较运算

包括“==”,“!=”,“<”,“<=”,“>”,“>=”,“&&”,“||”,“!”等等,与Javascript的比较运算逻辑类似。

  • input(用户输入)
    • 用户输入的是string类型的值,如果输入的值是整数,则需要提前写好类型,如下所示:
package main

import "fmt"

func main() {
  var input int
  fmt.Scanln(&input)

  fmt.Println(input*2)
}
  • if/else 函数

Go语言的if/else函数和Swift编程的if/else写法很类似,Go的表示方法如下所示:

package main

import "fmt"

func main() {
  num := 1
  if num == 1 {
    fmt.Println("One")
  }else if num == 2 {
    fmt.Println("Two")
  }else {
    fmt.Println("Three")
  }
}
  • Switch

该函数也类似于Javascript函数,只是写法稍微有些不同,上方的那个例子,改写成Switch函数是下面这个样子:

package main

import "fmt"

func main() {
  num := 1
  switch num {
  case 1:
    fmt.Println("One")
  case 2:
    fmt.Println("Two")
  }
  default:
    fmt.Println("Three")
}

函数

  • 构建函数

除了使用Go packages,还可以自定义自己的函数,使用关键词“func”来定义。

举例如下:

package main

import "fmt"

func welcome() {
  fmt.Println("Hello")
}

func main() {
  welcome()
  welcome()
}
  • Arguments

在构造的函数中添加赋值,示例如下:

package main

import "fmt"

func welcome(name string) {
  fmt.Println("Hello, " + name)
}

func main() {
  welcome("Zhang")
  welcome("Tan")
}

如果是多项赋值,则需要用逗号进行分隔。

  • return

示例如下:

package main

import "fmt"

func welcome(name string) string{
  return "Hello, " + name
}

func main() {
  result := welcome("Zhang")
  fmt.Println(result)
}
  • defer

defer声明确保function是在主函数运行完后才返回,示例如下:

package main

import "fmt"

func welcome() {
  fmt.Println("Welcome")
}

func main() {
  defer welcome()
  fmt.Println("Hey")
}

//运行结果如下:
//Hey
//Welcome
  • for...loop

Go的for循环也和Javascript函数很像,只是写法上略有不同,举例如下:

package main

import "fmt"

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

//返回结果就是0,1,2,3,4啦

指针

  • 基本概念
  • 很像c/c++中的指针,即程序中定义的值储存在电脑的内存地址
  • 在Go中,我们声明一个指针用“*”这个符号

示例如下:

var p *int

现在,p就是一个指针,并且赋予了一个整数值

如何让赋值返回内存地址呢?那么则需要使用“&”符号,示例如下:

package main

import "fmt"

func main() {
  x:=2
  p:=&x
  fmt.Println(p)
}
//运行结果如下:
//0xc000018040

如果想访问构建指针的值,则需要用到“*”,示例如下:

package main

import "fmt"

func main(){
  x := 1
  p := &x
  *p = 2
  fmt.Println(*p)
  fmt.Println(x)
}
//返回的结果是2,2
  • Structs

Go函数不支持classes,替代它的是structs,很类似Javascript的Constrution。

type Contact struct {
  name string
  age int
}

那么这个Contact struct有两个fields,一个是string和整数,以下面的例子进行使用:

x := Contact{"James", 42}
//x.name = "James"
//x.age = 42

可以给构建的Contact增加方法,以上面构建的Contact为例,增加一个Welcome():

package main

import "fmt"

type Contact struct {
  name string
  age int
}

func welcome(x Contact) {
  fmt.Println(x.name)
  fmt.Println(x.age)
}

func main() {
  y := Contact{"Zhang", 29}
  welcome(y)
}
Array
  • 基本概念

数组,这个在其他函数中也有,以javascript中的数组为例,样子是这样的[1,2,3,4,5],而在Go语言中,形式是这个样子的:

a := [5]int{1,2,3,4,5}

index也是从0开始

a[0] //是1
a[1] //是2
//...
a[4] //是5
  • slice
    • Go的数组切割很类似于python的方法
var s []int = a[1:3]
fmt.Printlns(s) //结果为[2 3]
  • make
    • Go语言提供了make()函数以进行切割,可以创造灵活的数组
a := make([]int, 5)
fmt.Println(a)
//结果是[0 0 0 0 0]
  • for...loop
    • 之前有介绍了它,在数组中,它可以使用另一种写法:
package main

import "fmt"

func main() {
  a := make([]int, 5)
  a[1] = 2
  a[2] = 3
  for _, y := range a {
    fmt.Println(y)
  }
}
//输出结果如下:
/*
0
2
3
0
0
*/

//如果是string数据类型的话,输出的就是对应的ascii码值了

package main

import "fmt"

func main() {
  x := "hello"
  for _, c := range x {
    fmt.Println(c)
    //fmt.Println("%c \n", c)这种写法则会生成string中的字母了
  }
}

//生成结果如下:
/*
104
101
108
108
111
*/
  • append
    • 类似于python的append,可以从数组末尾添加元素进去
  • map
    • map就是类似于python的dictinary,javascript的object
package main

import "fmt"

func main() {
  m := make(map[string]int)
  m["Zhang"] = 29
  m["Li"] = 28
  fmt.Println(m)
}

//生成的结果是map[Amy:24 James:42]
  • delete
    • 从map中删除元素
delete(m, "Zhang")
  • 从map中添加元素
m["Jerry"] = 30
  • Variadic Functions
    • fmt.Println()函数就是属于这种

还可以自定义,如下所示:

package main

import "fmt"

func sum(nums ...int) {
  total := 0
  for _, v := range nums {
    total += v
  }
  fmt.Println(total)
}

func main() {
  sum(1,2,3)
}

//得到结果是6

并发

  • Goroutine

并发意味着多个运算可以同时发生,当您的程序有多项事情要同时要做时使用它。 并发是关于创建独立执行的多个进程。

例如,想象一个射击游戏,其中许多事情同时发生:敌人正在奔跑和射击,同时计算积分,达到等级解锁武器等。

所有这些事情都需要同时发生,为了使用并发,程序被分成几个部分,然后分别执行。

为了实现并发,Go 语言提供了 Goroutines模块。

Goroutine 很像一个线程来完成多个任务,但它比 OS 线程消耗更少的资源。

当一个程序被分解成单独的任务时,每个 Goroutine 都可以用来完成一个任务,从而在我们的程序中实现并发。

Goroutine 不是操作系统线程:它们是虚拟线程;由 Go 管理。你可以在一个 Go 程序中运行几千个 Goroutine。

举例如下:

package main
import (
    "fmt"
    "time"
)

func out(from, to int) {
  for i:=from; i<=to; i++ {
    time.Sleep(50 * time.Millisecond)
    fmt.Println(i)
  }
}
func main() {
  out(0, 5)
  out(6, 10)
}

现在,如果我们在 main 中调用该函数两次,第一个调用将首先执行,然后是第二个调用。

在运行并发程序时,不要经常希望等待一项任务完成后再开始新的任务。为了实现并发,让我们使用 go 关键字将函数调用作为 Goroutines 启动:

package main
import (
    "fmt"
    "time"
    )

func out(from, to int) {
    for i:=from; i<=to; i++ {
        time.Sleep(50 * time.Millisecond)
        fmt.Println(i)
    }
    
}

func main() {
    go out(0, 5)
    go out(6, 10)
}

但是输出的结果是没有输出结果,之所以出现这样的结果是因为 main() 函数在 Goroutines 完成之前退出。

改进一下:

package main
import (
    "fmt"
    "time"
    )

func out(from, to int) {
    for i:=from; i<=to; i++ {
        time.Sleep(50 * time.Millisecond)
        fmt.Println(i)
    }
    
}

func main() {
    go out(0, 5)
    go out(6, 10)

    time.Sleep(500 * time.Millisecond)
}

500 毫秒的等待时间应该足以让 Goroutine 完成执行并生成输出。现在当你运行代码时,你会看到输出不是按照顺序生成的,这是因为每个 Goroutine 独立并发地工作。

  • Channels

正如我们在上一课中看到的,Goroutines 独立运行,它们不知道另一个 Goroutines 何时完成执行。例如,这会导致 main() 函数在任何启动的 Goroutine 完成之前退出。

为了实现 Goroutines 之间的通信,Go 提供了 Channels。

通道就像一个管道,允许你从 Goroutines 发送和接收数据,并使它们能够通信和同步。

要使用通道,我们首先需要使用 make() 函数创建一个:

ch := make(chan int) 

chan 关键字后面的类型表示我们将通过通道发送的数据类型。

我们可以使用以下语法将数据发送到通道:

ch <- 8 

同样,我们可以使用以下语法从通道接收数据:

value := <- ch 

如果我们不需要该值作为变量,我们可以简单地使用:

<- ch

现在我们可以使用我们的频道并在 main() 中不用 time.Sleep() 重写前面的示例

package main
import (
    "fmt"
    "time"
    )

func out(from, to int, ch chan bool) {
    for i:=from; i<=to; i++ {
        time.Sleep(50 * time.Millisecond)
        fmt.Println(i)
    }
    ch <- true
}

func main() {
    ch := make(chan bool)

    go out(0, 5, ch)
    go out(6, 10, ch)

    <-ch
}

让我们使用通道从 Goroutine 发送数据并在 main() 中使用它,我们的程序需要计算并输出给定​​范围内所有偶数的和加上它们的平方和并输出结果:输出 = evenSum+squareSum

我们将使用两个 Goroutines:一个计算 evenSum,另一个计算 squareSum。

我们将使用 main() 中的通道获取数据,然后计算并输出最终和。

package main
import "fmt"

func evenSum(from, to int, ch chan int) {
    result := 0
    for i:=from; i<=to; i++ {
        if i%2 == 0 {
            result += i
        }    
    }
    ch <- result
}
func squareSum(from, to int, ch chan int) {
    result := 0
    for i:=from; i<=to; i++ {
        if i%2 == 0 {
            result += i*i
        }    
    }
    ch <- result
}

func main() {
    evenCh := make(chan int)
    sqCh := make(chan int)

    go evenSum(0, 100, evenCh)
    go squareSum(0, 100, sqCh)

    fmt.Println(<-evenCh + <- sqCh)
}
  • Select

select 语句用于等待多个通道操作。

语法与 switch 类似,只是每个 case 语句都是一个通道操作。

我们使用上一个示例中的相同程序并选择首先准备好的通道:

package main
import "fmt"

func evenSum(from, to int, ch chan int) {
    result := 0
    for i:=from; i<=to; i++ {
        if i%2 == 0 {
            result += i
        }    
    }
    ch <- result
}
func squareSum(from, to int, ch chan int) {
    result := 0
    for i:=from; i<=to; i++ {
        if i%2 == 0 {
            result += i*i
        }    
    }
    ch <- result
}

func main() {
    evenCh := make(chan int)
    sqCh := make(chan int)

    go evenSum(0, 100, evenCh)
    go squareSum(0, 100, sqCh)

    select {
        case x := <- evenCh:
            fmt.Println(x)
        case y := <- sqCh:
            fmt.Println(y)
    }
}

这意味着只有一种情况会执行——对应于首先接收数据的通道,如果两个通道同时接收数据,则随机选择其中一种情况。

选择可以有一个默认情况,它会在没有通道准备好时执行。例如,我们可以有一个无限循环,等待其中一个通道接收数据:

package main
import (
    "fmt"
    "time"
)

func evenSum(from, to int, ch chan int) {
    result := 0
    for i:=from; i<=to; i++ {
        if i%2 == 0 {
            result += i
        }    
    }
    ch <- result
}
func squareSum(from, to int, ch chan int) {
    result := 0
    for i:=from; i<=to; i++ {
        if i%2 == 0 {
            result += i*i
        }    
    }
    ch <- result
}

func main() {
    evenCh := make(chan int)
    sqCh := make(chan int)

    go evenSum(0, 100, evenCh)
    go squareSum(0, 100, sqCh)

    for {
        select {
            case x := <- evenCh:
                fmt.Println(x)
                return
            case y := <- sqCh:
                fmt.Println(y)
                return
            default:
                fmt.Println("Nothing available")
                time.Sleep(50 * time.Millisecond)
        }
    }
}

for 循环使用 select 来检查哪个通道获得了数据。如果它们都没有准备好,默认情况将执行,这将等待 50 毫秒。
一旦通道获得数据,return 语句将退出循环。

select 语句会阻塞,直到至少它的一种情况可以继续。默认情况在防止死锁方面很有用——没有它,选择将永远等待一个通道,如果没有一个通道接收到数据,程序就会崩溃。

  • 我的微信
  • 微信扫一扫加好友
  • weinxin
  • 我的微信公众号
  • 扫描关注公众号
  • weinxin

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: