Go语言特点

  • 没有头文件概念,.go后缀
  • 强类型语言,编译型语言
  • 一个go语言的应用程序,在运行的时候是不需要依赖外部库的
    • 把执行时需要的库都打包到程序中
    • go程序比较大
    • 如果import的包没有使用,那么程序不允许编译
  • go语法不区分平台的,在windos下面编译的一个程序,可以在Linux上运行,需要配置环境变量来控制
    • GOOS:设定运行平台
      • mac:darwin
      • linux:linux
      • windos:windos
    • GOARCH:目标平台的体系架构
      • 386
      • amd64
      • arm

Go命令

  • go build -o 生成文件名.exe 编译文件名.go

  • go run *.go

    • 直接运行程序不会编译成exe文件
  • 安装程序

    • ./configure
    • make
    • make install —>将编译好的程序安装到指定目录
  • go install

    • 将编译后的可执行文件安装到 GOBIN 目录下
  • go mod

    参数

    • go mod 资料连接

    go mod 使用

    开始使用 Go Module

依赖包存储位置

  • 使用go get获取的包放在$GOPATH/src/目录下
  • 使用go mod下载的依赖包放在$GOPATH/pkg/mod/目录下,所有项目共享

Go目录结构

一般的,一个Go项目在GOPATH下,会有如下三个目录:

一般,bin和pkg目录可以不创建,go命令会自动创建(如 go install),只需要创建src目录即可。

1
2
3
|--bin
|--pkg
|--src
  • bin存放编译后的可执行文件

  • 1
    
    pkg
    

    存放编译后的包文件

    • pkg中的文件是Go编译生成的,而不是手动放进去的
  • src存放项目源文件

Go数据类型

  • go语言不支持隐式类型转换
    • 比如从int 转为 int 64就会发生编译错误

显示类型转换和隐式类型转换

  • 当两种或多种数据类型进行某种操作时,不需要干预,系统会自动进行隐式转换。
  • 但你需要把一个 long 类型的数据转成 int 时,又或者让 string 与 int 互转,当数据小数点太多时,这时候就必须使用 显式转型

Golang的零值

  • Go语言中的零值是变量没有做初始化时系统默认设置的值。 所有其他数值型的类型(包括complex64/128)零值都是0,可以用常量表达式代表数值0的任何形式表示出来。 但是类型不能混用,变量类型和零值类型必须匹配。
  • 布尔类型的零值(初始值)为 false,数值类型的零值为 0,字符串类型的零值为空字符串"",而指针、切片、映射、通道、函数和接口的零值则是 nil。

Go基础语法

变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package main

import "fmt"

func main() {
	// 变量定义: var
	// 常量定义: const

	// 1.先定义变量,var 变量名 类型 ;再赋值 变量=XX
	var name string
	name = "Luenci"

	var age int
	age = 22

	fmt.Println("name:", name)

	// 2.定义时直接赋值
	var sex = "man"

	fmt.Printf("sex is %s \\n", sex)

	fmt.Printf("name is %s age is %d \\n", name, age)

	// 3.定义时直接赋值,使用时自动推导类型(常用)
	hobby := "篮球"

	fmt.Println("my hobby is ", hobby)

	// 灰色部分表示形参
	test(10, "ll")

	// 4. 平行赋值
	i, j := 10, 22
	fmt.Println(i)
	fmt.Println(j)
	i, j = j, i
	fmt.Println(i)
	fmt.Println(j)
}

func test(a int, b string) {
	fmt.Println(a)
	fmt.Println(b)
}

自增/自减 语法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main

func main() {
   i := 11
   println("i 自增前", i)
   i++
   println("i 自增后", i)
   i--
   println("i 自减后", i)

}

指针

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main

func main() {
	// go语言在使用指针时,会使用内部的垃圾回收机制(garbage collector),开发人员不需要手动释放内存
	// go语言可以返回栈上指针,程序在编译的时候就确定了变量的分配位置
	// 在编译的时候,如果发现有必要的话,就将变量分配到堆上

	// 定义指针,方式一
	name := "luenci"
	namePtr := &name

	println("指针地址是", namePtr)
	println("指针内容是", *namePtr)

	// 定义指针关键字 new,方式二
	name2Ptr := new(string)
	*name2Ptr = "luenci"

	println("指针地址是", name2Ptr)
	println("指针内容是", *name2Ptr)

	res := testPtr()

	if res == nil {
		println("指针为空")
	} else {
		println("指针值为", *res)
	}

}

