Go语言基础(二)|青训营笔记

134 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记;主要学习了GO语言的init函数和包管理等机制,以及数据结构相关的内容。

init 函数 和 import 导包

一般情况下以main包存在的go文件作为主入口,以递归的方式 import 所需的包, 每个包执行结束后自动调用init() 函数(如果没有就调用默认的空init函数)

1.JPG

go 函数名的起名规则:

  • 只有函数名首字母大写的函数,该函数才对外开放,否则只能包内使用

一般的导包方式

自定义包的小demo 注意go mod的问题

/* lib1 */  
package lib1

import "fmt"

func init() {
	fmt.Println("lib1 is working")
}

// lib1包提供的api
func Lib1Test() {
	fmt.Println("api of the lib1")
}

/* lib2 */  
package lib2

import "fmt"

func init() {
	fmt.Println("lib2 is working")
}

// lib2包提供的api
func Lib2Test() {
	fmt.Println("api of the lib2")
}

/* main */  
package main

import (
	"fmt"
	"lib1" // 自定义的包
	"lib2"
)

func main() {
	lib1.Lib1Test()
	lib2.Lib2Test()
}

import 匿名及别名导包

一个场景下,如果不想使用一个包的接口,但是想要调用该包的init函数,那么需要用到匿名import

下划线表示lib1包的别名,_ 表示这个匿名导入,之后就算不使用该包的任何接口函数,也不会报错

import (
	"fmt"
	_ "lib1" 
	mylib2 "lib2" 
    . "lib3" // 代表包名可省略,直接调用包中的函数(慎用)
)

指针

类似C/C++的指针,

package main

import (
	"fmt"
)

func set(p *int) {
	*p = 2
}

func main() {
	var a int = 1
	set(&a)
	fmt.Println("a = ",a)
}

关键字 defer

机制类似于钩子函数,exit()结束之前调用所有的钩子函数

package main

import (
	"fmt"
)

func main() {
	// defer 关键字是指 经过该关键字修饰的语句直到函数结束前的最后一刻才执行
	defer fmt.Println("end") 
	fmt.Println("hello")
	fmt.Println("start")
}

tips1: defer 可以存在多个,以压栈的方式存放,所以最后调用时,以LIFO(后入先出)的方式调用

package main

import (
	"fmt"
)

func main() {
	// defer 关键字是指 经过该关键字修饰的语句直到函数结束前的最后一刻才执行
	defer fmt.Println("a") 
	defer fmt.Println("b")
	defer fmt.Println("c")
}

tips2: defer 是在当前函数生命周期结束之后才会出栈,被调用,所以defer的调用是在return之后的

package main

import (
	"fmt"
)

func defFunc() int {
	fmt.Println("defer")
	return 0
}

func returnFunc() int {
	fmt.Println("return")
	return 0
}

func return_defer() int {
	defer defFunc()
	return returnFunc()
}

func main() {
	return_defer()
}

slice

定长数组

Go 语言提供了定长数组,类似于c的数组,但是定长数组不管是在扩展方面,还是传参方面都没有优势,所以尽量少用,多用动态数组。

定长数组的声明与使用方式如下:

// 函数传参是值拷贝

package main

import (
	"fmt"
)

// 定长数组只能传与之相匹配的形参(和c有区别)
func put_arr(arr [10]int) {
	fmt.Println("func put_arr: ",arr)
  arr[0] = 100
}

func main() {
	// 固定长度的数组
	var arr[10] int 
	for i := 0; i < len(arr); i++ {
		fmt.Print(arr[i])
	}
	fmt.Println()

	// 带初始化的定长数组声明
	arr2 := [10]int {1,2,3,4}
	for i := 0; i < len(arr2); i++ {
		fmt.Print(arr2[i])
	}
	fmt.Println()

	// for 的遍历
	fmt.Println("range 遍历:")
	for index,val := range arr2 {
		fmt.Println(index,val)
	}
	
	// 查看数组的数据类型
	fmt.Printf("arr types = %T\n,arr2 types = %T\n",arr,arr2);
	put_arr(arr)
}

动态数组/slice

这是Go中使用的最常见的数据结构,类似于python的list(但是只能存储单类型),声明一个slice本质上是声明了一个指针,指向了分配的内存,还有类似list的切片操作。

// 基本使用 
package main
import (
	"fmt"
)

func put_arr(arr []int) {
	// 下划线表示匿名
	for _, val := range arr {
		fmt.Println("val = ", val)
	}
	arr[0] = 99 // slice 是真的去数组存放位置修改
}

func main() {
	// 动态数组/slice 的声明
	// slice 是引用传递,实际上传的是指针
	arr := []int {1,2,3,4}
	fmt.Printf("arr types = %T\n",arr)
	put_arr(arr)
	
	
	for i := 0; i < len(arr); i++ {
		fmt.Print(arr[i]," ")
	}
	fmt.Println()

	
}

slice 的声明方法

总共有四种:

  • 带初始化的声明
  • 声明一个空的slice,是一个切片类型,但并没有分配空间
  • 利用make合并方法二的两种操作
  • 通过 := 推导 , 这也是我们最常使用方式

