Go语言开发笔记

2020-04-02  本文已影响0人  阿帕奇UP

一、Go开发环境

1. GOROOT

就是Go的安装路径。

2. GOPATH

GOPATH 是作为编译后二进制的存放目的地和 import 包时的搜索路径 (其实也是你的工作目录,你可以在 src 下创建你自己的 go 源文件,然后开始工作)。

GOPATH 之下主要包含三个目录:

3. 程序的运行

$ go run xxx.go    // go run 命令, 将 .go源文件进行编译、链接,然后运行生产可执行文件
$ go build xxx.go  // go build 命令,编译生成一个可执行程序

二、Go语言基础

1. 常量

const limit = 512
const top uint16 = 1421
const Pi float64 = 3.1415926
const x, y, z int = 1, 3, 5   //多重赋值

iota 是一个可以被编译器修改的常量,在 const 关键字出现时被重置为 0,在下一个 const 出现之前,每出现一次 iota,其所代表的数字自动加 1。

const (
  a = iota  // a == 0
  b = iota  // b == 1
  c = iota  // c == 2
)
const d = iota  // d == 0, 因为const的出现,iota被重置为0。

2. 变量

var a int
var b string
var c float64
var d [5] int    // 数组
var e [] int     // 数组切片
var f int = 5
var g = 55       // 自动类型推导(省略了int的显式声明)
h := 666         // 省略 var,省略 int

交换两个变量:

x := 2
y := 3
x, y = y, x      // Go支持多重赋值

3. 数据类型

3.1 整型

类型 说明
byte 等同于 uint8
int 依赖于不同平台下的实现,可以是 int32 或者 int64
int8 [-128, 127]
int16 [-32768, 32767]
int32 [-2147483648, 2147483647]
int64 [-9223372036854775808, 9223372036854775807]
rune 等同于 int32
uint 依赖于不同平台下的实现,可以是 uint32 或者 uint64
uint8 [0, 255]
uint16 [0, 65535]
uint32 [0, 4294967295]
uint64 [0, 18446744073709551615]
uintptr 一个可以恰好容纳指针值的无符号整型(对 32 位平台是 uint32, 对 64 位平台是 uint64)

查看某数据类型的字节长度:

var x int64 = 12
fmt.Println("length of int64 :", unsafe.Sizeof(x))    // length of int64 : 8

3.2 浮点型

类型 说明
float32 ±3.402 823 466 385 288 598 117 041 834 845 169 254 40x1038 计算精度大概是小数点后 7 个十进制数
float64 ±1.797 693 134 862 315 708 145 274 237 317 043 567 981x1038 计算精度大概是小数点后 15 个十进制数
complex32 复数,实部和虚部都是 float32
complex64 复数,实部和虚部都是 float64

3.3 布尔型

go语言提供了布尔值 true 和 false. 不接受其他类型的赋值,不支持类型转换。

var a bool
a = true 
b := (2 == 3)

3.4 字符串

转义字符 含义
\ 表示反斜线
' 单引号
" 双引号
\n 换行符
\uhhhh 4 个 16 进制数字给定的 Unicode 字符

在Go语言中单个字符可以使用单引号' ' 来创建。

一个单一的字符可以用一个单一的rune来表示。 rune类型,等同于 int32。

字符串支持的操作如下:

语法 描述
s += t 将字符串 t 追加到 s 末尾
s + t 将字符串 s 和 t 级联
s[n] 从字符串 s 中索引位置为 n 处的原始字节
s[n:m] 从位置 n 到位置 m-1 处取得的字符(字节)串
s[n:] 从位置 n 到位置 len(s)-1 处取得的字符(字节)串
s[:m] 从位置 0 到位置 m-1 处取得的字符(字节)串
len(s) 字符串 s 中的字节数
len([]rune(s)) 字符串 s 中字符的个数,可以使用更快的方法 utf8.RuneCountInString()
[ ]rune(s) 将字符串 s 转换为一个 unicode 值组成的串
string(chars) chars 类型是 []rune 或者 []int32, 将之转换为字符串
[ ]byte(s) 无副本的将字符串 s 转换为一个原始的字节的切片数组,不保证转换的字节是合法的 UTF-8 编码字节

3.5 格式化字符串

格式化指令 含义
%% % 字面量
%b 一个二进制整数,将一个整数格式化为二进制的表达方式
%c 一个 Unicode 的字符
%d 十进制数值
%o 八进制数值
%x 小写的十六进制数值
%X 大写的十六进制数值
%U 一个 Unicode 表示法表示的整形码值,默认是 4 个数字字符
%s 输出以原生的 UTF-8 字节表示的字符,如果 console 不支持 UTF-8 编码,则会输出乱码
%t 以 true 或者 false 的方式输出布尔值
%v 使用默认格式输出值,或者使用类型的 String() 方法输出的自定义值,如果该方法存在的话
%T 输出值的类型

3.6 字符类型

Go语言中支持两个字符类型:

