Loading

Go基础笔记

变量四种声明

var a int	// var a int = 0
var b int = 10
var c = 100
d := 1000 // 只能用在函数体内来声明

注意:全局变量只能用前三种方式,第四种方式只能用在函数体内来声明。

const

const 标识只读属性,可用于定义枚举类型。

// 可以在const() 添加一个关键字 iota,每行iota会累加1,第一行默认值为0
const (
    BEIJING = iota	// iota= 0
    SHANGHAI		// iota = 1
    GUANGZHOU		// iota = 2
    SHENZHEN		// iota = 3
)

函数返回值

// 无返回值
func foo0(a string, b int) {
	fmt.Println("-----foo0------")
	fmt.Println("a = ", a)
	fmt.Println("b = ", b)
}
// 一个返回值
func foo1(a string, b int) int {
	fmt.Println("-----foo1------")
	fmt.Println("a = ", a)
	fmt.Println("b = ", b)
	c := 10
	return c
}
// 返回多个值,匿名
func foo2(a string, b int) (int, int) {
	fmt.Println("-----foo2------")
	fmt.Println("a = ", a)
	fmt.Println("b = ", b)
	c := 10
	return c, 100
}
// 返回多个值,有形参名称
func foo3(a string, b int) (r1 int, r2 int) {
	fmt.Println("-----foo3------")
	fmt.Println("a = ", a)
	fmt.Println("b = ", b)
	// r1 r2 属于形参,初始化值为0,作用域是foo3整个函数体 {} 空间
	fmt.Println("r1 = ", r1, ", r2 = ", r2)
	// 给有名返回值变量赋值
	r1 = 10
	r2 = 100
	return
}
// 返回值同类型
func foo4(a string, b int) (r1, r2 int) {
	fmt.Println("-----foo4------")
	fmt.Println("a = ", a)
	fmt.Println("b = ", b)
	// 给有名返回值变量赋值
	r1 = 10
	r2 = 100
	return
}

init函数与import导包

注意:默认情况,导入包但是不使用会报错。

import (
    _ "mylib/lib1" // 匿名,不再报错,会执行 init()
	"mylib/lib2"
)
  • import导包方式
    • import _ "fmt" 给 fmt 包请一个别名,匿名,无法使用当前包的方法,但是会执行当前包内部的 init() 方法
    • impot aa "fmt" 给当前包起一个别名,aa,aa.Println() 形式来调用包的方法
    • import . "fmt" 将 fmt 包中的全部方法导入当前本包的作用中,fmt 的全部方法可以直接API调用,不需要 fmt.API (应避免使用)

指针

package main

import "fmt"

func changeValue1(val int)  {
	val = 10
}

func changeValue2(pval *int)  {
	*pval = 10
}

func main() {
	a := 1
	changeValue1(a)
	fmt.Println("changeValue1 a = ", a) // changeValue1 a =  1

	changeValue2(&a)
	fmt.Println("changeValue2 a = ", a) // changeValue2 a =  10
}

defer

当前函数生命周期结束执行。return 以后执行

数组和切片

数组

  • 声明方式

    var myArray1 [10]int
    myArray2 := [10]int{1, 2, 3, 4}	// 长度为10
    myArray3 := [4]int{1, 2, 3, 4}
    
  • 数组是固定长度的

  • 固定长度的数组在传参的时候,是严格匹配数组类型的

    func printArray(myArray [4]int)
    
  • 数组作为函数参数采用值传递

切片(动态数组)

切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。

声明方式

// 1、声明切片,并初始化,长度为4
mySlice1 := []int{1, 2, 3, 4}
// 2、声明切片,并没有分配空间
var mySlice2 []int
mySlice2 = make([]int, 4) // 分配4个空间,初始化值为0
// 3、声明切片,分配空间,初始化为0
mySlice3 := make([]int, 4)

引用传递

动态数组在传参上是引用传递,而且不同元素长度的动态数组的形参是一致的

func printSlice(mySlice []int)

切片扩容

切片中添加元素,容量2倍增长

func main() {
    // 创建切片,len = 3, cap = 5
	var numbers = make([]int, 3, 5)
	fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)
	// 向切片追加一个元素
	numbers = append(numbers, 4)
	fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)

	// 向切片追加两个元素
	numbers = append(numbers, 5, 6)
	fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)
}

len = 3, cap = 5, slice = [0 0 0]
len = 4, cap = 5, slice = [0 0 0 4]
len = 6, cap = 10, slice = [0 0 0 4 5 6]

扩容源码$GOROOT/src/runtime/slice.go源码,其中扩容相关代码如下:

