Go 基础

125 阅读19分钟

本文章仅是个人基于go语言和其他语言中的一些不同之处做的笔记,并非完全手册

go 基本命令

  • build: 编译包和依赖
  • clean:移除对象文件
  • doc: 显示包或者符号文档
  • env:打印go的环境信息
  • bug:启动错误报告
  • fix:运行go tool fix
  • fmt:运行gofmt 进行格式化
  • generate: 从processing source生成go文件
  • get: 下载并安装依赖包
go get github.com/go-sql-driver/mysql
  • install:编译并安装依赖
  • list: 列出依赖包
  • run: 编译并运行go程序
  • test:运行测试
  • tool:运行go提供的工具
  • version:显示go的版本
  • vet: 运行go tool vet

快捷键

  • pkgm:
package main

func main() {
	
}
  • fp:
fmt.Println("")

项目管理工具 gomod

gomod类似OC中的cocoapods,js中npm,dart中的pub

  • go mod init xxx

变量

快速声明

a,b,c := 1,2,4;

快速交换

a,b,c := 1,2,4;
a,b,c = b,c,a;
fmt.Println(a,b,c);//2 4 1

常量

  • 常量是一个简单值的标识符,在程序运行时,不会被修改的量
  • 常量中的数据类型只可以布尔型、数字型(整数型、浮点型和复数)和字符串型
-   显式类型定义: `const b string = "abc"`
-   隐式类型定义: `const b = "abc"`//自行推断
-   const a, b, c = 1false"str" //多重赋值

iota

  • iota,特殊常量,可以认为是一个可以被编译器修改的常量。
  • 第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;
const (
    a = iota   //0
    b          //1      iota += 1
    c          //2      iota += 1
    d = "ha"   //独立值,iota += 1 中间插队
    e          //"ha"   iota += 1
    f = 100    //100    iota +=1 中间插队
    g          //100    iota +=1
    h = iota   //7,恢复计数 iota += 1
    _          //8 使用_跳过,但也会 iota += 1
    i          //9       iota += 1
    
   )

数据类型

打印数据类型 %T

func main() { }
a := "dddd"
b := true
c := 5
d := 3.1415
p := &a                    //指针类型
list := [4]int{1, 2, 3, 4} //数组类型
slice := []int{1, 2, 3, 4} //切片类型

fmt.Printf("a: %T\n", a)
fmt.Printf("b: %T\n", b)
fmt.Printf("c: %T\n", c)
fmt.Printf("d: %T\n", d)
fmt.Printf("p: %T\n", p)
fmt.Printf("list: %T\n", list)
fmt.Printf("slice: %T\n", slice)
fmt.Printf("func: %T\n", main) //函数类型 

打印结果

a: string
b: bool
c: int
d: float64
p: *string
list: [4]int
slice: []int
func: func()

进制打印

var x int = 10
fmt.Printf("x: %d\n", x) //10 %d  十进制
fmt.Printf("x: %b\n", x) //1010 %b  二进制

var y int = 077          //八进制以0开头
fmt.Printf("y: %o\n", y) //77 %o 八进制

var z int = 0xff         //十六进制以0x开头
fmt.Printf("z: %x\n", z) //ff %x 十六进制小写
fmt.Printf("z: %X\n", z) //FF %X 十六进制大写

打印结果

x: 10
x: 1010
y: 77
z: ff
z: FF

浮点类型打印

ff := 3.1455
fmt.Printf("ff: %f\n", ff)   // 3.145500
fmt.Printf("ff: %.2f\n", ff) //3.15 会自动四舍五入

字符串

  • 反引号多行
s := `
   line1
   line2
   line3
`
fmt.Printf("s: %v\n", s)
  • 字符串拼接
//使用加号拼接
s1 := "tom"
s2 := "20"
msg := s1 + s2
fmt.Printf("msg: %v\n", msg)//msg: tom20

//使用 fmt.Sprintf 
msg1 := fmt.Sprintf("name=%s,age=%s", s1, s2)
fmt.Printf("msg1: %v\n", msg1)//msg1: name=tom,age=20

//使用字符串数组 strings.join()
msg3 := strings.Join([]string{s1, s2}, ",")
fmt.Printf("msg3: %v\n", msg3)//msg3: tom,20

//使用buffer
var buffer bytes.Buffer
buffer.WriteString("tom")
buffer.WriteString("20")
fmt.Printf("buffer.String(): %v\n", buffer.String())//buffer.String(): tom20

运算符

运算符描述实例
&返回变量存储地址&a; 将给出变量的实际地址。
*指针变量。*a; 是一个指针变量
var a int = 4;
var ptr *int;
ptr = &a;
fmt.Println(ptr);//0x14000126008

条件语句

goto 跳出多重循环体

func fc1() {
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			fmt.Printf("i: %v\n", i)
			fmt.Printf("j: %v\n", j)
			if i == 1 && j == 1 {
				goto END
			}
		}

	}

END://End标签
	fmt.Printf("END...")
}

