Go是一门比较新的语言,是由一群牛人在Google推出的,这些牛人大多来自贝尔实验室的Plan 9操作系统项目和Limbo语言设计项目,因此大多认为Go的前身就是Limbo。同时这些牛人中有C语言的设计者Ken Thompson,因此Go的设计里面也包含了一些C的思想或者去解决一些C语言上效率的问题。
它的主要语言特性有:
果然是一门“现代”语言,一门“更强的C语言”。
这个系列的几篇博客,主要就是记录下Go的基础要点,以及一些学习过程中的思考,也希望对有缘的人有所帮助。
本文中的内容绝大多数都参考了大牛许式伟的《Go语言编程》,所以也相当于是学习笔记了。
开始一门语言的学习,基本上先要了解这门语言的程序是怎样组织起来的,代码的基础组成是怎样的,怎样能让一个程序运行起来。Go在程序结构上就体现出了它的先进性。 如下就是一个非常简单的Go程序:
package main
import "fmt"
import "os"
import "strings"
func f(x int) int {
if x == 0 {
x = 5
} else {
x = 7
}
return x
}
func main() {
fmt.Println(os.Args[0])
for index, arg := range os.Args[1:] {
fmt.Println(index, ":", arg)
}
fmt.Println(strings.Join(os.Args[1:], " "))
fmt.Println(f(3))
fmt.Println(f(5))
}
除了上面的一些关键字外,还有个特点,支持多重赋值,for循环的index,arg两变量就是在一条语句中赋值的,这个特点也非常使用。
实现很多很多这种格式的代码文件,然后通过import关联起来,这样就组成了我们的Go项目了。
<calcproj> ├─<src>
├─<calc>
├─calc.go
├─<simplemath>
├─add.go
├─add_test.go
├─sqrt.go
├─sqrt_test.go
|─<bin>
将calcproj目录加到环境变量GOPATH中,export GOPATH=$GOPATH:xxx:xxx:calcproj,然后运行go build calcproj即可获得相关的执行文件了。
编译是不是也非常简单?工程效率太高了,不用管理复杂的makefile了。
var i int
。第三种方法最简单,这个该说是真现代,还是真懒呢?但是用多了,可能就会潜移默化吧。第二种和第三种编译器都会去根据赋的值推到出变量类型。但需要铭记的是Go是一个强类型的静态语言,即使这个特定和一些动态语言类型,它也是编译器静态编译出确切的类型的。
_, a, _ = f()
假设f返回3个值,a获得第二个值const (
c0 = iota // c0 = 0
c1 = iota // c1 = 1
c2 = iota // c2 = 2
)
const (
c3 = 1 << iota // c3 = 1
c4 = 1 << iota // c4 = 2
)
const (
c5 = 1 << iota // c5 = 1
c6 // c6 = 2
c7 // c7 = 4
)
在一些特殊场景下,用起来还是非常方便。
除了常见类型,支持一些常见的其他语言需要用库来实现的类型。
除了C中支持的类型外,还额外支持如下类型,或者和C有一些小的区别:
value1 := 3.2 + 12i
value2 := complex(3.2, 12)
[100] *int
,这表示申明了一个int型的指针数组a := [10]int{1, 2, 3, 4, 5}
c := &a
c[3] = 1
这样a就被修改了。
v := make([]int)
,效果等同。var v map[string] int
,string是key的类型,int是value的类型v = make(map[string] int)
v["beijing"] = 0
delete(v, "beijing")
value, ok = v["beijing"]
,ok是一个bool值,通过它判断是否查找到元素是不是非常使用,增加的类型基本上都是日常工作中经常要使用的,在一些其他语言中,往往需要以来库来实现,增加了理解和调试的难度,同时Go�又没有随便增加一些从语言层面很难理解的类型,或者小众类型。
所以,我还是认为Go就是一门充分考虑工程的语言,而不是一些金玉其外的语言。
类型系统,是指一个语言的类型体系结构。一个典型的类型体系结构需要考虑如下内容(在这里需要说明下,下面内容是大牛许式伟Go语言编程中专门提及的):
Go语言中一个非常特殊的地方在于,任何类型,都可以直接添加额外的方法,而不用继承或者其他高级语言的一些特性来实现。
比如给常见的int类型增加一个比较的方法,可以用如下方法实现:
type Integer int
func (a Integer)Less(b Integer) bool {
return a < b
}
var a Integer = 3
if a.Less(2) {
fmt.Println("a is less than 2")
} else {
fmt.Println("a is more than 2")
}
非常非常简单!
Go语言中只有如下四种类型看起来像引用类型,其他类型都是纯粹的值类型,包括数组:
Go语言的结构仅仅具有组合的功能,不同于C++中那么丰富的重载、继承等丰富。另外结构体函数的支持,也如前面3.2.1介绍的和其他类型一样支持。
需要注意下结构体初始化的方式有一下几种:
rect1 := new(Rect)
rect2 := &Rect{}
rect3 := &Rect{0, 0, 100, 200}
rect4 := &Rect{width: 100, height: 200}
确切的说,Go语言也提供了“继承”的功能,但是采用组合的文法。
这种类型提供了一些比较灵活的对象处理方式,计划后续专门写文章探讨这个类型。
除了类型,计算机语言第二个比较基础的部分就是流程控制。Go语言提供了和C语言类似的流程控制语义。有些流程控制在前面的例子也有所提及。
函数在前面例子中已经多次提到,这里仅仅说一些和C语言中函数不太一样的地方。
Go语言在函数中传递不定参数非常容易,仅需要在函数定义时使用...type
“类型”接口,例如:
func myfunc(args ...int) {
for _, arg := range args {
fmt.Println(arg)
}
}
上面这段简单的代码就实现一个可以传入不定参数的函数,每个参数类型是int。
那么在其他函数中就可以通过如下两种形式进行访问:
func myfunc3(args ...int) {
// 使用不定个数的int参数进行调用
myfunc(2, 3, 4)
// 按照原样传递参数
myfunc(args...)
// 传递不定参数的数组切片
myfunc(args[1:]...)
}
如果传入的不定参数类型不一样,只需要在定义的时候使用interface{}即可。interface是一个特殊的接口,类似于java中的java.lang.Obeject,可以引用任意对象。具体内容在后续面向对象的内容中会介绍。
func Printf(format string, args ...interface{}) {
// ...
}
前面介绍过,函数可以返回很多值,同时通过匿名变量获得自己感兴趣的返回值。非常简单,不多废话。
就是没有名字的函数,可以直接赋值给一个变量,后续就用变量那调用该函数,也可以作为代码块直接执行。
匿名函数主要就是为了实现“闭包”的概念。闭包可以引用到函数外的变量,只要闭包还被使用,相关变量就会一直存在。
func main() {
var j int = 5
a := func()(func()) {
var i int = 10
return func() {
fmt.Printf("i, j: %d, %d\n", i, j)
}
}()
a()
j *= 2
a()
}
上述代码中将一个匿名函数赋给a,func()(func())
,即没有输入参数,返回参数是另外一匿名函数,主要功能就是打印变量。
它和直接在函数中打印对应的变量有什么区别呢?如果要在这段代码中匿名函数外修改i的值是不可能的,因为它被匿名函数包围起来,只能在匿名函数内部使用,反而j可以被匿名函数访问。也就是说匿名函数对i起到了保护作用,就是将i“包”了起来。
同时定义的匿名函数最后有对括号,它可以用来传递输入参数,同时执行匿名函数。上面代码对意思也就是说,定义了一个没有输入参数,返回值是匿名函数的匿名函数,同时直接运行,将返回的匿名函数赋值给了a。比较绕,但多想想就明白了。
其他Go语言入门文章: