title: 初识go语言
tag:
- golang
- 编程语言
categories: notes
简介
Go语言是一门全新的静态类型开发语言,与当前的开发语言相比具备众多令人兴奋不已的新特性。最主要的新特性如下:
- 自动垃圾回收
- 更丰富的内置类型
- 函数多返回值
- 错误处理
- 匿名函数和闭包
- 类型和接口
- 并发编程
- 反射
- 语言交互性
自动垃圾回收
手动管理内存的一个问题就是由于指针的到处传递而无法确定何时可以释放该指针的指向的内存块。假如在代码中的某个位置我们释放了内存,而另一些地方还在使用指向这块内存的指针,那么这些指针就变成了所谓的野指针或者悬空指针,对这些进行任何读写操作都会导致不可预料的后果。就比如以下的c++代码,进行内存释放。
int *p = new int;
p +=10 ; //这里对指针进行了偏移,因此那块内存不再被引用
// ...... 这里可能发生针对这块int内存的垃圾回收。
p -=10; //居然又偏移到原来的位置
*p = 10; //如果有垃圾收集,这里就无法保证可以正常运行了
所谓的垃圾回收,即所有的内存分配动作都会被在运行时记录,同时任何对该内存的使用也都会被记录,然后垃圾回收器一会对所有已经分配的内存进行跟踪监测,一旦发现有些内存已经不再使用,就会阶段性的回收这些没人用的内存。
更丰富的内置类型
除了几乎所有语言都支持的简单内置类型外,Go语言也内置了一些比较新的语言学中的内置的高级类型,比如C#和Java中的数组和字符串,除此之外,Go语言中还内置了一个对于其他静态类型语言通常用库支持的字典类型。另外又一个新增的数据结构,数组切片,我们可以认为数组切片是一种可动态增长的数组。
函数多返回值
目前主流语言中除了Python外基本都不支持函数的多返回值功能,比如我们如果要定义一个函数用于返回个人名字信息,而名字信息因为包含多个部分—-姓名,中间名和别名,在不支持多返回值的语言中我们又以下两种做法,要么专门定义一个结构体用于返回,要么以传出参数的方式返回多个结构使用指针。
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'
}
并不是每一个返回值都必须赋值,没有明赋值的返回值将保留默认的空值,函数的调用也比较简单
fn,mn,ln,nn := getName()
如果我们只对函数其中的某几个返回值感兴趣的话。也可以直接用下划线作为占位符来忽略其他不关心的返回值。
_,_,lastName,_:=getName()
错误处理
Go语言引入了关键字defer用于标准的错误处理流程,并提供了内置函数panic,recover完成异常的抛出与捕获。
匿名函数和闭包
f := func(x,y int)int{
return x+y
}
接口类型
Go语言的类型定义非常接近于C语言中的结构,甚至沿用了struct关键字。相比而言,Go语言并没有直接沿袭c++和JAVA的传统去设计一个超级复杂的类型系统,不支持继承和重载,而只是支持了最基本的类型组合功能。
Go语言并不是简单的对面向对象开发语言做减法,他还引入了一个无比强大的“非侵入式”接口的概念,在C++中,我们通常会这样来确定接口和类型的关系。
//抽象接口
interface IFly{
virtual void Fly()=0;
};
//实现类
class Bird:public IFly{
public:
Bird(){}
virtual ~Bird(){}
public:
void Fly(){
//以鸟的方式飞行
}
};
void main(){
IFly *pFly = new Bird();
pFly ->Fly();
delete pFly;
}
显然,在实现一个接口之前必须先定义该接口,并且将类型和接口紧密绑定,即接口的修改会影响到所有实现了该接口的类型。而Go语言的接口避免这类问题
type Bird struct{
...
}
func (b *Bird) Fly(){
//以鸟的方式飞行
}
我们在实现Bird类型时完全没有任何IFLY的信息,我们可以在另一个地方定义这个IFly接口:
type IFly interface {
Fly()
}
这两者目前看起来完全没有关系,现在看看我们怎么使用他们
func main(){
var fly IFly = new(Bird)
fly.Fly()
}
并发编程
Go语言引入了goroutine概念,他使得并发编程便得很简单,通过使用goroutine而不是裸用操作系统的并发机制,以及使用消息传递来共享内存而不是使用共享内存来进行通信。
通过在函数调用前使用关键字go,我们即可让该函数以goroutine方式执行,goroutime是一种比线程更加轻盈,更省资源的协程。Go语言通过系统的线程来多路派遣这些函数的执行,使得每个用go关键字执行的函数可以运行成为一个单位协程,当一个协程阻塞的时候,调度器就会自动把其他协程安排到另外的线程中去执行,从而实现了程序无等待并行化运行。而且调度的开销非常小。
Go语言实现了CSP(通信顺序进程)模型来作为goroutine间推荐通信方式。在CSP模型中,一个并发系统由若干并行运行的顺序进程组成,每个进程不能对其他进程的变量赋值。进程之间只能通过一对通信原语实现协作。Go语言用channel(通道)这个概念来轻巧地实现CSP模型,channel的使用方式比较接近Unix系统中的管道pipe概念,可以方便的进行跨goroutine的通信。
另外。由于一个进程内创建的所有goroutine运行在同一个地址内存空间,因此如果不同的goroutine不得不去访问共享的内存变量,访问前应该先获取相应的读写锁,Go语言标准库中的sync包提供了完备的读写锁功能。
一个简单的例子来演示goroutine和channel的使用方式,这是一个并行计算的例子,用两个goroutine进行并行的累加计算,待两个计算过程都完成后打印计算结果。
package main
import "fmt"
/*
这是一个并行计算的例子,由两个goroutine进行并行的累加计算
*/
func sum(values []int, resultChan chan int) {
sum := 0
for _, value := range values {
sum += value
}
resultChan <- sum //将计算结果发送到channel中
}
func main() {
values := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
resultChan := make(chan int, 2)
go sum(values[:len(values)/2], resultChan)
go sum(values[len(values)/2:], resultChan)
sum1, sum2 := <-resultChan, <-resultChan //接收结果
fmt.Println("Result:", sum1, sum2, sum2+sum1)
}
反射
通过反射,你可以获取对象类型的详细信息,并可动态操作对象。反射最常见的使用场景是做对象的序列化。
package main
/*
利用反射功能列出某个类型中所有成员变量的值
*/
import (
"fmt"
"reflect"
)
// 创建一个新类型
type Bird struct {
Name string
LifeExpectance int
}
func (b *Bird) Fly() {
fmt.Println("I'm 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())
}
}
语言交互性
由于Go语言和C语言有天生联系,Go语言的设计者们自然不会忽略如何重用现有C模块的这个问题,这个功能直接被命名为Cgo,Cgo即是语言特性,同时也是一个工具的名称。
在Go语言中,可以按Cgo的特定语法混合编写C语言代码,然后Cgo工具可以将这些混合的C代码提取并生成对于C功能的调用包装代码。这里需要安装gcc编译环境
package main
/*
在Go语言中调用C语言标准库的puts函数
#include<stdio.h>
#include<stdlib.h>
*/
import (
"C"
"unsafe"
)
func main() {
cstr := C.CString("Hello, world")
C.puts(cstr)
C.free(unsafe.Pointer(cstr))
}