一、程序基础
- 环境变量
go env 命令可以查看 go 的环境变量。
GOROOT 是 go 的安装路径
GOPATH 是 go 的工作目录:
GOPATH/src 用于存放项目源码;
GOPATH/bin 用于存放项目编译后的二进制文件;
GOPATH/pkg 用于存放项目下载的依赖包文件。
- 基本知识
(1)一个路径下的所有文件只能声明同一个包名
(2)文件名跟函数名没关系
(3)函数名需要大写开头,推荐大驼峰
- go命令
go get ["github.com/jinzhu/gorm/dialects/mysql"] // 下载包到GOPATH/pkg/mod
go build [main.go] // 编译,生产main.exe
go clean [main.go] // 清理编译生成的文件
go run [main.go] // 运行
go vet [main.go] // 检查错误
go fmt [main.go] // 格式化文件
二、知识点
1、声明时类型后置;
2、可以并列初始化、赋值;
3、switch 的 case 自带 break 属性;
func test09() {
var a int
for true {
fmt.Scan(&a)
switch a {
case 1:
fmt.Println("the value is ", "111")
case 2:
fmt.Println("the value is ", "222")
case 3, 4, 5:
fmt.Println("the value is ", "333")
default:
fmt.Println("default")
}
}
}
4、闭包/匿名,多次调用,可以使局部变量保持静态属性;
匿名函数:
(1)如果某个函数只是用一次,就可以考虑使用匿名函数;
(2)将匿名函数赋值给一个变量,然后通过变量调用;匿名函数作为参数传递,可以使程序解耦合;
闭包:
(1)由 一个匿名函数 与其 引用的外部变量 组成的整体,其中引用的外部变量将随着闭包的调用变化。引用的外部变量 相当于 C的局部静态变量,只初始化一次。或者可以将闭包立即为一个类,其中引用的外部变量相当于类的成员变量,匿名函数相当于类的方法;
(2)与普通函数相比,闭包可以保留开始引用的值,传入一次反复使用,避免反复传递相同的参数值;(可以用类替代)
package main
import (
"fmt"
"strings"
)
type suffixMake struct {
suffix string
}
func (sm *suffixMake) make(name string) string {
if !strings.Contains(name, sm.suffix) {
return name + sm.suffix
}
return name
}
func main() {
//累加器
f := addNumber1()
fmt.Println(f(1)) //11
fmt.Println(f(1)) //12
//补后缀-闭包
f3 := makeSuffix(".jpg")
fmt.Println(f3("cat")) //11
fmt.Println(f3("dog.jpg")) //12
//补后缀-用类代替
f4 := suffixMake{".jpg"}
fmt.Println(f4.make("cat")) //11
fmt.Println(f4.make("dog.jpg")) //12
}
//闭包-累加器
func addNumber1() func(i int) int {
var n int = 10
return func(i int) int {
n += i
return n
}
}
//闭包-补后缀
func makeSuffix(suffix string) func(string) string {
return func(name string) string {
if !strings.Contains(name, suffix) {
return name + suffix
}
return name
}
}
5、iota 属性
func test05() {
const (
a = iota
b = iota
c = iota
d = 10
e = iota
f = iota + 10
)
const g = 10
const (
h = iota
)
fmt.Println(a, b, c, d, e, f)
fmt.Println(g, h)
}
0 1 2 10 4 15
10 0
6、局部变量可以简单初始化,全局变量不可以这样初始化
a := 2
7、不需要分号结尾
8、类方法的读/写
9、局部变量会被初始化
10、引用传递 与 值传递
(1)传递数组 不是 引用传递,传递数组指针是引用
func main() {
var arr1 = [2]int{1}
arrfun(arr1)
fmt.Println(arr1)
}
func arrfun (arr1 [2]int) {
arr1[0] = 10
arr1[1] = 20
}
[1 0]
func main() {
var arr1 = [2]int{1}
arrfun(&arr1)
fmt.Println(arr1)
}
func arrfun (arr1 *[2]int) {
arr1[0] = 10
arr1[1] = 20
}
[10 20]
(2)引用类型和值类型
引用类型:指针、slice,map、channel、interface
值类型:bool,int簇,float簇,array,string,struct
(3)不管是值传递还是引用传递,传递的都是变量的副本,不同的是,值传递是值的拷贝,引用传递是地址的拷贝。一般来说,传地址效率高,因为数据量小。
(4)内存分配
值类型:变量直接存储值,内存通常在栈中分配;
引用类型:变量存储一个地址,该地址的内存空间才存储真正地数据,内存通常在堆上分配;当没有任何变量引用这个地址时,该地址对应的数据空间就变成一个垃圾,由GC来回收。
11、结构体指针访问成员也是用【.】号,而不是【->】。
12、切片,即动态数组,定义时可不指定大小,也可make大小。通过追加方式添加,可通过主动拷贝和扩容。
(1)reslice
s2 := s1[1:2]
(2)向后扩展slice
reslice 和 append 的时候,只要cap不超过底层数组的大小,新slice就会继承cap的大小,一旦触发扩容,cap为扩容后的新数组的大小。
s[i] 不能超过len的大小
s[i:j] 可以向后超过len的范围,但不能超过cap的范围
13、打印输出
import "fmt"
fmt.Print("中国的首都是\n", capital) //输出
fmt.Printf("中国的首都是", %s\n", capital) //格式化输出
fmt.Println("中国的首都是", capital) //换行输出,自动判断类型
14、不支持隐式类型转换,需要强制类型转换
var a int = 10
var b float32
b = float(a)
15、map,为无序 map
16、全局变量不需要前置声明,函数也不需要前置声明
17、只有后置++/--,并且只能单独使用,不能参与其他运算
func test06() {
var a int
a++
fmt.Println(a)
a--
fmt.Println(a)
}
18、条件语句中,必须使用 bool 表达式;数值不能被认为是 true 或者 false,数值要使用逻辑表达式,产出 bool 结果;
func test07() {
a, b := 1, 2
if a == 1 && b == 2 {
fmt.Println(a, b)
}
if a > 0 {
fmt.Println(a)
}
if a != 0 {
fmt.Println(a)
}
}
19、输入输出
func test08() {
var (
a int
b int
)
fmt.Scan(&a, &b) //输入
fmt.Println(a, b) //输出
}
20、外部包要使用内部函数,需要将包内函数的首字母大写,否则会报错
Unexported function 'get' usage
21、通道 chan 是有类型的
var c chan int
22、通道阻塞
(1)通道的 生产者一直不能放入数据 或 消费者一直不能消费数据,都会使程序死锁 !
(2)通道是用于协程之间通信的,其 生产者 和 消费者 需要位于不同的协程中。
(3)通道可以设置大小(带缓冲区),带缓冲区可以解耦生产者,生产由同步变为异步,但 通道满 依然会造成 生产者 等待甚至死锁,通道空 依然会造成 消费者 等待甚至死锁。
all goroutines are asleep - deadlock!
(4)通道是阻塞型的,因此下面的用法和不加ok一样,因为要么可以从通道获取值ok=true,要么通道会阻塞
x, ok := <-c
23、通道可以使主程序等待。
24、初始化
(1)切片初始化
不指定容量的切片,后续append即可(超过容量自动翻倍扩容),相当于容器。
s:=[]int{} //相当于 make([]int, 0, 0)
指定大小和容量的切片,不超过大小使用下标即可,相当于数组,超过大小使用append(超过容量自动翻倍扩容)。
s := make([]int, 1, 2)
(2)其他
# 数组初始化
var arr [2]int = [2]int{}
var arr [2]int = [2]int{1}
var arr [2]int = [2]int{1,2}
var arr = [2]int{1,2}
arr := [2]int{1,2}
# 切片初始化,切片应该
var arr []int = []int{} // len=cap=0,空切片,不能用
var arr []int = []int{1,2} // len=cap=2
var arr []int = make([]int, 0, 4) // len=0(从0开始填内容), cap=4
var arr = make([]int, 0, 4) // len=0, cap=4
arr := make([]int, 0, 4) // len=2, cap=4
arr := []int{}
arr = append(arr, 1,2,3,4)
# map 初始化
m2 := map[string]string{"k":"l"}
fmt.Println(m2)
m3 := make(map[string]string)
m3["kk"] = "ll"
fmt.Println(m3)
25、不支持重载
26、面向接口编程,interface 接口类 相当于 指针类型
接口类 作为 形参,把它看做是 指针,实参 传递 实现类 的地址。
type human interface {
}
type man struct {
}
func test1 (human *human) { //错误
}
func test2 (human human) { //正确
}
func main() {
man1 := &man{}
test1(man1) //错误
man2 := man{}
test2(man2) //错误
man3 := &man{}
test2(man3) //正确
}
27、select 多路复用
(1)用于通信,每一个case代表一个通信操作,即在某个channel上进行发送或者接收
(2)类似 switch,执行满足条件的分支中的一个分支,且只执行一次
(3)有阻塞性质,如果没有一个分支满足,会阻塞等待,甚至死锁
(4)如果需要循环,外层需要加 for,并且需要注意避免死锁:
加 default 和 sleep,如果条件不满足,可能会不停执行 default,因此需要加延时
加 tick,每隔一段时间执行一次 tick 分支
(5)一个没有任何 case 的 select 语句写作 select{},表示永久阻塞
package main
import "fmt"
func main() {
var ch1 = make(chan int, 1)
var ch2 = make(chan int, 1)
ch1 <- 1
ch2 <- 2
select {
case x := <-ch1:
fmt.Println("ch1 x = ", x)
case x := <-ch2:
fmt.Println("ch2 x = ", x)
default:
fmt.Println("default")
}
}
package main
import "fmt"
func main() {
var ch1 = make(chan int, 1)
var ch2 = make(chan int, 1)
ch1 <- 1
ch2 <- 2
for {
select {
case x := <-ch1:
fmt.Println("ch1 x = ", x)
case x := <-ch2:
fmt.Println("ch2 x = ", x)
default:
fmt.Println("default")
time.Sleep(1*time.Second)
}
}
}
package main
import "fmt"
func main() {
var ch1 = make(chan int, 1)
var ch2 = make(chan int, 1)
ch1 <- 1
ch2 <- 2
tick := time.Tick(1*time.Second)
for {
select {
case x := <-ch1:
fmt.Println("ch1 x = ", x)
case x := <-ch2:
fmt.Println("ch2 x = ", x)
case <-tick
fmt.Println("tick")
}
}
}
28、init函数
(1)执行顺序
执行被引入包内的全局变量定义
执行被引入包内的init
执行main包内的全局变量定义
执行main包内的init
(2)一个包内可以有多个init函数
(3)主要场景,初始化变量,初始化链接
29、defer
(1)defer栈,先进后出
(2)在调用的地方进栈,进栈的同时,使用的变量也会被压入栈中。
如下,因此两次打印的结果 n 不一致,defer 压入的 n 值是10,并不是11
package main
import "fmt"
func main() {
var n int = 10
test(n)
}
func test(n int) {
defer fmt.Println("n1", n) // 10
n++
fmt.Println("n2", n) // 11
}
(3)defer 的使用主要是用来关闭一些打开的资源,如 文件句柄、数据库链接 等。
30、len
对于string,len计算的是字节数,因为string本质上是[]byte;
go是uft-8编码,字母和数字等(ascii)都是一个字节,中文为3个字节,因此
len("hello世界")=5+3*2=11
存在中文时,for range 是按字符个数遍历的,因此仍然可以正常遍历,但 for index 则不行,需要将 string 转化为 []rune类型,然后才能 for index 遍历;
rune类型占用4个字节;