Go语言快速上手-基础语言 | 青训营笔记

50 阅读9分钟

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

Go的语法与C语言高度相似,本文中将以代码为中心,需要注意的内容将写在注释中

简介

什么是Go语言

  • 高性能、高并发

    Go使用标准库或基于标准库的第三方库来实现高并发

  • 语法简单、学习曲线平缓

     package main
     ​
     import "net/http"
     ​
     func main() {
         //将标准库的http包里,将“/”路由指向静态文件夹“.”
         http.Handle("/", http.FileServer(http.Dir(".")))
         //监听端口并启动服务
         http.ListenAndServe(":8080", nil)
     }
     ​
    

    标准库实现一个Http服务器

  • 丰富的标准库

    可以保障稳定性、兼容性

  • 完善的工具链

    编译、代码格式化、错误检查、帮助文档、包管理等……

  • 静态链接

  • 快速编译

    Go语言有着静态语言里最快的编译速度

  • 跨平台

    支持交叉编译

  • 垃圾回收

    无需考虑内存分配释放

应用领域:云计算、微服务、大数据、区块链、物联网等

Docker、Kubernetes、Istio、etcd、prometheus 几乎所有的云原生组件全是用Go实现的。

基础语法

变量

声明

  • 未使用不能声明
  • 不可以重复声明
 package main
 import "fmt"
 func main(){
 ​
     //小驼峰userName
 //  大驼峰UserName
 ​
     var a int
     //  未使用不能声明
     fmt.Println(a)
 }

初始化

 package main
 import "fmt"
 func main(){
     //变量初始化
     var b int = 100
     fmt.Println(b)
 }

赋值

 package main
 import "fmt"
 func main(){
     //赋值
     var c float64 = 123.321
     c = 321.123
     fmt.Println(c)
 }

可以同时给多个变量赋值

 package main
 import "fmt"
 func main(){
     var a,b,c=100,200,300;
 }

自动推导

自动推导是让变量根据声明时候初始化的值去自动判断变量的类型

 package main
 import "fmt"
 func main(){
     //  自动推导
     var sum = 789.654
     fmt.Printf("%T\n",sum)//%T是打印输出类型的格式控制符
 //  常用的自动推导,相当于声明+初始化
 //  a := 100 //会报错,因为a已经声明过了
     pi := 3.1415926
     fmt.Println(pi)
 }

这里的变量sum被自动推导出了为float64类型

字符串

 package main
 ​
 import (
     "fmt"
 )
 ​
 func main() {
     var str string
     str = "Hello World"
     fmt.Println(str)
 ​
     //字符串长度
     fmt.Println(len(str))
 ​
     //一个汉字3字符
     ch := "中"
     fmt.Println(len(ch))
 ​
     //字符串拼接
     str1 := "Hello"
     str2 := " World"
     str3 := str1 + str2
     fmt.Println(str3)
 ​
     //字符串取单个字符
     strr := "Hello World"
     fmt.Println(str[0], strr[1])          //对应的ASCII码
     fmt.Printf("%c,%c", strr[0], strr[1]) //输出字符
 }
 ​

判断结构

if-else

 package main
 ​
 import "fmt"
 ​
 func main() {
 ​
    //if支持一个初始化语句,只在当前判断生效
    if a := 700; a == 700 {
       fmt.Printf("%d\n", a)
    }
 ​
    if a := 500; a > 700 {
       fmt.Println("a>700")
    } else if a == 700 {
       fmt.Println("a==700")
    } else {
       fmt.Println("a<700")
    }
 ​
 }

switch

Go的Switch与C的switch不一样

C的switch如果不遇到break,会跑完所有的case

而Go的switch只会跑完符合条件的case

    switch a := 120; a {
    case 80:
       fmt.Println("80")
    case 90:
       fmt.Println("90")
    case 100, 110, 120:
       fmt.Println("100,110,120") //可以有多个值匹配
       fallthrough                //相当于continue
    default:
       fmt.Println("default")
    }
 ​

Go的switch支持任意的数据类型,字符串,结构体等

Go的switch判断变量可以留空,在case里添加条件,起到代替if-else的作用

    //判断字符留空
    switch sc := 100; {
    case sc >= 100:
       fmt.Println("A")
    case sc <= 100:
       fmt.Println("B")
    default:
       fmt.Println("C")
    }

数组

 package main
 ​
 import "fmt"
 ​
 func main() {
    //一维数组
    var a [5]int
    a[4] = 100
    fmt.Println(a[4], len(a))
    //初始化
    b := [5]int{1, 2, 3, 4, 5}
    fmt.Println(b)
 ​
    //二维数组
    var twoD [2][3]int
    for i := 0; i < 2; i++ {
       for j := 0; j < 3; j++ {
          twoD[i][j] = i + j
       }
    }
    fmt.Println(twoD)
 ​
 }

切片

  • 切片是可变长度的数组,可以任意时刻去更改长度
  • Golang里Slice的结构是长度+容量+指向数组的指针,当容量不够时,会进行扩容,并返回新的Slice,因此需要赋值回去
  • Golang不支持负索引,需要借助len()进行运算
 package main
 ​
 import "fmt"
 ​
 func main() {
    //用make创建切片
    s := make([]string, 3)
    s[0], s[1], s[2] = "a", "b", "c"
    fmt.Println(s[2])
    fmt.Println(len(s))
 ​
    //使用append追加元素
    //append必须将结果赋值给原数组
    s = append(s, "d")
    s = append(s, "e", "f")
    fmt.Println(s, len(s))
 ​
    //make指定长度
    c := make([]string, len(s))
 ​
    //拷贝数据
    copy(c, s)
    fmt.Println(c)
 ​
    //切片取值
    fmt.Println(s[2:5]) //左闭右开
    fmt.Println(s[:5])
    fmt.Println(s[2:])
 ​
 }

map

其他语言又被称为哈希、字典

 package main
 ​
 import "fmt"
 ​
 func main() {
    //make创建空map
    //key的类型为string,value的类型为int
    m := make(map[string]int)
 ​
    //读取、写入map
    m["one"] = 1
    m["two"] = 2
    fmt.Println(m)
    fmt.Println(len(m))
    fmt.Println(m["one"])
    fmt.Println(m["unknow"])
 ​
    //可以通过ok来检查指定的key是否存在
    r, ok := m["unknow"]
    fmt.Println(r, ok)
    r, ok = m["one"]
    fmt.Println(r, ok)
 ​
    //删除k-v对
    delete(m, "one")
 ​
    //map的kv对是完全无序的,遍历时候不会以任何顺序输出
    m2 := map[string]int{"one": 1, "two": 2}
    var m3 = map[string]int{"one": 1, "two": 2}
    fmt.Println(m2, m3)
 ​
 }

range

对于一个slice或者map可以用range进行遍历

 package main
 ​
 import "fmt"
 ​
 func main() {
    //遍历数组
    nums := []int{2, 3, 4}
    sum := 0
    //第一个参数是索引,第二个参数是索引对应的值
    for i, num := range nums {
       sum += num
       if num == 2 {
          fmt.Println("index:", i, "num:", num)
       }
    }
    fmt.Println(sum)
 ​
    //遍历map
    m := map[string]string{"a": "A", "b": "B"}
    for k, v := range m {
       fmt.Println(k, v)
    }
 ​
    //遍历输出map的k
    for k := range m {
       fmt.Println("k=", k)
    }
 }

函数

  • 返回值类型后置
  • 可以返回多个值
  • 通常第一个值返回真正的结果,第二个值返回错误信息
 package main
 ​
 import "fmt"
 ​
 //返回值类型后置
 func add(a, b int) int {
    return a + b
 }
 ​
 //可以返回多个值
 //通常第一个值返回真正的结果,第二个值返回错误信息
 func exists(m map[string]string, k string) (v string, ok bool) {
    v, ok = m[k]
    return v, ok
 }
 ​
 func main() {
    fmt.Println(add(1, 2))
    v, ok := exists(map[string]string{"a": "A"}, "a")
    fmt.Println(v, ok)
 }

指针

 package main
 ​
 import "fmt"
 ​
 func Add(n int) {
     //这里的n只是一个拷贝,不会改变原变量的值(内存地址不同)
     n += 2
 }
 ​
 func Addptr(n *int) {
     *n += 2
 }
 ​
 func main() {
     n := 5
     Add(n)
     fmt.Println(n)
     Addptr(&n)
     fmt.Println(n)
 }
 ​

结构体

  • 结构体的三种初始化赋值方式

    • 完整初始化各字段(有关键字)
    • 完整初始化各字段(无关键字)
    • 只初始化其中一部分字段(未初始化的字段默认为空值)
    • 只初始化结构体变量,后给字段赋值
  • 结构体也可以作为函数参数

    • 指针式
    • 非指针式
 package main
 ​
 import "fmt"
 ​
 type user struct {
     name     string
     password string
 }
 ​
 func main() {
     //第一种
     a := user{name: "XiaoZhi", password: "123456"}
     //第二种
     b := user{"XiaoZhi", "123456"}
     //第三种
     //可以只初始化一个字段,但必须指明初始化哪个字段,没有初始化的字段默认为空
     c := user{name: "XiaoZhi"}
     fmt.Println(c.password)
     c.password = "123456"
     fmt.Println(c.password)
     //第四种
     var d user
     d.name = "XiaoZhi"
     d.password = "123456"
     fmt.Println(a, b, c, d)
 ​
 }
 ​
 //结构体作为函数参数
 func CheckPassword(u user, password string) bool {
     return u.password == password
 }
 ​
 //指针式
 func CheckPassword2(u *user, password string) bool {
     return u.password == password
 }
 ​

使用指针可以实现对结构体变量的修改, 同时避免大结构变量产生拷贝的开销

结构体函数

类似于类成员函数,

 package main
 ​
 import "fmt"
 ​
 type user struct {
    name     string
    password string
 }
 ​
 func (u user) CheckPassword(password string) bool {
    return u.password == password
 }
 func (u *user) ResetPassword(password string) {
    u.password = password
 }
 ​
 func main() {
    u := user{"XiaoZhi", "admin"}
    fmt.Println(u.CheckPassword("admin"))
    u.ResetPassword("adminadmin")
    fmt.Println(u.password)
 }

错误处理

在Golang里,符合语言错误处理的习惯是,使用一个单独的返回值来传递错误信息

区别于Java中的异常处理,Go中可以清晰地知道哪个函数返回了错误,并可以用简单的if-else去处理错误

 package main
 ​
 import (
    "errors"
    "fmt"
 )
 ​
 type User struct {
    name     string
    password string
 }
 ​
 func findUser(users []User, name string) (v *User, err error) {
    for _, u := range users {
       if u.name == name {
          //找到了返回该结构体变量的地址,并返回空的错误信息
          return &u, nil
       }
    }
    //没找到返回空,并返回错误信息
    return nil, errors.New("Not Found")
 }
 ​
 func main() {
    v, err := findUser([]User{{"Yang", "1024"}}, "Yang")
    if err != nil {
       fmt.Println(err)
       return
    }
    //查找成功,继续执行,打印用户姓名
    fmt.Println(v.name)
 ​
    //查找失败,输出错误信息
    if u, err := findUser([]User{{"wang", "1234"}}, "li"); err != nil {
       fmt.Println(err)
       return
    } else {
       fmt.Println(u.name)
    }
 }

字符串操作

 package main
 ​
 import (
    "fmt"
    "strings"
 )
 ​
 func main() {
    a := "Hello"
    fmt.Println(strings.Contains(a, "ll"))                //查询字串是否存在
    fmt.Println(strings.Count(a, "l"))                    //字串出现次数计数
    fmt.Println(strings.HasPrefix(a, "he"))               //前缀判断
    fmt.Println(strings.HasSuffix(a, "llo"))              //后缀判断
    fmt.Println(strings.Index(a, "ll"))                   //字串位置
    fmt.Println(strings.Join([]string{"he", "llo"}, "-")) //字符串拼接,自定义分隔符
    fmt.Println(strings.Repeat(a, 2))                     //重复多次字符串
    fmt.Println(strings.Replace(a, "e", "E", -1))         //替换字符
    fmt.Println(strings.Split("a-b-c", "-"))              //分割字符串为数组
    fmt.Println(strings.ToUpper("abc"))                   //转大写
    fmt.Println(strings.ToLower("abc"))                   //转小写
    fmt.Println(len(a))                                   //获取字符串长度
    fmt.Println(len("你好"))                                //一个中文3个字符长度
 ​
 }

