前言
之前我主要学习与使用的是Java语言,该篇笔记主要结合青训营Go基础语法课程与课外查阅的部分资料,记录Go语言中部分之前我不熟悉或与Java中不同的特性。
正文
变量声明与使用
Go是强类型语言,常见变量类型包括字符串、整型、浮点型、布尔型等。使用var自动推导类型或指定变量类型可以声明单个或多个变量;不指定变量类型使用":="可以实现声明变量并赋值,该操作符不能用于已声明过的变量,且只能用于函数体内的临时变量,不能用于全局变量。
var b, c int = 1, 2
var d = true
// :=为声明变量并赋值,不能重复声明(:=左边至少需要有一个新声明的变量)
f := float32(e)
if else
if/ else if语句的布尔表达式前有一个可选语句,可定义只可在该判断逻辑内访问的变量。
if num := 9; num < 0 {
fmt.Println(num, "是负数")
} else if n := 3; n == 3 && num < 10 {
fmt.Println(n, "= 3且0 <", num, "< 10")
} else {
fmt.Println(n, "!=3且", num, ">= 10")
}
switch
无需添加break,默认case命中后只执行当前语句,不往下执行。case可指定为任意变量类型,包括结构体。
switch case语句中switch后不加变量并将case指定为布尔表达式可替换任意if else系列语句,逻辑更加清晰。
switch {
case t.Hour() < 12:
fmt.Println("中午之前")
default:
fmt.Println("中午之后")
}
切片
使用make关键字创建切片,类似变长数组。Go语言中切片被存储为一个长度,容量加指向数组的指针。
var 切片变量 = make([]类型,长度, 容量(可不填,默认等于长度,不能指定小于长度的值))。
使用append向切片中追加元素,由于可能发生的扩容操作会返回新的切片,所以需要赋值给原切片。
s := make([]string, 3)
s = append(s, "d")
map
声明并取map中的值时可以通过逗号分隔多定义一个布尔类型变量存储是否取到该值。
m := make(map[string]int)
r, ok := m["unknow"]
range
range关键字用于for循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。
遍历数组时声明的第一个变量为索引,第二个变量为索引对应的值。
遍历map时声明的第一个变量为key,第二个变量为value。遍历map时也可只声明单个变量,此时为key。
nums := []int{2, 3, 4}
for i, num := range nums {
fmt.Println("index:", i, "num:", num)
}
m := map[string]string{"a": "A", "b": "B"}
for k, v := range m {
fmt.Println(k, v)
}
for k := range m {
fmt.Println("key", k)
}
函数
函数可以返回多个不同类型的参数,连续的同类型的参数可以只在最后一个变量后声明类型,其后仍可以定义其他参数。
func exists(m map[string]string, k string) (j, v string, ok bool) {
v, ok = m[k]
j = "run"
return j, v, ok
}
指针
变量定义为指针类型,可在函数内变更原变量的值。方法参数为指针类型,调用时需要传入变量的地址。
func add2ptr(n *int) {
*n += 2
}
func main() {
add2ptr(&n)
}
结构体
结构体通过方法调用实现内容变更时与普通类型变量一致,需要将函数参数定义为指针形式,调用时传入结构体地址。
如果是相同内容的无复杂类型的结构体创建的对象,可以直接使用==对比值和指针。
type user struct {
name string
password string
}
a := user{name: "wang", password: "1024"}
b := user{"wang", "1024"}
fmt.Println(a == b) // true
fmt.Println(a.equal(b)) // true
错误处理
Go语言中符合语言习惯的错误处理方式即使用一个单独的返回值来传递错误信息。
空白标识符"_"是一个只写变量,被用于抛弃值。因为Go语言中要求使用所有被声明的变量。
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")
}
字符串
go 语言的字符串都以 UTF-8 格式保存,每个中文占用 3 个字节,使用 len() 可以获得两个中文文字对应的6个字节长度。
可以用utf8.RuneCountInString()获取utf8编码下的字符长度。
%v占位符,以默认形式打印值。
%+v占位符,打印结构体时包含字段名称。
%#v占位符,打印结构体时先输出结构体名称,在输出字段名与字段值。
b := "你好"
fmt.Println(len(b)) // 6
fmt.Println(utf8.RuneCountInString(b)) // 2
type point struct {
x, y int
}
p := point{1, 2}
fmt.Printf("p=%v\n", p) // p={1 2}
fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}
json处理
json.Marshal(v any) ([]byte, error)将数据编码成json字符串并返回编码结果与错误信息。
json.MarshalIndent(v any, prefix string, indent string) ([]byte, error)类似Marshal,但通过缩进实现格式化输出。
go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,之后正常处理。
string()可以将json编码字符串解析为普通字符串。
时间处理
时间格式化(time.Time).Format(layout string) string的固定占位符"2006-01-02 15:04:05",貌似是语言设计者认为这样容易记忆。上述占位符为24小时制,将其中的"15"替换为"3"后为12小时制。
t := time.Date(2023, 8, 7, 14, 48, 36, 0, time.UTC)
fmt.Println(t.Format("2006-01-02 15:04:05")) // 2023-08-07 14:48:36
fmt.Println(t.Format("2006-01-02 3:04:05")) // 2023-08-07 2:48:36
数字解析
以strconv.ParseInt(s string, base int, bitSize int) (i int64, err error)为例。
第一个参数传入可以表达整型的字符串;
第二个参数base指定进制(2到36),如果base为0,则会从字符串前置判断,"0x"是16进制,"0"是8进制,否则是10进制;
第三个参数bitSize指定结果必须能无溢出赋值的整数类型。0、8、16、32、64 分别代表 int、int8、int16、int32、int64 ;
返回的err是NumErr类型。
strconv.Atoi(s)等价于strconv.ParseInt(s, 10, 0)。
n, _ := strconv.ParseInt("0x1000", 0, 64) // 4096
n2, _ := strconv.Atoi("123") // 123
结语
没学Go语言之前,看相关代码觉得Go和Java有很多相似之处。真正学习后,发现还是有很多新特性以及简洁的语法,不过变量与类型的先后顺序倒过来是不太习惯。而且Go语言虽然对于面向对象有一定支持,但目前接触下来感觉更多还是面向过程的编程思想。因为本科时的C语言基础,结构体与指针的使用也可以接受。看了一些使用Go语言进行Web开发的视频,项目代码的简洁程度感觉真正能做到轻量级,而且性能也很高。
学习新语言的主要方式还是和自己常用的语言进行对比,编程语言之间的共性是较多的,花费多数时间学习新特性并通过编码验证即可快速入门新语言。