打印结果
i: 0
j: 0
i: 0
j: 1
i: 0
j: 2
i: 1
j: 0
i: 1
j: 1
END...

select

select 是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。

  • 它会按照顺序执行case中语句(如果满足)
  • 不满足就执行default
func main() {
   var c1, c2, c3 chan int
   var i1, i2 int
   select {
      case i1 = <-c1:
         fmt.Printf("received ", i1, " from c1\n")
      case c2 <- i2:
         fmt.Printf("sent ", i2, " to c2\n")
      case i3, ok := (<-c3):  // same as: i3, ok := <-c3
         if ok {
            fmt.Printf("received ", i3, " from c3\n")
         } else {
            fmt.Printf("c3 is closed\n")
         }
      default:
         fmt.Printf("no communication\n")
   }    
}

以上代码执行结果为:

no communication

循环语句

for

Go 语言的 For 循环有 3 种形式,只有其中的一种使用分号。

和 C 语言的 for 一样:

for init; condition; post { }
eg:
for i := 0i <= 10i++ {
    sum += i
}

和 C 的 while 一样:

for condition { }
eg:
for sum <= 10 {
  sum += sum
}

和 C 的 for(;;) 一样:

for { }
eg: 无线循环
sum := 0
for {
  sum++ // 无限循环下去
}
for循环的range格式

for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。格式如下:

pList := [...]int{1,2,3}
for index,value := range pList {
    fmt.Printf("index: %v\n", index)
}
for _,value := range pList {
    fmt.Printf("value: %v\n", value)
}
for key, value := range oldMap {
    newMap[key] = value
}

以上代码中的 key 和 value 是可以省略。

如果只想读取 key,格式如下:

for key := range oldMap {

}

for _, value := range oldMap {

}

eg:

func main() {
    map1 := make(map[int]float32)
    map1[1] = 1.0
    map1[2] = 2.0
    map1[3] = 3.0
    map1[4] = 4.0
   
    // 读取 key 和 value\
    for key, value := range map1 {
      fmt.Printf("key is: %d - value is: %f\n", key, value)
    }

    // 读取 key\
    for key := range map1 {
      fmt.Printf("key is: %d\n", key)
    }

    // 读取 value
    for _, value := range map1 {
      fmt.Printf("value is: %f\n", value)
    }
}

函数

  • 定义
func function_name( [parameter list] ) [return_types] {
   函数体
}
  • parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数
  • return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。 e
/* 函数返回两个数的最大值 */
func max(num1, num2 intint {
   /* 声明局部变量 */
   var result int

   if (num1 > num2) {
      result = num1
   } else {
      result = num2
   }
   return result
}
  • 函数返回多个值Go 函数可以返回多个值,例如:
package main

import "fmt"

func swap(x, y string) (stringstring) {
   return y, x
}

func main() {
   a, b := swap("Google""Runoob")
   fmt.Println(a, b)
}

函数的引用传递

引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

/* 定义交换值函数*/
func swap(x *int, y *int) {
   var temp int
   temp = *x    /* 保持 x 地址上的值 */
   *x = *y      /* 将 y 值赋给 x */
   *y = temp    /* 将 temp 值赋给 y */
}

eg:

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 100
   var b int= 200

   fmt.Printf("交换前,a 的值 : %d\n", a )
   fmt.Printf("交换前,b 的值 : %d\n", b )

   /* 调用 swap() 函数
   * &a 指向 a 指针,a 变量的地址
   * &b 指向 b 指针,b 变量的地址
   */
   swap(&a, &b)

   fmt.Printf("交换后,a 的值 : %d\n", a )
   fmt.Printf("交换后,b 的值 : %d\n", b )
}

func swap(x *int, y *int) {
   var temp int
   temp = *x    /* 保存 x 地址上的值 */
   *x = *y      /* 将 y 值赋给 x */
   *y = temp    /* 将 temp 值赋给 y */
}

以上代码执行结果为:

交换前,a 的值 : 100
交换前,b 的值 : 200
交换后,a 的值 : 200
交换后,b 的值 : 100

函数作为参数

package main

import (
   "fmt"
   "math"
)

func main(){
   /* 声明函数变量 */
   getSquareRoot := func(x float64) float64 {
      return math.Sqrt(x)
   }

   /* 使用函数 */
   fmt.Println(getSquareRoot(9))
}

以上代码执行结果为:

3

闭包

  • Go 语言支持匿名函数,可作为闭包
  • 匿名函数是一个"内联"语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。
func getSequence() func() int {
   i:=0
   return func() int {
      i+=1
     return i  
   }
}

func main(){
   /* nextNumber 为一个函数,函数 i 为 0 */
   nextNumber := getSequence()  

   /* 调用 nextNumber 函数,i 变量自增 1 并返回 */
   fmt.Println(nextNumber())
   fmt.Println(nextNumber())
   fmt.Println(nextNumber())
   
   /* 创建新的函数 nextNumber1,并查看结果 */
   nextNumber1 := getSequence()  
   fmt.Println(nextNumber1())
   fmt.Println(nextNumber1())
}

