[01编程语言 - go] 01 - go语言语法入门 | 青训营笔记

40 阅读7分钟

这是我参与「第五届青训营」伴学笔记创作活动的第1天。今天的课程主要是go语言的入门,内容主要包括基础语法和几个实践案例。

作为有编程基础的人来说,上手一门新的语言时应该注重知识的迁移,在学习的时候就与已熟悉的语言进行对比,通过实践去巩固,这样才能快速高效地学习。这篇笔记主要是真的语法的总结和与一些其它语言进行对照。

基础语法

Hello world 结构

go语言代码结构主要分为3块:package main入口包、import导入标准库和主函数。

package main

import (
    "fmt"
)

func main() {
    fmt.Println("hello world")
}

代码文件需要经过编译得到.exe执行文件才能执行。在这一点上和c语言是极为相似的。

// 编译main.go文件
go build main.go
// 执行编译后得到的main.exe文件
./main.exe

// 编译+运行
go run main.go

变量

主要的基础数据类型包括:字符串、整形、浮点型(分为float32和float64)、布尔型

变量定义有2种方法:var自动识别数据类型(有需要也可显示声明数据类型)和“变量名 := 值”,第二种方法是go比较具有特点的。常量的定义仅使用const,数据类型自动确定,这一点和一些编程语言不同。

// var定义格式:var 变量名 数据类型 = 值
var a = 10
var b, c int = 2, 3

// 变量名 := 值
c := a + 2

数组(array)和切片(slice)

数组的长度大多固定,因此在实际的代码中使用较少。定义数组时如果不知道数组的长度,可以在数组长度部分使用"..."表示,编译器会根据值的数量计算(本质上还是需要知道数组个数)。

// 数组定义格式1:var 变量名 [数组长度]数据类型
var a [5]int
var b [...]int

// 数组定义格式2:变量名 := [数组长度]数据类型{值}
c := [5]int{1, 2, 3, 4, 5}

// 二维数组的定义
var twoD [2][3]int

切片主要用于可变长度数组。直观感觉和Python3中的list非常相似。复制、切片功能都可以从Python3迁移过来。

2个不同点:

  1. 在添加元素时注意要将结果重新赋值给变量,因为在内存层面slice存储的是长度+指向数组的指针,一旦涉及扩容就会导致指针改变。
  2. 打印切片时,数组元素使用空格分隔。
// 使用make创建切片,参数为数据类型、初始长度和容量(可选)
s := make([]string, 3)

// 在切片尾部添加元素
s = append(s, "d")

// 复制
c := make([]string, len(s))
copy(c, s)

// 切片
fmt.Println(s[2:5]) // [c d e]

map

map的本质就是哈希映射,和其它编程语言中的HashMap、dictionary是同一个东西。默认的map是无序的。

需要注意的是对于键是否存在的判断方法,可以通过在map后添加关键字ok的方法判断。

// map的定义:
m := make(map[string]int)

// 键值对的添加与删除
m["one"] = 1
delete(m, "one")  // 参数为map变量名和key

// 在map后添加ok关键字用于判断对应键是否存在
r, ok := m["unknow"]
fmt.Println(r, ok) // 0 false

range

range在go语言中的含义较为不同,主要用于遍历数组/切片和map,同时获取下标和值或键和值。

for i, num := range nums{
    代码块
}

for k, v := range m{
    代码块
}

条件分支

go语言中的条件分支和C/C++较为相似,分为if-else和switch-case两种。

if-else语句的结构C/C++较为相似,但是判断条件无需使用小括号括起来,但判断后执行的代码块必须使用大括号。如果涉及多个判断条件,需使用逻辑运算符。

赋值语句同样可以使用在条件里,和判断语句用分号隔开。

if 条件1 {
    代码块
} else if 条件2 {
    代码块
} else {
    代码块3
}

switch-case语句类似,判断的对象无需使用小括号括起来,但判断后执行的代码块必须使用大括号。不同点在于,在执行完一个case后会自动跳出switch,无需通过break跳出。除此之外,switch后也可不写条件,直接在case后面写判断条件,从而实现多分支判断。

a := 5
switch a {
    case 1:
        fmt.Println("One.")
    case 5:
        fmt.Println("Five.")
    default:
        fmt.Println("Other.")
}

循环语句

循环语句的结构同样类似于C/C++,但在go语言中只有for循环。和条件语句一样,条件部分不使用小括号,3段条件每一段都可省略(全部省略时可以不写分号,即没有条件部分)。循环的代码块需要用大括号括起来。跳出循环同样可以使用break和continue关键字。

for 条件 {
    代码块
}

函数

go语言最大的特点是数据类型后置,因此函数的结构为:

func 函数名(参数, 参数数据类型) 返回值类型

在实际应用中,绝大多数函数返回2个值,第一个是正常的返回值,第二个是错误信息。

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
}

指针

和C/C++类似,"*"代表指针,"&"用于取地址。指针在功能上主要用于对传入值进行原地修改。

func add2ptr(n *int) {
    *n += 2
}

结构体和结构体方法

结构体是带类型的字段的集合。

type user struct {
    name     string
    password string
}

结构体方法类似于Java中的类成员函数,用于实现对于结构体特有的功能。

// (u *user) 用于指定函数对应的结构体类型
func (u *user) resetPassword(password string) {
    u.password = password
}

a := user{name: "wang", password: "1024"}
a.resetPassword("2048")

错误处理

go语言中的空值为nil。

函数出现错误时可以在返回值返回err类型参数,用于表示错误类型。此时会有2个return,错误信息可用errors.New生成。需要在代码开头import "error".

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")
}

string包

字符串控制函数主要包含在string包中,需要通过import引用。常用的包含string.Contains、string.Count、string.Index、string.Join、string.Split、string.Replace等。

获取长度的函数len()不包含在其中,无需import。

fmt包

主要包含字符串格式化方法,总体上类似C/C++。使用Println和Printf进行格式化输入输出,使用"%v"、"%.2f"替换变量。其中"%+v"和"%#+v"可以更加细致地打印出变量信息。

json格式化

如果结构体内的变量首字母为大写,即可通过函数转换为json格式。

type userInfo struct {
    Name  string
    Age   int `json:"age"`
    Hobby []string
}

// 序列化为json格式
a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
buf, err := json.Marshal(a)

// 反序列化
var b userInfo
err = json.Unmarshal(buf, &b)

其它常用包

time主要用于获取时间,strconv主要用于字符串和数字之间的转换,os多用于获取进程信息。

总结

go语言总的来说还是比较容易上手的,内置的库和简洁的代码结构相对容易理解,尤其对于有其它语言基础的学习者。

从代码结构来说,go和C/C++是非常相似的。从代码结构、条件语句、for循环都可以轻松迁移。

从数据类型来说,slice和Python3中的list更为相似,结构体和指针与C/C++更为相似。

在包的引用和结构体方法上,和Java更为相似。