字符串格式化

 package main
 ​
 import "fmt"
 ​
 type Point struct {
    x, y int
 }
 ​
 func main() {
    s := "hello"
    n := "123"
    p := Point{1, 2}
    fmt.Println(s, n)
    fmt.Println(p)
 ​
    //%v自动识别类型
    fmt.Printf("s=%v\n", s)
    fmt.Println("n=%v\n", n)
    fmt.Println("p=%v\n", p)
 ​
    //%+v得到更完整的结构
    fmt.Printf("p=%+v\n", p)
 ​
    //%#v更更进一步细化
    fmt.Printf("p=%#v\n", p) //结构体的类型名称、字段名字及值
 ​
    //设置小数点、精确度
    b := 3.1415926535
    fmt.Println(b)
    fmt.Printf("%.2f\n", b)
 ​
 }

JSON处理

  • JSON首字母必须大写
  • 序列出来的字符串是一个大写字母开头的,如果输出需要小写,则需要在结构体中添加tag,在输出时会小写
  • 需要使用强制类型转换为字符串,否则为一个16进制字符串
 package main
 ​
 import (
    "encoding/json"
    "fmt"
 )
 ​
 type userInfo struct {
    //JSON首字母必须大写
    Name  string
    Age   int `json:"age"` //添加tag
    Hobby []string
 }
 ​
 func main() {
    a := userInfo{"XiaoZhi", 24, []string{"sing", "dance", "rap", "play basketball"}}
 ​
    //序列化
    //序列出来的字符串是一个大写字母开头的
    //如果输出需要小写,则需要在结构体中添加tag,在输出时会小写
    buf, err := json.Marshal(a)
    if err != nil {
       //运行时恐慌
       //在panic被抛出之后,如果没有在程序里添加任何保护措施的话,程序就会在打印出panic的详情,终止运行
       panic(err)
    }
 ​
    // 序列化为字符串
    fmt.Println(buf) //十六进制字符串
    // 强制类型转换
    fmt.Println(string(buf))
 ​
    //应用缩进来格式化输出
    buf, err = json.MarshalIndent(a, "", "\t")
    if err != nil {
       panic(err)
    }
    fmt.Println(string(buf))
 ​
    var b userInfo
    //反序列化到空的变量里
    err = json.Unmarshal(buf, &b)
    if err != nil {
       panic(err)
    }
    fmt.Printf("%#v\n", b)
 }

时间处理

 package main
 ​
 import (
    "fmt"
    "time"
 )
 ​
 func main() {
    //快速获取当前时间
    now := time.Now()
    fmt.Println(now)
 ​
    //构造一个带时区的时间
    t := time.Date(2023, 1, 15, 12, 0, 36, 6, time.UTC)
    t2 := time.Date(2023, 1, 15, 12, 20, 36, 6, time.UTC)
    fmt.Println(t)
    //获取时间点信息
    fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute())
 ​
    //格式化一个时间字符串
    fmt.Println(t.Format("2006-01-02 15:04:05"))
 ​
    //时间相减,获得时间段
    diff := t2.Sub(t)
    fmt.Println(diff)
    //输出时间段内有多少分钟,多少秒
    fmt.Println(diff.Minutes(), diff.Seconds())
 ​
    //第一个参数是时间字符串格式,第二个为具体时间字符串
    t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
    if err != nil {
       panic(err)
    }
    fmt.Println(t3 == t)
    //获取时间戳
    fmt.Println(now.Unix())
 }

数字解析

常用数字解析函数

 package main
 ​
 import (
    "fmt"
    "strconv" //string convert缩写
 )
 ​
 func main() {
    f, _ := strconv.ParseFloat("1.234", 64)
    fmt.Println(f)
 ​
    //第二个参数表示进制(8、10、16,如果传0为自动推测)
    //第三个参数为精度
    n, _ := strconv.ParseInt("111", 10, 64)
    fmt.Println(n)
    n, _ = strconv.ParseInt("0x1000", 0, 64)
    fmt.Println(n)
 ​
    //字符串快速转数字
    n2, _ := strconv.Atoi("123")
    fmt.Println(n2)
    //数字转字符串
    n3 := strconv.Itoa(132)
    fmt.Println(n3)
 ​
 }

进程信息

 package main
 ​
 import (
    "fmt"
    "os"
    "os/exec"
 )
 ​
 func main() {
    //当执行当前程序 包含参数时,读取参数
    fmt.Println(os.Args)
    //打印环境变量
    fmt.Println(os.Getenv("PATH"))
    // 设置环境变量
    fmt.Println(os.Setenv("AA", "BB"))
    // 启动子进程,获取输入输出
    buf, err := exec.Command("java", "--version").CombinedOutput()
    if err != nil {
       panic(err)
    }
    fmt.Println(string(buf))
 }