Byte (实际上是 Unit8的别名), 代表UTF-8字符串的单个字节的值。

rune , 代表单个 Unicode字符。

3.7 数组

数组是一个定长的序列,其中每个元素的类型都相同。 固定长度,不可修改。

var a [5] int
var b = [5]int{1,2,3,4,5}
var c = [...]int{1,2,3,4,5}    // 如果使用了省略号,Go语言会为我们自动计算数组的长度。

3.8 切片

Go语言的切片比数组更加灵活、强大、方便! 切片不定长,可以随时调整长度。

数组是按值传递的(即传递的副本),而切片是引用类型,传递切片的成本非常小。

var a make([]int, 10, 20)      // 切片的长度和容量分别为 10 和 20
var b = []int{1,2,3,4,5}

3.9 包

Go语言中的代码组织方式是包。 包是各种类型和函数的集合。

在包中,如果标识符的首字母是大写的,那这些标识符是可以被导出的,可以在包以外直接使用。

4. 流程控制

if aBoolValue {
  block1
}else if anotherBoolValue {
  block2
}else {
  block3
}

go语言只支持for循环语句,不支持 while / do-while语句。

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

for boolVlaue {
                            // while 循环。 Go语言中没有while关键字
}
for index, char := range aString {
                  // 迭代字符串
}
for item := range aChannel {
                  // 迭代通道
}
switch aExpression {
  case expression1:
      block1
  ... 
  case expressionN:
        blockN
  default:
        BlockD
}

5. 函数

package Divide
func divide(a int, b int) (num int, err error) {
  if b == 0 {
    err := errors.New("被除数不能为0")
    return
  }
  return a / b, nil           // 支持返回多个值。不必创建临时的结构体来包装。
}

// 下面是函数的调用
package main
import (
  "Divide"          // 导入 Add 包
  "fmt"
)
func main() {
  c, err := Divide.divide(1,2)
  if err==nil {
    fmt.Println(c)
  }
}

5.1 匿名函数

匿名函数由一个不带函数名的函数声明和函数体组成。

func (a,b,c int) bool {
  return a * b < c
}

你可以将匿名函数直接赋值给一个变量,也可以直接调用运行。

x := func (a,b,c int) bool {
  return a * b < c
}

func (a,b,c int) bool {
  return a * b < c
} (1,2,3)                 // 小括号内直接给参数列表 表示函数调用

接口是一个自定义类型。它声明了一个或者多个方法。任何实现了这些方法的对象(类型)都满足这个接口。

var i interface{} = 99              // 创建一个interface{}类型,其值为99
    var s interface{} = []string{"left", "right"}
    j := i.(int)                    // 我们假设i是兼容int类型,并使用类型断言将其转换为int类型
    fmt.Printf("type and value of j is: %T and %d\n", j, j)

    if s, ok := s.([]string); ok {  // 创建了影子变量,if的作用域中覆盖了外部的变量s
        fmt.Printf("%T -> %q\n", s, s)
    }

5.2 defer

defer 语句是在 return 之后执行的。

func CopyFile(dst, src string) (w int64, err error) {
    srcFile, err := os.Open(src)
    if err != nil {
        return
    }
    defer srcFile.Close()
    dstFile, err := os.Create(dst)
    if err != nil {
        return
    }
    defer dstFile.Close()
    return io.Copy(dstFile, srcFile)
}

5.3 panic & recover

panic() 函数用于抛出异常,recover() 函数用于捕获异常。

6. 结构体

type ColorPoint struct {
  color.Color            // 匿名字段(嵌入)
  x, y int                           // 具名字段(聚合)
}

7. 方法

方法是作用在自定义类型上的一类特殊函数。

定义方法和定义函数几乎相同,只是需要在 func 关键字和方法名之间必须写上接接受者。

type Count int
func (count *Count) Increment() {        // 接受者是一个 `Count` 类型的指针
  *count++
}

8. 组合

Go 语言中没有继承。 但是提供了一个组合特性。

相对于继承的编译期确定实现,组合的运行态指定实现,更加灵活。

9. 接口

Go中接口是一组方法签名。当一个类型为接口中的所有方法提供定义时,它被称为实现该接口。

infterface{} 类型是声明了空方法集的接口类型。任何一个值都满足 interface{} 类型,也就是说如果一个函数或者方法接收 interface{} 类型的参数,那么任意类型的参数都可以传递给该函数。

package main
import "fmt"
type Human struct { // 结构体
    name  string
    age   int
    phone string
}

//Human实现SayHi方法
func (h Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

//Human实现Sing方法
func (h Human) Sing(lyrics string) {
    fmt.Println("La la la la...", lyrics)
}

type Student struct {
    Human  //匿名字段
    school string
    loan   float32
}

type Employee struct {
    Human   //匿名字段
    company string
    money   float32
}

// Employee重载Human的SayHi方法
func (e Employee) SayHi() {
    fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
        e.company, e.phone)
}

