内容简介:Golang:语法
Golang也是有相当的一段历史了(学新东西总是慢人一步,好可怕),特点是:
- 跨平台
- 垃圾回收(毕业后一直在用有GC的语言)
- 编译快
为工业开发量身定做,语法感觉并没有那么完备(复杂)。从传统入门程序来看它大概是这个样子:
package main
import (
"fmt"
)
func main() {
fmt.Print("Hello World!")
}
下面来看一些细节,跟其他语言对比着学会快些。
基础类型
变量创建的第一种方式为(不知道学的哪门子编程语言的套路):
func main() {
var a int
fmt.Println(a) // 0
a = 1
fmt.Println(a) // 1
}
多个变量可以省一些var,合并起来一起声明:
func main() {
var (
a int
b int
)
fmt.Println(a, b) // 0 0
}
多个同类型的则可以更简单一些,包括赋值:
func main() {
var a, b int
fmt.Println(a, b) // 0 0
a, b = 3, 4
fmt.Println(a, b) // 3 4
}
常量的玩法和变量差不多,比较有意思和特别的自增,感觉为了省代码已经伤心病狂:
func main() {
const (
a = iota // 自增
b
c
)
fmt.Println(a, b, c) // 0 1 2
}
不一样的地方:
高级类型
数组和C里面的有点像,声明之后就有内存了,而且大小不能再变:
func main() {
var a [10]int
fmt.Println(a) // [0 0 0 0 0 0 0 0 0 0]
b := [2][2]int{[2]int{1, 2}, [2]int{3, 4}}
fmt.Println(b) // [[1 2] [3 4]]
c := [...]int{1, 2, 3}
fmt.Println(c) // [1 2 3]
d := [2][2]int{{1, 2}, {3, 4}}
fmt.Println(d) // [[1 2] [3 4]]
}
大小不确定的可以使用slice(跟 Python 有点像:joy:):
func main() {
a := [100]int{}
fmt.Println(len(a[10:80])) // 70
fmt.Println(cap(a[10:80])) // 90
// 追加
b := make([]int, 0)
b1 := append(b, 10)
fmt.Println(b1) // [10]
b2 := append(b1, 20, 30)
fmt.Println(b2) // [10 20 30]
b3 := append(b2, b2...)
fmt.Println(b3) // [10 20 30 10 20 30]
}
另一种超高频结构是map:
func main() {
a := map[int]map[int]int{
1: map[int]int{2: 3},
}
fmt.Println(a) // map[1:map[2:3]]
fmt.Println(a[1]) // map[2:3]
}
不一样的地方:
- 在用数组赋值时,会进行拷贝,而不是设置指针(值传递)
- 多维数组的长度必须一致
自定义类型
没有了class、enum,只能用struct来搞定一切:
// 定义结构体
type Mutex struct {
a string
b int
}
// 定义类型上的方法
func (m *Mutex)Lock() {}
func (m *Mutex)Unlock() {}
// 定义新类型
type MyMutex1 Mutex // 别名,不会集成方法
type MyMutex2 struct{ Mutex } // 会继承方法
func main() {
b := new(MyMutex2)
b.Mutex.Lock()
}
不一样的地方:
- 通过首字母大小写来控制权限
- 方法的声明和实现都放到结构体外面
控制与方法
控制语句变得更加灵活和“随意”,尝试将多行代码合并成一行:
func main() {
// 可以赋值,注意变量的作用域
if a, b := 1, 2; a < b {
fmt.Println(a, b) // 1 2
}
// 大名鼎鼎的GOTO回来了
var a int = 1
Label:
if a < 10 {
a += 1
goto Label
}
fmt.Println(a) // 10
// 三种循环形式
for a := 1; a < 10; a += 1 {
}
for a < 9 {
}
for {
break
}
// SWITCH的两种形式
switch {
case a < 1:
case a < 2:
}
switch "123" {
case "123":
fmt.Println("123") // 123
break
case "23":
}
}
比较新的Java也在变得随意,一些不一样的地方:
- while、do-while不见了
- goto回来了
- 省略了一些括号(感觉多一些括号会结构更清晰些)
一直把函数也当做了结构控制的一种方式,定义形式和多值返回都跟Python像:
func test(i1int, i2 []string)(o1int, o2string) {
i2[0] = "a"
return 1, "2"
}
func main() {
fmt.Println(test(1, []string{"abc"})) // 1 2
}
在方法中使用关键字defer来处理函数退出前的操作,按照LIFO的顺序执行(有点像finally的替代):
func test() {
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
}
func main() {
test() // 4 3 2 1 0
}
相对复杂一些的逻辑可以用函数封装,有没有看到些JavaScript中闭包的影子:
func test() {
defer func() {
fmt.Println("finish!")
}()
}
func main() {
test() // finish!
}
同时也可以将函数搞成个变量,当做参数、返回值什么的,跟普通的变量没啥区别了:
func main() {
add := func(aint)int {
return a + 1
}
fmt.Println(add(1))
}
不一样的地方:
- 多值返回
- 没有了异常机制
错误处理
在Golang中使用“恐慌”机制代替了异常机制:
- panic:中断执行,不影响defer的处理,如果没有在本函数中没有恢复,会在调用方产生“恐慌”
- recover:“恐慌”修复,必须在defer中调用
举个栗子:
func test() {
defer func() {
if x := recover(); x != nil {
fmt.Println(x) // 出错
}
}()
panic("出错")
fmt.Println("继续执行") // 不会执行
}
func main() {
test()
}
由于支持多值返回,所以错误的信息也经常可以顺带返回(而不是在方法结尾加个throws,感觉方便太多了,有点C的处理风格):
import (
"fmt"
"errors"
)
func test()(int, error) {
return 1, errors.New("abc")
}
func main() {
ret, err := test()
fmt.Println(ret, err)
}
不一样的地方:
- “错误”与“异常”分开两种方式进行处理,不用再管运行时异常什么的了
接口
接口的作用是定义函数的集合:
type MyInterface interface {
Get() int
Put(int)
}
具体实现不用显式地implements、entend相应的接口(原理是什么样的?):
type MyImpl struct {
value int
}
func (self *MyImpl)Get()int {
return self.value
}
func (self *MyImpl)Put(valueint) {
self.value = value
}
func test(i MyInterface) {
i.Put(1)
println(i.Get()) // 1
}
func main() {
var i MyInterface = new(MyImpl)
test(i)
}
要想获取接口具体是什么,可以在switch中使用type进行判断(只能在switch中用,比较奇怪的语法):
func main() {
var i MyInterface = new(MyImpl)
switch i.(type) {
case MyInterface:
println("got MyInterface!")
break
}
}
另外一种可以在switch外面判断的方法:
func main() {
var i interface{} = new(MyImpl)
t, ok := i.(MyInterface)
fmt.Println(t, ok) // &{0} true
}
不一样的地方:
- 不需要显式地实现接口,但是这样会不会容易漏掉实现一些方法
反射
拿到一个空接口时我们是比较懵逼的,因为压根不知道它里面有什么东西,用Java的同学会想到reflect,Go也有而且用法也差不多:
func main() {
var i interface{} = new(MyImpl)
t := reflect.TypeOf(i)
fmt.Println(t) // *main.MyImpl
fmt.Println(t.Kind()) // ptr
fmt.Println(t.Elem()) // main.MyImpl
fmt.Println(t.Elem().FieldByName("value")) // {value main int 0 [0] false} true
}
类似的可以通过反射来调用方法!
I/O
对文件的读写跟Java比较像,打开文件后直接读取字节:
import "os"
func main() {
buf := make([]byte, 1024)
f, _ := os.Open("/etc/passwd")
defer f.Close()
for {
n, _ := f.Read(buf)
if n == 0 {
break
}
os.Stdout.Write(buf[0:n])
}
}
带有缓存的读写像是搞了一层装饰:
import (
"bufio"
"os"
)
func main() {
buf := make([]byte, 1024)
f, _ := os.Open("/etc/passwd")
defer f.Close()
r := bufio.NewReader(f)
w := bufio.NewWriter(os.Stdout)
defer w.Flush()
for {
n, _ := r.Read(buf)
r.R
if n == 0 {
break
}
w.Write(buf[0:n])
}
}
其他的关于网络的,并没有涉及到什么新语法,后面再专门仔细看吧。
并发编程
并行所使用的不是线程、进程、协程,这里不纠结底层的原理,想并行处理用 go 即可,跟调用方法没多少区别:
func test(wstring, secint) {
time.Sleep(time.Duration(sec) * time.Second)
fmt.Println(w, " is ready!")
}
func main() {
go test("a", 2)
go test("b", 1)
fmt.Println("main")
time.Sleep(time.Duration(5) * time.Second)
// main
// b is ready!
// a is ready!
}
在并发编程领域有一个观点是用消息传递代替共享内存,于是便有了chan(消息的管道):
var c chan int
func test(wstring, secint) {
time.Sleep(time.Duration(sec) * time.Second)
c <- sec // 向 c 中写入数据
}
func main() {
c = make(chan int)
go test("a", 2)
go test("b", 1)
fmt.Println(<-c) // 1 从c中读取数据
fmt.Println(<-c) // 2 从c中读取数据
}
默认的情况下管道是没有缓存的,也就是说:
在数据没有被读走时,写会被阻塞;在没有写入数据时,读会被阻塞;
创建带缓冲的管道的方法是:
c = make(chan int, 4)
管道不用了可以使用close关闭管道,于是读取时判断是否关闭的方法为:
if a, ok := <-c; ok {
fmt.Println(a)
}
当需要从多个管道中读取数据时可以使用select(有点像I/O中的玩法):
var c chan int
var d chan int
func main() {
c = make(chan int, 4)
d = make(chan int, 4)
var i int
for {
select {
case i = <-c:
break
case i = <-d:
break
}
}
}
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Swift语法快速入门(一)之 Swift基础语法
- 在ES6中使用扩展语法有什么好处?它与rest语法有什么不同?
- Python 基础语法
- go语法
- JPQL 语言语法
- reStructuredText简明语法
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。