初学者眼中的Go语言
初学者眼中的Go语言
最近花了一些时间过了一遍Go编程语言中的Go语言之旅, 也算是入门了, 下面谈谈我认知的Go语言
类型
不像绝大数语言,类型定义在了变量名之前,Go中类型在变量名之后
,如下:
1 | func add(x int, y int) int { |
官方也在博客Go’s Declaration Syntax中给出了理由, 但可能是由于惯性, 我仍然不是很喜欢这种设计🤥
关于变量初始化
:
1 | var i, j int = 1, 2 |
幸亏变量初始化时类型声明可以忽略, 不然真的看上去很怪, 忽略后一看, 这不是JavaScript吗🥰
Go没有while
绝大多数编程语言都是有while循环的, 虽然while循环的本质还是for循环, Go说: for是Go中的while
1 | func main() { |
defer
defer 语句会将函数推迟到外层函数返回之后执行。其他编程语言中没见过这个特性, 但在Go中, 它被广泛使用1
2
3
4
5
6
7
8
9func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
//输出结果
hello
world
在实际使用上来说, 这有点像Java和Python中的finally
, 但不同的是, finally
是异常处理的一部分, 靠控制流机制实现, 而defer
是通过栈机制实现的。推迟调用的函数调用会被压入一个栈中。 当外层函数返回时,被推迟的调用会按照后进先出的顺序调用。
切片
说到切片其实很容易让人想到Python, 它和Go中的切片有什么不同点呢?
通用性: Python中的切片操作非常通用,可以对列表、元组、字符串等进行切片。Go中的切片是一种具体的数据结构,只能用于切片类型。
语法: 两者语法相似, 都可以写做[a,b,c], 但Python中a,b,c的取值可以是负值, 而Go中不可以, 并且c这个位置在两种语言中的含义也不相同, Python中的c表示步长, 而Go中的c表示切片的容量。
返回值: Python切片操作返回一个新的对象,与原对象在内存中是分离的。而Go中的切片操作返回的是原切片的一个视图,共享相同的底层数组。
1 | func main() { |
切片的长度和容量之间的关系有以下几点:
- 切片的长度(
len
)总是小于或等于其容量(cap
)。 - 当切片被创建时,长度和容量通常是相等的,但如果从一个较大的数组或切片创建一个新的切片时,新切片的容量可能会大于其长度。
- 当使用内建函数
append
向切片追加元素时,如果追加的元素超出了当前长度但未超出容量,切片的长度会增加,而容量保持不变。 - 如果
append
导致切片长度超过容量,Go运行时会分配一个新的底层数组,并将现有的元素和新元素复制到这个新数组中。这时,切片的容量会变为新的长度值,因为新数组的大小至少是新长度的两倍(具体增长策略可能更复杂,但至少是这个保证)。 - 切片可以通过重新切片(reslicing)来减少长度,但这样做不会改变其容量。重新切片的语法是
slice[low:high]
,其中low
是切片的起始索引,high
是结束索引(不包括)。如果省略low
,则默认从切片的开始;如果省略high
,则默认到切片的末尾。
并发编程
Go的并发机制个人认为比Java要更容易上手, 协程和信道的设计加上go
关键字启动, 使得并发变得更加简单
谈谈Go和Java的区别:
并发模型的本质: Java是基于线程的并发模型,其中每个线程都是操作系统的轻量级进程。Go语言采用了基于协程(goroutine)的并发模型。Goroutines是轻量级的线程,由Go运行时管理,而不是操作系统。
内存模型和同步: Java有一个明确定义的内存模型,它涵盖了变量的可见性、有序性和原子性。Java提供了多种同步机制,如synchronized关键字、Lock接口和volatile关键字,以确保并发安全。Go也有自己的内存模型,它通过goroutines和channels来保证数据的同步。Go鼓励使用channels进行通信,而不是传统的锁机制。这有助于减少竞争条件和死锁的问题。
运行时管理: Java的并发管理主要由JVM负责。Go的并发管理由Go运行时负责。
性能和可扩展性: Java线程的创建和上下文切换开销相对较高。因此,Java程序在创建大量线程时可能会遇到性能问题。goroutines的创建和上下文切换开销非常低,这使得Go程序能够轻松创建数以万计的goroutines。Go的运行时能够更有效地在多个OS线程上调度这些goroutines。