// 定义一个函数,返回一个string类型的指针,go语言返回值写在参数列表后面
func testPtr() *string {
	city := "shanghai"
	cityPtr := &city

	return cityPtr
}

string

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
   "fmt"
)

func main() {
   name := "Lynn"

   // 换行,原生字符串输出时候,使用反引号 ``
   usage := `./a.out <option>
         -h help
         -a xxx`

   fmt.Println("name:", name)
   fmt.Println("usage:", usage)

   // 长度 自由函数 len()
   fmt.Println("name len", len(name))

   // 基本循环
   for i := 0; i < len(name); i++ {
      fmt.Printf("name[%d] %c \\n", i, name[i])
   }

   // 拼接
   i, j := "hello", "world"
   fmt.Println("i+j", i+j)

   // 使用const 修饰为常量不能修改
   constip= "127.0.0.1"
   fmt.Println("const 常量:",ip)
}

定长数组

在不考虑逃逸分析的情况下,如果数组中元素的个数小于或者等于 4 个,那么所有的变量会直接在栈上初始化,如果数组元素大于 4 个,变量就会在静态存储区初始化然后拷贝到栈上,这些转换后的代码才会继续进入中间代码生成和机器码生成两个阶段,最后生成可以执行的二进制文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import "fmt"

func main() {

   // 定义数组 建议使用自动推导
   //var num = [10] int {1,2,3}
   //var num [10] int = [10]int{1,2,3}
   num := [10]int{1, 2, 3}

   // make 创建
   //var nums[]int
   //nums = make([]int,10)

   // 遍历方式一
   for i := 0; i < len(num); i++ {
      fmt.Printf("num[%d] %d \\n", i, num[i])
   }

   // 遍历方式二
   // key 是数组下标, value是数组值(副本)
   // 如果想忽略某个值 可使用 _
   // for _, value := range num {...}
   for key, value := range num {
      // value是一个临时变量,不断的被重新赋值,修改value并不会更改原来num的值
      fmt.Printf("key %d value %d \\n", key, value)
   }

}

切片

扩容是为切片分配新的内存空间并拷贝原切片中元素的过程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import "fmt"

func main() {
   // slice 切片 底层也是数组,可以动态改变长度
   names := []string{"lynn", "luenci"}

   // 对于一个切片不仅仅只有长度概念,还有'容量'概念
   // 追加元素前 长度是 2 容量是 2
   // 追加元素前 长度是 3 容量是 4
   // 在一定量级的时候,动态追加元素, 容量一般是2倍增长
   fmt.Printf("追加元素前 长度是 %d 容量是 %d \\n", len(names), cap(names))

   // append 追加元素
   names = append(names, "kk")
   fmt.Printf("追加元素前 长度是 %d 容量是 %d \\n", len(names), cap(names))

   // 在一定量级的时候,动态追加元素, 容量一般是2倍增长
   num1 := [] int {}
   for i := 0; i < 50; i++ {
      num1 = append(num1, i)
      fmt.Printf("长度 %d 容量 %d \\n", len(num1), cap(num1))
   }

   // 使用make创建数组
   //mnu2 := make([]int, 10)
   //mnu2
}

切片2

在分配内存空间之前需要先确定新的切片容量,运行时根据切片的当前容量选择不同的策略进行扩容:

  1. 如果期望容量大于当前容量的两倍就会使用期望容量;
  2. 如果当前切片的长度小于 1024 就会将容量翻倍;
  3. 如果当前切片的长度大于 1024 就会每次增加 25% 的容量,直到新容量大于期望容量;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

