谷歌系统所使用的编程语言Go,近年来发展的越来越成熟、方便易用。现在,我们可以通过使用LiteIDE让Go语言编程变得更加简单。
第一步 语言基础
数据、类型、函数、控制
Go语言是一个很容易上手同时功能无比强大的编程语言。你可以将它看做是C的现代版,至于更多的东西,还有待你自己去发掘。Go语言有着清晰简明的静态语法结构,但它表现出来的确是一种动态的效果。它还可以编译成本地代码,但却像解释性语言那样去工作。
总的来说,Go语言是一门完全值得你去尝试的语言,同时本文将告诉你一切你上手这门语言所需要知识。在第二部分,我们将重点分析Go语言是如何处理对象和并发的。
安装
安装和配置Go语言,你只需要下载合适的二进制文件至正确的位置,再为Go工具定位那些文件所在的目录路径就好了。
如果你使用的是OSX或Windows操作系统,那么你可以使用安装包来完成这一些列工作。
在Windows下,我推荐MSI安装包,这个安装包虽然还在试验中,但其实它的表现非常不错,它能迅速地为你完成配置工作。 你需要的只是根据你的系统下载32位或64位安装包然后在你的电脑上运行就好了。
安装程序会默认在C:\Go目录进行安装。
你可以通过下面的小程序验证环境是否搭建完成:
package main
import "fmt"
func main() {
fmt.Printf("hello, world\n")
}
你可以使用记事本或其他文本编辑器编辑上面这段代码,并将代码保存为hello.go文件。然后打开终端,输入:
go run hello.go
接下来你就可以进行LiteIDE的安装并尝试运行程序了。
不管从什么方面来说,LiteIDE并不是编写Go程序的必备工具,你所需要的只是一个编辑器而已。这样说的确没错,但是对于一个新人,一个好的IDE可以使他更容易上手一门语言,并迅速投入开发。
美中不足的是,LiteIDE没有使用手册。
你可以从下面的网址上下载tar或zip压缩包
https://code.google.com/p/golangide/
下载完成后将其解压至合适的目录。如果你是用的是Windows,那你可能需要使用7z来解压。
如此简洁的安装过程必然不会为你创建快捷方式,因此你可能需要打开...\liteide\bin然后找到liteide.exe并手动创建快捷方式。
打开LiteIDE后你会看到欢迎界面:
现在我们暂时无需理会这些组件,让我们先从一个简单的小程序开始。
在工具栏上选择File->New或直接在欢迎界面上点击New按钮,在弹出的对话框中选择Go1 Command Project。LiteIDE将为你创建一个Go控制台工程,工程目录放在C:/Go/src下。如果你为你的项目起名Hello,那你的代码文件将被放在 C:/Go/src/Hello下。
LiteIDE会预先为你在工程目录下创建main.go和doc.go文件。main.go文件中包含以下内容:
// Hello project main.go
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello World!")
}
你可以点击工具栏上蓝色的编译执行按钮BR运行代码。你可以在在Build菜单中的B和BR按钮菜单中找到更多的关于编译和执行代码的命令。
如果你运行了你的程序,你可以在底部的编译输出(Build Output)窗口中看到程序运行结果。
如果程序没有运行,那么很有可能是你创建的工程类型不对或者文件路径错误。
LiteIDE中还有很多功能等待你去发掘,不过目前为止的这些已经足够我们使用Go语言了。
变量和简单数据类型
Go语言包含了你所期望的所有从uint8 到 float64的简单数据类型。
uint8 无符号8位 整型数(0 到 255)
uint16 无符号16位 整型数(0 到 65535)
uint32 无符号32位 整型数(0 到 4294967295)
uint64 无符号 64位 整型数(0 到 18446744073709551615)
int8 8位 整型数(-128 到 127)
int16 16位 整型数(-32768 到 32767)
int32 32位 整型数(-2147483648 到 2147483647)
int64 64位 整型数(-9223372036854775808 到 9223372036854775807)
float32 IEEE-754 32位 浮点数
float64 IEEE-754 64位 浮点数
complex64 复数 32位实数+32位虚数
complex128 复数 64位实数+64位虚数
byte uint8的别称
rune int32的别称
最大的惊喜就在于,Go语言支持复数类型的数据:
var z complex64
z = 1.0 + 2.0i
fmt.Println(z)
如果你想知道rune是什么,那么当你知道rune被用来存储一个Unicode字符的时候,这个问题也就应该迎刃而解了吧。换句话说,rune在Go语言中等价于字符(char)类型。
当然你也可以使用uint、int卷二uintptr这些依赖于系统类型(32位或64位)的整数类型。
另外一个新颖的地方,当你定义一个变量的时候,你变量的后面对其类型进行定义,而不是在前面。
当你在定义中初始化了变量,你无需对变量指定数据类型。如果在定义的时候未初始化,则变量将会被赋予0值:
var i=0
var x,y float32=1.0,2.0
和数字类型一样,Boolean 类型也有相似的特征。
编译器会完成相应的工作。
一个使用Go语言定义和初始化变量的简单例子:
x,y:=1,2
你也可以定义和使用常量。
数据结构
常用的数据结构有字符串(strings),数组(arrays)和结构体(structs),以及另一位颇受欢迎的成员map。
字符串是Unicode编码,其值不能修改,而其他方面和你想的差不多。
s="Hello"
可以使用len函数获取字符串的长度,使用索引操作符[0]可以访问字符串中的字符。Go语言中的字符串类型相当简陋,但使用stirng package可以实现类似其他语言字符串的所有功能。
数组(arrays)以中括号([])声明,索引从零开始。例如:
var buff [32]byte
fmt.Println(buff[10])
多维数组通过数组的数组实现,
var buff [32][32]byte
fmt.Println(buff[10][0])
数组(array)不是动态的,不能动态分配大小。但可以使用切片(slice)实现同样的效果。切片包含数组(array)的一部分,可以动态变更大小。
结构体(structs)与其他语言类似,如下:
func main() {
type point struct {
x, y int
}
var p = point{10, 10}
fmt.Println(p.x)
}
上例声明了新的结构体类型,包括两个成员x和y。在main函数中创建并初始化了该结构体类型的实例(instance)。Go语言通常不使用术语“实例(instance)”,而更喜欢使用术语“值(value)”,所以你是创建了该类型的一个值(value)。
结构体定义中可以嵌套结构体作为成员。初始化器(initializer){10,10}是结构体literal(译注:literal可以理解为立即数,见维基)。在结构体literal中也可以使用成员名例如{X:10}。
这是我们首次介绍Go类型,关于这个话题之后还有更多内容。
最后一个数据类型是Map,等价于其他语言中的hash map,关联数组(associative array)或者字典(dictionary)。
给定键的类型以及值的类型就能创建Map。如果从来没有使用过关联数组,那就把它想象成一个数组,数组的值不是通过索引访问,而是通过通用类型的键访问。例如。:
var m = make( map[string]int)
m["mike"] = 10
m["lucy"] = 30
fmt.Println(m["lucy"])
显示结果是30.
make函数是能够基于Type(类型)创建Value(值)(译注:可以理解为实例)的两个函数之一,要详细了解它,我们需要学习更多关于类型的内容。
类型
在Go语言中,类型特指一系列的值和操作。Go语言的类型起到的作用与众所周知的面向对象的设计语言(Java、C++)有很大的不同,它没有所谓的层次划分、没有类的概念也不存在继承。类型是可以被推断出来的,如:Go使用鸭子类型。
你可以用一个立即数(literal )或指定的类型来定义一个类型变量,以达到类型重用的目的。
自定义类型是由一些小的数据类型整合而成的,如数组、结构体、指针、很熟、接口、片、map和channel。
定义类型的方法:
type 类型名 数据类型
例:
type myint int
定义myint为一个整型类型。如果你想创建一个扩展类型,重新定义之前声明过的类型的数据类型也很常用,实现的函数和方法我们之后再讲。
更为通常的做法,你可以使用某些数据类型组成你自定义的类型:
type point struct {
x, y int
}
这就是一个新的类型结构。
你也可以声明数组类型:
type myarray [20]int
你可以在定义类型时使用自定义类型:
type point struct {
x, y int
}
type arrayPoints [10]point
这就创建了一个point类型的数组。
你可以自行探索其他的类型定义方式。接下来我们要做的是理解Go能用这些类型完成什么工作?
类型主要被用于下面两个方面:
类型检测
和
创建值
类型检测很常见——你只能赋予你定义的变量与之相同类型的值。在编译时编译器会依此对静态类型进行检查。
例:
var c myint
c = "string"
上面的代码编译将不会通过。但下面的代码:
var c myint
c = 1
将会通过编译。因为“c”和“1”都是整型数据。
类型所做的第二件事:在你用类型声明变量时构造对应类型的变量。如:
var i int
或
var p point
但对于slice(片)、map和channel来说他们必须使用make函数创建对应类型的值。
var m = make( map[string]int)
make函数是Go语言支持的两个分配函数中的一个,另一个是new函数。make函数创建了一个指定类型的值,并把该值得指针返回给变量。在大多数地方,Go中的指针与C中的指针使用方法类似。
你可以使用*引用一个指针的值,也可以用&获取值得地址。但是,Go和C的指针也存在差异,这种区别在于Go语言不存在指针计算。在Go语言中,指针存在的意义是让你可以用引用的方式在函数之间传递参数。 如果你有一个类型T,那么*T就是一个指向类型T的指针。
举一个new函数的例子:
var add= new(int)
在这里,new函数创建了一个整型变量并放回了它的地址存放在add中。变量add的类型为*int。
如果你写出如下语句
fmt.Print(add)
那么你将得到这个整型值得地址。那么,为了打印这个整型变量的值,我们需要这样书写打印语句:
fmt.Print(*add)
就像之前提到过的那样,你可以直接使用类型的值而无需给这个类型命名:
var p struct {
x, y int
}
如果你不需要重用这个类型,那么这样做也是可以的。
函数
Go不是一种基于类并且有层次结构的语言,也不使用通常的方式处理对象。如果你仅仅打算实现一个函数,那就不用考虑有关对象的内容。函数就是一个值(Values),是“一等对象“。
如下,声明一个函数
var myFunc = func(a, b int) int {
return a + b
}
可以指定参数类型和返回值类型,如果指定了返回值类型,则函数中必须有return语句。
函数值(value)被赋值给了变量myFunc。也可以按照通常的方式定义函数,这是变量myFunc就是函数的名称。
func myFunc(a, b int) int {
return a + b
}
无论那种方式,函数都可以使用下面的方式调用:
fmt.Println(myFunc(1, 2))
可以在return语句中返回多个值,并且可以在函数头中指定返回值的名称。
例如:
func myFunc(a, b int) (sum int) {
sum = a + b
return
}
sum就是函数的返回值。
返回多个值也很简单:
func myFunc(a, b int) (int, int) {
return a + b, a - b
}
必须全部接收函数的两个返回值:
x,y := myFunc2(1, 4)
fmt.Println(x,y)
其他语言中可以选择只接收一个返回值,但在Go语言中不可以。
传值——指针
所有的形参都是以传值的方式传入,所以对形参做的任何改变都不会影响实参。例如:
func myFunc(a, b int) int {
a = 1
return a + b
}
函数中对形参a的赋值语句,对实参没有任何影响。就是说
x, y := 2, 3
var sum = myFunc(x, y)
fmt.Println(sum, x)
显示结果是4和2。x的值没有变化。
如果想要改变实参的值,就需要传入指针(译注:即传地址或传引用)作为参数。例如,变更函数定义如下:
func myFunc(a *int, b int) int {
*a = 1
return *a + b
}
参数a以指针的形式传入,对a的赋值语句改变a指向的变量。调用函数时,我们需要传入变量的地址作为参数:
var sum = myFunc(&x, y)
fmt.Println(sum, x)
现在显示结果是4和1,x的值变更了。
*和&操作符的用法对C程序员来说是非常熟悉的,这体现了Go语言较为初级的一面。有争议说在现代语言中所有的参数都应该以传引用的方式传入。
如果函数定义中的参数是*int类型,而调用该函数时没有使用&操作数,那么在编译阶段类型检查时就会报错,而C语言没有这个功能。
总之,Go语言的指针类型,可以作为实参传递给函数,但无法在数据上耍一些”聪明“的技巧。
作用域和闭包
你可以以嵌套的方式在函数中定义函数。在某块代码中定义的变量只会在该块代码区域和该代码区域内的区域生效。这意味着你可以在函数之外定义全局变量,那么所有的函数将都能使用这个变量。
例:
var a1 int = 1
func main() {
fmt.Println(a1)
var a2 int = 2
var myFunc = func() int {
return a2
}
fmt.Println(myFunc())
}
在这个例子中,a1是一个全局变量,可以被所有函数访问。a2在main函数内定义,因此它可以被main函数和main中的myFunc函数访问。
Go同样支持闭包。如果你在一个函数中定义了另一个函数,那么这个在内部的函数将能够访问外部函数的变量,即使外部函数已经终止运行。在外部函数停止后保持内部函数的唯一方法是将其作为一个返回值返回给外部函数。
例:
func myFunc() func() int {
var a int = 1
return func() int {
return a
}
}
在这里,内部函数以func() int的方式返回给外部函数。函数和它的内容都是以类型的方式返回的。返回的函数将会返回外部函数定义的变量的值,这就是闭包的作用。
因此
myClosure := myFunc()
fmt.Println(myClosure())
输出结果为 1.
每个闭包都有一份与自己绑定的变量副本,闭包不会实现不同函数副本之间的数据共享。
Go 控制
现在我们已经了解过了数据、类型和函数。接下来我们将讨论另一个重要的问题:Go语言提供的控制语句。
实际上,Go语言只提供了很少的控制结构,它极大简化了控制语句的使用。
Go语言是一种块结构的编程语言,它使用"{}"将一组代码组成块。如果你一直在奇怪其他编程语言中经常使用的“;”去了哪里,我可以很明确的告诉你,在Go中它依然存在,只是在编译过程中它会自动为你加上“;”。如果你也在代码末尾加上分号,那么编译器将会认为它们是不需要的字符,从而自动剔除这些分号。
for循环是Go语言中的唯一一种循环。for循环可以被用来创建条件循环和枚举循环。
for循环具有下面这种形式:
for 条件{
操作
}
需要注意的是,你无需将循环的条件置于一对大括号“{}”中。循环将会在不满足条件时终止。循环将会在每次执行循环体前检查条件是否满足,因此循环体可以被执行0次或很多次,类似于while循环。
例:
i:=0
for a<10 {
fmt.print(a)
a=a+1
}
你可以通过使用for true {" 或者 "for {" 来创建一个不会终止的循环。
枚举循环与其他类似C的语言基本相同:
for 表达式1 ; 条件 ; 表达式3{
操作
}
表达式1会在循环开始前执行一次,表达式3会在每次循环体执行结束后执行一次,条件语句会在每次循环体执行之前被检查,如果为true则继续执行循环。
例:
for i:=0; i<10; i++ {
fmt.print(a)
}
你可以在for表达式中加入任何语句,但前提是你得加入分号以区分你的语句属于表达式的哪个部分。但也有一种情况例外,你创建的条件表达式无需条件语句。
你也可以在for表达式中反复申明数组、片、字符串、map或channel中的值,用法与其他语言中的for循环类似。
例如:
var array= [] int {1,2,3,4}
for i,v:= range array {
fmt.print(i,v)
}
for表达式的循环次数取决于索引和数组的大小,好比这里的i和v。
在Go语言中,还存在另外两种控制语句。if语句除了没有大括号包围的条件语句外,与其他语言中的if语句基本相同。
例:
if a<0 {
fmt.print("Negative")
} else {
fmt.print("Positive")
}
else条件不是必须的,但一对大括号必须完整:
if a<0 {
fmt.print("Negative")
}
你也可以通过使用else if创建一个符合条件表达式:
if a<0 {
fmt.print("Negative")
} else if a==0 {
fmt.print("Zero")
} else {
fmt.print("Positive")
}
你也可以在if主体内容执行之前执行初始化语句:
if a:=myfunc() a<0 {
fmt.print("Negative")
}
所有在条件语句中创建的变量,只在条件表达式中适用。
另一种条件表达式为switch,它的存在是为了应对在一个条件中有较多选项的情况。如:
switch a {
case 0,1:
fmt.print("a is 0 or 1)
case 2.3:
fmt.print("a is 2 or 3)
default:
fmt.print("a is some other value")
}
你也可以用下面的方式书写条件语句:
case a<0:
在Go语言中,你无须用break跳出条件选择。如过你想从一个case进入接着的另一个case,那么你可以使用fallthrough语句 (注:fallthrough表示继续执行下面的Case而不是退出Switch)。case语句是按顺序执行的,一旦有相对应的情况,执行完case中的语句后程序将会自动调用break跳出选择,因此default选项往往被放在最后。
除了对值和条件的匹配,你可以对类型进行匹配,如:
switch a.type{
case int:
fmt.print("a is an int")
case float64:
fmt.print("a is a float")
default:
fmt.print("some other type")
}
最后,你可以使用break终止循环或continue终止当前循环并直接进入下一次循环,break也可以被用于switch语句中。
虽然Go语言中也有goto语句,但这还是不讲为好。