动态的slice逃不掉的一环是当容量不够时如何扩增,Go默认的方式是以两倍的量去扩展,在项目开发中,如果提前知晓slice实际的使用量,去合理声明初始分配空间,可以有效提高代码性能。

package main
import (
	"fmt"
)

func main() {
	// 方法一: 带初始化的声明
	slice1 := []int {1,2,3}
		// %v 表示打印所有的详细信息
	fmt.Printf("len = %d, slice = %v\n", len(slice1), slice1)

	// 方法二,声明一个空的slice,是一个切片类型,但并没有分配空间
	var slice2 []int
	fmt.Println(len(slice2))
	slice2 = make([]int, 3) // 开辟三个空间大小
	fmt.Println(len(slice2))

	// 方法三,合并方法二的两种操作
	var slice3 []int = make([]int, 4)
	fmt.Println(len(slice3))

	// 方法四: 通过 := 推导 , 最常见方式
	slice4 := make([]int, 5)
	fmt.Println(len(slice4))

	// 判断slice 是否有空间
	// else 必须和if的括号写一行
	var slice []int
	if slice == nil {
		fmt.Println("slice 是空切片")
	} else {
		fmt.Println("slice 不是空切片")
	}
}

slice切片追加与截取

slice 有类似python 切片的操作,操作方式也和python类似,极大的方便了日常使用。 值得注意的是 ,通过切片的来的新slice并不会重新分配新空间,而是在底层数组中用指针指向,即原slice和新slice公用一块空间,就是所谓的浅拷贝,如果想要实现深拷贝,需要使用到copy函数。

package main
import (
	"fmt"
)

func main() {
	// 切片的长度和容量
	var slice = make([]int, 3, 5)
	fmt.Printf("len = %d, capcictiy = %d\n",len(slice), cap(slice))
	fmt.Println(slice)

	// append 增加元素
	// capcictiy 成倍增长
	slice = append(slice, 1,2,2) // 后面的参数类似cpp 的 ... 接收任意数量
	fmt.Println("len = ", len(slice), "capcictiy = ", cap(slice), slice)

	// 切片截取,类似python的切片,左闭右开 
	fmt.Println("slice[1:4] = ", slice[1:4])

	// 截取全部
	fmt.Println("slice[:] = ", slice[:])

	
	// 截取前段
	fmt.Println("slice[:4] = ",slice[:4])

	// 截取后段
	fmt.Println("slice [4:] = ",slice[4:])

	// 创建新切片(浅拷贝)
	// 但是 新切片 和 老切片指向的位置相同, 修改切片内容可能会导致两个切片都发生改变
	slice_tmp := slice[1:5]
	fmt.Println("new slice = ", slice_tmp)

	slice[1] = 100
	fmt.Println("slice = ", slice, "\nnew slice = ", slice_tmp)


	// 深拷贝版,利用copy(dest,src)函数
	slice2 := make([]int, 6)
	copy(slice2,slice)
	slice[2] = 99
	fmt.Println("slice = ", slice, "\nnew slice = ", slice_tmp, "\ncopy slice = ", slice2)
}

map

声明方式

map 的底层是由hashtable实现,所以效率非常之高,类似于STL中的unordered_map,存取效率为O(1). 常见的一些使用方式如下:

package main
import (
	"fmt"
)

func main() {
	// 声明一个map类型, []内是key,括号外的是value
	var myMap map[string]string
	// 刚声明的map没有capcictiy
	if myMap == nil {
		fmt.Println("myMap is nil")
	} else {
		fmt.Println("myMap is not nil")
	}
	// 使用map前需要申请内存空间(map 没有cap 哎!)
	myMap = make(map[string]string, 10)
	
	// 或者合起来写,容量可以默认不写
	myMap2 := make(map[string]string, 10)

	myMap2["one"] = "java"
	myMap2["two"] = "C++"
	myMap2["three"] = "Golang"

	fmt.Println(myMap2)
	for key,value := range myMap2 {
		fmt.Println("key = ", key, ",value = ", value)
	}
	
	// 第二种声明方式
	myMap3 := map[int]string {
		1 : "C",
		2 : "php",
		3 : "python",
	}
	fmt.Println(myMap3)

}

使用方式

tips: map 以指针方式传参,即他对应的变量名实际也只是个指针,指向了底层的hashtable

package main
import (
	"fmt"
)

func main() {

	myMap := map[int]string {
		1 : "C",
		2 : "php",
		3 : "python",
	}
	fmt.Println(myMap)


	// 遍历
	for key,value := range myMap {
		fmt.Println("key = ", key, ",value = ", value)
	}

	// 删除, 以key作为索引
	delete(myMap,2)
	fmt.Println("删除2后")
	for key,value := range myMap {
		fmt.Println("key = ", key, ",value = ", value)
	}

	// 修改
	myMap[1] = "Go"
	fmt.Println("修改1")
	for key,value := range myMap {
		fmt.Println("key = ", key, ",value = ", value)
	}

	// 添加
	myMap[5] = "java"
	fmt.Println("添加2")
	for key,value := range myMap {
		fmt.Println("key = ", key, ",value = ", value)
	}

}