开篇寄语
伯衡君日前正在学习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啦
- 局部变量与全局变量
- 可以参看这篇文章《Web开发人员应该掌握的8个Javascript概念》
指针
- 基本概念
- 很像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 语句会阻塞,直到至少它的一种情况可以继续。默认情况在防止死锁方面很有用——没有它,选择将永远等待一个通道,如果没有一个通道接收到数据,程序就会崩溃。
ArrayArrayArray- 我的微信
- 微信扫一扫加好友
- 我的微信公众号
- 扫描关注公众号