Golang语言在2009年诞生于谷歌,相较而言是一门年轻的语言。面对C++等老牌语言众多繁重的特性,几名谷歌员工希望能够甩开历史包袱设计一门更加简洁的编程语言,避免过度的设计,通过较少的特性组合连接就可实现复杂的功能。体现“少即是多”设计哲学。
以下是用一个Go实现的栈数据结构
package collection //声明当前代码文件所在的包,相同路径下的Go文件包名必须相同
import "errors" //导入error包,Go中错误只能显式的定义/返回/处理,不能抛出/捕获
//结构体struct:类似于Java中的类,但是结构体中只能定义属性,且不存在继承的概念,只有组合和接口
type Stack struct {
data []string //切片:类似于Java中的数组,但是Go中切片的大小是可动态扩展的,底层基于数组,更像是Java中的ArrayList
}
//函数func:参数前添加*号表示传递引用,不加表示传递数据的副本
func (s *Stack) Push(x string) { //访问权限:Go中函数和变量有两级访问权限,大写字母开头表示包外可访问,小写开头表示只在包内可见,例如Stack中的data变量
s.data = append(s.data, x) //append()是Go中的内置函数,可直接调用,用于向切片尾部添加一个元素,并返回新的切片
}
//函数支持多重返回值
func (s *Stack) Pop() (string, error) {
n := len(s.data) //len()也是内置函数 用于获取切片数组长度
if n == 0 {
//支持多重赋值
r, b := "", errors.New("Pop empty stack error!") //定义一个错误
return r, b //函数中定义的变量必须有使用否则会报错
}
res := s.data[n-1] //:=符号用于快速创建并赋值一个新变量(编译时会自动进行类型推断,Go本身是强类型语言)
s.data[n-1] = "" //为了避免内存泄漏
s.data = s.data[:n-1] //s.data[:n-1]表示返回对原始数组的一个新的切片视图[0,n-1),不会改变原数组元素
return res, nil //nil可表示切片、struct、接口等类型的空值
}
//返回栈的大小
func (s *Stack) Size() int {
return len(s.data)
}
使用这个栈
package main //main函数只有定义在main包下才可执行
import (
"HelloGo/collection" //导入collection包
"fmt" //Go的标准输入输出包
)
//输出: Hello, world!
func main() {
var s collection.Stack //var表示声明一个变量
pop, err := s.Pop()
if err != nil { //if条件可不带括号
fmt.Println(pop + err.Error()) //打印错误信息
}
s.Push("world!") //Go中没有构造器的概念,当声明一个变量而没有明确地初始化时,会自动初始化为默认值
s.Push("Hello, ") //对于结构体变量,即内部所有字段初始化为默认值,切片默认为nil表示初始化一个空数组。
for s.Size() > 0 { //故在这里s可以直接使用
res, _ := s.Pop() //使用下划线忽略第二个返回值
fmt.Print(res)
}
}
函数是Go的一等公民,被赋予了与其他数据类型(如整数、字符串和结构体)相同的地位。
//声明函数类型
type binOp func(int, int) int
//定义函数变量op
var op binOp
//声明并初始化函数变量add
add := func(i, j int) int { return i + j }
op = add//赋值
//函数调用
n := op(100, 200) // n = 100 + 200
使用go关键字可直接开启一个协程执行对应函数,channel通道用于在协程间通信
package main
import (
"fmt"
"time"
)
func main() {
//内置函数make()用于创建指定初始大小的哈希表、通道、切片等
ch := make(chan int, 10) //创建缓冲区大小为10字节的channel通道
go func() { //go关键字可直接开启一个协程执行对应函数
time.Sleep(time.Second*5)
ch <- 123 //在goroutine中向channel发送数据
}() //这里定义了一个匿名函数,后面的()为传参
val := <-ch //主goroutine阻塞式从channel接收数据
fmt.Println(val)
close(ch) //关闭后可读不可写
}
WaitGroup:用于等待一组goroutines的完成。
Mutex:Go中的锁,用于保护共享资源,防止协程同时访问。
package main
import (
"fmt"
"sync" //并发包
)
func main() {
var wg sync.WaitGroup
var mu sync.Mutex //独占锁
var num int
for i := 1; i <= 5; i++ {
wg.Add(1) //增加一个计数
go func() {
defer wg.Done() //减少一个计数,defer确保函数执行完毕后wg.Done()再执行,类似finally块
mu.Lock() // 获取锁
num++ // 修改共享资源
mu.Unlock() // 释放锁
}()
}
wg.Wait() // 等待所有goroutine完成,即wg内部计数为0
fmt.Println(num) //5
}
Cond:条件等待队列,用于在特定条件下阻塞或唤醒一个或多个goroutines。
当在函数中使用 defer 关键字时,它会使后面的函数调用被推迟执行,直到当前函数的执行结束。这意味着无论函数是正常返回还是发生了异常,defer 的函数都会被执行,类似Java中的finally块。如果在同一个函数中多次使用 defer,它们的执行顺序是后进先出(LIFO),也就是最后一个 defer 的函数最先执行,依次类推。
defer语句只会影响其声明位置之后的代码。
Go有两种错误处理机制:
package main
import "fmt"
func main() {
arr := []int{1, 2, 3}
defer func() {
//if可接受一个初始化语句,该语句通常用于设置局部变量
if r := recover(); r != nil { //使用recover函数可捕获panic
fmt.Println("发生了运行时错误:", r)
}
}()
for i := 0; i <= 4; i++ {
value := arr[i] // 当i=3时数组越界,触发panic
fmt.Println("数组元素值:", value)
}
}
参考:https://zhuanlan.zhihu.com/p/492270360