函数方法

方法就是一个包含了接受者的函数

/* 定义结构体 */
type Circle struct {
  radius float64
}

func main() {
  var c1 Circle
  c1.radius = 10.00
  fmt.Println("圆的面积 = ", c1.getArea())
}

//该 method 属于 Circle 类型对象中的方法
func (c Circle) getArea() float64 {
  //c.radius 即为 Circle 类型对象中的属性
  return 3.14 * c.radius * c.radius
}

defer语句

defer特性
  • 关键字defer用于注册延迟调用
  • 这些调用直到return前才被执行。因此可以用来做资源清理
  • 多个defer语句,按先进后出的方式执行(stack)
  • defer语句中的变量,在defer声明时就决定了
defer用途
  • 关闭文件句柄
  • 锁资源释放
  • 数据库链接释放
defer演示
func fc3() {
    println("start")
    defer println("1")
    defer println("2")
    defer println("3")
    println("end")
}

打印结果

start
end
3
2
1

init函数

init函数特点
  • init函数先于main函数自动执行,不能被其他函数调用
  • init函数没有输入参数、返回值
  • 每个包可以有多个init函数
  • 包的每个源文件也可以有多个init函数
  • 同一个包的init函数执行顺序,golang没有明确定义,编程时要注意程序不要依赖这个执行顺序
  • 不同包的init函数按照包导入的依赖关系决定执行顺序
golang初始化顺序

初始化顺序: 变量初始化 --> init() --> main()

//变量初始化
var a int = initVar()

func initVar() int {
    fmt.Printf("initVar...")
    return 100
}

//可以写多个 谁在上面 谁先执行
func init() {
    fmt.Println("init1...")
}

func init() {
   fmt.Printf("init...")
}

func main() {
    fmt.Printf("main...")
}

打印结果

initVar...
init1...
init...
main...

构造函数

type Person struct {
    name string
    age  int
}

func NewPerson(name string, age int) (person *Person, err error) {
    if name == "" {
            return nil, fmt.Errorf("name 不能为空")
    }
    if age < 0 {
            return nil, fmt.Errorf("age 不能小于0")
    }
    return &Person{name: name, age: age}, nil
}

func main() {
    person, err := NewPerson("dingding", 2)
    if err == nil {
            fmt.Printf("person: %v\n", person)//person: &{dingding 2}
    } else {
            fmt.Printf("err: %v\n", err)
    }
}        

变量作用域

package main

import "fmt"

/* 声明全局变量 */
var a int = 20;

func main() {\
   /* main 函数中声明局部变量 */
   var a int = 10
   var b int = 20
   var c int = 0

   fmt.Printf("main()函数中 a = %d\n",  a);
   c = sum( ab);
   fmt.Printf("main()函数中 c = %d\n",  c);
}
  • 初始化局部和全局变量 不同类型的局部和全局变量默认值为:
数据类型初始化默认值
int0
float320
pointernil

语言指针

  • 什么是指针:一个指针变量指向了一个值的内存地址。
var ip *int        /* 指向整型*/
var fp *float32    /* 指向浮点型 */

如何使用指针

指针使用流程:

  • 定义指针变量。
  • 为指针变量赋值。
  • 访问指针变量中指向地址的值。

在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。

终极口诀: &取地址,*根据地址取值

var ip *int
fmt.Printf("ip: %v\n", ip) //nil
fmt.Printf("ip: %T\n", ip) //*int

i := 10
ip = &i
fmt.Printf("ip: %v\n", ip)  //0x140000140a8
fmt.Printf("ip: %v\n", *ip) //10 取值
package main

import "fmt"

func main() {
   var a int20   /* 声明实际变量 */
   var ip *int        /* 声明指针变量 */

   ip = &a  /* 指针变量的存储地址 */

   fmt.Printf("a 变量的地址是: %x\n", &a  )

   /* 指针变量的存储地址 */
   fmt.Printf("ip 变量储存的指针地址: %x\n", ip )

   /* 使用指针访问值 */
   fmt.Printf("*ip 变量的值: %d\n", *ip )
}

以上实例执行输出结果为:

a 变量的地址是: 20818a220
ip 变量储存的指针地址: 20818a220
*ip 变量的值: 20

空指针 nil

if(ptr != nil)     /* ptr 不是空指针 */
if(ptr == nil)    /* ptr 是空指针 */

结构体

定义

type struct_variable_type struct {
   member definition
   member definition
   ...
   member definition
}

一旦定义了结构体类型,它就能用于变量的声明,语法格式如下:

variable_name := structure_variable_type {value1, value2...valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}

eg:

package main

import "fmt"

type Books struct {
   title string
   author string
   subject string
   book_id int
}


func main() {

    // 创建一个新的结构体
    fmt.Println(Books{"Go 语言""www.runoob.com""Go 语言教程"6495407})

    // 也可以使用 key => value 格式
    fmt.Println(Books{title: "Go 语言", author: "www.runoob.com", subject: "Go 语言教程", book_id: 6495407})

    // 忽略的字段为 0 或 空
   fmt.Println(Books{title: "Go 语言", author: "www.runoob.com"})
}