// Interface Men被Human,Student和Employee实现
// 因为这三个类型都实现了这两个方法
type Men interface {
    SayHi()
    Sing(lyrics string)
}

func main() {
    mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
    paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
    sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
    Tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000}

    //定义Men类型的变量i
    var i Men

    //i能存储Student
    i = mike
    fmt.Println("This is Mike, a Student:")
    i.SayHi()
    i.Sing("November rain")

    //i也能存储Employee
    i = Tom
    fmt.Println("This is Tom, an Employee:")
    i.SayHi()
    i.Sing("Born to be wild")

    //定义了slice Men
    fmt.Println("Let's use a slice of Men and see what happens")
    x := make([]Men, 3)
    //这三个都是不同类型的元素,但是他们实现了interface同一个接口
    x[0], x[1], x[2] = paul, sam, mike

    for _, value := range x {
        value.SayHi()
    }
}

三、并发编程

1. 并发与并行

并发指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,通过 CPU 时间片轮转使多个进程快速交替的执行。而并行的关键是你有同时处理多个任务的能力。

并发和并行都可以是很多个线程,就看这些线程能不能同时被(多个)CPU 执行,如果可以就说明是并行,而并发是多个线程被(一个)CPU 轮流切换着执行。

并发与并行的区别:并发是两个队列,使用一台咖啡机;并行是两个队列,使用两台咖啡机。如果串行,一个队列使用一台咖啡机,那么哪怕前面那个人有事出去了半天,后面的人也只能等着他回来才能去接咖啡,这效率无疑是最低的。

2. 协程

协程也叫轻量级线程。与传统的进程和线程相比,协程最大的优点就在于其足够“轻”,操作系统可以轻松创建上百万个协程而不会导致系统资源枯竭,而线程和进程通常最多不过近万个。

多数语言在语法层面上是不支持协程的,一般都是通过库的方式进行支持,但库的支持方式和功能不够完善,经常会引发阻塞等一系列问题,而 Go 语言在语法层面上支持协程,也叫 goroutine。这让协程变得非常简单,让轻量级线程的切换管理不再依赖于系统的进程和线程,也不依赖 CPU 的数量。

3. goroutine

goroutine 是 Go 语言并行设计的核心。goroutine 是一种比线程更轻量的实现,十几个 goroutine 可能在底层就是几个线程。

要使用 goroutine 只需要简单的在需要执行的函数前添加 go 关键字即可。

func Add(a, b int) {
  c := a + b
  fmt.Println(c)
}
go Add(1, 2)                  // 使用 go 关键字让函数并发执行

当在一个函数前加上 go 关键字,该函数就会在一个新的 goroutine 中并发执行,当该函数执行完毕时,这个新的 goroutine 也就结束了。

4. channel

Go 语言提供的信道(channel)就是专门解决并发通信问题的.

channelgoroutine 之间互相通讯的东西。类似我们 Unix 上的管道(可以在进程间传递消息),用来 goroutine 之间发消息和接收消息。其实,就是在做 goroutine 之间的内存共享。

var a chan int            // 声明一个传递元素类型为int的channel
var b chan float64
var c chan string
x := make(chan int)       // 声明并初始化一个int型的名为a的channel
y := make(chan float64)
z := make(chan string)

channel 最频繁的操作就是写入和读取,这两个操作也非常简单:

a := make(chan int)
a <- 1                    // 将数据写入channel
z := <-a                  // 从channel中读取数据

channel的关闭非常简单,使用 Go 语言内置的 close() 函数即可关闭 channel

ch := make(chan int)
close(ch)

5. select

select 用于处理异步 IO 问题,它的语法与 switch 非常类似。由 select 开始一个新的选择块,每个选择条件由 case 语句来描述,并且每个 case 语句里必须是一个 channel 操作。

func main() {
    c1 := make(chan string)    // 初始化两个 channel c1 和 c2
    c2 := make(chan string)
    go func() {            // 开启两个 goroutine 分别往 c1 和 c2 写入数据
        time.Sleep(time.Second * 1)
        c1 <- "one"
    }()
    go func() {
        time.Sleep(time.Second * 2)
        c2 <- "two"
    }()
    for i := 0; i < 2; i++ {
        select {                  // 通过 select 监听两个 channel
        case msg1 := <-c1:
            fmt.Println("received", msg1)
        case msg2 := <-c2:
            fmt.Println("received", msg2)
        }
    }
}

6. 超时机制

t := make(chan bool)
go func {
    time.Sleep(1e9) //等待1秒
    t <- true
}
select {
    case <-ch:  //从ch中读取数据
    case <-t:  //如果1秒后没有从ch中读取到数据,那么从t中读取,并进行下一步操作
}

这样的方法就可以让程序在等待 1 秒后继续执行,而不会因为 ch 读取等待而导致程序停滞,从而巧妙地实现了超时处理机制,这种方法不仅简单,在实际项目开发中也是非常实用的。

上一篇下一篇

猜你喜欢

热点阅读