go语言基础学习 | 青训营笔记

98 阅读8分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天

前情提要

由于博主主要学习语言为java,学习 GO 的过程中也将对比java的语法,达到快速上手的目的。

GO环境搭建

如果同学们没有科学上网,国外的下载链接太慢,这里推荐一个国内的下载地址Go下载 - Go语言中文网 - Golang中文社区 (studygolang.com),windows版本选择go1.19.5.windows-amd64.msi即可

编译器这里推荐Goland => 其他版本 - GoLand (jetbrains.com)

  1. 试用30天
  2. 学生邮箱申请
  3. b站自搜破解

基础知识

GO 的优势

  1. 高性能、高并发
  2. 语法简单、学习曲线平缓
  3. 丰富的标准库
  4. 完善的工具链
  5. 静态链接
  6. 快速编译
  7. 跨平台
  8. 垃圾回收

GO 语言结构

  • 包声明
  • 引入包
  • 函数
  • 变量
  • 语句 & 表达式
  • 注释

与java类似,不过多介绍

GO 数据类型

  1. 数字类型 int int有int8,int16,int32,int64,分别对应了java中的byte,short,int,long。 float 分为float 32,float64,分别对应了java中的float和double

  2. 字符串类型 string

  3. 布尔型 bool true , false

  4. 派生类型:

    • 指针类型(Pointer)
    • 数组类型
    • 结构化类型(struct)
    • Channel 类型
    • 函数类型
    • 切片类型
    • 接口类型(interface)
    • Map 类型

在 GO 中 ,nil可以代表下面这些类型的零值:

  • 指针类型(包括unsafe中的)
  • map类型
  • slice类型
  • function类型
  • channel类型
  • interface类型

快速入门

package main
​
// GO 的标准输入输出包
import "fmt"// 主函数
func main() {
    //注意不能有未使用的变量,GO 的检查比较严格
    var a, b int = 0, 1
    var s1 string = "hello,world!"
    var c = 1
    s2 := "小广播同学"
    fmt.Println(s1+s2, a) //hello,world!小广播同学 0
    fmt.Println(b == c)   //true
}
  1. var a, b int = 0, 1 这句是声明多变量,结构是var + 变量名 + 数据类型 + 赋值
  2. var c = 1 根据值自行判定变量类型。类似js的类型声明
  3. s2 := "小广播同学" 与var c = 1 类似,是一种简写。

注意:如果 前面已经使用var声明过变量,再使用 := 会报错

GO 还可以在函数体外声明变量,第三种方法不能在函数体外使用

当使用等号 = 将一个变量的值赋值给另一个变量时,如:j = i,实际上是在内存中将 i 的值进行了拷贝

条件判断if

与大多数语言不同,GO 的if语句无需再条件处添加括号,所以不能与其它语言一样能不写大括号。

package main
​
import "fmt"
​
func main() {
    a, b := 1, 1
    if a == b {
        fmt.Println("a==b")
    } else {
        fmt.Println("a!=b")
    }
​
}

循环for

GO 的循环只有一种==>for循环,下面是输出0-9的一个例子

package main
​
import "fmt"
​
func main() {
    a := 10
    for i := 0; i < a; i++ {
        fmt.Println(i)
    }
}

GO 也可以当成while使用

package main
​
import "fmt"
​
func main() {
    a := 1
    for a < 3 {
        fmt.Println(a)
        a++
    }
}

循环体可以用break跳出循环,continue进入下次循环。

switch case

switch case语法也与大部分类似,但是再case后无需手动添加break

package main
​
import "fmt"func main() {
    a := 1
    switch a {
    case 0:
        fmt.Println("///")
    case 1:
        fmt.Println("好好好") // 好好好
    default:
        fmt.Println("???")
    }
}
​

数组

与c++和java都不同,这里给出两种初始化方法

package main
​
import "fmt"
​
func main() {
    //第一种
    a := [3]int{1, 2, 3}
    fmt.Println(a)//[1 2 3]
    //第二种
    var b [5]int
    b[4] = 8
    fmt.Println(b[4], len(b))//8 5
}