newcap := old.cap
doublecap := newcap + newcap
// 1、新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)
if cap > doublecap {
    newcap = cap
} else {
    // 2、旧切片的长度小于1024,则最终容量 (newcap) 就是旧容量 (old.cap) 的两倍
    if old.cap < 1024 {
        newcap = doublecap
    } else {
        // Check 0 < newcap to detect overflow
        // and prevent an infinite loop.
        // 3、最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,直到最终容量(newcap)大于等于新申请的容量
        for 0 < newcap && newcap < cap {
            newcap += newcap / 4
        }
        // Set newcap to the requested cap when
        // the newcap calculation overflowed.
        // 4、如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。
        if newcap <= 0 {
            newcap = cap
        }
    }
}
  1. 新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)
  2. 否则 (cap <= 2 * old.cap) :
    1. 旧切片的长度小于1024,则最终容量 (newcap) 就是旧容量 (old.cap) 的两倍
    2. 旧切片长度大于等于1024:
      1. 最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,直到最终容量(newcap)大于等于新申请的容量
      2. 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)

切片截取

左闭右开区间 [startIndex:endIndex]

mySlice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}
	fmt.Println("origin\t", mySlice)
	fmt.Println("[:3]\t", mySlice[:3])		// [0, 3)
	fmt.Println("[1:4]\t", mySlice[1:4])	// [1, 4)
	fmt.Println("[2:]\t", mySlice[2:])		// [2, len(mySlice))
	fmt.Println("[:]\t\t", mySlice[:])		// [0, len(mySlice))

注意截取的切片底层是一致的,看起来就像浅拷贝一样

切片复制

copy(destSlice, srcSlice []T)
func main() {
	// copy()复制切片
	a := []int{1, 2, 3, 4, 5}
	b := make([]int, 5, 5)
	copy(b, a)     //使用copy()函数将切片a中的元素复制到切片b
	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(b) //[1 2 3 4 5]
	b[0] = 1000
	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(b) //[1000 2 3 4 5]
}

切片元素删除

要从切片a中删除索引为index的元素,操作方法是a = append(a[:index], a[index+1:]...)

func main() {
	// 从切片中删除元素
	a := []int{30, 31, 32, 33, 34, 35, 36, 37}
	// 要删除索引为2的元素
	a = append(a[:2], a[3:]...)
	fmt.Println(a) //[30 31 33 34 35 36 37]
}

映射--map

map声明方式

  • 方式一:声明空map,使用前需要make分配数据空间

    //声明myMap1是一种map类型 key是string, value是string
    var myMap1 map[string]string
    if myMap1 == nil {
        fmt.Println("myMap1 是一个空map")
    }
    
    //在使用map前, 需要先用make给map分配数据空间
    myMap1 = make(map[string]string, 2)
    
    myMap1["one"] = "java"
    myMap1["two"] = "c++"
    myMap1["three"] = "python"
    
    fmt.Println(myMap1)
    
  • 方式二:声明并使用make

    myMap2 := make(map[int]string)
    
    myMap2[1] = "java"
    myMap2[2] = "c++"
    myMap2[3] = "python"
    
    fmt.Println(myMap2)
    
  • 方式三:不用make,直接创建并初始化

    myMap3 := map[string]string{
        "one":   "php",
        "two":   "c++",
        "three": "python",
    }
    fmt.Println(myMap3)
    

map使用

  • map 底层使用哈希,Go 采用的是数组 + 链地址法解决哈希冲突

  • 删除元素

    delete(myMap, key)
    
  • map做为形参,使用引用传递

OOP

struct

结构体作为参数,使用值传递

//声明一种行的数据类型 myint,是int的一个别名
type myint int

//定义一个结构体
type Book struct {
	title string
	auth  string
}

func changeBook(book Book) {
	//传递一个book的副本
	book.auth = "Jake"
}

func changeBook2(book *Book) {
	//指针传递
	book.auth = "Jake"
}

class

封装

  • 如果类名首字母大写,表示其他包也能够访问
  • 如果说类的属性首字母大写, 表示该属性是对外能够访问的,否则的话只能够类的内部访问
  • 方法首字母大写,其他包也能访问
//如果类名首字母大写,表示其他包也能够访问
type Hero struct {
	//如果说类的属性首字母大写, 表示该属性是对外能够访问的,否则的话只能够类的内部访问
	Name  string
	Ad    int
	level int
}

func (obj *Hero) GetName() string {
	return obj.Name
}

func (obj *Hero) SetName(newName string) {
	obj.Name = newName
}

继承

package main

import "fmt"

type Human struct {
	name string
	sex  string
}

func (obj *Human) Eat() {
	fmt.Println("Human.Eat()...")
}
func (obj *Human) Walk() {
	fmt.Println("Human.Walk()...")
}

//=================
type SuperMan struct {
	Human // SuperMan 类继承了 Human 类的方法
	level int
}

