Golang:语法

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

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

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

查看所有标签

猜你喜欢:

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

AJAX企业级开发

AJAX企业级开发

Davec Johnson、Alexeic White、Andrec Charland / 张祖良、荣浩、高冰 / 人民邮电出版社 / 2008 / 49.00元

本书首先解释了AJAX 为什么在大规模的开发中能有如此广阔的应用前景,接着系统地介绍了当前重要的AJAX 技术和组件。你将看到把数据表、Web 窗体、图表、搜索和过滤连接在一起用于构建AJAX应用程序的框架开发的整个过程;在此基础上,本书给出了已经过证实的AJAX 架构模式,以及来源于实际的.NET 和Java AJAX 应用程序的案例研究。一起来看看 《AJAX企业级开发》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

在线进制转换器
在线进制转换器

各进制数互转换器