切片slice

GO 比较常用的用法,比数组更加灵活,支持和python一样的切片,但不支持步长,也不支持负数索引

package main
​
import "fmt"func main() {
    a := make([]string, 3)
    a[0] = "a"
    a[1] = "b"
    a[2] = "c"
    b := append(a, "s")
    fmt.Println(a) // [a b c]
    fmt.Println(b) // [a b c s]
    c := make([]string, 2)
    //拷贝切片,第二个元素的值赋值给第一个,拷贝的长度为min( len(a), len(b) )
    copy(c, b)
    fmt.Println(c)      // [a b]
    fmt.Println(b[1:])  // [b c s]
    fmt.Println(b[1:3]) // [b c]
    fmt.Println(b[:4])  // [a b c s]
}

map

map是无序的,输出的顺序与插入顺序无关

package main
​
import "fmt"func main() {
    m := make(map[string]int) // 这里是string是键的类型,int是值的类型
    m["one"] = 1 //one 是 键 , 1是值
    m["two"] = 2
    fmt.Println(m)                 // map[one:1 two:2]
    fmt.Println(len(m))            // 2
    fmt.Println(m["one"])          // 1
    fmt.Println(m["???"])          // 0
    
    value, isExistKey := m["one"]  // 第二个是bool类型,可用作判断key是否存在
    fmt.Println(value, isExistKey) // 1 true
    
    delete(m, "two")               // 删除key为two的键
​
    m2 := map[string]int{"one": 1, "two": 2}
    var m3 = map[string]int{"one": 1, "two": 2}
    fmt.Println(m2, m3) // map[one:1 two:2] map[one:1 two:2]
}
​

循环for range

更便捷的for循环,可以用于数组(array)、切片(slice)、通道(channel)或集合(map)

package main
​
import "fmt"var pow = []int{1, 2, 4, 8}
​
func main() {
    // i 是索引位, v是索引位的值
    for i, v := range pow {
        fmt.Printf("2**%d = %d\n", i, v)
    }
    m := make(map[int]float32)
    m[1] = 1.0
    m[2] = 2.0// 读取 key 和 value
    for key, value := range m {
        fmt.Printf("key is: %d - value is: %f\n", key, value)
    }
​
    // 读取 key
    for key := range m {
        fmt.Printf("key is: %d\n", key)
    }
​
    // 读取 value
    for _, value := range m {
        fmt.Printf("value is: %f\n", value)
    }
}
​

自定义函数

GO 的传递类型和返回类型都后置,函数可以返回多值

package main
​
import "fmt"func swap(x, y string) (string, string) {
    return y, x
}
​
func main() {
    a, b := swap("a", "b")
    fmt.Println(a, b) // b a
}
​

指针

GO 语言中一切都是值传递,没有引用传递,那么如何修改传入函数的值,就用到了指针。指针相对 c 或 c++ 的操作有限 ,大多数只是用来修改值

package main
​
import "fmt"func addReal(a *int) {
    *a++
}
​
func addVirtual(a int) {
    a++
}
​
func main() {
    var a int = 10
​
    fmt.Printf("变量的地址: %x\n", &a) // 变量的地址: c00001a0a8
    addVirtual(a)
    fmt.Println(a) // 10
    addReal(&a)
    fmt.Println(a) // 11
}

结构体

结构体就是java中的类,不过结构体是值类型,类是引用类型。他们的存储位置一个在栈上,一个在堆上。

结构体定义需要使用 typestruct 语句 。struct 语句定义一个新的数据类型,结构体中有一个或多个成员。type 语句设定了结构体的名称。

结构体函数是在普通函数基础上,在名字前面加上结构体变量名结构体名

注意,如果需要在函数体内修改结构体对象,那么需要用到指针。

