这是我参与「第五届青训营」伴学笔记创作活动的第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个不同点:
- 在添加元素时注意要将结果重新赋值给变量,因为在内存层面slice存储的是长度+指向数组的指针,一旦涉及扩容就会导致指针改变。
- 打印切片时,数组元素使用空格分隔。
// 使用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更为相似。