访问

package main

import "fmt"

type Books struct {
   title string
   author string
   subject string
   book_id int
}

func main() {
   var Book1 Books        /* 声明 Book1 为 Books 类型 */
   var Book2 Books        /* 声明 Book2 为 Books 类型 */

   /* book 1 描述 */
   Book1.title = "Go 语言"
   Book1.author = "www.runoob.com"
   Book1.subject = "Go 语言教程"
   Book1.book_id = 6495407

   /* book 2 描述 */
   Book2.title = "Python 教程"
   Book2.author = "www.runoob.com"
   Book2.subject = "Python 语言教程"
   Book2.book_id = 6495700

   /* 打印 Book1 信息 */
   fmt.Printf"Book 1 title : %s\n"Book1.title)
   fmt.Printf"Book 1 author : %s\n"Book1.author)
   fmt.Printf"Book 1 subject : %s\n"Book1.subject)
   fmt.Printf"Book 1 book_id : %d\n"Book1.book_id)

   /* 打印 Book2 信息 */
   fmt.Printf"Book 2 title : %s\n"Book2.title)
   fmt.Printf"Book 2 author : %s\n"Book2.author)
   fmt.Printf"Book 2 subject : %s\n"Book2.subject)
   fmt.Printf"Book 2 book_id : %d\n"Book2.book_id)
}

匿名结构体

func main() {
    var dog struct {
            id   int
            name string
    }
    dog.id = 5
    dog.name = "wangwang"
    fmt.Printf("dog: %v\n", dog)
}

结构体作为函数参数

  • 值传递(copy了一份副本)
func showChangePerson(person Person) {
	person.name = "tom"
	person.age = 20
}
  • 引用传递 加了个*
func showChangePerson2(person *Person) {
	person.name = "tom"
	person.age = 20
}
  • 调用
var person Person
person.name = "dingding"
person.age = 16
fmt.Printf("person: %v\n", person)//person: {dingding 16}

showChangePerson(person)//直接传结构体对象
fmt.Printf("person: %v\n", person)//person: {dingding 16}

showChangePerson2(&person)//注意这里是&取地址
fmt.Printf("person: %v\n", person)//person: {tom 20}

结构体指针

var struct_pointer *Books

结构体嵌套

type Person struct {
    name string
    age  int
    dog  Dog
}

type Dog struct {
    name string
    age  int
}
  • 实例
var person Person
person.name = "dingding"
person.age = 18
person.dog.name = "wangwang"
person.dog.age = 2
fmt.Printf("person: %v\n", person)//person: {dingding 18 {wangwang 2}}

结构体方法

go是没有面向对象特性,也没有类对象的概念。但是可以用结构体去模拟这些特性。方法就是属于某个结构体的

  • 语法格式
type MyStruct struct {}

//值类型
func (reciv myStruct) my_method(para) return_type{}
指针类型
func (reciv *myStruct) my_method(para) return_type{}
  • 实例
type Person struct {
    name string
}

// person 就是方法的接受者 值类型
func (person Person) eat() {
    person.name = "小刚"
    fmt.Printf("%v eat watermelon \n", person.name)
}

// person 就是方法的接受者 指针类型
func (person *Person) changeName() {
    person.name = "闪电侠"
}
  • 调用
var person Person
person.name = "dingding"
fmt.Printf("person: %v\n", person) //person: {dingding}

person.eat()                       //dingding eat watermelon
fmt.Printf("person: %v\n", person) //person: {dingding}

person.changeName()
fmt.Printf("person: %v\n", person) //person: {闪电侠}

数组

声明

定义了数组 balance 长度为 10 类型为 float32:

var balance [10] float32

数组初始化:

var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

balance := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}//也可以通过字面量在声明数组的同时快速初始化数组

如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:

var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
或
balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

如果设置了数组的长度,我们还可以通过指定下标来初始化元素:

//  将索引为 1 和 3 的元素初始化
balance := [5]float32{1:2.0,3:7.0}

访问数组元素

var balance_1 = balance[0];

多维数组

  • 三维的整型数组:
var threedim [5][10][4]int
  • 二维数组+append()
package main

import "fmt"

func main() {
    // Step 1: 创建数组
    values := [][]int{}

    // Step 2: 使用 append() 函数向空的二维数组添加两行一维数组
    row1 := []int{123}
    row2 := []int{456}
    values = append(values, row1)
    values = append(values, row2)

    // Step 3: 显示两行数据
    fmt.Println("Row 1")
    fmt.Println(values[0])
    fmt.Println("Row 2")
    fmt.Println(values[1])

    // Step 4: 访问第一个元素\
    fmt.Println("第一个元素为:")
    fmt.Println(values[0][0])
}

函数传递数组

  • 方式一

形参设定数组大小:

void myFunction(param [10]int) {
.
.
.
}
  • 方式二

形参未设定数组大小:

