这是我参与「第五届青训营 」笔记创作活动的第1天。
前言
相信参加青训营的不少小伙伴都是Java选手,对于Golang语言先前可能没有使用经验,不过关系不大,学习基本语法之后,便可上手。
那么,与Java相比,Golang有哪些不同之处呢?
- 高性能、高并发
- 编译型语言,不依赖于JVM
- 不带继承、多态的OOP
- 没有异常处理机制
- 基于首字母大小写的访问特性
- 基于Channel的并发,Java对应于JUC
- 生态来说,目前没有Java丰富,不过云原生时代,具有十分大的潜力
私以为,语法层面的联系都是一脉相承的,重点关注语言特性即可,例如Golang的Channel机制。
语法层面
变量
Golang是强类型语言,每个变量都有其类型。
package main
import (
"fmt"
"math"
)
func main() {
var a = "initial"
var b, c int = 1, 2
var d = true
var e float64
f := float32(e)
g := a + "foo"
fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
fmt.Println(g) // initialapple
const s string = "constant"
const h = 500000000
const i = 3e20 / h
fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
}
数组
长度固定,一般很少使用,使用切片比较多。不能进行扩容,在复制和传递过程中均为值复制
package main
import "fmt"
func main() {
var a [5]int
a[4] = 100
fmt.Println("get:", a[2])
fmt.Println("len:", 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("2d: ", twoD)
}
切片
切片,可以理解成动态数组。
package main
import "fmt"
func main() {
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get:", s[2]) // c
fmt.Println("len:", len(s)) // 3
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s) // [a b c d e f]
c := make([]string, len(s))
copy(c, s)
fmt.Println(c) // [a b c d e f]
fmt.Println(s[2:5]) // [c d e]
fmt.Println(s[:5]) // [a b c d e]
fmt.Println(s[2:]) // [c d e f]
good := []string{"g", "o", "o", "d"}
fmt.Println(good) // [g o o d]
}
底层实现
// runtime/slice.go
type slice struct {
array unsafe.Pointer // 元素指针
len int // 长度
cap int // 容量
}
和数组的区别
slice 的底层数据是数组,slice 是对数组的封装,它描述一个数组的片段。两者都可以通过下标来访问单个元素。
数组是定长的。在 Go 中,数组是不常见的,因为其长度是类型的一部分,限制了它的表达能力,比如 [3]int 和 [4]int 就是不同的类型。
而切片则非常灵活,它可以动态地扩容。切片的类型和长度无关。
一言以蔽之
- 切片是对底层数组的一个抽象,描述了它的一个片段。
- 切片实际上是一个结构体,它有三个字段:长度,容量,底层数据的地址。
- 多个切片可能共享同一个底层数组,这种情况下,对其中一个切片或者底层数组的更改,会影响到其他切片。
append函数会在切片容量不够的情况下,调用growslice函数获取所需要的内存,这称为扩容,扩容会改变元素原来的位置。- 扩容策略并不是简单的扩为原切片容量的
2倍或1.25倍,还有内存对齐的操作。扩容后的容量 >= 原容量的2倍或1.25倍。 - 当直接用切片作为函数参数时,可以改变切片的元素,不能改变切片本身;想要改变切片本身,可以将改变后的切片返回,函数调用者接收改变后的切片或者将切片指针作为函数参数。