前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang学习笔记之一 - 基础知识与概念

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

作者头像
躺平程序员老修
发布2023-09-05 16:02:01
2000
发布2023-09-05 16:02:01
举报
文章被收录于专栏:躺平程序员老修

学习笔记 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}
*/
代码语言: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
 */
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ch1. 基础知识学习笔记
  • ch2. 基础概念理解
  • ch3. 语言基础
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档