go基础语法

注意点

包和目录的关系

go 用文件目录来管理包,一个文件目录可以有多个.go 文件,要求目录内所有的文件的包名要一致
默认包名和目录名称保持一致,不强制要求

用包来管理元素的可见行,大写开头的元素包外也可见,小写开头的元素只能在包内使用

目录 p1 内的俩个go 文件

第一个.go 文件

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

import "fmt"

func F1() {
F3()
f4()
fmt.Println("F1")
}
func f2() {
fmt.Println("f2")
}

第二个 .go 文件

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

import "fmt"

func F3() {
F1()
f2()
fmt.Println("F3")
}
func f4() {
fmt.Println("f4")
}

main .go 文件

1
2
3
4
5
6
7
8
9
10
11
12
package main
//引用包
import "awesomeProject/p1"

func main() {
p1.F1()
//私有成员包外不可见
//p1.f2()
p1.F3()
//私有成员包外不可见
//p1.f4()
}

引入包的注意点

  • 包别名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package main
    //引用包别名
    import p2 "awesomeProject/p1"

    func main() {
    p2.F1()
    //私有成员包外不可见
    //p2.f2()
    p2.F3()
    //私有成员包外不可见
    //p2.f4()
    }
  • 省略包名直接使用

    通过 . 号 来省略包名

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

    //引用包
    import ."awesomeProject/p1"

    func main() {
    F1()
    //私有成员包外不可见
    //f2()
    F3()
    //私有成员包外不可见
    //f4()
    }
  • 不使用包但是使用包里的初始化方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package p1
    import "fmt"
    var I = 10

    //包的初始化
    //在包被引入的时候执行
    func init() {
    fmt.Printf("初始化包,%d\n", I)
    }

    func F1() {
    fmt.Println("F1")
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package main
    //引用包
    import (
    //通过 _ 引入的包表示只执行包的初始化方法
    _ "awesomeProject/p1"
    )

    func main() {
    //不能使用包内成员
    //p1.F1()
    }

值类型和引用类型

值类型包括基本数据类型,int,float,bool,string,以及数组和结构体(struct)。值类型变量声明后,不管是否已经赋值,编译器为其分配内存,此时该值存储于上.
当使用等号=将一个变量的值赋给另一个变量时,如 j = i ,实际上是在内存中将 i 的值进行了拷贝,可以通过 &i 获取变量 i 的内存地址。此时如果修改某个变量的值,不会影响另一个

引用类型包括指针,slice切片,map,chan,interface,func。变量直接存放的就是一个内存地址值,这个地址值指向的空间存的才是值。所以修改其中一个,另外一个也会修改(同一个内存地址)。引用类型必须申请内存才可以使用,make()是给引用类型申请内存空间

数组是值类型 ,切片是引用类型

数组是一组同类型数据的集合,它是值类型,通过从0开始的下标索引访问元素值。在初始化后长度是固定的,无法修改其长度。当作为方法的参数传入时将复制一份数组而不是引用同一指针。数组的长度也是其类型的一部分,通过内置函数len(array)获取其长度

1
2
3
4
5
6
7
8
9
10
11
//数组 值类型
{
var i = [3]int{1, 2, 3}
//[...] 表示其长度有元素个数决定 var i = [...]int{1, 2, 3}
var j = i
j[0] = 10
fmt.Printf("i type %T, value %v ,addr %p\n", i, i, &i)
//i type [3]int, value [1 2 3] ,addr 0xc0000ae078
fmt.Printf("j type %T, value %v ,addr %p\n", j, j, &j)
//j type [3]int, value [10 2 3] ,addr 0xc0000ae090
}

与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。切片中有两个概念:一是len长度,二是cap容量,长度是指已经被赋过值的最大下标+1,可通过内置函数len()获得。容量是指切片目前可容纳的最多元素个数,可通过内置函数cap()获得。切片是引用类型,因此在当传递切片时将引用同一指针,修改值将会影响其他的对象。
切片可以通过数组来初始化,也可以通过内置函数make()初始化.初始化时len=cap,在追加元素时如果容量cap不足时将按len的2倍扩容

1
2
3
4
5
6
7
8
9
10
//切片 引用类型
{
var i = []int{1, 2, 3}
var j = i
j[0] = 10
fmt.Printf("i type %T, value %v ,addr %p\n", i, i, i)
//i type []int, value [10 2 3] ,addr 0xc00000c168
fmt.Printf("j type %T, value %v ,addr %p\n", j, j, j)
//j type []int, value [10 2 3] ,addr 0xc00000c168
}

切片切出来的部分和原来的切片是共享一个堆内存对象

1
2
3
4
5
6
7
8
9
10
{
var i = []int{1, 2, 3}
//切片切出来的部分和原来的切片是共享一个堆内存对象
var j = i[0:2]
j[0] = 100
fmt.Printf("i type %T, value %v ,addr %p\n", i, i, i)
//i type []int, value [100 2 3] ,addr 0xc00000c168
fmt.Printf("j type %T, value %v ,addr %p\n", j, j, j)
//j type []int, value [100 2] ,addr 0xc00000c168
}

空切片

1
2
3
4
{
var i = []int{1, 2, 3}
var j = i[0:0]
}

完整的切片和原来切片一样

1
2
3
4
{
var i = []int{1, 2, 3}
var l = i[:]
}

用 copy 在原有切片的基础上生成一个全新的切片

1
2
3
4
5
6
7
8
9
{
var i = []int{1, 2, 3}
var l = make([]int, 10)
copy(l, i)
fmt.Printf("i type %T, value %v ,addr %p\n", i, i, i)
//i type []int, value [1 2 3] ,addr 0xc00000c168
fmt.Printf("i type %T, value %v ,addr %p\n", l, l, l)
//i type []int, value [1 2 3 0 0 0 0 0 0 0] ,addr 0xc000014370
}

结构体

结构体定义

结构体是值类型,类似与Java 的 Class ,但是 Go 的结构体内不能定义方法实现,静态成员
结构体没有继承的概念,使用组合代替继承

定义父类

1
2
3
type Animal struct {
name string
}

父类构造函数

1
2
3
func New(name string) *Animal {
return &Animal{name: name}
}

父类的实例方法

1
2
3
4
5
6
7
8
9
//(this *Animal) 通过指针确保方法内的 this 和调用方法的变量指向的是同一个实例
func (this *Animal) SetName(name string) {
this.name = name
}

//(this Animal) 读取取 copy 的值对象
func (this Animal) GetName() string {
return this.name
}

子类定义 通过嵌套继承父类

1
2
3
4
5
6
//子类
type Bird struct {
flyDistance int
//匿名父类 等价于Animal Animal
Animal
}

结构体初始化及使用

初始化方法1

1
2
3
4
var i Animal = Animal{name: ""}
i.SetName("name1")
fmt.Printf("i type %T, value %v ,addr %p\n", i, i, &i)
//i type main.Animal, value {name1} ,addr 0xc00004e250

初始化方法2
对结构体进行&取地址操作时,视为对该类型进行一次 new 的实例化操作

1
2
3
4
var l *Animal = &Animal{name: ""}
i.SetName("name2")
fmt.Printf("l type %T, value %v ,addr %p\n", l, l, l)
//l type *main.Animal, value &{name2} ,addr 0xc00004e280

初始化方法3

1
2
3
4
5
//创建指针类型的结构体
var m *Animal = new(Animal)
m.SetName("name3")
fmt.Printf("m type %T, value %v ,addr %p\n", m, m, m)
//m type *main.Animal, value &{name3} ,addr 0xc00004e2a0

子类初始化

1
2
3
4
5
6
var iSon = Bird{maxFlyDistance: 1000, Animal: Animal{name: ""}}
iSon.SetName("bird1")
iSon.name = "bird_1"
iSon.maxFlyDistance = 1000
fmt.Printf("iSon type %T, value %v ,addr %p\n", iSon, iSon, &iSon)
//iSon type main.Bird, value {1000 {bird_1}} ,addr 0xc000004078

接口

接口是引用类型,接口可以通过组合的形式形成继承接口

接口定义

接口内定义方法的声明,接口可以通过组合的形式达到继承接口的效果

1
2
3
4
5
6
7
8
9
type Base interface {
eat()
}

type Animal interface {
walk()
Base
}

接口的实现

接口有多个实现的实例,多态的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type Dog struct {
name string
}
type Pig struct {
name string
}

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

func (this Pig) eat() {
fmt.Printf("pig name %s eat\n", this.name)
}

func (this Dog) walk() {
fmt.Printf("dog name %s walk\n", this.name)
}

func (this Pig) walk() {
fmt.Printf("pig name %s walk\n", this.name)
}

接口的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func bar(i Animal) {
i.walk()
i.eat()
}

func main() {
var i Animal = Dog{name: "dog1"}
var j Animal = Pig{name: "pig1"}
bar(i)
//dog name dog1 walk
//dog name dog1 eat
bar(j)
//pig name pig1 walk
//pig name pig1 eat
}

反射

反射可以反射基础类型和和明确类型

反射是指在程序运行期对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。

支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。

Go程序在运行期使用reflect包访问程序的反射信息。

反射是由 reflect 包提供的。它定义了两个重要的类型,Type 和 Value。一个 Type 表示一个Go类型。它是一个接口

反射字段,方法,tag 及基础类型

需要反射的结构体
结构体可以加标签 相当于是 Java 注解

1
2
3
4
5
6
7
8
9
type str struct {
id int `dis:"id" value:"1"`
name string `dis:"name" value:"name1"`
}

func (this str) F(x string) string {
return x
}

反射字段

1
2
3
4
5
6
7
func GetFields(ty reflect.Type, val reflect.Value) {
for i := 0; i < ty.NumField(); i++ {
var field reflect.StructField = ty.Field(i)
var value = val.Field(i)
fmt.Printf("field name %s, type %v , value %v\n", field.Name, field.Type, value)
}
}

反射公有方法

1
2
3
4
5
6
7
func GetMethods(ty reflect.Type, val reflect.Value) {
for i := 0; i < ty.NumMethod(); i++ {
var field reflect.Method = ty.Method(i)
var value = val.Method(i)
fmt.Printf("field name %s, type %v , value %v\n", field.Name, field.Type, value)
}
}

反射 tag

1
2
3
4
5
6
7
func GetTags(ty reflect.Type) {
for i := 0; i < ty.NumField(); i++ {
var value1 = ty.Field(i).Tag.Get("dis")
var value2 = ty.Field(i).Tag.Get("value")
fmt.Printf("field name %s, tag1 %s , tag2 %s\n", ty.Field(i).Name, value1, value2)
}
}
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
func main() {
//基础类型的反射
var i = 10
var ty1 reflect.Type = reflect.TypeOf(i)
var val1 reflect.Value = reflect.ValueOf(i)
fmt.Printf("type name %s, value %v\n", ty1.Name(), val1)
//type name int, value 10

//结构体反射
var j = str{id: 10, name: "bar"}
var ty2 = reflect.TypeOf(j)
var val2 = reflect.ValueOf(j)
fmt.Printf("type name %s, value %v\n", ty2.Name(), val2)
//type name str, value {10 bar}

GetFields(ty2, val2)
//field name id, type int , value 10
//field name name, type string , value bar


GetMethods(ty2, val2)
//method name F, type func(main.str, string) string , value 0xde99e0


GetTags(ty2)
//tag name id, tag1 id , tag2 1
//tag name name, tag1 name , tag2 name1
}

GOPATH 工作方式

在 GOPATH 指定的工作目录下,代码总是会保存在 $GOPATH/src 目录下。在工程经过 go build、go install 或 go get 等指令后,会将产生的二进制可执行文件放在 $GOPATH/bin 目录下,生成的中间缓存文件会被保存在 $GOPATH/pkg 下。

GOPATH 有两种

  1. 全局的GOPATH,来源于系统环境变量中的 GOPATH
  2. 项目的GOPATH

指定了GOPATH 也就指定了包的搜索路径 ,项目的GOPATH 随项目设定 GOPATH 定义了绝对的文件搜索路径

GOPATH 项目演示

  1. 指定工作目录 F:/GoWorkPath

  2. 工作目录下建立src 目录

  3. 在 src 下建立目录 libs1,libs2,main 目录

    libs1 目录 .go 代码

    1
    2
    3
    4
    5
    6
    7
    package libs1

    import "fmt"

    func F() {
    fmt.Println("libs1 func F Z 执行")
    }

    libs2 目录 .go 代码

    1
    2
    3
    4
    5
    6
    7
    package libs2

    import "fmt"

    func F() {
    fmt.Println("libs2 func F Z 执行")
    }

    main 目录 .go 代码
    import 根据指定的 项目 GOPATH 可以直接导入 src 下的 包,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package main

    import (
    //不需要指定路径
    "libs1"
    //如果没有指定项目 GOPATH ,需要根据当前的 .go 文件的目录指定相对的.go 包文件路径
    //"../libs1"
    "libs2"
    "time"
    )

    func main() {
    libs1.F()
    libs2.F()
    time.Sleep(5 * time.Second)
    }

GO MOD 工作方式

Modules是相关Go包的集合,是源代码交换和版本控制的单元。go命令直接支持使用Modules,包括记录和解析对其他模块的依赖性。Modules替换旧的基于GOPATH的方法,来指定使用哪些源文件。

Modules和传统的GOPATH不同,不需要包含例如src,bin这样的子目录,一个源代码目录甚至是空目录都可以作为Modules,只要其中包含有go.mod文件.

父目录里有.mod 文件,父目录里的文件及父目录的子目录里的文件都受mod管理,
目前的推荐在 MOD 模式下工作.

GO MOD 项目演示

  1. GO 版本1.11以上

  2. 设置GO111MODULE
    GO111MODULE=off,go命令行将不会支持module功能,寻找依赖包的方式将会沿用旧版本那种 通过vendor目录或者GOPATH模式来查找。

    GO111MODULE=on,go命令行会使用modules,而一点也不会去GOPATH目录下查找。

    GO111MODULE=auto,默认值,go命令行将会根据当前目录来决定是否启用module功能。这种情况下可以分为两种情形:

    • 当前目录在GOPATH/src之外且该目录包含go.mod文件
    • 当前文件在包含go.mod文件的目录下面。
  3. 在GOPATH/src 目录之外新建工程目录

    • 在此目录下新建俩个目录 project1,project2 分别在这俩个目录内

    • 执行 go mod int project1, go mod int project2在俩个目录生成 go.mod 文件
      这是一个关键文件,之后的包的管理都是通过这个文件管理

    • 在目录project2内新建目录libs 目录,在此目录新建f.go 文件

      1
      2
      3
      4
      5
      6
      7
      package libs

      import "fmt"

      func F() {
      fmt.Println("project2 libs F 执行")
      }
    • 在目录project1内修改 go.mod 文件

      project1 这个模块需要引用本地的其他自定义模块 project2 需要指定相对当前go.mode 文件的路径
      replace project2 => ../project2
      require project2 v0.0.0

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      module project1

      replace project2 => ../project2
      require project2 v0.0.0

      //引入第三模块的依赖
      require (
      github.com/sirupsen/logrus v1.8.1 // indirect
      golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect
      )

      go 1.18

    • 在在目录project1 内新建目录libs 在此目录新建 f.go 文件

      1
      2
      3
      4
      5
      6
      7
      package libs

      import "fmt"

      func F() {
      fmt.Println("project1 libs F 执行")
      }
    • 在目录project1 内新建目录mainX 在此目录新建 main.go 文件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      package mainX

      import (
      "fmt"
      log "github.com/sirupsen/lours"
      p1 "project1/libs"
      p2 "project2/libs"
      "time"
      )

      func main() {
      fmt.Println("main")
      p1.F()
      p2.F()
      log.Info("我是一条日志")
      time.Sleep(5 * time.Second)
      }
    • 在 main.go文件所在目录执行 go mod tidy 会清理无用的模块引用
      go build 下载引用的模块及执行编译生成 mainX.exe 文件
      执行文件 ./mainX.exe 结果

      mainX
      project1 libs F 执行
      project2 libs F 执行
      time=”2022-04-20T11:33:05+08:00” level=info msg=”我是一条日志”

本次笔记记录时间 2022-04-20,疫情期间,下次开始学习框架

mvc 数据库
mvc 数据库 网络通讯