func main() {
	citys := [6]string{"shanghai", "beijing", "wuhan", "hangzhou", "nanjing", "shenzhen"}
	fmt.Printf("city 源地址 %p \\n", &citys[0])

	// 使用索引切片访问 前开后闭 浅拷贝(副本)
	loveCity := citys[0:1]
	//fmt.Println("my love city", loveCity)
	fmt.Printf("切片地址 %p \\n", &loveCity[0])
	loveCity[0] = "kk"

	fmt.Println("修改元素",loveCity)
	fmt.Println("citys",citys[0])

	// 如果想拷贝一份独立与源数组的 使用自由函数 copy()
	loveCitys := copy(loveCity, citys[:])
	fmt.Printf("copy 地址 %p \\n", &loveCitys)

}

切片的很多功能都是由运行时实现的,无论是初始化切片,还是对切片进行追加或扩容都需要运行时的支持,需要注意的是在遇到大切片扩容或者复制时可能会发生大规模的内存拷贝,一定要减少类似操作避免影响程序的性能。

map 字典

$$当桶的数量小于2^4时,由于数据较少、使用溢出桶的可能性较低,会省略创建的过程以减少额外开销;$$

$$当桶的数量多于 2^4 时,会额外创建 2^(B−4)个溢出桶;$$

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package main

import (
   "fmt"
)

func main() {
   // key -> value key是经过hash运算,是无顺序的
   // 使用map之前一定要分配空间

   var idName map[int]string

   idName = make(map[int]string, 10)

   idName[0] = "luenci"
   idName[1] = "lynn"

   //遍历map
   for key, value := range idName {
      fmt.Println("id为", key, "value为", value)
   }

   // 确定key是否在map中
   // 在map中不存在访问越界,访问一个不存在的key,map不会崩溃,会返回零值
   // 零值: bool-》true/false string-》空 int-》0
   name := idName[9]
   fmt.Println("name值为", name)

   idScore := make(map[int]int, 10)
   fmt.Println("int零值", idScore[0])

   idFalse := make(map[int]bool, 10)
   fmt.Println("bool零值", idFalse[0])

   // map无法通过获取value来判断这个对应的key是否存在
   // 可用过下面方法来确定是否存在key  ok -》 bool值
   value, ok := idName[99]
   fmt.Println("ok值为",ok)
   if ok {
      fmt.Println("idname[99]存在,值为", value)
   } else {
      fmt.Println("key不存在")
   }

   // 删除map中的元素
   // 删除不存在的key也不会报错
   delete(idName,0)
   fmt.Println("删除后的map为",idName)

   // 并发处理时需要对map进行上锁TODO

}

func 函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import "fmt"

func test1(a int, b int, c string) (int, string, bool) {

   return a + b, c, false
}

func test2(a, b int, c string) (res int, str string, bl bool) {

   // 直接使用返回值变量名参与运算
   res = a + b
   str = c
   bl = false

   // 当返回值有名称时候,可以直接return
   return
}

func main() {
   a, b, c := test1(1, 2, "luenci")
   fmt.Println("test1函数返回值为", a, b, c)

   res, str, bl := test2(1, 2, "luenci")
   fmt.Println("test2函数返回值为", res, str, bl)
}

内存逃逸

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

func main() {
   resPtr := test3()
   fmt.Println("rePtr:", *resPtr)
}

func test3() *string {
   // 没有被返回,没有逃逸
   name := "lynn"
   p0 := &name
   fmt.Println("p0", *p0)

   // 地址返回 内存逃逸
   city := "上海"
   ptr := &city
   fmt.Println("地址为:", ptr)

   return ptr
}

Switch选择分支

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
   "fmt"
   "os"
   "reflect"
)

func main() {
   cmds := os.Args

   fmt.Println("cmds 类型为", reflect.TypeOf(cmds))

   switch cmds[1] {
   // case 中默认加了 break 不需要手动break
   case "luenci":
      // 如果想向下穿透(执行下一个条件中的代码),使用 fallthrough 关键字
      fmt.Println("i am luenci")
      //fallthrough
   case "Lynn":
      fmt.Println("i am Lynn")
   case "kk":
      fmt.Println("i am kk")
   default:
      fmt.Println("默认值")
   }

   for key, value := range cmds {
      fmt.Println("key", key, "value", value)
   }

}