void myFunction(param []int) {
.
.
.
}

切片(Slice)

  •  Go 语言切片是对数组的抽象。
  • Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

定义

声明一个未指定大小的数组来定义切片:

var identifier []type

切片不需要说明长度。

或使用 make()  函数来创建切片:

var slice1 []type = make([]type, len)

也可以简写为

slice1 := make([]type, len)

也可以指定容量,其中 capacity 为可选参数。

make([]T, length, capacity)

切片初始化

s :=[] int {1,2,3 } 
  • 初始化切片 s,是数组 arr 的引用。
s := arr[startIndex:endIndex] 
  • 默认 endIndex 时将表示一直到arr的最后一个元素。
s := arr[:endIndex] 
  • 将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片。
s := arr[startIndex:] 
  • 默认 startIndex 时将表示从 arr 的第一个元素开始。
s1 := s[startIndex:endIndex] 
  • 通过切片 s 初始化切片 s1。
s :=make([]int,len,cap) 

空(nil)切片

一个切片在未初始化之前默认为 nil,长度为 0。

切片增删改查

list := []int{}
list = append(list, 1)
list = append(list, 20)
fmt.Printf("list: %v\n", list)//list: [1 20]
  • 删 也是使用append, 公式list1 = append(list1[:index], list[index+1:]...)
list1 := []int{1, 2, 3, 4}
list1 = append(list1[:1], list1[2:]...)
fmt.Printf("list1: %v\n", list1)//list1: [1 3 4]
list1[1] = 100
fmt.Printf("list1: %v\n", list1)

切片拷贝

var list1 = []int{1, 2, 3, 4}
var listCopy = make([]int, len(list1)
//listCopy := []int{} 不能这样初始化,因为两个切片长度不一致,无法拷贝
copy(listCopy, list1)
list1[1] = 100
fmt.Printf("list1: %v\n", list1)//list1: [1 100 3 4]
fmt.Printf("listCopy: %v\n", listCopy)//listCopy: [1 2 3 4]

Map

定义 Map

可以使用内建函数 make 也可以使用 map 关键字来定义 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 不能用来存放键值对

package main\
\
import "fmt"\
\
func main() {\
    var countryCapitalMap map[string]string /*创建集合 */
    countryCapitalMap = make(map[string]string)

    /* map插入key - value对,各个国家对应的首都 */
    countryCapitalMap [ "France" ] = "巴黎"
    countryCapitalMap [ "Italy" ] = "罗马"
    countryCapitalMap [ "Japan" ] = "东京"
    countryCapitalMap [ "India " ] = "新德里"

    /*使用键输出地图值 */
    for country := range countryCapitalMap {
        fmt.Println(country, "首都是", countryCapitalMap [country])
    }

    /*查看元素在集合中是否存在 */
    capital, ok := countryCapitalMap [ "American" ] /*如果确定是真实的,则存在,否则不存在 */
    /*fmt.Println(capital) */
    /*fmt.Println(ok) */
    if (ok) {
        fmt.Println("American 的首都是", capital)
    } else {
        fmt.Println("American 的首都不存在")
    }
}

delete() 函数

/*删除元素*/ 
delete(countryCapitalMap, "France")

类型

类型定义

  • 语法
type NewType Type
  • 实例
func main() {
    type MyInt int
    var i MyInt
    i = 100
    fmt.Printf("i: %v\n", i)//i: 100, main.MyInt
}

类型别名

  • 语法 多个等号
type NewType = Type
  • 实例
type MyInt = int
var i MyInt
i = 100
fmt.Printf("i: %v, %T\n", i, i)//i: 100, int 注意类型还是int

类型转换

类型转换用于将一种数据类型的变量转换为另外一种类型的变量。Go 语言类型转换基本格式如下:

type_name(expression)

type_name 为类型,expression 为表达式。

var mean float32
mean = float32(3);//3.000000

接口

Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。

/* 定义接口 */\
type interface_name interface {
   method_name1 [return_type]
   method_name2 [return_type]
   method_name3 [return_type]
   ...
   method_namen [return_type]
}

/* 定义结构体 */
type struct_name struct {
   /* variables */
}

/* 实现接口方法 */
func (struct_name_variable struct_name) method_namen() [return_type] {
   /* 方法实现*/
}

eg:

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()

}

接口嵌套

接口可以嵌套创建新的接口;例如飞鱼既可以飞又可以游泳,我们创建一个fly接口和一个swim接口,飞鱼接口可以由这两个接口组成。

  • 创建两个接口
type Flyer interface {
   fly()
}

type Swimer interface {
   swim()
}
  • 组合这两个接口
type FlyFisher interface {
    Flyer
    Swimer
}
  • 定义一个Flyfish结构体
type Flyfish struct {
}
  • 实现这两个接口
func (f Flyfish) fly() {
	println("Flyfish fly")
}

func (f Flyfish) swim() {
	println("Flyfish swim")
}
  • 调用
var flyfish Flyfish
flyfish.fly()//Flyfish fly
flyfish.swim()//Flyfish swim