package main
​
import "fmt"type People struct {
    height float32
    weight float32
    name   string
}
​
func checkName(people *People) {
    people.name = "lisi"
}
func checkName2(people People) {
    people.name = "lisi"
}
func (p *People) modifyName(name string) {
    p.name = name
}
func main() {
    p := People{height: 179.9}
    p.weight = 141.5
    p.name = "zhangsan"
    checkName2(p)
    fmt.Println(p) // {179.9 141.5 zhangsan}
    checkName(&p)
    fmt.Println(p) // {179.9 141.5 lisi}
    fmt.Println(p.name) // zhangsan
}

错误处理

假设一个真实的例子,我们接受一个用户传递来的数据,需要从数据库中匹配,如果匹配不成功,可以返回nil,但是更好的做法是可以返回一个错误信息,通常通过枚举错误信息得出。在java里面就是抛出一个异常,在 GO 中就是返回一个error了,error可以填入一个错误信息用于提示。error存在的函数,调用相当与java的try,接下来的 if 相当于catch。以下是error的简单用法。

package main
​
import (
    "errors"
    "fmt"
)
​
type Users struct {
    account  string
    password string
}
​
func (user Users) checkLogin(users map[string]string) error {
    if users[user.account] != user.password {
        return errors.New("用户名或密码不存在")
    } else {
        return nil
    }
}
func main() {
    var users = make(map[string]string)
    users["lisi"] = "123456"
    users["zhangsan"] = "654321"
    user := Users{
        account:  "lisi",
        password: "654321",
    }
    err := user.checkLogin(users)
    if err != nil {
        fmt.Println(err) // 用户名或密码不存在
    }
}

字符串操作

  • strings.Contains 字面意思,如果有子字符串则返回true,否则返回false
  • strings.Count 返回子字符串的出现次数
  • strings.HasPrefix 字符串是否以给定前缀开头,是则true,否则false
  • strings.Index 返回给定字符串在要判断字符串的起始位置,从0开始
  • strings.Join 用于字符串之间拼接,中间的连接符可以自定义,返回拼接后的字符串
  • strings.Repeat 拼接指定次数字符串,返回拼接好的字符串
  • strings.Replace 替换指定子字符串
  • strings.Split 以给定分割符,将原字符串分割成一个数组
  • strings.ToLower 全部小写
  • strings.ToUpper 全部大写

字符串格式化

任意变量都可以用 %v 格式化打出

package main
​
import (
    "fmt"
)
​
func main() {
    s := "sss"
    fmt.Printf("s=%v", s) //s=sss
}

需要注意fmt.Scanf处理输入时是需要具体的类型的

例如: fmt.Scanf("%i%s", &a, &b) , %i 代表int类型,%s代表sting类型

JSON处理

如果结构体中的字段名开头的第一个字母是大写,即公开字段,类似java中加了 public 的变量,那么直接用json.Marshal(结构体变量)json.Marshal 返回两个值(buf,err),buf是json字符串,可以用string(buf)转为人能看懂的字符串,err是错误信息,以防调用者出错。buf 还可以用json.Unmarshal反序列到一个空变量里,这样的变量是一个对象,会带有些除属性外的基本信息。

数字处理

我们可以用 strconv.Atoi 把一个十进制字符串转成数字。可以用 strconv.itoA 把数字转成字符串。 如果输入不合法,那么这些函数都会返回error

简单使用:

package main
​
import (
    "fmt"
    "strconv"
)
​
func main() {
    s := "123456"
    num, err := strconv.Atoi(s)
    if err == nil {
        fmt.Println(num)
    }
​
}

进程信息

  • os.Args 获得程序启动得到的命令行参数
  • os.Getenv 可以得到对应环境变量的值,如os.Getenv("PATH") 获得环境变量PATH的参数
  • os.Setenv 可以设置一个环境变量

基础语法总结

至此,go的基础语法就完结了,通过看文章或视频并不能印在脑海,我还是建议大家手动敲一遍,不会的情况可以多百度。若有理解不了或者需要讲解的地方也可以私信我,我高强度冲浪。