文章目录
1. 第一个 Go 程序
1.1 Go 代码结构
Go 代码基础组成有以下几个部分:
- 包声明(package main)
- 引入包(import “fmt”)
- 函数(func main())
- 变量
- 语句 & 表达式
- 注释(// /**/)
1.1.1 包声明
package main
必须在非注释的第一行指明文件属于哪个包;每个Go 程序都包含一个名为main的包。
1.1.2 引入包
import "fmt"
fmt包实现了格式化IO函数。
1.1.3 函数
func main()
main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。
1.2 标识符大小写
当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。
1.3 注意
“{”不能是一行代码的第一个字符,以下代码是错误的:
package main
import "fmt"
func main()
{// 错误,{ 不能单独放一行
fmt.Println("Hello , World!")
}
1.4 运行
$ go run hello.go
1.5 编译
$ go build hello.go
2. Go 语言基础
Go 语言语句可用 “;” 符号分割,也可以用换行分割。
2.1 Go 语言数据类型
- 布尔型,var b bool = true
- 数字型,有int、float32、float64等
- 字符串类型,Go 文本默认使用UTF-8编码
- 派生类型
- 指针类型(Pointer)
- 数组类型
- 结构化类型(struct)
- Channel类型
- 函数类型
- 切片类型
- 接口类型(interface)
- Map类型
2.2 Go 变量
2.2.1 声明变量及赋值
第一种,指定变量类型
如果没有初始化,变量默认值为零
var age int = 100
age = 100
var id int
第二种,根据值自行判断变量类型
var age = 100
第三种,省略var
name := "david"
多变量声明和赋值
age,name,id := 30,"david",99
var mobile,addr,nation string
mobile = "18888888888"
addr = "shenzhen"
nation = "china"
2.3 Go 常量
const identifier [type] = value
特殊常量
iota 是一个特殊常量,是一个可以被编译器修改的常量
3. Go 语言函数
func function_name ( [parameter list] ) [return_types]{
// 函数体
}
4. 切片(Slice)
Go 语言切片是对数组的抽象。
Go 数组的长度是不可改变的;而切片类似动态数组,长度不固定,可以追加元素,在追加时可能使切片的容量增大。
定义切片:
var identifier []type
创建切片:
len := 10
var slice1 []int = make([]int ,len)
// 简写
slice2 := make([]int, len)
指定容量,其中capacity为可选参数:
make([]T, length, capacity)
这里len 是数组的长度并且也是切片的初始长度。
切片初始化
s :=[] int {1,2,3 }
[] 表示是切片类型
{1,2,3 } 是初始化的值
capacity=length=3
s := arr[:]
以数组arr来初始化切片s
s := arr[startIndex:endIndex ]
s := arr[startIndex: ]
s := arr[ :endIndex ]
5. 范围(Range)
Go 语言种range关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中返回元素的索引和索引对应的值,在集合中返回key-value对应的key值。
// range测试
package main
import "fmt"
func main(){
nums := []int{1,2,3,4,5}
sum := 0
for i, num := range nums {
sum += num
fmt.Println("index =",i,",Value = ",num)
}
fmt.Println("sum:",sum)
}
6. Map集合
Go 语言中的map是无序键值对集合。Map最重要的一点的通过key检索数据。Map是无序的,我们无法决定它的返回顺序,这是因为Map是使用hash表来实现的。
定义Map
// 声明变量,默认 map 是nil
var map_variable map[key_data_type]value_data_type
// 使用 make 函数
map_variable := make(map[key_data_type]value_data_type)
如果不初始化map,就会创建一个nil map。nil map不能用来存放键值对。
7. Go 语言接口
Go 语言的接口有点奇葩,需要多转几个弯才能理解。
Go 语言把类型的具有共性的方法定义在一起,其他类型只需要实现这些方法就是实现了这个接口。
// 定义接口
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
// ...
}
// 定义结构体
type struct_name struct{
// ...
}
func (struct_name_variable struct_name) method_name1() [return_type] {
// ...
}
func (struct_name_variable struct_name) method_name2() [return_type] {
// ...
}
还是直接上代码吧:
package main
import (
"fmt"
)
type Phone interface {
call()
}
type NokiaPhone struct {
}
func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}
type IPhone struct {
}
func (iPhone IPhone) call() {
fmt.Println("I am iPhone, I can call you!")
}
func main() {
var phone Phone
phone = new(NokiaPhone)
phone.call()
phone = new(IPhone)
phone.call()
}
8. Go 语言错误处理
Go 语言通过内置的错误接口提供了非常简单的错误处理机制:
type error interface {
Error() string
}
9. Go 并发和协程(goroutine)
Go 语言支持并发,我们只需要通过go关键字来开启goroutine即可。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
goroutine 的语法格式:
go 函数名 (参数列表)
例如:
go f(x,y,z)
开启一个新的 goroutine:
f(x, y, z)
Go 允许使用go 语句来开启一个新的运行期线程,即 goroutine,以一个不同的、新创建的goroutine 来执行一个函数。同一个程序中的所有 goroutine 共享一个地址空间。
代码:
// goroutine并发测试
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")
}
10. 通道(channel)
channel是用来传递数据的一个数据结构。
通道可用于两个goroutine之间传递一个指定类型的值来同步运行和通信。操作符<-用于指定通道的方向,发送或接收。如果未指定方向,则未双向通道。
ch <- v // 把 v 发送到通道 ch
v := <-ch // 从 ch 接收数据,并把值赋给 v
使用chan关键字声明一个通道,通道在使用前必须先创建:
ch := make(chan int)
默认情况下,通道是不带缓冲的。发送端发送数据,同时必须有相应的接收端接收数据:
// channel测试
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 把 sum 发送到通道 c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 从通道 c 中接收
fmt.Println(x, y, x+y)
}
10.1 通道缓冲
通道可以设置缓冲区,通过make的第二个参数指定缓冲区大小:
ch := make(chan int,100)
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态;不过由于缓冲区的大小是有限的,当缓冲区一满,数据发送端就无法再发送数据了。
**注意:**如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方会阻塞直到发送的值被拷贝至缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。
10.2 遍历通道与关闭通道
Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下:
v, ok := <-ch
如果通道接收不到数据后ok就为 false,这时通道就可以使用 close() 函数来关闭。
package main
import (
"fmt"
)
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)
// range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
// 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
// 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
// 会结束,从而在接收第 11 个数据的时候就阻塞了。
for i := range c {
fmt.Println(i)
}
}