接口的OCP设计原则

OCP(open-closed-principle): 对扩展是开放的,对修改是关闭

  • 定义一个宠物接口Peter
type Peter interface {
	eat()
	sleep()
}
  • 定义Dog和Cat结构并实现Peter接口
type Dog struct {
	name string
}

func (dog Dog) eat() {
	fmt.Printf("dog:%v eat\n", dog.name)
}

func (dog Dog) sleep() {
	fmt.Printf("dog:%v sleep\n", dog.name)
}

type Cat struct {
	name string
}

func (cat Cat) eat() {
	fmt.Printf("cat:%v eat\n", cat.name)
}

func (cat Cat) sleep() {
	fmt.Printf("cat:%v sleep\n", cat.name)
}
  • 定义Person结构体,并实现care方法 传参为Peter接口
type Person struct {
	name string
}

func (per Person) care(pet Peter) {
	pet.eat()
	pet.sleep()
}
  • 调用
func main() {
    person := Person{
            name: "小刚",
    }

    cat := Cat{
            name: "喵喵",
    }

    dog := Dog{
            name: "汪汪",
    }
    
    //因为dog 和 cat 都实现了 Peter接口
    person.care(dog)
    person.care(cat)
}        
  • 打印结果
dog:汪汪 eat
dog:汪汪 sleep
cat:喵喵 eat
cat:喵喵 sleep
  • 后面如果我们想添加Pig这个结构体时,只要Pig实现了eatsleep,person就能调用care方法传pig进去,这就是扩展是开放的

继承

使用结构体模拟继承

type Animal struct {
	name string
}

func (animal Animal) eat() {
	fmt.Printf("animal: %v eat...\n", animal.name)
}

type Dog struct {
	Animal // 可以理解为继承,其实和嵌套类似
	age int
}

func main() {
	dog := Dog{
		Animal{name: "汪汪"},
		age: 2,
	}
	dog.eat()//可以直接调用eat方法
}        

错误处理

  • Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
  • error类型是一个接口类型,这是它的定义:
type error interface {
    Error() string
}

eg

func Sqrt(f float64) (float64, error) {
    if f < 0 {
        return 0, errors.New("math: square root of negative number")
    }
    // 实现
}

result, err:= Sqrt(-1)

if err != nil {
   fmt.Println(err)
}

并发

  • Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。
  • goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。

goroutine 语法格式:

go 函数名( 参数列表 )

例如:

go f(x, y, z)

开启一个新的 goroutine:

f(x, y, z)

通道

  • 通道(channel)是用来传递数据的一个数据结构。
  • 通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。
  • 操作符 <- 用于指定通道的方向,发送或接收。
  • 如果未指定方向,则为双向通道。
ch <- v    // 把 v 发送到通道 ch
v := <-ch  // 从 ch 接收数据
           // 并把值赋给 v

声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建:

ch := make(chan int)
  • 默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。

eg:以下实例通过两个 goroutine 来计算数字之和,在 goroutine 完成计算后,它会计算两个结果的和:

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{728-940}

        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)
}

通道缓冲区

通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:

ch := make(chan int, 100)
  • 带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
  • 不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
  • 如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。
  • 如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;
  • 如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。
  • 接收方在有值可以接收之前会一直阻塞。
package main

import "fmt"

func main() {
    // 这里我们定义了一个可以存储整数类型的带缓冲通道
        // 缓冲区大小为2
        ch := make(chan int2)

        // 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
        // 而不用立刻需要去同步读取数据
        ch <- 1
        ch <- 2

        // 获取这两个数据
        fmt.Println(<-ch)
        fmt.Println(<-ch)
}
执行输出结果为:
1
2

遍历通道与关闭通道

package main

import (
        "fmt"
)

func fibonacci(n int, c chan int) {
        x, y := 01
        for i := 0; i < n; i++ {
                c <- x
                x, y = y, x+y
        }
        close(c)//记得关闭 不然会死锁
}

func main() {
        c := make(chan int10)
        go fibonacci(cap(c), c)
        // range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
        // 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
        // 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
        // 会结束,从而在接收第 11 个数据的时候就阻塞了。
        for i := range c {
                fmt.Println(i)
        }
}

执行输出结果为:
0
1
1
2
3
5
8
13
21
34

WaitGroup实现同步

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func hello(i int) {
    defer wg.Done() // goroutine结束就登记-1
    fmt.Printf("goroutine i: %v\n", i)
}

