Golang:语法

栏目: Go · 发布时间: 7年前

内容简介: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
}

不一样的地方:

  • 弱化了常量
  • 程序员 的角度来看整数类型:int8、int16、int32等
  • 不同长度的整数混用时会报错(在 Java 中会自动提升)
  • 字符串的比较貌似和equals差不多了,会比较内容

高级类型

数组和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
		}
	}
}

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Using Google App Engine

Using Google App Engine

Charles Severance / O'Reilly Media / 2009-5-23 / USD 29.99

With this book, you can build exciting, scalable web applications quickly and confidently, using Google App Engine - even if you have little or no experience in programming or web development. App Eng......一起来看看 《Using Google App Engine》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试