标签LABEL

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

func main() {
   // 标签 LABLE1
   // goto LABEL1 >> 下次进入循环时,i 不会保存之前状态,i=0,重新向下运行
   // break LABEL1 >> 直接跳出指定位置的循环
   // continue LABEL1 >> 跳到指定位置,会记录之前的状态,向下执行

   // 标签名称可以自定义命名
LABEL1:
   for i := 0; i < 5; i++ {
      for j := 0; j < 5; j++ {
         if j == 3 {
            goto LABEL1
         }
         fmt.Println("i", i, ",j", j)
      }
   }
}

枚举iota

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import "fmt"

// iota 是常量组计数器
// iota从0开始,每换行递增1,从第一次出现iota开始计算
// 常量组如果不赋值,默认上一行表达式相同
// iota是以行为单位递增

// 每个常量组的iota是独立的,都是从零开始递增

const
(
MONDAY= iota // 0
TUESDAY
   WEDNESDAY
   THURSDAY
   FRIDAY
   SATURDAY
   M,N= iota, iota // const 属于预编译,所以不需要 := 自动推导

)

// go语言中没有枚举类型,但是可以使用 const + iota(常量累加器)来进行模拟
func main() {
   // 变量组统一命名变量
   var
   (
      number int
      name   string
      flag   bool
   )
   fmt.Println(number, name, flag)

   fmt.Println(MONDAY)
   fmt.Println(TUESDAY)
   fmt.Println(WEDNESDAY)
   fmt.Println(THURSDAY)
   fmt.Println(FRIDAY)
   fmt.Println(SATURDAY)
   fmt.Println(M,N)

}

结构体

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import "fmt"

// 结构体声明 type + 别名 + struct
type Student struct {
   name  string
   age   int
   sex   string
   score float64
}

func main() {
   lynn := Student{
      name:  "lynn",
      age:   20,
      sex:   "girl",
      score: 100, // 最后换一个变量后必须加`,`, 或者以`}`结尾
   }

   fmt.Println("lynn", lynn)

   s1 := &lynn
   fmt.Println("s1", s1.name, s1.age, s1.sex, s1.score)
   fmt.Println("s1", (*s1).name, (*s1).age, (*s1).sex, (*s1).score)

   // 如果只对结构体部分变量赋值,name应该指定变量名称
   luenci := Student{
      name: "luenci",
      age:  21,
   }
   fmt.Println("leunci", luenci)

}

defer函数 & 匿名函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main

import (
   "fmt"
   "os"
)

func main() {
   readFile("4.结构体.go")
}

func readFile(filename string) {
   // os.Open 打开文件,会返回一个文件指针和err信息,如果无错误 err 是 nil
   // defer 当你的堆栈退出的时候会调用 (必须函数调用结束)
   // func (){...}() 不声明函数名表示匿名函数 后面加括号() 调用

   fp, err := os.Open(filename)
   defer func() {
      fmt.Println("文件关闭!")
      _ = fp.Close()
   }()

   defer fmt.Println("00000")
   defer fmt.Println("00001")
   defer fmt.Println("00002")

   if err != nil {
      fmt.Println("文件读取错误,error:", err)
      return
   }
   buf := make([]byte, 1024)
   n, _ := fp.Read(buf)
   fmt.Println(string(buf))
   fmt.Println("读取文件长度为", n)
}

init函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package sub

import "fmt"

// 在go语言同一层级目录,不允许出现多个 package 名称

func init() {
   fmt.Println("sub库下的init函数")
}

func Sub(a, b int) int {
   test5()
   return a - b
}
package main

// sub 是文件夹名,也是package名
import (
   _ "luenci/github.com/day02/5.init函数/sub" // 只会调用sub中的init函数
)

// init 函数没有参数和返回值,使用如下
// 同一个包中包含多个init函数时候,调用顺序是不确定的(同一个package下的多个文件都可以有init)
// init 函数是不允许调用(显示调用)的
// 如果只想调用一个package中的 init函数,只需在导包前加上 _

func main() {

   //res := sub.Sub(10, 5)
   //fmt.Println("sub res", res)

}