语言特性
- 自动垃圾回收
- 更丰富的内置类型
- 函数多返回值
- 错误处理
- 匿名函数和闭包
- 类型和接口
- 并发编程
- 反射
- 语言交互性
自动垃圾回收
编程中,一个难题就是内存管理,如果管理不好对内存的使用,发生内存泄露可能会引发严重的问题,比如使程序崩溃。C和C++这样的编程语言,提供了指针这样灵活的类型,但却必须手动管理内存,增加了编程的复杂性。Java等编程语言本身提供了自动的垃圾回收机制,减少了编程的不少负担。go语言融合了众多编程语言的优秀特点,自然也引入了自动的垃圾回收机制。
所谓垃圾回收,即所有的内存分配动作都会被在运行时记录,同时任何对该内存的使用也都会被记录,然后垃圾回收器会对所有已经分配的内存进行跟踪监测,一旦发现有些内存已经不再被任何人使用,就阶段性地回收这些没人用的内存。当然,因为需要尽量最小化垃圾回收的性能损耗,以及降低对正常程序执行过程的影响,现实中的垃圾回收算法要比这个复杂得多,比如为对象增加年龄属性等,但基本原理都是如此。
更丰富的内置类型
go语言不仅提供了几乎所有语言都提供的整形,浮点型等简单类型,数组和字符串等高级类型,还提供了字典类型(map),数组切片(Slice)。
函数多返回值
Go语言革命性地在静态开发语言阵营中率先提供了多返回值功能。这个特性让开发者可以从原来用各种比较别扭的方式返回多个值的痛苦中解脱出来,既不用再区分参数列表中哪几个用于输入,哪几个用于输出,也不用再只为了返回多个值而专门定义一个数据结构。
go语言函数返回多个值的简单示例:
func getName() (firstName, middleName, lastName, nickName string) {
return "May", "M", "Chen", "Babe"
}
因为返回值都已经有名字,因此各个返回值也可以用如下方式来在不同的位置进行赋值,从而提供了极大的灵活性:
func getName() (firstName, middleName, lastName, nickName string) {
firstName = "May"
middleName = "M"
lastName = "Chen"
nickName = "Babe"
return
}
并不是每一个返回值都必须赋值,没有被明确赋值的返回值将保持默认的空值。
多返回值函数调用:
fn, mn, ln, nn := getName()
如果只对该函数其中的某几个返回值感兴趣的话,也可以直接用下划线作为占位符来忽略其他不关心的返回值:
_, _, lastName, _ := getName()
错误处理
Go语言引入了3个关键字用于标准的错误处理流程。
- defer
- panic
- recover
与C++和Java等语言中的异常捕获机制相比, Go语言的错误处理机制可以大量减少代码量,让开发者也无需仅仅为了程序安全性而添加大量一层套一层的try-catch语句。
代码中过多的嵌套,真是让人抓狂。
匿名函数和闭包
在Go语言中,所有的函数也是值类型,可以作为参数传递。
类型和接口
Go语言不支持继承和重载,而只是支持了最基本的类型组合功能。
Go语言引入了一个无比强大的“非侵入式” 接口的概念,让开发者从以往对C++和Java开发中的接口管理问题中解脱出来。
go语言简单的接口使用示例:
type Bird struct {
...
}
func (b *Bird) Fly() { //以鸟的方式飞行
}
在实现Bird类型时完全没有任何IFly的信息。我们可以在另外一个地方定义这个IFly。
type IFly interface {
Fly()
}
上面两个定义看起来丝毫没有关系,现在看看如何使用它们:
func main() {
var fly IFly = new(Bird)
fly.Fly()
}
可以看出,虽然Bird类型实现的时候,没有声明与接口IFly的关系,但接口和类型可以直接转换,甚至接口的定义都不用在类型定义之前,这种比较松散的对应关系可以大幅降低因为接口调整而导致的大量代码调整工作。
并发编程
Go语言引入了goroutine概念,它使得并发编程变得非常简单。通过使用goroutine而不是裸用操作系统的并发机制,以及使用消息传递而不是共享内存来通信,Go语言让并发编程变得更加轻盈和安全。
通过在函数调用前使用关键字go,我们即可让该函数以goroutine方式执行。goroutine是一种比线程更加轻盈、更省资源的协程。Go语言通过系统的线程来多路派遣这些函数的执行,使得每个用go关键字执行的函数可以运行成为一个单位协程。当一个协程阻塞的时候,调度器就会自动把其他协程安排到另外的线程中去执行,从而实现了程序无等待并行化运行。而且调度的开销非常小,这使得我们能够创建大量的goroutine,从而可以很轻松地编写高并发程序。
Go语言实现了CSP(通信顺序进程,Communicating Sequential Process)模型来作为goroutine间的推荐通信方式。在CSP模型中,一个并发系统由若干并行运行的顺序进程组成,每个进程不能对其他进程的变量赋值。进程之间只能通过一对通信原语实现协作。Go语言用channel(通道) 这个概念来轻巧地实现了CSP模型。channel的使用方式比较接近Unix系统中的管道(pipe)概念,可以方便地进行跨goroutine的通信。
另外,由于一个进程内创建的所有goroutine运行在同一个内存地址空间中,因此如果不同的goroutine不得不去访问共享的内存变量,访问前应该先获取相应的读写锁。Go语言标准库中的sync包提供了完备的读写锁功能。
反射
Go语言的反射实现了反射的大部分功能,但没有像Java语言那样内置类型工厂,所以无法做到像Java那样通过类型字符串创建对象实例。
反射最常见的使用场景是做对象的序列化(serialization)。 例如,Go语言标准库的encoding/json、encoding/xml、encoding/gob、encoding/binary等包就大量依赖于反射功能来实现。
go语言发射的简单示例:
package main
import (
"fmt"
"reflect"
)
type Bird struct {
Name string
LifeExpectance int
}
func (b *Bird) Fly() {
fmt.Println("I am flying...")
}
func main() {
sparrow := &Bird{"Sparrow", 3}
s := reflect.ValueOf(sparrow).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
fmt.Printf("%d: %s %s = %v\n", i, typeOfT.Field(i).Name, f.Type(),
f.Interface())
}
}
输出结果:
0: Name string = Sparrow
1: LifeExpectance int = 3
语言交互性
Go语言为了方便的重用现有的C模块,提供了Cgo。
在Go代码中,可以按Cgo的特定语法混合编写C语言代码,然后Cgo工具可以将这些混合的C代码提取并生成对于C功能的调用包装代码。
Go语言使用C模块的简单示例:
package main
/*
#include <stdio.h>
*/
import "C"
import "unsafe"
func main() {
cstr := C.CString("Hello, world")
C.puts(cstr)
C.free(unsafe.Pointer(cstr))
}
语言结构
基础组成
- 包声明
- 引入包
- 函数
- 变量
- 语句 & 表达式
- 注释
简单示例
学习一门语言,从输出“Hello,world!”开始。
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")/* 输出Hello, World! */
}
-
package main
定义了包名。必须在源文件中非注释的第一行指明这个文件属于哪个包。package main
表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为main
的包。 -
import "fmt"
告诉 Go 编译器这个程序需要使用fmt
包中的内容。 -
func main()
是程序开始执行的函数。main
函数是每一个可执行程序所必须包含的。 -
/*...*/
是多行注释。你也可以使用单行注释//
-
fmt.Println("Hello, World!")
,使用fmt
包中的输出函数Println
将“Hello, World!”字符串输出到控制台。
执行go程序
- 在你的机器上,安装go语言开发环境。
- 在你设置的工作目录下,新建hello.go文件。
- 将上面示例代码复制到hello.go文件中。
- 进入hello.go所在目录,执行go run hello.go命令行
$ go run hello.go
Hello, World!