//重定义父类的方法Eat()
func (obj *SuperMan) Eat() {
	fmt.Println("SuperMan.Eat()...")
}
//子类的新方法
func (obj *SuperMan) Fly() {
	fmt.Println("SuperMan.Fly()...")
}

func main() {
	h := Human{"Jake", "male"}
	h.Eat()
	h.Walk()

	fmt.Println("----------------")
	//定义一个子类对象
	//s := SuperMan{Human{"Jake", "male"}, 666}
	var s SuperMan
	s.name = "Jake"
	s.sex = "male"
	s.level = 666

	s.Walk() //父类的方法
	s.Eat()  //子类的方法
	s.Fly()  //子类的方法

}

多态

通过接口实现,接口本质是一个指针

package main

import "fmt"

// 本质是一个指针
type AnimalIF interface {
	Sleep()
	GetColor() string // 获取动物的颜色
	GetType() string  // 获取动物的种类
}

// 具体的类
type Cat struct {
	color string // 猫的颜色
}
func (obj *Cat) Sleep() {
	fmt.Println("Cat is Sleep")
}
func (obj *Cat) GetColor() string {
	return obj.color
}
func (obj *Cat) GetType() string {
	return "Cat"
}

// 具体的类
type Dog struct {
	color string
}
func (obj *Dog) Sleep() {
	fmt.Println("Dog is Sleep")
}
func (obj *Dog) GetColor() string {
	return obj.color
}
func (obj *Dog) GetType() string {
	return "Dog"
}

func showAnimal(animal AnimalIF) {
	animal.Sleep() // 多态
	fmt.Printf("color = %s, kind = %s\n", animal.GetColor(), animal.GetType())
}

func main() {

	var animal AnimalIF // 接口的数据类型, 父类指针
	animal = &Cat{"Green"}
	animal.Sleep() // 调用的就是Cat的Sleep()方法, 多态的现象

	animal = &Dog{"Yellow"}
	animal.Sleep() // 调用Dog的Sleep方法,多态的现象

	cat := Cat{"Green"}
	dog := Dog{"Yellow"}

	showAnimal(&cat)
	showAnimal(&dog)
}

空接口 interface{} 是万能数据类型。interface{} 改如何区分此时引用的底层数据类型到底是什么?“类型断言” 的机制

package main

import "fmt"

// interface{} 是万能数据类型
func myFunc(arg interface{}) {
	// interface{} 改如何区分 此时引用的底层数据类型到底是什么?
	// 给 interface{} 提供 “类型断言” 的机制
	value, ok := arg.(string) // 判断是否是 string 类型
	if !ok {
		fmt.Printf("arg is not string type, type is %T\n", arg)
	} else {
		fmt.Println("arg is string type, value = ", value)
	}
}

func main() {
	myFunc(3.14)
	myFunc("abc")
}

反射

变量结构

var a string
// pair<statictype:string, value:"abcd">
a = "abcd"
// pair<type: ,value:>
var allType interface{}
//pair<type:string, value:a地址>
allType = a

reflect包

reflect包文档:http://docscn.studygolang.com/pkg/reflect/

func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
func (v Value) NumField() int
func (v Value) Field(i int) Value
func DoFiledAndMethod(input interface{}) {
	//获取input的type
	inputType := reflect.TypeOf(input)
	fmt.Println("inputType is :", inputType.Name())

	//获取input的value
	inputValue := reflect.ValueOf(input)
	fmt.Println("inputValue is:", inputValue)

	//通过type 获取里面的字段
	//1. 获取interface的reflect.Type,通过Type得到NumField ,进行遍历
	//2. 得到每个field,数据类型
	//3. 通过filed有一个Interface()方法等到 对应的value
	for i := 0; i < inputType.NumField(); i++ {
		field := inputType.Field(i)
		value := inputValue.Field(i).Interface()

		fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
	}

	//通过type 获取里面的方法,调用
	for i := 0; i < inputType.NumMethod(); i++ {
		m := inputType.Method(i)
		fmt.Printf("%s: %v\n", m.Name, m.Type)
	}
}

结构体标签

type resume struct {
	Name string `info:"name" doc:"名字"`
	Sex  string `info:"sex"`
}

func findTag(str interface{}) {
	t := reflect.TypeOf(str).Elem()

	for i := 0; i < t.NumField(); i++ {
		taginfo := t.Field(i).Tag.Get("info")
		tagdoc := t.Field(i).Tag.Get("doc")
		fmt.Println("info: ", taginfo, " doc: ", tagdoc)
	}
}

info: name doc: 名字
info: sex doc:

结构体标签应用:

  1. json 编码
  2. orm 映射关系

Goroutine

  • Goroutine 就是协程
  • go func() 形式创建 Goroutine
  • main 退出所有协程都终止
  • runtime.Goexit() 退出当前 Goroutine
  • 无法通过返回获取结果