func main() {
    for i := 0; i < 5; i++ {
            wg.Add(1) //启动一个goroutine 就登记+1
            go hello(i)
    }
    wg.Wait()
    
    //主协程
    fmt.Printf("end...")

打印结果

goroutine i: 4
goroutine i: 1
goroutine i: 2
goroutine i: 0
goroutine i: 3
end...

runtime包

runtime包里面定义了一些协程管理相关的api

  • runtime.Gosched() 让其他协程先执行
  • runtime.Goexit() 退出协程
  • runtime.GOMAXPROCS() //默认使用最大核心数,也可以指定核心数

Mutex互斥锁实现同步

除了channel实现同步外,还可以使用Mutex互斥锁实现同步

var wg sync.WaitGroup
var lock sync.Mutex

var i int = 100

func add() {
	defer wg.Done()
	lock.Lock()
	i += 1
	fmt.Printf("i++: %v\n", i)
	lock.Unlock()
}

func sub() {
	defer wg.Done()
	lock.Lock()
	i -= 1
	fmt.Printf("i--: %v\n", i)
	lock.Unlock()
}

func main() {
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go add()
		wg.Add(1)
		go sub()
	}
	wg.Wait()
	fmt.Printf("end i: %v\n", i)
}        

Timer

Time: 定时器,内部也是通过channel实现的

  • NewTimer 初始化一个timer
timer1 := time.NewTimer(time.Second * 2)
fmt.Printf("timer1: %v\n", timer1) //timer1: &{0x14000122120 {1374389725696 148124316031000 0 0x104fb2900 0x14000122120 0 0 1}}

t1 := time.Now()
fmt.Printf("time.Now() t1: %v\n", t1)//time.Now() t1: 2022-11-24 15:20:36.619265 +0800 CST m=+0.000434418


t2 := <-timer1.C //C 其实就是通道,是阻塞的(2s)
fmt.Printf("t2: %v\n", t2)//t2: 2022-11-24 15:20:38.620049 +0800 CST m=+2.001206168

//t1和t2 相差了两秒时间 
  • time.Sleep 等待
fmt.Printf("time.Now(): %v\n", time.Now())
time.Sleep(time.Second * 3)
fmt.Printf("time.Now(): %v\n", time.Now())

打印
time.Now(): 2022-11-24 15:24:44.49739 +0800 CST m=+0.000191376
time.Now(): 2022-11-24 15:24:46.498841 +0800 CST m=+2.001629251
  • time.After 直接延时
fmt.Printf("time.Now(): %v\n", time.Now())
<-time.After(time.Second * 3)
fmt.Printf("time.Now(): %v\n", time.Now())

打印
time.Now(): 2022-11-24 15:27:42.966416 +0800 CST m=+0.000221043
time.Now(): 2022-11-24 15:27:45.967912 +0800 CST m=+3.001681751
  • timer.Stop() 取消定时
func main() {
    timer := time.NewTimer(time.Second)

    go func() {
            <-timer.C
            fmt.Println("func...")
    }() //后面加() 立即调用

    stop := timer.Stop() //func函数就不会被执行,因为被timer.Stop取消掉了
    if stop {
            fmt.Println("stop...")
    }

    time.Sleep(time.Second * 3)
    fmt.Println("end...")
}

打印
stop...
end...
  • timer.Reset() 重新设置计时
func main() {
    timer := time.NewTimer(time.Second)
    timer.Reset(time.Second * 3)
    <-timer.C
    fmt.Println("end...") //3s后打印
}    

Ticker

Timer只执行一次,Ticker可以周期执行

import "time"
func main() {
    ticker := time.NewTicker(time.Second)
    counter := 1
    for _ = range ticker.C {
        fmt.Printf("time.Now(): %v\n", time.Now())
        counter++
        if counter > 5 {
             break
        }
    }

    ticker.Stop()
    fmt.Println("end...")
}    

原子变量的引入 - lock

import (
    "fmt"
    "sync/atomic"
    "time"
)



var i int32 = 100

func add() {
    // cas: compare and swap old new
    //操作之前会进行新旧值比较,保证线程安全
    atomic.AddInt32(&i, 1)
}

func sub() {
    atomic.AddInt32(&i, -1)
}

func main() {
    for i := 0; i < 100; i++ {
        go add()
        go sub()
    }

    time.Sleep(time.Second * 2)
    fmt.Printf("end i: %v\n", i)
}        
end i : 100

原子操作详解

  • 增减 原子性写入
func AddInt32(addr *int32, delta int32) (new int32)
func AddUint32(addr *uint32, delta uint32) (new uint32)
func AddInt64(addr *int64, delta int64) (new int64)
func AddUint64(addr *uint64, delta uint64) (new uint64)
func AddUintptr(addr *uintptr, delta uintptr) (new uintptr)

eg:
func add() {
    var i int32 = 100
    atomic.AddInt32(&i, 1)
}
  • 载入 read 载入操作能保证读取操作时,任何其他CPU操作无法对当前原子变量进行读写
func LoadInt32(addr *int32) (val int32)
func LoadInt64(addr *int64) (val int64)
func LoadUint32(addr *uint32) (val uint32)
func LoadUint64(addr *uint64) (val uint64)
func LoadUintptr(addr *uintptr) (val uintptr)
func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)

eg:
var j int32 = 100
atomic.LoadInt32(&j)
fmt.Printf("j: %v\n", j)
  • 比较交换cas:compare and swap old new 该操首先确保变量的值未被更改,即仍然保持参数old所记录的值,满足此前提下才进行交换操作,返回值是bool(是否操作成功)。cas的做法类似数据库中乐观锁
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool)
func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool)
func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool)
func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool)
func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool)

