学习笔记 golang
ch1. 基础知识学习笔记
- 引入的包名要用双引号 如import "fmt"
- 输出的字符串要用双引号 如fmt.Println("hello worle, 你好中国", a, aa)
{
不能单独放在一行,与psr-2规范是不同的。- 不需要每行末尾加
;
,也不鼓励多个语句写在一行。 - 不能重复声明变量,赋新值是可以的;声明的变量必须被使用(全局变量除外)
var b = &a
此时b为内存地址,输出的话应使用指针指向该内存地址*b
- switch 从第一个判断表达式为 true 的 case 开始执行,每个case默认带有break,如果 case 带有 fallthrough,程序会继续执行下一条 case,且它不会去判断下一个 case 的表达式是否为 true。但是如果在fallthrough前break,则跳出switch
- 数组只能存储同一类型的数据,并且长度固定不能改变
var arr2 = [5]float32{1.0, 2.2, 3.4} // 数组个数必须小于等于[]中设置的长度
var arr3 = [...]int{1, 2, 3, 4} // 个数不确定可以用[...]代替,是一样的
- 结构体可以存不同类型的数据(map、数组都是一种类型)
// 定义结构体
/*
type struct_variable_type struct {
member definition;
member definition;
...
member definition;
}
*/
//声明结构体类型的变量
/*
variable_name := structure_variable_type {value1, value2...valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}
*/
// 实例演示
type Books struct {
title string
author string
price int
bookId int
}
func struceTest() {
struct1 := Books{"水浒", "施耐庵", 199, 3}
struct2 := Books{title: "西游记", author: "吴承恩", price: 199, bookId: 4}
fmt.Println(struct1.title, struct1)
fmt.Println(struct2.title, struct2)
var struct3 Books
struct3.title = "三国演义"
struct3.author = "罗贯中"
fmt.Println(struct3)
}
/* 输出
水浒 {水浒 施耐庵 199 3}
西游记 {西游记 吴承恩 199 4}
{三国演义 罗贯中 0 0}
*/
- 切片方式 https://blog.csdn.net/cyk2396/article/details/78893420
- range,类似php中的foreach
func foreach2() {
var arr1 = []string{"q", "w", "e", "r"}
var arr2 = []int{'q', 'w', 'e', 'r'}
fmt.Println(arr1, arr2)
for key, val := range arr1 {
fmt.Println(key, val)
}
}
func foreach3() {
var map1 = map[string]string{"a":"apple", "b":"banana", "c":"car"}
for key, val := range map1 {
fmt.Println(key, val)
}
}
/* 输出
[q w e r] [113 119 101 114]
0 q
1 w
2 e
3 r
a apple
b banana
c car
*/
- map定义
/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type
/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)
// 实例
map1 = map[string]int{"a":11, "b":22, "c":33}
fmt.Println(map1["a"]) // 11
map1["d"] = 444 // 动态赋值
fmt.Println(map1) // map[a:11 b:22 c:33 d:444]
value1, isset := map1["d"]
fmt.Println(value1,"|", isset) // 444 | true
// 删除元素
delete(map1, "d")
value1, isset = map1["d"]
fmt.Println(value1,"|", isset) // | false
- interface接口
package main
import "fmt"
type People interface {
name() string
age() int
}
type Man struct {
}
type Woman struct {
}
func (man Man) name() string {
return "亚当"
}
func (man Man) age() int {
return 22
}
func (woman Woman) name() string {
return "夏娃"
}
func (woman Woman) age() int {
return 18
}
func main() {
var people People
people = new(Woman)
fmt.Println(people.name(), people.age())
people = new(Man)
fmt.Println(people.name(), people.age())
}
/* 输出
夏娃 18
亚当 22
*/
- 错误处理
// 实现 error 接口类型
func Sqrt(f int) (int, error) {
if f < 0 {
return 0, errors.New("something wrong")
} else {
return f, nil
}
}
// 调用
func errTest() {
result, err := Sqrt(-44)
if err != nil {
fmt.Println(result, "是负数,err:", err)
} else {
fmt.Println(result, "正数没错,err:", err)
}
}
// 输出
// 0 是负数,err: something wrong
- go并发
func say(string1 string) {
for i := 0; i < 5; i {
time.Sleep(time.Second)
fmt.Println(string1)
}
}
// 不使用并发,阻塞模式
func noGoroutine() {
say("hello111")
say("world222")
// 输出:5个hello111之后5个world222 共用时10s
}
// 使用并发
func goroutine() {
go say("hello333")
say("world444")
// 同时输出,共用时5s,因为是两个不同的 goroutine
// 当主协程结束时,子协程也是会被终止掉。所以有时候最后如果只打开了这一个函数,丢包是正常现象
}
func main() {
//noGoroutine()
goroutine()
}
- 通道
func sum(s []int, ch chan int) {
var sum = 0
for _, value := range s {
sum = value
}
ch <- sum // 把 sum 发送到通道 ch
}
func main() {
channelTest1()
channelTest2()
}
// 通道
func channelTest1() {
var s1 = []int{1, 2, 3, 4, 5}
var s2 = []int{1, 2, 3, 4, 5, 6}
ch1 := make(chan int, 2)
//ch2 := make(chan int)
go sum(s1, ch1) // 如果不是goroutine模式,必须设置缓冲通道!!!
go sum(s2, ch1)
res1 := <- ch1 // 从通道 ch1 中接收
res2 := <- ch1 // 从通道 ch2 中接收
fmt.Println(res1, res2)
}
// 带有缓冲区的通道
func channelTest2() {
ch := make(chan int, 3)
ch <- 11
ch <- 22
ch <- 33
fmt.Println(<-ch, <-ch, <-ch)
// 输出 11 22 33
}
- go run | go build | go clean | go fmt | go get | go install
ch2. 基础概念理解
- 每一个可独立运行的Go程序,必定包含一个package main,在这个main包中必定包含一个入口函数main,而这个函数既没有参数,也没有返回值。
- 包名main则告诉我们它是一个可独立运行的包,它在编译后会产生可执行文件。除了main包之外,其它的包最后都会生成*.a文件(也就是包文件)并放置在GOPATH/pkg/GOOS_
vname1, vname2, vname3 := v1, v2, v3
,使用这种方式声明变量时,只能用在函数内部,外部使用无法编译通过。_, b := 34, 35
,下划线是特殊的变量名,任何赋值都会被丢弃。- 当定义了明确的数值类型时,不同类型是无法互相赋值操作的,如
var a int8 = 23; var b int32 = 19; c := a b;
go中字符称为rune,等价于C中的char,可直接与整数转换。rune实际是整型,必需先将其转换为string才能打印出来,否则打印出来的是一个ascii整数
代码语言:javascript复制 var s string = "abcd"
fmt.Println(s, s[0], s[1], s[2]) // abcd 97 98 99
fmt.Println(s, string(s[0]), string(s[1]), string(s[2])) // abcd a b c
s := "hello"
m := "world"
a := s m[2:] // 用 链接两个字符串,字符串可以切片
q := 'a'
w := 'b'
e := q w
r := string(q) string(w)
fmt.Println(a, q, w, e, string(e), r) // helloorld 97 98 195 Ã ab
大写字母开头的变量是可导出的,也就是其它包可以读取的,是公有变量;小写字母开头的就是不可导出的,是私有变量。
大写字母开头的函数也是一样,相当于class中的带public关键词的公有函数;小写字母开头的就是有private关键词的私有函数。
数组声明:由于长度也是数组类型的一部分,因此[3]int与[4]int是不同的类型,数组也就不能改变长度。数组之间的赋值是值的赋值,即当把一个数组作为参数传入函数的时候,传入的其实是该数组的副本,而不是它的指针。如果要使用指针,就需要slice类型了
代码语言:javascript复制// var arr [n]type 长度,类型
var arr1 = [5]int{11, 22, 33, 44, 55}
var arr2 = [3][2]string{{"1","2"},{"3","4"},{"5","6"}}
切片slice:在很多应用场景中,数组并不能满足我们的需求。在初始定义数组时,我们并不知道需要多大的数组,因此我们就需要“动态数组”,在Go里面这种数据结构叫slice。 slice并不是真正意义上的动态数组,而是一个引用类型。slice总是指向一个底层array,slice的声明也可以像array一样,只是不需要长度。 slice通过array[i:j]来获取,其中i是数组的开始位置,j是结束位置,但不包含array[j],它的长度是j-i 注意slice和数组在声明时的区别:声明数组时,方括号内写明了数组的长度或使用...自动计算长度,而声明slice时,方括号内没有任何字符。
代码语言:javascript复制var slice1 = []byte{'a', 'b', 'c'} // [97 98 99]
var slice2 = arr1[3:] // [44 55]
slice的默认开始位置是0,arr[:n]等价于arr[0:n] slice的第二个序列默认是数组的长度,arr[n:]等价于arr[n:len(arr)] 如果从一个数组里面直接获取slice,可以这样arr[:],因为默认第一个序列是0,第二个是数组的长度,即等价于arr[0:len(arr)]
- 字典map: map的读取和设置也类似slice一样,通过key来操作,只是slice的index只能是`int`类型,而map多了很多类型,可以是int,可以是string及所有完全定义了==与!=操作的类型。 map是无序的,每次打印出来的map都会不一样,它不能通过index获取,而必须通过key获取 map的长度是不固定的,也就是和slice一样,也是一种引用类型 内置的len函数同样适用于map,返回map拥有的key的数量 map的值可以很方便的修改,通过numbers["one"]=11可以很容易的把key为one的字典值改为11 map和其他基本型别不同,它不是thread-safe,在多个go-routine存取时,必须使用mutex lock机制 map的初始化可以通过key:val的方式初始化值,同时map内置有判断是否存在key的方式 map也是一种引用类型,如果两个map同时指向一个底层,那么一个改变,另一个也相应的改变
var map1 = map[string]string{"name":"shy"}
map1["first"] = "zhen"
map1["last"] = "huaixiu"
fmt.Println(map1, map1["last"]) // map[name:shy first:zhen last:huaixiu] huaixiu
// 判断“name”键是否存在 return bool
_, ok := map1["name"]
fmt.Println(ok) // true
- make 和 new:make用于内建类型(map、slice 和channel)的内存分配。new用于各种类型的内存分配。
内建函数
new
本质上说跟其它语言中的同名函数功能一样,new(T)
分配了零值填充的T类型的内存空间,并且返回其地址,即一个*T
类型的值。用Go的术语说,它返回了一个指针,指向新分配的类型T的零值。
内建函数`make(T, args)`与`new(T)`有着不同的功能,make只能创建slice、map和channel,并且返回一个有初始值(非零)的`T`类型,而不是`*T`。
new返回指针,make返回初始化后的(非零)值。
ch3. 语言基础
Go里面if条件判断语句中不需要括号。
条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内
代码语言:javascript复制var arr4 = [...]int{1, 2, 3, 4, 5}
if x := cap(arr4); x > 3 {
fmt.Println(x, "大于三个")
} else {
fmt.Println(x, "小于三个")
}
fmt.Println(x) // 这里报错,不属于x的作用域
for : 冒泡排序案例
代码语言:javascript复制var arr5 = []int{44, 2, 3, 123, 55, 8, 5}
for i := 0; i < cap(arr5) - 1; i {
for j:= 0; j < cap(arr5) -i -1; j {
if arr5[j] > arr5[j 1] {
temp := arr5[j]
arr5[j] = arr5[j 1]
arr5[j 1] = temp
}
}
}
- 在循环里有两个关键操作:break操作是跳出当前循环,continue是跳过本次循环。与其他语言没差。
- go函数能够返回多个值:
func main() {
x, y := sumTest(4, 8)
fmt.Println(x, y)
}
func sumTest(a int, b int) (c, d int) {
return a b, a * b
}
/* 可以在函数体中命名返回值,这样返回的时候不用带上变量名,可以直接return
func sumTest(a, b int) (add, multiply int) {
add = a b
multiply = a * b
return
}
*/
- go函数变参:
func main() {
myFunc(2, 4)
}
func myFunc(arg ...int) {
fmt.Println("arg:", arg) // arg: [2 4] 这里arg会自动转换为一个int类型的slice
}
- 指针: 传指针使得多个函数能操作同一个对象。 传指针比较轻量级 (8bytes),只是传内存地址,我们可以用指针传递体积大的结构体。如果用参数值传递的话, 在每次copy上面就会花费相对较多的系统开销(内存和时间)。所以当你要传递大的结构体的时候,用指针是一个明智的选择。 Go语言中channel,slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变slice的长度,则仍需要取地址传递指针)
func main() {
x := 3
fmt.Println(x) // 3
x1 := add1(&x) // 传递引用(内存地址)
fmt.Println(x1, x) // 4 4
}
func add1(a *int) int { // 指针,指向内存地址
*a = *a 1
return *a
}
- defer 延迟执行:你可以在函数中添加多个defer语句。当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回。
func deferTest() {
defer fmt.Println("defer1") // 最后逆序输出defer语句
defer fmt.Println("defer2") // 最后逆序输出defer语句
fmt.Println("1")
for i := 0; i < 3; i {
fmt.Println(i)
}
fmt.Println("end")
}
// 输出
1
0
1
2
end
defer2
defer1
- 函数作为参数类型进行传递:函数当做值和类型在我们写一些通用接口的时候非常有用
// 定义一个函数类型
type someFunc func(int) bool
// 满足上面条件的具体函数,被当值传递的函数
func isBigThan10(int2 int) bool {
if int2 > 10 {
return true
}
return false
}
// 函数主体,将定义的函数类型传递进来.
func numTest(slice1 []int, func1 someFunc) {
for _, value := range slice1 {
if func1(value) {
fmt.Println(value)
}
}
}
// 实现
func main() {
var slice = []int{3, 54, 2, 55, 23, 5}
numTest(slice, isBigThan10)
}
- 异常机制 panic 和 recover recover 是一个内建的函数,可以让进入panic状态的goroutine恢复过来。recover仅在延迟函数中有效。在正常的执行过程中,调用recover会返回nil,并且没有其它任何效果。如果当前的goroutine陷入panic状态,调用recover可以捕获到panic的输入值,并且恢复正常的执行。
func main() {
defer errProcess()
f()
}
func errProcess() {
fmt.Println("errProcess start")
if err := recover(); err != nil {
fmt.Println(err)
}
fmt.Println("errProcess end")
}
// 当函数F调用panic,函数F的执行被中断,但是F中的延迟函数会正常执行,然后F返回到调用它的地方
func f() {
defer fmt.Println("e") // 会输出
fmt.Println("a") // 会输出
defer fmt.Println("h") // 会输出
panic("some thing err") // 会输出
fmt.Println("b") // 不会输出
fmt.Println("c") // 不会输出
}
/* 输出
a
h
e
errProcess start
some thing err
errProcess end
*/
- main函数 和 init函数:
Go里面有两个保留的函数:init函数(能够应用于所有的package)和main函数(只能应用于package main)。这两个函数在定义时不能有任何的参数和返回值。虽然一个package里面可以写任意多个init函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个package中每个文件只写一个init函数。
Go程序会自动调用init()和main(),所以你不需要在任何地方调用这两个函数。每个package中的init函数都是可选的,但package main就必须包含一个main函数。
程序的初始化和执行都起始于main包。如果main包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多次)。当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的话),依次类推。等所有被导入的包都加载完毕了,就会开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执行main函数。
即 : main包先执行 import -> 被import包的import -> 被import包的 init()函数 -> main包的init()函数 -> main包的main()函数。如果中间被import包又import了别的包,同样适用此流程,直到最底层包的init()函数执行完毕,才返回上一层。
代码语言:javascript复制 package main
import "fmt"
func init() {
fmt.Println("init11")
}
func init() {
fmt.Println("init22")
}
func main() {
fmt.Println("main00")
}
/* 输出
init11
init22
main00
*/