channel

  • channel 定义

    make(chan Type) // 等价于 make(chan Type, 0)
    make(chan Type, capacity)
    
  • channel 使用

    channel <- value	// 发送value到channel
    <-channel			// 接收并丢弃数据
    x := <-channel		// 从channel中接收数据,斌赋值给x
    x, ok := <-channel	// 从channel中接收数据,斌赋值给x,同时检查通道是否已关闭或者是否为空
    
  • 无缓冲channel

    用于读写的两个 Goroutine 都会阻塞等待对端操作(写Goroutine写入数据并等待读端读取,读Goroutine等待写端写入数据)

  • 有缓冲channel

    • 当channel已满,再向里面写数据,会阻塞
    • 当channel为空,从里面取数据,会阻塞
  • 关闭channel

    close(channel)
    
    • 关闭channel后,无法向channel再发送数据(向关闭后的channel发数据,会引发 panic 错误,导致接收立即返回零值)
    • 关闭channel后,可以继续从channel接收数据
    • 对于 nil channel,无论收发都会被阻塞
  • channel与range

    for {
        //ok如果为true表示channel没有关闭,如果为false表示channel已经关闭
        if data, ok := <-c; ok {
            fmt.Println(data)
        } else {
            break
        }
    }
    // ========= 等价 ==============
    //可以使用range来迭代不断操作channel
    for data := range c {
        fmt.Println(data)
    }
    
  • channel与select

    单流程下,一个go同时只能监控一个channel的状态,select可以完成监控多个channel状态的任务

    select {
        case <- chan1:
        	// 如果chan1成功读到数据,则进行该处理流程
        case chan2 <- 1:
        	// 如果成功向chan2写入数据,则进行该处理流程
        default:
        	// 如果上面都没成功,则进行该处理流程
    }
    

Go modules

GOPATH弊端

  • 无版本控制概念
  • 无法同步一致第三方版本号
  • 无法指定当前项目引用的第三方版本号

go mod 命令

命令 功能
go mod init 生成 go.mod 文件
go mod download 下载 go.mod 文件中指明的所有依赖
go mod tidy 整理现有的依赖
go mod graph 查看现有的依赖结构
go mod edit 编辑 go.mod 文件
go mod vendor 导出项目所有的依赖到vendor目录
go mod verify 校验一个模块是否被篡改过
go mod why 查看为什么需要依赖某模块

go mod 环境变量

环境变量名 作用 补充
GO111MODULE 是否开启go modules模式 建议go V1.11之后,都设置为on
GOPROXY 1. 项目的第三方依赖库的下载源地址;
2. 建议设置国内的地址;
3. direct,用于指示 Go 回源到模块版本的源地址去抓取(比如 GitHub 等)
阿里云:https://mirrors.aliyun.com/goproxy/
七牛云:https://goproxy.cn,direct
GOSUMDB 用来校验拉取的第三方库是否是完整的;默认也是国外的网站,如果设置了GOPROXY,这个就不用设置了
GONOPROXY 通过设置GOPRIVATE即可
GONOSUMDB 通过设置GOPRIVATE即可
GOPRIVATE 表示是私有仓库,不会进行GOPROXY下载和校验 go evn -w GOPRIVATE="*.example.com";表示所有模块路路径为example.com的子域名都不会进行GOPROXY下载和校验

Go Modules初始化项目

  1. 开启Go Modules模块

    GO111MODULE=on

  2. 初始化项目

    1. 任意文件夹创建一个项目(不要求在$GOPATH/src)

    2. 创建 go.mod 文件,同时起当前项目的模块名称

      go mod init github.com/xxx/module_test

      go.mod

      module github.com/xxx/module_test
      
      go 1.16
      
    3. 在该项目中编写源代码,源代码依赖某个库(假定为 github.com/gin-gonic/gin )

    4. 获取到依赖库,依赖库会下载到 $GOPATH/pkg/ 下

      1. 手动 down

        go get github.com/gin-gonic/gin

      2. 自动 down

      go.mod

      module github.com/xxx/module_test
      
      go 1.16
      
      require github.com/gin-gonic/gin v1.7.1 // indirect
      

      go.sum,罗列当前项目直接或间接的依赖所有模块版本,保证今后项目依赖的版本不会被篡改。

      • h1:hash :表示整体项目的zip文件打开之后的全部文件的校验和来生成的hash。如果不存在,可能表示依赖的库可能用不上。
      • xxx/go.mod h1:hash :表示go.mod文件做的hash
    5. 修改项目模块的版本依赖关系

      go mod edit -replace=被替换的版本=替换版本

posted @ 2021-11-25 19:00  JakeLin  阅读(80)  评论(0编辑  收藏  举报