eg:
var j int32 = 100
b := atomic.CompareAndSwapInt32(&j, 100, 200)
fmt.Printf("b: %v\n", b)//b: true 交换成功
  • 交换 直接交换,比较暴力 一般不用
func SwapInt32(addr *int32, new int32) (old int32)
func SwapInt64(addr *int64, new int64) (old int64)
func SwapUint32(addr *uint32, new uint32) (old uint32)
func SwapUint64(addr *uint64, new uint64) (old uint64)
func SwapUintptr(addr *uintptr, new uintptr) (old uintptr)
func SwapPointer(addr *unsafe.Pointer, new unsafe.Pointer) (old unsafe.Pointer)

eg:
var j int32 = 100
b := atomic.SwapInt32(&j, 200)
fmt.Printf("b: %v\n", b)//b: 100
  • 储存 write 保证写变量的线程安全
func StoreInt32(addr *int32, val int32)
func StoreInt64(addr *int64, val int64)
func StoreUint32(addr *uint32, val uint32)
func StoreUint64(addr *uint64, val uint64)
func StoreUintptr(addr *uintptr, val uintptr)
func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer)

eg:
var k int32 = 100
atomic.StoreInt32(&k, 200)
fmt.Printf("k: %v\n", k)

标准库-OS模块

os标准库实现了平台(操作系统)相关的编程接口

文件操作

  • 创建文件 os.Create
f, err := os.Create("a.txt")
if err != nil {
    fmt.Printf("err: %v\n", err)
} else {
    fmt.Printf("f.Name(): %v\n", f.Name())//f.Name(): a.txt
}
  • 创建目录 os.Mkdir/os.MkdirAll
//创建单个目录 
//第一个参数是文件夹的名字,第二个参数是读写权限os.ModePerm 最高权限777
err := os.Mkdir("test", os.ModePerm)
if err != nil {
        fmt.Printf("err: %v\n", err)
}

//创建多个目录
err2 := os.MkdirAll("a/b/c", os.ModePerm)
if err2 != nil {
        fmt.Printf("err2: %v\n", err2)
}
  • 删除文件/目录 os.Remove/os.RemoveAll
//删除单个文件
err := os.Remove("a.txt")
if err != nil {
        fmt.Printf("err: %v\n", err)
}

//删除目录
err2 := os.RemoveAll("a/b/c")//会删除c目录
if err2 != nil {
        fmt.Printf("err2: %v\n", err2)
}
//删除多个目录
err3 := os.RemoveAll("a") //会删除a及下面所有的目录
if err3 != nil {
        fmt.Printf("err3: %v\n", err3)
}
  • 获取工作目录
//获取当前目录
dir, err := os.Getwd()
if err != nil {
    fmt.Printf("err: %v\n", err)
} else {
    fmt.Printf("dir: %v\n", dir)//dir: /Users/tiantian/Desktop/code/godemo
}
  • 获取临时目录
path := os.TempDir()
fmt.Printf("path: %v\n", path)// /var/folders/64/fsfsb3_n0v93c01_wp537rdr0000gn/T/
  • 修改目录
//修改目录
err := os.Chdir("d:/")
if err != nil {
    fmt.Printf("err: %v\n", err)
}
  • 读文件
b, err := os.ReadFile("b.txt")
if err != nil {
    fmt.Printf("err: %v\n", err)
} else {
    fmt.Printf("b: %v\n", string(b[:]))
}
  • 写文件
s := "hello world"
os.WriteFile("a.txt", []byte(s), os.ModePerm)

标准库-IO包

Go 语言中,为了方便开发者使用,将 IO 操作封装在了如下几个包中:

  • io 为 IO 原语(I/O primitives)提供基本的接口 os File Reader Writer
  • io/ioutil 封装一些实用的 I/O 函数
  • fmt 实现格式化 I/O,类似 C 语言中的 printf 和 scanf format fmt
  • bufio 实现带缓冲I/O

io — 基本的 IO 接口

在 io 包中最重要的是两个接口:Reader 和 Writer 接口。本章所提到的各种 IO 包,都跟这两个接口有关, 截屏2022-11-28 14.07.27.png

哪些类型实现 Reader 和 Writer 接口

截屏2022-11-28 14.08.12.png

标准库-IO包

截屏2022-11-28 14.16.35.png

标准库-log

截屏2022-11-28 14.30.13.png

标准库 builtin

new和make的区别

  • make只能用来分配初始化类型为 slice、map、chan的数据; new可以分配任意类型的数据
  • new分配返回的是指针,即类型 *Tmake返回引用,即T;
  • new 分配的空间被清零,make分配后,会进行初始化

截屏2022-11-28 14.47.15.png

标准库 bytes

bytes包提供了对字节切片进行读写操作的一系列函数,字节切片处理的函数比较多分为基本处理函数、比较函数、后缀检查函数、索引函数、分割函数、大小写处理函数和子切片处理函数等