golang学习笔记之一 - 基础知识与概念

2023-09-05 16:02:01 浏览数 (1)

学习笔记 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
  • 数组只能存储同一类型的数据,并且长度固定不能改变
代码语言:javascript复制
var arr2 = [5]float32{1.0, 2.2, 3.4}    // 数组个数必须小于等于[]中设置的长度
var arr3 = [...]int{1, 2, 3, 4}         // 个数不确定可以用[...]代替,是一样的
  • 结构体可以存不同类型的数据(map、数组都是一种类型)
代码语言:javascript复制
// 定义结构体
/*
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
代码语言:javascript复制
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定义
代码语言:javascript复制
/* 声明变量,默认 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接口
代码语言:javascript复制
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
*/
  • 错误处理
代码语言:javascript复制
// 实现 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并发
代码语言:javascript复制
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()
}
  • 通道
代码语言:javascript复制
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同时指向一个底层,那么一个改变,另一个也相应的改变
代码语言:javascript复制
    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的零值。
代码语言:javascript复制
 内建函数`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函数能够返回多个值:
代码语言:javascript复制
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函数变参:
代码语言:javascript复制
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的长度,则仍需要取地址传递指针)
代码语言:javascript复制
 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语句会按照逆序执行,最后该函数返回。
代码语言:javascript复制
 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
  • 函数作为参数类型进行传递:函数当做值和类型在我们写一些通用接口的时候非常有用
代码语言:javascript复制
  // 定义一个函数类型
 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的输入值,并且恢复正常的执行。
代码语言:javascript复制
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
 */

0 人点赞